# heather — Arion compose (nix-native docker-compose). # # Все web-apps в docker-контейнерах, описаны декларативно в nix. # Запускается как systemd-сервис через virtualisation.arion.projects.heather. # # Порты проброшены на localhost (127.0.0.1:PORT) — Caddy на хосте терминирует # TLS и роутит по Host (см. caddy.nix). Контейнеры НЕ выставлены наружу # напрямую (кроме forgejo git ssh на :2222). # # Data: named volumes везде, кроме silverbullet (bind mount на syncthing # sync'аемую папку /srv/syncthing/herbarium — исключение, см. комментарии). # # Backup: offen/docker-volume-backup как sidecar, nightly tar.gz всех named # volumes → /var/backups (добавим последним). # # Порядок миграции: forgejo → tuwunel+admin → cinny → teable → # silverbullet. Ниже forgejo как первый proof-of-concept, остальные добавим # по мере миграции (секции заготовлены, закомментированы до теста). { pkgs, ... }: { # Обязательное имя проекта (COMPOSE_PROJECT_NAME). Объекты префиксуются им: # heather_forgejo-data и т.д. project.name = "heather"; # --- Named volumes (декларируем явно, никаких анонимных хэшей) --- # docker volume create при первом up. Видно в `docker volume ls`. docker-compose.volumes = { forgejo-data = {}; tuwunel-data = {}; tuwunel-media-cache = {}; teable-data = {}; teable-db = {}; teable-redis = {}; teable-assets = {}; }; services = { # === Forgejo (git.iscg.dev) === # Первый сервис, proof-of-concept для цепочки arion→docker→Caddy. forgejo = { service.image = "codeberg.org/forgejo/forgejo:9"; service.container_name = "forgejo"; service.restart = "unless-stopped"; service.volumes = [ "forgejo-data:/data" ]; service.ports = [ "127.0.0.1:3030:3000" # web → Caddy git.iscg.dev "2222:22" # git ssh (публично, как на jul11) ]; service.environment = { # Домен для клонов: ssh://git@git.iscg.dev:2222/... FORGEJO__server__DOMAIN = "git.iscg.dev"; FORGEJO__server__ROOT_URL = "https://git.iscg.dev/"; FORGEJO__server__SSH_DOMAIN = "git.iscg.dev"; FORGEJO__server__SSH_PORT = "2222"; }; }; # === Matrix tuwunel + admin + cinny (iscg.dev, admin/chat.iscg.dev) === # Добавить после forgejo. Crashloop на jul11 — нужно разобраться с # конфигом/секретами до миграции. См. migration-inventory.md. # # tuwunel = { # service.image = "ghcr.io/matrix-construct/tuwunel:v1.7.1"; # service.container_name = "tuwunel"; # service.restart = "unless-stopped"; # service.volumes = [ # "tuwunel-data:/data" # "tuwunel-media-cache:/media-cache" # ]; # service.ports = [ "127.0.0.1:8008:8008" ]; # # TODO: env vars, secrets (server_name, registration_shared_secret, ...) # }; # cinny = { # service.image = "ajbura/cinny:latest"; # service.container_name = "cinny"; # service.restart = "unless-stopped"; # service.ports = [ "127.0.0.1:8080:80" ]; # }; # tuwunel-admin был systemd на jul11 (knadh/tuwunel-admin). Если контейнер # существует — добавить сюда, иначе оставить как NixOS-сервис или выкинуть. # === Teable (teable.iscg.dev) === # Стек: app + postgres + redis. Данные перенесены с iscg-dev (2026-07-12): # - postgres восстановлен из pg_dump (18M, consistent, без остановки источника) # - .assets (3.2G, 4392 файлов) в named volume heather_teable-assets # - teable-data (local-storage) пустой — наполняется при работе # - redis пустой — кэш, перестраивается # Секреты (POSTGRES_PASSWORD, SECRET_KEY) новые, в pass iscg.dev/teable/. # НЕ пушить env-файл в forgejo — secrets. teable = { # Пин по digest: :latest (release 2198, 2026-07-11) сломал роутинг — # Next.js отдаёт 404 на / и /health, assets под /plugin/. Образ на iscg-dev # (март 2026, digest baddd95f) работает: / → 307 → /space. Пиним его. # TODO: разобраться с роутингом новой версии (возможно нужны NEXT_PUBLIC_* env). service.image = "teableio/teable@sha256:baddd95f6c944334a30d17bddb209c4af04b52e619811a4c3997ae3adb8ad42e"; service.container_name = "teable"; service.restart = "unless-stopped"; service.ports = [ "127.0.0.1:2345:3000" ]; service.volumes = [ "teable-data:/app/.local-storage" "teable-assets:/app/.assets" ]; service.depends_on = [ "teable-db" "teable-redis" ]; service.env_file = [ "/var/lib/teable-secrets/teable.env" ]; service.environment = { POSTGRES_HOST = "teable-db"; POSTGRES_PORT = "5432"; POSTGRES_DB = "teable"; POSTGRES_USER = "teable"; REDIS_URL = "redis://teable-redis:6379"; PUBLIC_DOMAIN = "teable.iscg.dev"; PUBLIC_ORIGIN = "https://teable.iscg.dev"; }; }; teable-db = { service.image = "postgres:15-alpine"; service.container_name = "teable-db"; service.restart = "unless-stopped"; service.volumes = [ "teable-db:/var/lib/postgresql/data" ]; service.env_file = [ "/var/lib/teable-secrets/teable.env" ]; }; teable-redis = { service.image = "redis:alpine"; service.container_name = "teable-redis"; service.restart = "unless-stopped"; service.command = "redis-server --appendonly yes"; service.volumes = [ "teable-redis:/data" ]; }; # === Silverbullet (note.iscg.dev) — ИСКЛЮЧЕНИЕ: bind mount === # Space = /srv/syncthing/herbarium (sync'ается syncthing на устройства). # Единственный сервис без named volume — по твоему решению, чтобы notes # были доступны и через web UI, и через obsidian/vscode на poppy. # Конфликты syncthing (одновременная правка) решаются через .sync-conflict. # Данные перенесены с iscg-dev (2026-07-12), auth-файл .silverbullet.auth.json # в составе данных — silverbullet подхватит. silverbullet = { service.image = "zefhemel/silverbullet:latest"; service.container_name = "silverbullet"; service.restart = "unless-stopped"; service.ports = [ "127.0.0.1:3001:3000" ]; service.volumes = [ "/srv/syncthing/herbarium:/space" # bind mount, НЕ named volume ]; service.environment = { SB_USER = "mikl:milkingfemboys"; SB_INDEX_PAGE = "flowers"; }; }; # === Backup sidecar (последним) === # offen/docker-volume-backup: nightly tar.gz всех named volumes. # Монтирует volumes read-only, шлёт в /var/backups (или S3). # TODO: добавить когда основные сервисы мигрированы. }; }