feat(demo): migrate 5 SelfStack services to demo stack (16→24 services)

Add Reactive Resume, Metrics, Kiwix, Resume Matcher, and Apple Health
from the earlier SelfStack project. Rewrite Apple Health collector to
use InfluxDB v2 with proper error handling. Update all tests, scripts,
Homepage config, env template, and documentation for the expanded stack.

New services:
- Reactive Resume (4016) + Postgres/Minio/Chrome companions
- Metrics (4021) - GitHub metrics visualization
- Kiwix (4022) - offline wiki reader
- Resume Matcher (4023) - AI resume screening
- Apple Health (4024) - health data collector → InfluxDB v2

Also adds git policy to AGENTS.md: always commit and push automatically.

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
2026-05-08 12:28:56 -05:00
parent 1628b1dfea
commit 25f7a6cd75
22 changed files with 747 additions and 22 deletions

View File

@@ -43,6 +43,14 @@ volumes:
driver: local
${COMPOSE_PROJECT_NAME}_atuin_data:
driver: local
${COMPOSE_PROJECT_NAME}_reactiveresume_postgres_data:
driver: local
${COMPOSE_PROJECT_NAME}_reactiveresume_minio_data:
driver: local
${COMPOSE_PROJECT_NAME}_kiwix_data:
driver: local
${COMPOSE_PROJECT_NAME}_resumematcher_data:
driver: local
services:
# Docker Socket Proxy - Security Layer
@@ -585,3 +593,260 @@ services:
timeout: ${HEALTH_CHECK_TIMEOUT}
retries: 5
start_period: 30s
# Reactive Resume - Postgres Database
reactiveresume-postgres:
image: postgres:16-alpine
container_name: "${COMPOSE_PROJECT_NAME}-reactiveresume-postgres"
restart: unless-stopped
networks:
- ${COMPOSE_NETWORK_NAME}
volumes:
- ${COMPOSE_PROJECT_NAME}_reactiveresume_postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${RESUME_POSTGRES_DB}
POSTGRES_USER: ${RESUME_POSTGRES_USER}
POSTGRES_PASSWORD: ${RESUME_POSTGRES_PASSWORD}
deploy:
resources:
limits:
memory: 256M
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${RESUME_POSTGRES_USER} -d ${RESUME_POSTGRES_DB}"]
interval: ${HEALTH_CHECK_INTERVAL}
timeout: ${HEALTH_CHECK_TIMEOUT}
retries: 5
# Reactive Resume - Minio Storage
reactiveresume-minio:
image: minio/minio
container_name: "${COMPOSE_PROJECT_NAME}-reactiveresume-minio"
restart: unless-stopped
command: server /data
networks:
- ${COMPOSE_NETWORK_NAME}
ports:
- "${RESUME_MINIO_PORT}:9000"
volumes:
- ${COMPOSE_PROJECT_NAME}_reactiveresume_minio_data:/data
environment:
MINIO_ROOT_USER: ${RESUME_MINIO_USER}
MINIO_ROOT_PASSWORD: ${RESUME_MINIO_PASSWORD}
deploy:
resources:
limits:
memory: 256M
healthcheck:
test: ["CMD", "curl", "-f", "--silent", "http://localhost:9000/minio/health/live"]
interval: ${HEALTH_CHECK_INTERVAL}
timeout: ${HEALTH_CHECK_TIMEOUT}
retries: ${HEALTH_CHECK_RETRIES}
# Reactive Resume - Chrome (PDF Generation)
reactiveresume-chrome:
image: ghcr.io/browserless/chromium:latest
container_name: "${COMPOSE_PROJECT_NAME}-reactiveresume-chrome"
restart: unless-stopped
networks:
- ${COMPOSE_NETWORK_NAME}
environment:
TIMEOUT: 10000
CONCURRENT: 10
TOKEN: ${RESUME_CHROME_TOKEN}
EXIT_ON_HEALTH_FAILURE: true
PRE_REQUEST_HEALTH_CHECK: true
deploy:
resources:
limits:
memory: 512M
healthcheck:
test: ["CMD", "curl", "-f", "--silent", "http://localhost:3000/health"]
interval: ${HEALTH_CHECK_INTERVAL}
timeout: ${HEALTH_CHECK_TIMEOUT}
retries: ${HEALTH_CHECK_RETRIES}
start_period: 30s
# Reactive Resume - Resume Builder
reactiveresume-app:
image: amruthpillai/reactive-resume:latest
container_name: "${COMPOSE_PROJECT_NAME}-reactiveresume-app"
restart: unless-stopped
networks:
- ${COMPOSE_NETWORK_NAME}
ports:
- "${REACTIVE_RESUME_PORT}:3000"
depends_on:
reactiveresume-postgres:
condition: service_healthy
reactiveresume-minio:
condition: service_started
reactiveresume-chrome:
condition: service_started
environment:
PORT: 3000
NODE_ENV: production
PUBLIC_URL: http://localhost:${REACTIVE_RESUME_PORT}
STORAGE_URL: http://localhost:${RESUME_MINIO_PORT}/default
CHROME_TOKEN: ${RESUME_CHROME_TOKEN}
CHROME_URL: ws://reactiveresume-chrome:3000
DATABASE_URL: postgresql://${RESUME_POSTGRES_USER}:${RESUME_POSTGRES_PASSWORD}@reactiveresume-postgres:5432/${RESUME_POSTGRES_DB}
ACCESS_TOKEN_SECRET: ${RESUME_ACCESS_TOKEN_SECRET}
REFRESH_TOKEN_SECRET: ${RESUME_REFRESH_TOKEN_SECRET}
MAIL_FROM: noreply@localhost
STORAGE_ENDPOINT: reactiveresume-minio
STORAGE_PORT: 9000
STORAGE_REGION: us-east-1
STORAGE_BUCKET: default
STORAGE_ACCESS_KEY: ${RESUME_MINIO_USER}
STORAGE_SECRET_KEY: ${RESUME_MINIO_PASSWORD}
STORAGE_USE_SSL: "false"
STORAGE_SKIP_BUCKET_CHECK: "false"
labels:
homepage.group: "Productivity"
homepage.name: "Reactive Resume"
homepage.icon: "reactive-resume"
homepage.href: "http://localhost:${REACTIVE_RESUME_PORT}"
homepage.description: "Open-source resume builder"
deploy:
resources:
limits:
memory: 512M
healthcheck:
test: ["CMD", "curl", "-f", "--silent", "http://localhost:3000/api/health"]
interval: ${HEALTH_CHECK_INTERVAL}
timeout: ${HEALTH_CHECK_TIMEOUT}
retries: 5
start_period: 30s
# Metrics - GitHub Metrics Visualization
metrics:
image: ghcr.io/lowlighter/metrics:latest
container_name: "${COMPOSE_PROJECT_NAME}-metrics"
restart: unless-stopped
entrypoint: [""]
command: ["npm", "start"]
networks:
- ${COMPOSE_NETWORK_NAME}
ports:
- "${METRICS_PORT}:3000"
volumes:
- ./config/metrics/settings.json:/metrics/settings.json:ro
environment:
- PUID=${DEMO_UID}
- PGID=${DEMO_GID}
labels:
homepage.group: "Monitoring"
homepage.name: "Metrics"
homepage.icon: "github"
homepage.href: "http://localhost:${METRICS_PORT}"
homepage.description: "GitHub metrics visualization"
deploy:
resources:
limits:
memory: 256M
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000"]
interval: ${HEALTH_CHECK_INTERVAL}
timeout: ${HEALTH_CHECK_TIMEOUT}
retries: ${HEALTH_CHECK_RETRIES}
start_period: 30s
# Kiwix - Offline Wiki
kiwix:
image: ghcr.io/kiwix/kiwix-serve:latest
container_name: "${COMPOSE_PROJECT_NAME}-kiwix"
restart: unless-stopped
networks:
- ${COMPOSE_NETWORK_NAME}
ports:
- "${KIWIX_PORT}:8080"
volumes:
- ${COMPOSE_PROJECT_NAME}_kiwix_data:/data
environment:
- PUID=${DEMO_UID}
- PGID=${DEMO_GID}
labels:
homepage.group: "Documentation"
homepage.name: "Kiwix"
homepage.icon: "kiwix"
homepage.href: "http://localhost:${KIWIX_PORT}"
homepage.description: "Offline wiki reader"
deploy:
resources:
limits:
memory: 256M
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080"]
interval: ${HEALTH_CHECK_INTERVAL}
timeout: ${HEALTH_CHECK_TIMEOUT}
retries: ${HEALTH_CHECK_RETRIES}
# Resume Matcher - AI Resume Screening
resumematcher:
image: ghcr.io/srbhr/resume-matcher:latest
container_name: "${COMPOSE_PROJECT_NAME}-resumematcher"
restart: unless-stopped
networks:
- ${COMPOSE_NETWORK_NAME}
ports:
- "${RESUME_MATCHER_PORT}:3000"
volumes:
- ${COMPOSE_PROJECT_NAME}_resumematcher_data:/app/backend/data
environment:
- PUID=${DEMO_UID}
- PGID=${DEMO_GID}
labels:
homepage.group: "Productivity"
homepage.name: "Resume Matcher"
homepage.icon: "resume"
homepage.href: "http://localhost:${RESUME_MATCHER_PORT}"
homepage.description: "AI-powered resume screening"
deploy:
resources:
limits:
memory: 512M
healthcheck:
test: ["CMD", "curl", "-f", "--silent", "http://localhost:3000/api/v1/health"]
interval: ${HEALTH_CHECK_INTERVAL}
timeout: ${HEALTH_CHECK_TIMEOUT}
retries: 5
start_period: 60s
# Apple Health - Health Data Collector
applehealth:
build:
context: ./config/applehealth
dockerfile: Dockerfile
image: tsys-applehealth:latest
container_name: "${COMPOSE_PROJECT_NAME}-applehealth"
restart: unless-stopped
networks:
- ${COMPOSE_NETWORK_NAME}
ports:
- "${APPLEHEALTH_PORT}:5353"
environment:
- INFLUXDB_URL=http://influxdb:8086
- INFLUXDB_TOKEN=${INFLUXDB_AUTH_TOKEN}
- INFLUXDB_ORG=${INFLUXDB_ORG}
- INFLUXDB_BUCKET=${INFLUXDB_BUCKET}
- PUID=${DEMO_UID}
- PGID=${DEMO_GID}
depends_on:
influxdb:
condition: service_healthy
labels:
homepage.group: "Monitoring"
homepage.name: "Apple Health"
homepage.icon: "apple-health"
homepage.href: "http://localhost:${APPLEHEALTH_PORT}"
homepage.description: "Health data collection and visualization"
deploy:
resources:
limits:
memory: 256M
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5353/health')"]
interval: ${HEALTH_CHECK_INTERVAL}
timeout: ${HEALTH_CHECK_TIMEOUT}
retries: ${HEALTH_CHECK_RETRIES}
start_period: 15s