x-restart: &restart restart: unless-stopped services: postgres: image: postgres:16-alpine profiles: [db] <<: *restart env_file: .env volumes: - postgres-data:/var/lib/postgresql/data ports: - "127.0.0.1:${PORT_POSTGRES:-5432}:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 5s timeout: 3s retries: 20 obsidian-headless: container_name: beaver-obsidian build: context: . dockerfile: obsidian.Dockerfile profiles: [obsidian] <<: *restart volumes: - vault:/vault - obsidian-config:/root/.config working_dir: /vault gateway: container_name: beaver-gateway build: context: https://git.kotikot.com/beaver/beaver-gateway.git#${GATEWAY_REF:-main} profiles: [gateway] <<: *restart depends_on: postgres: condition: service_healthy env_file: .env environment: CONFIG_PATH: /config/config.py DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-beaver}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-beaver} RAYCAST_CONFIG_PATH: /config/config.json # Процесс модели: свой uid, свой HOME (там ~/.claude), белый список env # собирает адаптер. В env модели нет DATABASE_URL, POSTGRES_*, *_PAT. CLAUDE_RUNNER_USER: beaver-runner CLAUDE_HOME: /home/beaver-runner ports: # один порт: /anthropic, /mcp, /admin, /api, /md, / -> админка - "${PORT_GATEWAY:-62990}:62990" volumes: - ./config.py:/config/config.py:ro - ./policy.py:/config/policy.py:ro - ./mcps:/config/mcps:ro - ./config.json:/config/config.json:ro # §3.7: vault - дом диспетчера, rw целиком; границы (`.obsidian`, # `мета` вне `бобер`, удаление чужого) держит policy.py (2026-08-30). - vault:/vault # клон репы комнаты вайбграма (сервис vibegram-repo), только читать - vibegram:/vibegram:ro - claude-home:/home/beaver-runner/.claude extra_hosts: # Home Assistant и прочее с network_mode: host - "host.docker.internal:host-gateway" healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:62990/healthz', timeout=3)"] interval: 30s timeout: 5s start_period: 60s retries: 3 entrypoint: - /bin/sh - -c - | set -e runner=beaver-runner mkdir -p /home/$$runner/.claude [ -e /home/$$runner/.claude/.claude.json ] || echo '{}' > /home/$$runner/.claude/.claude.json chown -R $$runner /home/$$runner # ACL, не chown: Obsidian Sync переписывает файлы под root, а default-ACL # на папках даёт runner'у права и на то, что появится позже. setfacl -R -m "u:$$runner:rwX" -m "d:u:$$runner:rwX" /vault \ || echo "warning: cannot grant $$runner write access to /vault" exec python -m beaver_gateway t3code-mcp: container_name: beaver-t3code-mcp build: context: https://git.kotikot.com/beaver/t3code-mcp.git#${T3CODE_MCP_REF:-main} profiles: [t3] <<: *restart env_file: .env environment: # события тредов (завершён / упал / спрашивает) → инжект в мастер (§4.5) T3CODE_MCP_HOOK_URL: http://gateway:62990/hooks/t3code T3CODE_MCP_STATE: /data/t3code-mcp.json volumes: - ./t3code.toml:/config/t3code.toml:ro - t3code-state:/data vibegram-repo: container_name: beaver-vibegram-repo image: alpine/git:latest profiles: [vibegram] <<: *restart env_file: .env volumes: - vibegram:/vibegram # Клон репы комнаты и pull раз в 10 минут. Токен - только заголовком # через GIT_CONFIG_*, в .git/config он не попадает: том читает модель. entrypoint: - /bin/sh - -c - | repo="$${VIBEGRAM_REPO:?VIBEGRAM_REPO=org/repo}" dir="/vibegram/$${repo##*/}" if [ -n "$$VIBEGRAM_REPO_TOKEN" ]; then export GIT_CONFIG_COUNT=1 export GIT_CONFIG_KEY_0="http.https://github.com/.extraheader" export GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $$(printf 'x-access-token:%s' "$$VIBEGRAM_REPO_TOKEN" | base64 | tr -d '\n')" fi while true; do if [ -d "$$dir/.git" ]; then git -C "$$dir" pull -q --ff-only || echo "vibegram-repo: pull failed" else git clone -q "https://github.com/$$repo.git" "$$dir" || echo "vibegram-repo: clone failed" fi chmod -R a+rX "$$dir" 2>/dev/null sleep "$${VIBEGRAM_PULL_SECONDS:-600}" done volumes: t3code-state: postgres-data: vault: vibegram: obsidian-config: claude-home: