feat: rebuild xiteng.site homelab platform

This commit is contained in:
2026-08-12 09:48:25 +08:00
parent 557b0eca33
commit 5b84988789
128 changed files with 14979 additions and 292 deletions
+15
View File
@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid slice" role="img" aria-labelledby="title description">
<title id="title">Xiteng login background</title>
<desc id="description">A warm cream grid with simple red, yellow, blue and green geometric shapes.</desc>
<defs>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M40 0H0V40" fill="none" stroke="#1c1712" stroke-opacity=".09" stroke-width="2"/>
</pattern>
</defs>
<rect width="1600" height="1000" fill="#f7efe0"/>
<rect width="1600" height="1000" fill="url(#grid)"/>
<rect x="80" y="90" width="210" height="210" rx="32" fill="#f0c94b" stroke="#1c1712" stroke-width="8" transform="rotate(-6 185 195)"/>
<circle cx="1430" cy="170" r="110" fill="#93c9d7" stroke="#1c1712" stroke-width="8"/>
<path d="M1330 775h190v155h-190z" fill="#177a48" stroke="#1c1712" stroke-width="8" transform="rotate(5 1425 852)"/>
<path d="M92 760l160 76-124 112z" fill="#d83b21" stroke="#1c1712" stroke-width="8" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title description">
<title id="title">Xiteng</title>
<desc id="description">A red geometric X on a cream tile with a yellow background.</desc>
<rect width="512" height="512" rx="104" fill="#f0c94b"/>
<rect x="48" y="48" width="416" height="416" rx="80" fill="#fffaf0" stroke="#1c1712" stroke-width="16"/>
<path d="M150 146h70l36 65 36-65h70l-66 110 66 110h-70l-36-65-36 65h-70l66-110z" fill="#d83b21" stroke="#1c1712" stroke-width="14" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 569 B

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 112" role="img" aria-labelledby="title description">
<title id="title">Xiteng</title>
<desc id="description">Xiteng wordmark with the red X symbol.</desc>
<rect x="4" y="4" width="104" height="104" rx="22" fill="#f0c94b" stroke="#1c1712" stroke-width="8"/>
<rect x="20" y="20" width="72" height="72" rx="14" fill="#fffaf0" stroke="#1c1712" stroke-width="5"/>
<path d="M34 31h15l7 13 7-13h15L64 56l14 25H63l-7-13-7 13H34l14-25z" fill="#d83b21" stroke="#1c1712" stroke-width="3" stroke-linejoin="round"/>
<text x="132" y="70" fill="#1c1712" font-family="Inter, ui-sans-serif, system-ui, sans-serif" font-size="64" font-weight="850" letter-spacing="-2">Xiteng</text>
<text x="136" y="98" fill="#675f55" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="15" font-weight="700" letter-spacing="4">HOME SERVICES</text>
</svg>

After

Width:  |  Height:  |  Size: 912 B

