mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
feat: owner accounts and in-app platform settings
- First registered account becomes admin (users.is_admin, backfilled to earliest account); better-auth mirror path does the same - Public registration closes after bootstrap; admin can reopen it via GET/PUT /api/v1/settings (admin-gated) or SIGNUP_ENABLED env - In-app settings override env vars; secrets stored in app_settings, token values never returned by the API - Saving a Cloudflare Tunnel token pulls and runs a managed cloudflared sidecar on the backend's networks; clearing removes it; startup restores it from saved settings - group_add DOCKER_GID grants the non-root backend user Docker socket access; install.sh probes the in-container socket gid - Settings page gains an owner-only Platform section (registration toggle, tunnel token, link to Cloudflare dashboard); sign-up page stays reachable while registration is open - OpenAPI spec + generated types updated; manual user creation now requires admin Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
c3959d13e2
commit
c16b32f56a
18 files changed
+802
-34
No files matched your search
@@ -18,10 +18,20 @@ export type UserProfile = {
|
||||
email: string;
|
||||
name: string;
|
||||
avatarUrl?: string;
|
||||
isAdmin: boolean;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
};
|
||||
|
||||
export type PlatformSettings = {
|
||||
signupEnabled: boolean;
|
||||
cloudflareTunnel: {
|
||||
tokenSet: boolean;
|
||||
source: 'app' | 'env' | 'none';
|
||||
container: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ServiceEntity = {
|
||||
id: string;
|
||||
projectId: string;
|
||||
@@ -442,11 +452,46 @@ function normalizeUserProfile(profile: RawUserProfile): UserProfile | null {
|
||||
email: profile.email,
|
||||
name: profile.name,
|
||||
avatarUrl: profile.avatar_url ?? undefined,
|
||||
isAdmin: profile.is_admin ?? false,
|
||||
createdAt: profile.created_at ?? undefined,
|
||||
updatedAt: profile.updated_at ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePlatformSettings(raw: components['schemas']['PlatformSettings']): PlatformSettings {
|
||||
return {
|
||||
signupEnabled: raw.signup_enabled ?? false,
|
||||
cloudflareTunnel: {
|
||||
tokenSet: raw.cloudflare_tunnel?.token_set ?? false,
|
||||
source: raw.cloudflare_tunnel?.source ?? 'none',
|
||||
container: raw.cloudflare_tunnel?.container ?? 'missing',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function getPlatformSettings(): Promise<PlatformSettings> {
|
||||
const raw = await requestJson<components['schemas']['PlatformSettings']>('/settings');
|
||||
return normalizePlatformSettings(raw);
|
||||
}
|
||||
|
||||
export async function updatePlatformSettings(input: {
|
||||
signupEnabled?: boolean;
|
||||
cloudflareTunnelToken?: string;
|
||||
}): Promise<PlatformSettings> {
|
||||
const body: Record<string, unknown> = {};
|
||||
if (input.signupEnabled !== undefined) {
|
||||
body.signup_enabled = input.signupEnabled;
|
||||
}
|
||||
if (input.cloudflareTunnelToken !== undefined) {
|
||||
body.cloudflare_tunnel_token = input.cloudflareTunnelToken;
|
||||
}
|
||||
const raw = await requestJson<components['schemas']['PlatformSettings']>('/settings', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return normalizePlatformSettings(raw);
|
||||
}
|
||||
|
||||
function normalizeService(service: RawService): ServiceEntity | null {
|
||||
if (!service.id || !service.name || !service.project_id) {
|
||||
return null;
|
||||
|
||||
Reference in new issue
Block a user