services: metrics: image: python:3-slim@sha256:a7fb1e634c4a578f9e0bd6327f11a3cde11b7a9395f48e24360c0988bcc5c2bc container_name: xiteng-site-metrics restart: unless-stopped command: ["python3", "/app/metrics.py"] environment: - PORT=8092 - HOST_PROC=/host/proc - DISK_PATH=/host/disk - CACHE_TTL_SECONDS=2 - NVIDIA_VISIBLE_DEVICES=all - NVIDIA_DRIVER_CAPABILITIES=utility volumes: - ./metrics.py:/app/metrics.py:ro - /proc:/host/proc:ro - /etc/hostname:/host/hostname:ro - ./:/host/disk:ro read_only: true security_opt: - no-new-privileges:true deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] healthcheck: test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8092/healthz', timeout=2)"] interval: 15s timeout: 5s retries: 3 start_period: 5s networks: - homelab_net labels: - "traefik.enable=false" - "xiteng.site.component.host-metrics.enabled=true" - "xiteng.site.component.host-metrics.name=Host Metrics" - "xiteng.site.component.host-metrics.description=采集并公开 CPU、内存、磁盘与 GPU 的实时汇总指标。" - "xiteng.site.component.host-metrics.section=infrastructure" - "xiteng.site.component.host-metrics.category=可观测性" - "xiteng.site.component.host-metrics.access=public" - "xiteng.site.component.host-metrics.access-label=公开只读指标" - "xiteng.site.component.host-metrics.icon=HW" - "xiteng.site.component.host-metrics.accent=green" - "xiteng.site.component.host-metrics.order=590" registry: image: node:24-alpine container_name: xiteng-site-registry restart: unless-stopped command: ["node", "/app/registry.mjs"] environment: - PORT=8091 - DOCKER_SOCKET=/var/run/docker.sock - DATABASE_PATH=/data/registry.db - DISCOVERY_INTERVAL_MS=5000 - MISSING_RETENTION_DAYS=30 - RAW_RETENTION_DAYS=30 - AGGREGATE_RETENTION_DAYS=365 - MONITOR_CONCURRENCY=4 volumes: - ./registry.mjs:/app/registry.mjs:ro - /var/run/docker.sock:/var/run/docker.sock:ro - ./data:/data read_only: true security_opt: - no-new-privileges:true healthcheck: test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8091/healthz').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"] interval: 15s timeout: 5s retries: 3 start_period: 5s networks: - homelab_net labels: - "traefik.enable=false" - "xiteng.site.component.site-registry.enabled=true" - "xiteng.site.component.site-registry.name=Component Registry" - "xiteng.site.component.site-registry.description=只读发现 Docker Label,并向门户输出白名单组件元数据。" - "xiteng.site.component.site-registry.section=infrastructure" - "xiteng.site.component.site-registry.category=门户" - "xiteng.site.component.site-registry.access=internal" - "xiteng.site.component.site-registry.access-label=仅容器网络" - "xiteng.site.component.site-registry.icon=API" - "xiteng.site.component.site-registry.accent=blue" - "xiteng.site.component.site-registry.order=12" xiteng-site: image: node:24-alpine container_name: xiteng-site restart: unless-stopped command: ["node", "/app/server.mjs"] environment: - PORT=8080 - REGISTRY_URL=http://xiteng-site-registry:8091/components - REGISTRY_ADMIN_URL=http://xiteng-site-registry:8091/admin - METRICS_URL=http://xiteng-site-metrics:8092/metrics - KEY_VAULT_URL=http://ai-gateway:8093 - PROVIDER_TEST_URL=http://xiteng-chat:3000/api/provider-test - KEY_VAULT_TOKEN_FILE=/run/secrets/portal_gateway_hmac - ADMIN_USERNAME=liooil - AUTHENTIK_ISSUER=https://auth.xiteng.site - AUTHENTIK_URL=https://auth.xiteng.site - AUTHENTIK_TOKEN_FILE=/run/authentik-secrets/portal_api_token - IDENTITY_AUDIT_PATH=/data/identity-audit.jsonl volumes: - ./index.html:/app/index.html:ro - ./styles.css:/app/styles.css:ro - ./app.js:/app/app.js:ro - ./sw.js:/app/sw.js:ro - ./manifest.webmanifest:/app/manifest.webmanifest:ro - ./favicon.svg:/app/favicon.svg:ro - ./favicon.ico:/app/favicon.ico:ro - ./icons:/app/icons:ro - ./admin.html:/app/admin.html:ro - ./admin.js:/app/admin.js:ro - ./account.html:/app/account.html:ro - ./account.js:/app/account.js:ro - ./authentik.mjs:/app/authentik.mjs:ro - ./server.mjs:/app/server.mjs:ro - ../ai-gateway/secrets/portal_gateway_hmac:/run/secrets/portal_gateway_hmac:ro - ../authentik/secrets/portal_api_token:/run/authentik-secrets/portal_api_token:ro - ./data:/data read_only: true security_opt: - no-new-privileges:true depends_on: registry: condition: service_healthy metrics: condition: service_healthy networks: - homelab_net labels: # Traefik - "traefik.enable=true" - "traefik.http.routers.xiteng-site.rule=Host(`xiteng.site`)" - "traefik.http.routers.xiteng-site.entrypoints=websecure" - "traefik.http.routers.xiteng-site.tls=true" - "traefik.http.routers.xiteng-site.tls.certresolver=cfresolver" - "traefik.http.routers.xiteng-site.service=xiteng-site" - "traefik.http.routers.xiteng-site.middlewares=xiteng-site-nocache" - "traefik.http.services.xiteng-site.loadbalancer.server.port=8080" - "traefik.http.routers.xiteng-site-http.rule=Host(`xiteng.site`)" - "traefik.http.routers.xiteng-site-http.service=xiteng-site" - "traefik.http.routers.xiteng-site-http.entrypoints=web" - "traefik.http.routers.xiteng-site-http.middlewares=xiteng-site-nocache" - "traefik.http.middlewares.xiteng-site-nocache.headers.customresponseheaders.Cache-Control=no-store, no-cache, must-revalidate" - "traefik.http.middlewares.xiteng-site-nocache.headers.customresponseheaders.Pragma=no-cache" - "traefik.http.middlewares.xiteng-site-nocache.headers.customresponseheaders.Expires=0" # Authentik ForwardAuth protects all Portal administration and account routes. - "traefik.http.middlewares.xiteng-admin-scheme.headers.customrequestheaders.X-Forwarded-Proto=https" - "traefik.http.middlewares.xiteng-admin-auth.forwardauth.address=http://authentik-outpost:9000/outpost.goauthentik.io/auth/traefik" - "traefik.http.middlewares.xiteng-admin-auth.forwardauth.trustForwardHeader=true" - "traefik.http.middlewares.xiteng-admin-auth.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-entitlements,X-authentik-email,X-authentik-name,X-authentik-uid" - "traefik.http.middlewares.xiteng-admin-context.headers.customrequestheaders.X-Portal-Authenticated=1" - "traefik.http.routers.xiteng-site-admin.rule=Host(`xiteng.site`) && (PathPrefix(`/admin`) || PathPrefix(`/account`) || PathPrefix(`/api/admin`) || PathPrefix(`/api/account`))" - "traefik.http.routers.xiteng-site-admin.entrypoints=websecure" - "traefik.http.routers.xiteng-site-admin.tls=true" - "traefik.http.routers.xiteng-site-admin.tls.certresolver=cfresolver" - "traefik.http.routers.xiteng-site-admin.service=xiteng-site" - "traefik.http.routers.xiteng-site-admin.middlewares=xiteng-admin-scheme,xiteng-admin-auth,xiteng-admin-context,xiteng-site-nocache" - "traefik.http.routers.xiteng-site-admin.priority=200" - "traefik.http.routers.xiteng-site-admin-http.rule=Host(`xiteng.site`) && (PathPrefix(`/admin`) || PathPrefix(`/account`) || PathPrefix(`/api/admin`) || PathPrefix(`/api/account`))" - "traefik.http.routers.xiteng-site-admin-http.entrypoints=web" - "traefik.http.routers.xiteng-site-admin-http.service=xiteng-site" - "traefik.http.routers.xiteng-site-admin-http.middlewares=xiteng-admin-scheme,xiteng-admin-auth,xiteng-admin-context,xiteng-site-nocache" - "traefik.http.routers.xiteng-site-admin-http.priority=200" # The native Authentik admin UI is hidden; login, OAuth/OIDC and API endpoints remain available. - "traefik.http.routers.authentik-admin-hidden.rule=Host(`auth.xiteng.site`) && PathPrefix(`/if/admin`)" - "traefik.http.routers.authentik-admin-hidden.entrypoints=websecure" - "traefik.http.routers.authentik-admin-hidden.tls=true" - "traefik.http.routers.authentik-admin-hidden.tls.certresolver=cfresolver" - "traefik.http.routers.authentik-admin-hidden.service=xiteng-site" - "traefik.http.routers.authentik-admin-hidden.priority=5000" - "traefik.http.routers.authentik-admin-hidden-http.rule=Host(`auth.xiteng.site`) && PathPrefix(`/if/admin`)" - "traefik.http.routers.authentik-admin-hidden-http.entrypoints=web" - "traefik.http.routers.authentik-admin-hidden-http.service=xiteng-site" - "traefik.http.routers.authentik-admin-hidden-http.priority=5000" - "xiteng.site.cache.portal-static.enabled=true" - "xiteng.site.cache.portal-static.routers=xiteng-site,xiteng-site-http" - "xiteng.site.cache.portal-static.paths=/icons/" - "xiteng.site.cache.portal-static.edge-ttl=604800" - "xiteng.site.cache.portal-static.stale-while-revalidate=86400" # Dynamic catalog - "xiteng.site.component.portal.enabled=true" - "xiteng.site.component.portal.name=Xiteng Portal" - "xiteng.site.component.portal.description=唯一公开入口、个人主页与 Authentik 保护的 Homelab 管理控制面。" - "xiteng.site.component.portal.section=infrastructure" - "xiteng.site.component.portal.category=门户" - "xiteng.site.component.portal.url=https://xiteng.site" - "xiteng.site.component.portal.access=public" - "xiteng.site.component.portal.access-label=公开目录" - "xiteng.site.component.portal.icon=XT" - "xiteng.site.component.portal.icon-url=https://xiteng.site/favicon.svg" - "xiteng.site.component.portal.accent=red" - "xiteng.site.component.portal.order=10" - "xiteng.site.component.portal.monitor.enabled=true" - "xiteng.site.component.portal.monitor.url=http://xiteng-site:8080/healthz" - "xiteng.site.component.portal.monitor.interval=60" - "xiteng.site.component.portal.monitor.failures=3" - "xiteng.site.component.portal.monitor.timeout=10" - "xiteng.site.component.portal.monitor.accept=200-299" networks: homelab_net: external: true