+102
View File
@@ -0,0 +1,102 @@
version: 1
metadata:
name: Xiteng - Brand and login flow
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
- model: authentik_brands.brand
state: present
identifiers:
domain: authentik-default
default: true
attrs:
branding_title: Xiteng
# Versioned repository assets live in Authentik's built-in static tree.
branding_logo: /static/dist/assets/xiteng/xiteng-wordmark-v1.svg
branding_favicon: /static/dist/assets/xiteng/xiteng-favicon-v1.svg
branding_default_flow_background: /static/dist/assets/xiteng/xiteng-background-v1.svg
attributes:
settings:
locale: zh-hans
branding_custom_css: |
:root {
--ak-font-family-sans-serif: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--ak-font-family-heading: var(--ak-font-family-sans-serif);
--ak-global--background-color: #f7efe0;
--ak-c-login--MaxWidth: 31rem;
--ak-c-login__content-BoxShadow: none;
--ak-c-login__main--BackgroundColor: #fffaf0;
--ak-c-login__main--Color: #1c1712;
--ak-c-login__main--BoxShadow: 8px 8px 0 #1c1712;
--ak-c-login__footer--Color: #675f55;
--pf-global--primary-color--100: #d83b21;
--pf-global--primary-color--200: #a92a18;
--pf-global--Color--100: #1c1712;
--pf-global--Color--200: #675f55;
--pf-global--BorderColor--100: #1c1712;
--pf-global--BorderRadius--sm: 6px;
--pf-c-login__main--BackgroundColor: #fffaf0;
--pf-c-card--BackgroundColor: #fffaf0;
--pf-c-button--m-primary--BackgroundColor: #d83b21;
--pf-c-button--m-primary--hover--BackgroundColor: #bc321d;
--pf-c-button--m-primary--active--BackgroundColor: #a92a18;
--pf-c-button--m-secondary--Color: #1c1712;
--pf-c-button--m-secondary--BorderColor: #1c1712;
}
ak-flow-executor::part(locale-select) {
display: none;
}
.pf-c-login__main,
.pf-c-card {
border: 2px solid #1c1712;
border-radius: 10px;
}
.pf-c-button.pf-m-primary {
border: 2px solid #1c1712;
border-radius: 6px;
box-shadow: 3px 3px 0 #1c1712;
font-weight: 800;
}
.pf-c-button.pf-m-primary:hover {
box-shadow: 1px 1px 0 #1c1712;
transform: translate(2px, 2px);
}
.pf-c-form-control {
border: 2px solid #1c1712;
border-radius: 6px;
}
@media (max-width: 576px) {
:root {
--ak-c-login--MaxWidth: calc(100vw - 2rem);
--ak-c-login__main--BoxShadow: 5px 5px 0 #1c1712;
}
}
- model: authentik_core.application
state: present
identifiers:
slug: xiteng-portal
attrs:
name: Xiteng Portal
meta_launch_url: https://xiteng.site/?focus=authentik#infrastructure
open_in_new_tab: false
meta_description: 返回 xiteng.site 服务目录
meta_publisher: Xiteng
meta_icon: https://xiteng.site/favicon.svg
- model: authentik_flows.flow
state: present
identifiers:
slug: default-authentication-flow
attrs:
name: Xiteng 登录
title: 欢迎回来
designation: authentication
authentication: none
layout: stacked
+39
View File
@@ -0,0 +1,39 @@
version: 1
# Managed ForwardAuth application and outpost assignment for chat.xiteng.site.
metadata:
name: Xiteng Chat ForwardAuth
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
- id: xiteng-chat-provider
model: authentik_providers_proxy.proxyprovider
state: present
identifiers:
name: Xiteng Chat
attrs:
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
external_host: https://chat.xiteng.site
mode: forward_single
- model: authentik_core.application
state: present
identifiers:
slug: xiteng-chat
attrs:
name: Xiteng Chat
provider: !KeyOf xiteng-chat-provider
meta_launch_url: https://chat.xiteng.site
open_in_new_tab: true
- model: authentik_outposts.outpost
state: present
identifiers:
name: uptime-kuma-outpost
attrs:
providers:
- !Find [authentik_core.provider, [name, Xiteng Portal Admin]]
- !Find [authentik_core.provider, [name, comfyui-proxy]]
- !Find [authentik_core.provider, [name, invokeai-proxy]]
- !Find [authentik_core.provider, [name, code-server-proxy]]
- !KeyOf xiteng-chat-provider
@@ -0,0 +1,35 @@
version: 1
metadata:
name: Xiteng - Passkey passwordless login
entries:
- model: authentik_stages_identification.identificationstage
state: present
identifiers:
name: default-authentication-identification
attrs:
user_fields:
- email
- username
password_stage: null
captcha_stage: null
webauthn_stage: !Find [authentik_stages_authenticator_validate.authenticatorvalidatestage, [name, default-authentication-mfa-validation]]
case_insensitive_matching: true
show_matched_user: true
pretend_user_exists: true
enable_remember_me: false
enrollment_flow: null
recovery_flow: null
passwordless_flow: null
show_source_labels: false
# Authentik 2026.5 ships the password/MFA skip policies in its default
# authentication blueprint. Remove the temporary local copies if present.
- model: authentik_policies_expression.expressionpolicy
state: absent
identifiers:
name: xiteng-authentication-password-optional
- model: authentik_policies_expression.expressionpolicy
state: absent
identifiers:
name: xiteng-authentication-mfa-after-passkey
+98
View File
@@ -0,0 +1,98 @@
version: 1
metadata:
name: Xiteng password recovery
labels:
blueprints.goauthentik.io/instantiate: "true"
entries:
- id: recovery-flow
model: authentik_flows.flow
state: present
identifiers:
slug: xiteng-recovery
attrs:
name: Xiteng 密码恢复
title: 设置新密码
designation: recovery
authentication: require_unauthenticated
- id: password
model: authentik_stages_prompt.prompt
state: present
identifiers:
name: xiteng-recovery-password
attrs:
field_key: password
label: 新密码
type: password
required: true
order: 0
placeholder: 新密码
placeholder_expression: false
- id: password-repeat
model: authentik_stages_prompt.prompt
state: present
identifiers:
name: xiteng-recovery-password-repeat
attrs:
field_key: password_repeat
label: 再次输入新密码
type: password
required: true
order: 1
placeholder: 再次输入新密码
placeholder_expression: false
- id: password-stage
model: authentik_stages_prompt.promptstage
state: present
identifiers:
name: Xiteng 设置新密码
attrs:
fields:
- !KeyOf password
- !KeyOf password-repeat
validation_policies: []
- id: user-write
model: authentik_stages_user_write.userwritestage
state: present
identifiers:
name: xiteng-recovery-user-write
attrs:
user_creation_mode: never_create
- id: user-login
model: authentik_stages_user_login.userloginstage
state: present
identifiers:
name: xiteng-recovery-user-login
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf recovery-flow
stage: !KeyOf password-stage
order: 10
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf recovery-flow
stage: !KeyOf user-write
order: 20
- model: authentik_flows.flowstagebinding
state: present
identifiers:
target: !KeyOf recovery-flow
stage: !KeyOf user-login
order: 30
- model: authentik_brands.brand
state: present
identifiers:
domain: authentik-default
default: true
attrs:
flow_recovery: !KeyOf recovery-flow
+66
View File
@@ -0,0 +1,66 @@
from pathlib import Path
from authentik.core.models import Application, Group, Token, TokenIntents, User
from authentik.policies.models import PolicyBinding
ADMIN_USERNAME = "liooil"
FAMILY_GROUP = "liuhome"
TOKEN_IDENTIFIER = "xiteng-portal-admin"
TOKEN_PATH = Path("/run/secrets/portal_api_token")
MANAGED_APPLICATIONS = [
"xiteng-portal-admin",
"xiteng-portal",
"xiteng-chat",
"code-server",
"comfyui",
"invokeai",
"gitea",
"hedgedoc",
"hedgedoc2",
"minio",
"seaweedfs",
"remark42",
]
liooil = User.objects.get(username=ADMIN_USERNAME)
admin_group = Group.objects.get(name="authentik Admins")
admin_group.users.add(liooil)
User.objects.filter(username="akadmin").delete()
liuhome, _ = Group.objects.get_or_create(
name=FAMILY_GROUP,
defaults={"is_superuser": False},
)
if liuhome.is_superuser:
liuhome.is_superuser = False
liuhome.save(update_fields=["is_superuser"])
for username in ["liooil", "ziyue"]:
liuhome.users.add(User.objects.get(username=username))
for application in Application.objects.filter(slug__in=MANAGED_APPLICATIONS):
PolicyBinding.objects.filter(target=application).delete()
PolicyBinding.objects.create(
target=application,
group=liuhome,
order=0,
enabled=True,
negate=False,
failure_result=False,
)
Token.objects.filter(identifier=TOKEN_IDENTIFIER).delete()
token = Token.objects.create(
identifier=TOKEN_IDENTIFIER,
intent=TokenIntents.INTENT_API,
user=liooil,
description="Xiteng Portal Authentik administration",
expiring=False,
)
TOKEN_PATH.write_text(token.key, encoding="utf-8")
TOKEN_PATH.chmod(0o600)
print(
f"Identity bootstrap complete: administrator={ADMIN_USERNAME}, "
f"group={FAMILY_GROUP}, applications={len(MANAGED_APPLICATIONS)}"
)
+97 -14
View File
@@ -1,6 +1,6 @@
services:
authentik:
image: ghcr.io/goauthentik/server:2026.5.0
image: ghcr.io/goauthentik/server:2026.5.6
container_name: authentik
restart: unless-stopped
command: server
@@ -18,6 +18,13 @@ services:
volumes:
- ./media:/media
- ./custom-templates:/templates
- ./assets:/web/dist/assets/xiteng:ro
- ./blueprints/xiteng-brand.yaml:/blueprints/xiteng-brand.yaml:ro
- ./blueprints/xiteng-chat.yaml:/blueprints/xiteng-chat.yaml:ro
- ./blueprints/xiteng-recovery.yaml:/blueprints/xiteng-recovery.yaml:ro
- ./blueprints/xiteng-passwordless.yaml:/blueprints/xiteng-passwordless.yaml:ro
- ./bootstrap.py:/bootstrap/portal_identity.py:ro
- ./secrets:/run/secrets
labels:
# --- Traefik 路由 ---
- "traefik.enable=true"
@@ -34,37 +41,113 @@ services:
- "traefik.http.routers.authentik-http.rule=Host(`auth.xiteng.site`)"
- "traefik.http.routers.authentik-http.service=authentik"
- "traefik.http.routers.authentik-http.entrypoints=web"
# --- Homepage ---
- "homepage.group=我的服务"
- "homepage.name=Authentik"
- "homepage.icon=authentik"
- "homepage.href=https://auth.xiteng.site"
- "homepage.description=统一认证"
# --- AutoKuma ---
- "kuma.authentik.http.name=Authentik"
- "kuma.authentik.http.url=http://authentik:9000/-/health/ready/"
- "kuma.authentik.http.interval=60"
- "kuma.authentik.http.max_retries=3"
# Label-managed static paths. The shared edge-cache controller creates
# higher-priority Traefik routes and the matching Cloudflare Cache Rule.
- "xiteng.site.cache.frontend.enabled=true"
- "xiteng.site.cache.frontend.routers=authentik,authentik-http"
- "xiteng.site.cache.frontend.paths=/static/dist/"
- "xiteng.site.cache.frontend.edge-ttl=604800"
- "xiteng.site.cache.frontend.stale-while-revalidate=86400"
# --- Xiteng Site dynamic catalog ---
- "xiteng.site.component.authentik.enabled=true"
- "xiteng.site.component.authentik.name=Authentik"
- "xiteng.site.component.authentik.description=隐藏的 OAuth2/OIDC、ForwardAuth 与身份数据引擎;通过 Portal 管理。"
- "xiteng.site.component.authentik.section=infrastructure"
- "xiteng.site.component.authentik.category=身份与访问"
- "xiteng.site.component.authentik.access=internal"
- "xiteng.site.component.authentik.access-label=后台身份引擎 · 通过 Portal 管理"
- "xiteng.site.component.authentik.icon=AK"
- "xiteng.site.component.authentik.icon-url=https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authentik.svg"
- "xiteng.site.component.authentik.accent=red"
- "xiteng.site.component.authentik.order=200"
- "xiteng.site.component.authentik.monitor.enabled=true"
- "xiteng.site.component.authentik.monitor.url=http://authentik:9000/-/health/ready/"
networks:
- homelab_net
worker:
image: ghcr.io/goauthentik/server:2026.5.6
container_name: authentik-worker
restart: unless-stopped
command: worker
env_file:
- .env
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
AUTHENTIK_AVATARS: none
AUTHENTIK_LOG_LEVEL: info
user: root
volumes:
- ./media:/media
- ./custom-templates:/templates
- ./blueprints/xiteng-recovery.yaml:/blueprints/xiteng-recovery.yaml:ro
- ./blueprints/xiteng-brand.yaml:/blueprints/xiteng-brand.yaml:ro
- ./blueprints/xiteng-chat.yaml:/blueprints/xiteng-chat.yaml:ro
- ./blueprints/xiteng-passwordless.yaml:/blueprints/xiteng-passwordless.yaml:ro
labels:
- "traefik.enable=false"
- "xiteng.site.component.authentik-worker.enabled=true"
- "xiteng.site.component.authentik-worker.name=Authentik Worker"
- "xiteng.site.component.authentik-worker.description=执行身份系统后台任务并自动应用 Blueprint。"
- "xiteng.site.component.authentik-worker.section=infrastructure"
- "xiteng.site.component.authentik-worker.category=身份与访问"
- "xiteng.site.component.authentik-worker.access=internal"
- "xiteng.site.component.authentik-worker.access-label=后台任务组件"
- "xiteng.site.component.authentik-worker.icon=WK"
- "xiteng.site.component.authentik-worker.icon-url=https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/authentik.svg"
- "xiteng.site.component.authentik-worker.accent=yellow"
- "xiteng.site.component.authentik-worker.order=205"
depends_on:
- postgres
- redis
networks:
- homelab_net
postgres:
image: postgres:16-alpine
image: postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
container_name: authentik-db
restart: unless-stopped
environment:
POSTGRES_USER: authentik
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASSWORD}
POSTGRES_DB: authentik
labels:
- "xiteng.site.component.authentik-db.enabled=true"
- "xiteng.site.component.authentik-db.name=Authentik PostgreSQL"
- "xiteng.site.component.authentik-db.description=保存身份、Provider、应用与策略配置。"
- "xiteng.site.component.authentik-db.section=infrastructure"
- "xiteng.site.component.authentik-db.category=数据层"
- "xiteng.site.component.authentik-db.access=internal"
- "xiteng.site.component.authentik-db.access-label=仅容器网络"
- "xiteng.site.component.authentik-db.icon=PG"
- "xiteng.site.component.authentik-db.icon-url=https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/postgresql.svg"
- "xiteng.site.component.authentik-db.accent=blue"
- "xiteng.site.component.authentik-db.order=510"
volumes:
- ./pgdata:/var/lib/postgresql/data
networks:
- homelab_net
redis:
image: redis:alpine
image: redis:alpine@sha256:978f0e01593e65eed801f2402944efcd936d43b5027e4908a7897baf88ed6241
container_name: authentik-redis
restart: unless-stopped
labels:
- "xiteng.site.component.authentik-redis.enabled=true"
- "xiteng.site.component.authentik-redis.name=Authentik Redis"
- "xiteng.site.component.authentik-redis.description=为身份服务提供缓存与任务状态。"
- "xiteng.site.component.authentik-redis.section=infrastructure"
- "xiteng.site.component.authentik-redis.category=数据层"
- "xiteng.site.component.authentik-redis.access=internal"
- "xiteng.site.component.authentik-redis.access-label=仅容器网络"
- "xiteng.site.component.authentik-redis.icon=RD"
- "xiteng.site.component.authentik-redis.icon-url=https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/redis.svg"
- "xiteng.site.component.authentik-redis.accent=red"
- "xiteng.site.component.authentik-redis.order=520"
volumes:
- ./redis:/data
networks:
View File