109 lines
3.8 KiB
TypeScript
109 lines
3.8 KiB
TypeScript
/**
|
|
* Canonical name + domain derivation for workspace-scoped resources.
|
|
*
|
|
* AI-generated Coolify apps live under a single subdomain namespace
|
|
* per workspace:
|
|
*
|
|
* https://{app-slug}.{workspace-slug}.vibnai.com
|
|
*
|
|
* e.g. `api.mark.vibnai.com` for the `api` app in workspace `mark`.
|
|
*
|
|
* The DNS record `*.vibnai.com` (or its subdomain) must resolve to
|
|
* the Coolify server. Traefik picks up the Host header and Coolify's
|
|
* per-app ACME handshake provisions a Let's Encrypt cert per FQDN.
|
|
*/
|
|
|
|
const VIBN_BASE_DOMAIN = process.env.VIBN_BASE_DOMAIN ?? 'vibnai.com';
|
|
const SLUG_STRIP = /[^a-z0-9-]+/g;
|
|
|
|
/** Lowercase, dash-sanitize a free-form name into a DNS-safe slug. */
|
|
export function slugify(name: string): string {
|
|
return name
|
|
.toLowerCase()
|
|
.replace(/[_\s]+/g, '-')
|
|
.replace(SLUG_STRIP, '')
|
|
.replace(/-+/g, '-')
|
|
.replace(/^-+|-+$/g, '')
|
|
.slice(0, 40) || 'app';
|
|
}
|
|
|
|
/**
|
|
* The default public FQDN for an app inside a workspace, given the
|
|
* workspace's slug (e.g. `mark`) and an app slug (e.g. `my-api`).
|
|
*
|
|
* workspaceAppFqdn('mark', 'my-api') === 'my-api.mark.vibnai.com'
|
|
*/
|
|
export function workspaceAppFqdn(workspaceSlug: string, appSlug: string): string {
|
|
return `${appSlug}-${workspaceSlug}.${VIBN_BASE_DOMAIN}`;
|
|
}
|
|
|
|
/** `https://{fqdn}` — what Coolify's `domains` field expects. */
|
|
export function toDomainsString(fqdns: string[]): string {
|
|
return fqdns.map(f => (f.startsWith('http') ? f : `https://${f}`)).join(',');
|
|
}
|
|
|
|
/** Parse a Coolify `domains` CSV back into bare FQDNs. */
|
|
export function parseDomainsString(domains: string | null | undefined): string[] {
|
|
if (!domains) return [];
|
|
return domains
|
|
.split(/[,\s]+/)
|
|
.map(d => d.trim())
|
|
.filter(Boolean)
|
|
.map(d => d.replace(/^https?:\/\//, '').replace(/\/+$/, ''));
|
|
}
|
|
|
|
/** Guard against cross-workspace or disallowed domains. */
|
|
export function isDomainUnderWorkspace(fqdn: string, workspaceSlug: string): boolean {
|
|
const f = fqdn.replace(/^https?:\/\//, '').toLowerCase();
|
|
return f === `${workspaceSlug}.${VIBN_BASE_DOMAIN}` || f.endsWith(`-${workspaceSlug}.${VIBN_BASE_DOMAIN}`) || f.endsWith(`.${workspaceSlug}.${VIBN_BASE_DOMAIN}`);
|
|
}
|
|
|
|
/**
|
|
* Build a Gitea SSH clone URL for a repo in a workspace's org.
|
|
*
|
|
* NOTE: As of 2026-04 this is deprecated for Coolify-driven deploys on
|
|
* vibnai.com — Gitea's builtin SSH is bound to host port 22222 which is
|
|
* not publicly reachable, and the default port 22 hits Ubuntu's host
|
|
* sshd which doesn't know about Gitea keys. Use {@link giteaHttpsUrl}
|
|
* instead and embed the workspace bot's PAT. Kept for read-only places
|
|
* (e.g. UI display) that want the canonical "clone with SSH" form.
|
|
*/
|
|
export function giteaSshUrl(org: string, repo: string, giteaHost = 'git.vibnai.com'): string {
|
|
return `git@${giteaHost}:${org}/${repo}.git`;
|
|
}
|
|
|
|
/**
|
|
* Build a Gitea HTTPS clone URL with basic-auth credentials embedded.
|
|
*
|
|
* https://{username}:{token}@{host}/{org}/{repo}.git
|
|
*
|
|
* This is what we pass to Coolify's `git_repository` field for every
|
|
* Vibn-provisioned app. Works regardless of SSH topology and scopes
|
|
* access to whatever the bot user can see. The token is usually the
|
|
* per-workspace Gitea bot PAT.
|
|
*
|
|
* We URL-encode the username and token so PATs with special chars
|
|
* (`:`, `/`, `@`, `#`, `?`, etc.) don't break URL parsing.
|
|
*/
|
|
export function giteaHttpsUrl(
|
|
org: string,
|
|
repo: string,
|
|
username: string,
|
|
token: string,
|
|
giteaHost = 'git.vibnai.com'
|
|
): string {
|
|
const u = encodeURIComponent(username);
|
|
const t = encodeURIComponent(token);
|
|
return `https://${u}:${t}@${giteaHost}/${org}/${repo}.git`;
|
|
}
|
|
|
|
/**
|
|
* Redact the credentials from a Gitea HTTPS URL for safe logging /
|
|
* display. Leaves the repo path intact. No-op for non-HTTPS URLs.
|
|
*/
|
|
export function redactGiteaHttpsUrl(url: string): string {
|
|
return url.replace(/^(https?:\/\/)[^@]+@/, '$1***:***@');
|
|
}
|
|
|
|
export { VIBN_BASE_DOMAIN };
|