Add a password-gated private instance mode for xaigen.

Keep Authentik on aigen, isolate library data per instance, and purge Comfy outputs from the desktop after they are saved on the server.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-25 19:24:00 -05:00
co-authored by Cursor
parent e2db183a8e
commit bc0ea613c1
22 changed files with 426 additions and 15 deletions
+9 -2
View File
@@ -5,7 +5,7 @@
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-xl bg-amber-400 text-zinc-950 font-display font-extrabold">A</div>
<div>
<h1 class="font-display text-xl font-bold leading-none">AIGen</h1>
<h1 class="font-display text-xl font-bold leading-none">{{ instanceName }}</h1>
<p class="text-[11px] uppercase tracking-[0.22em] text-zinc-500">MiniMax H3 · ComfyUI relay</p>
</div>
</div>
@@ -302,9 +302,12 @@
<SettingsModal
v-if="settingsOpen"
:folders="folders"
:auth-mode="authMode"
:username="userName"
@close="settingsOpen = false"
@updated="applyLibrary"
@error="toast"
@renamed="userName = $event"
/>
<ImagePickerModal
v-if="pickerOpen"
@@ -412,6 +415,8 @@ const toasts = ref<{ id: string; message: string }[]>([])
const comfyOk = ref(false)
const userName = ref('')
const authEnabled = ref(false)
const authMode = ref('')
const instanceName = ref('AIGen')
const folders = ref<LibraryFolder[]>([])
const clips = ref<LibraryClip[]>([])
const stills = ref<LibraryStill[]>([])
@@ -495,12 +500,14 @@ async function loadLibrary() {
onMounted(async () => {
const [health, me] = await Promise.all([
$fetch<{ comfy?: { ok?: boolean } }>('/api/health').catch(() => ({ comfy: { ok: false } })),
$fetch<{ user?: { name?: string; email?: string }; authEnabled?: boolean }>('/api/auth/me').catch(() => ({ user: null })),
$fetch<{ user?: { name?: string; email?: string }; authEnabled?: boolean; authMode?: string; instanceName?: string }>('/api/auth/me').catch(() => ({ user: null })),
loadLibrary().catch(() => applyLibrary({ folders: [], clips: [], stills: [] }))
])
comfyOk.value = Boolean(health.comfy?.ok)
userName.value = me.user?.name || me.user?.email || ''
authEnabled.value = Boolean(me.authEnabled)
authMode.value = me.authMode || ''
instanceName.value = me.instanceName || 'AIGen'
})
onBeforeUnmount(() => {
+54 -2
View File
@@ -2,9 +2,32 @@
<main class="min-h-screen flex items-center justify-center px-6">
<section class="glass w-full max-w-md rounded-3xl p-10 text-center">
<p class="text-amber-300/80 text-xs tracking-[0.3em] uppercase">Carr Garage</p>
<h1 class="font-display text-4xl font-extrabold mt-3">AIGen</h1>
<p class="mt-3 text-zinc-400">Headless MiniMax H3 image-to-video studio. Sign in with Authentik to reach the ComfyUI relay.</p>
<h1 class="font-display text-4xl font-extrabold mt-3">{{ instanceName }}</h1>
<p class="mt-3 text-zinc-400">
{{ authMode === 'password'
? 'Private MiniMax H3 studio. Sign in with the username and password for this instance.'
: 'Headless MiniMax H3 image-to-video studio. Sign in with Authentik to reach the ComfyUI relay.' }}
</p>
<form v-if="authMode === 'password'" class="mt-8 space-y-3 text-left" @submit.prevent="submit">
<label class="block text-sm">
<span class="mb-1 block text-zinc-400">Username</span>
<input v-model="username" class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm" autocomplete="username" required>
</label>
<label class="block text-sm">
<span class="mb-1 block text-zinc-400">Password</span>
<input v-model="password" type="password" class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm" autocomplete="current-password" required>
</label>
<p v-if="error" class="text-sm text-red-300">{{ error }}</p>
<button
type="submit"
class="inline-flex w-full items-center justify-center rounded-2xl bg-amber-400 px-5 py-3 font-semibold text-zinc-950 hover:bg-amber-300 transition disabled:opacity-50"
:disabled="busy"
>
{{ busy ? 'Signing in…' : 'Sign in' }}
</button>
</form>
<a
v-else
href="/api/auth/login"
class="mt-8 inline-flex w-full items-center justify-center rounded-2xl bg-amber-400 px-5 py-3 font-semibold text-zinc-950 hover:bg-amber-300 transition"
>
@@ -13,3 +36,32 @@
</section>
</main>
</template>
<script setup lang="ts">
const config = useRuntimeConfig()
const instanceName = ref(String(config.public.instanceName || 'AIGen'))
const authMode = ref(String(config.public.authMode || ''))
const username = ref('')
const password = ref('')
const error = ref('')
const busy = ref(false)
onMounted(async () => {
const me = await $fetch<{ authMode?: string; instanceName?: string }>('/api/auth/me').catch(() => ({}))
if (me.authMode) authMode.value = me.authMode
if (me.instanceName) instanceName.value = me.instanceName
})
async function submit() {
busy.value = true
error.value = ''
try {
await $fetch('/api/auth/login', { method: 'POST', body: { username: username.value, password: password.value } })
await navigateTo('/')
} catch (err: any) {
error.value = err?.data?.statusMessage || err?.statusMessage || 'Sign in failed'
} finally {
busy.value = false
}
}
</script>