Phase 4: AI-driven app/database/auth lifecycle
Workspace-owned deploy infra so AI agents can create and destroy
Coolify resources without ever touching the root admin token.
vibn_workspaces
+ coolify_server_uuid, coolify_destination_uuid
+ coolify_environment_name (default "production")
+ coolify_private_key_uuid, gitea_bot_ssh_key_id
ensureWorkspaceProvisioned
+ generates an ed25519 keypair per workspace
+ pushes pubkey to the Gitea bot user (read/write scoped by team)
+ registers privkey in Coolify as a reusable deploy key
New endpoints under /api/workspaces/[slug]/
apps/ POST (private-deploy-key from Gitea repo)
apps/[uuid] PATCH, DELETE?confirm=<name>
apps/[uuid]/domains GET, PATCH (policy: *.{ws}.vibnai.com only)
databases/ GET, POST (8 types incl. postgres, clickhouse, dragonfly)
databases/[uuid] GET, PATCH, DELETE?confirm=<name>
auth/ GET, POST (Pocketbase, Authentik, Keycloak, Pocket-ID, Logto, Supertokens)
auth/[uuid] DELETE?confirm=<name>
MCP (/api/mcp) gains 15 new tools that mirror the REST surface and
enforce the same workspace tenancy + delete-confirm guard.
Safety: destructive ops require ?confirm=<exact-resource-name>; volumes
are kept by default (pass delete_volumes=true to drop).
Made-with: Cursor
This commit is contained in:
48
lib/gitea.ts
48
lib/gitea.ts
@@ -334,6 +334,54 @@ export async function ensureOrgTeamMembership(opts: {
|
||||
return team;
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// Admin: SSH keys on a user (for Coolify deploy-key flow)
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
export interface GiteaSshKey {
|
||||
id: number;
|
||||
key: string;
|
||||
title: string;
|
||||
fingerprint?: string;
|
||||
read_only?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an SSH public key under a target user via the admin API.
|
||||
* The resulting key gives anyone holding the matching private key the
|
||||
* same repo-read access as the user (bounded by that user's team
|
||||
* memberships — for bots, usually read/write on one org only).
|
||||
*/
|
||||
export async function adminAddUserSshKey(opts: {
|
||||
username: string;
|
||||
title: string;
|
||||
key: string; // OpenSSH-format public key, e.g. "ssh-ed25519 AAAAC3... comment"
|
||||
readOnly?: boolean;
|
||||
}): Promise<GiteaSshKey> {
|
||||
return giteaFetch(`/admin/users/${opts.username}/keys`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title: opts.title,
|
||||
key: opts.key,
|
||||
read_only: opts.readOnly ?? false,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List SSH keys for a user (admin view).
|
||||
*/
|
||||
export async function adminListUserSshKeys(username: string): Promise<GiteaSshKey[]> {
|
||||
return giteaFetch(`/users/${username}/keys`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an SSH key by id (owned by a user). Used when rotating keys.
|
||||
*/
|
||||
export async function adminDeleteUserSshKey(keyId: number): Promise<void> {
|
||||
await giteaFetch(`/admin/users/keys/${keyId}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an existing repo.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user