Coolify v4's POST/PATCH /applications/{uuid}/envs only accepts key,
value, is_preview, is_literal, is_multiline, is_shown_once. Sending
is_build_time triggers a 422 "This field is not allowed." — it's now
a derived read-only flag (is_buildtime) computed from Dockerfile ARG
usage. Breaks agents trying to upsert env vars.
Three-layer fix so this can't regress:
- lib/coolify.ts: COOLIFY_ENV_WRITE_FIELDS whitelist enforced at the
network boundary, regardless of caller shape
- app/api/workspaces/[slug]/apps/[uuid]/envs: stops forwarding the
field; returns a deprecation warning when callers send it; GET
reads both is_buildtime and is_build_time for version parity
- app/api/mcp/route.ts: same treatment in the MCP dispatcher;
AI_CAPABILITIES.md doc corrected
Also bundles (not related to the above):
- Workspace API keys are now revealable from settings. New
key_encrypted column stores AES-256-GCM(VIBN_SECRETS_KEY, token).
POST /api/workspaces/[slug]/keys/[keyId]/reveal returns plaintext
for session principals only; API-key principals cannot reveal
siblings. Legacy keys stay valid for auth but can't reveal.
- P5.3 Object storage: lib/gcp/storage.ts + lib/workspace-gcs.ts
idempotently provision a per-workspace GCS bucket, service
account, IAM binding and HMAC key. New POST /api/workspaces/
[slug]/storage/buckets endpoint. Migration script + smoke test
included. Proven end-to-end against prod master-ai-484822.
Made-with: Cursor
87 lines
3.1 KiB
TypeScript
87 lines
3.1 KiB
TypeScript
/**
|
|
* One-shot: run ensureWorkspaceGcsProvisioned() for a specific workspace
|
|
* slug against PROD GCP + PROD Postgres. Idempotent — safe to re-run.
|
|
*
|
|
* Unlike scripts/smoke-storage-e2e.ts this does NOT clean up; the whole
|
|
* point is to persist the workspace's provisioned state into the DB.
|
|
*
|
|
* Usage:
|
|
* cd vibn-frontend
|
|
* npx -y dotenv-cli -e ../.google.env -e .env.local -- \
|
|
* npx tsx scripts/provision-workspace-gcs.ts <slug>
|
|
*
|
|
* Required env:
|
|
* GOOGLE_SERVICE_ACCOUNT_KEY_B64 (from ../.google.env)
|
|
* DATABASE_URL (from .env.local, points at prod vibn-postgres)
|
|
* VIBN_SECRETS_KEY (from .env.local, ≥16 chars)
|
|
*/
|
|
|
|
import { queryOne } from '../lib/db-postgres';
|
|
import { ensureWorkspaceGcsProvisioned } from '../lib/workspace-gcs';
|
|
import type { VibnWorkspace } from '../lib/workspaces';
|
|
|
|
async function main(): Promise<void> {
|
|
const slug = process.argv[2];
|
|
if (!slug) {
|
|
console.error('Usage: tsx scripts/provision-workspace-gcs.ts <workspace-slug>');
|
|
process.exit(2);
|
|
}
|
|
|
|
console.log('━'.repeat(72));
|
|
console.log(` Provision GCS for workspace: ${slug}`);
|
|
console.log('━'.repeat(72));
|
|
|
|
// Fetch the current row.
|
|
const ws = await queryOne<VibnWorkspace>(
|
|
`SELECT * FROM vibn_workspaces WHERE slug = $1`,
|
|
[slug],
|
|
);
|
|
if (!ws) {
|
|
console.error(`No vibn_workspaces row found for slug=${slug}`);
|
|
process.exit(1);
|
|
}
|
|
console.log(` id : ${ws.id}`);
|
|
console.log(` name : ${ws.name}`);
|
|
console.log(` owner_user_id : ${ws.owner_user_id}`);
|
|
// @ts-expect-error — new columns not yet in VibnWorkspace type
|
|
console.log(` gcp_status : ${ws.gcp_provision_status ?? 'pending'}`);
|
|
console.log('');
|
|
|
|
console.log('Running ensureWorkspaceGcsProvisioned()…');
|
|
const result = await ensureWorkspaceGcsProvisioned(ws);
|
|
|
|
console.log('');
|
|
console.log('━'.repeat(72));
|
|
console.log(' RESULT');
|
|
console.log('━'.repeat(72));
|
|
console.log(` status : ${result.status}`);
|
|
console.log(` SA : ${result.serviceAccountEmail}`);
|
|
console.log(` bucket : ${result.bucket.name}`);
|
|
console.log(` location : ${result.bucket.location}`);
|
|
console.log(` created : ${result.bucket.timeCreated ?? '(pre-existing)'}`);
|
|
console.log(` HMAC accessId : ${result.hmac.accessId}`);
|
|
console.log('');
|
|
|
|
// Re-read to confirm persistence.
|
|
const after = await queryOne<Record<string, unknown>>(
|
|
`SELECT gcp_service_account_email,
|
|
CASE WHEN gcp_service_account_key_enc IS NOT NULL THEN '<enc '||length(gcp_service_account_key_enc)||' b64>' ELSE 'null' END AS sa_key,
|
|
gcs_default_bucket_name,
|
|
gcs_hmac_access_id,
|
|
CASE WHEN gcs_hmac_secret_enc IS NOT NULL THEN '<enc '||length(gcs_hmac_secret_enc)||' b64>' ELSE 'null' END AS hmac_secret,
|
|
gcp_provision_status,
|
|
gcp_provision_error
|
|
FROM vibn_workspaces WHERE id = $1`,
|
|
[ws.id],
|
|
);
|
|
console.log('DB row after:');
|
|
console.log(JSON.stringify(after, null, 2));
|
|
|
|
process.exit(0);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('[provision-workspace-gcs] FAILED:', err);
|
|
process.exit(1);
|
|
});
|