- Melos 8.9 as the root's dev dependency, with scripts across the workspace (analyze, test:dart, test:flutter, generate, check) and its filters (--diff, --include-dependents); cli_util overridden to 0.5.2 for it, since Learn's flutter_launcher_icons asks for 0.4 and uses only its logger, unchanged in 0.5. - tool/check.dart in place of tool/check.sh: packages, formatting of our own files, analysis, the tools' tests, then every package's tests through Melos (--changed-since=<commit> for only what changed and what depends on it). tool/pre-push is the hook: the signature check, then the checks. CI generates every server's code and runs the same check. - The images build from the root with a workspace narrowed to what they need (tool/workspace_only.sh); compose, the deploy's migration test, the snap, the release script, the editor's launch files and Learn's asset tools (run from apps/learn) follow the new layout. - The project's source is named as project-halkyon/halkyon (source link, snap, F-Droid listing, release uploads); the AGPL packages carry the licence's own text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
174 lines
7.6 KiB
YAML
174 lines
7.6 KiB
YAML
# The Halkyon Learn instance: Caddy in front, the Serverpod server, PostgreSQL, and
|
|
# the dictionaries kept current beside them.
|
|
#
|
|
# cd deploy
|
|
# cp .env.example .env && chmod 600 .env # then fill in the secrets
|
|
# docker compose up -d --build
|
|
#
|
|
# Only Caddy publishes ports. Docker opens published ports directly in the kernel's
|
|
# packet filter, past ufw, and the Serverpod server listens on every interface, so the
|
|
# server and Postgres publish nothing: they're reachable only inside this project's
|
|
# networks, where Caddy finds the server by its service name.
|
|
#
|
|
# Two networks (decided 2026-10-14): `edge`, Caddy's, which the server shares with it,
|
|
# and the internal default one, where the server reaches Postgres. Caddy never reaches
|
|
# Postgres, nor does a sidecar behind Caddy.
|
|
#
|
|
# Sidecars (decided 2026-10-14) are each a compose file of their own beside this one,
|
|
# switched on in .env (COMPOSE_FILE=compose.yaml:compose.jellyfin.yaml): each adds its
|
|
# service and mounts its site into Caddy's sidecars/ folder (sidecars/README.md).
|
|
|
|
name: halkyon-learn
|
|
|
|
# Logs go to the host's journal, whose size and age limits apply (journald.conf).
|
|
x-logging: &logging
|
|
driver: journald
|
|
|
|
services:
|
|
caddy:
|
|
image: caddy:2
|
|
restart: unless-stopped
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
- "443:443/udp" # HTTP/3
|
|
environment:
|
|
# `learn.halkyon.eu` on its server (automatic Let's Encrypt certificate);
|
|
# `http://localhost` for the local rehearsal (plain HTTP, no certificate).
|
|
DOMAIN: ${DOMAIN:?set DOMAIN in deploy/.env}
|
|
# The account domain whose profile pages this instance serves, where it isn't
|
|
# DOMAIN; empty for none.
|
|
ACCOUNT_SITE: ${ACCOUNT_DOMAIN:-}
|
|
volumes:
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- caddy_data:/data # certificates: must survive restarts (rate limits)
|
|
- caddy_config:/config
|
|
networks:
|
|
edge:
|
|
# Fixed, so containers behind Caddy can trust it as their proxy (the
|
|
# visitor's own address comes in X-Forwarded-For).
|
|
ipv4_address: 172.30.80.2
|
|
depends_on:
|
|
- server
|
|
logging: *logging
|
|
|
|
server:
|
|
build:
|
|
context: ..
|
|
dockerfile: apps/learn/halkyon_learn_server/Dockerfile
|
|
image: halkyon-learn-server
|
|
restart: unless-stopped
|
|
# Applies pending migrations at every start, then serves. One instance only, so
|
|
# no two servers ever migrate at once. (On learn.halkyon.eu, deploy/redeploy.sh has
|
|
# applied them in a run of their own before, so nothing is left to do here.)
|
|
command: ["--apply-migrations"]
|
|
environment:
|
|
SERVERPOD_DATABASE_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in deploy/.env}
|
|
SERVERPOD_PASSWORD_serverSideSessionKeyHashPepper: ${SESSION_KEY_HASH_PEPPER:?set SESSION_KEY_HASH_PEPPER in deploy/.env}
|
|
# GitHub sign-in, only with this instance's GitHub OAuth app; empty: off.
|
|
SERVERPOD_PASSWORD_githubClientId: ${GITHUB_CLIENT_ID:-}
|
|
SERVERPOD_PASSWORD_githubClientSecret: ${GITHUB_CLIENT_SECRET:-}
|
|
# Email sign-in (ADR 0010), only with the pepper and an SMTP service; empty: off.
|
|
SERVERPOD_PASSWORD_emailSignInPepper: ${EMAIL_SIGN_IN_PEPPER:-}
|
|
SERVERPOD_PASSWORD_emailSmtpHost: ${EMAIL_SMTP_HOST:-}
|
|
SERVERPOD_PASSWORD_emailSmtpPort: ${EMAIL_SMTP_PORT:-}
|
|
SERVERPOD_PASSWORD_emailSmtpUser: ${EMAIL_SMTP_USER:-}
|
|
SERVERPOD_PASSWORD_emailSmtpPassword: ${EMAIL_SMTP_PASSWORD:-}
|
|
SERVERPOD_PASSWORD_emailFrom: ${EMAIL_FROM:-}
|
|
# Where the source of what this instance runs is; empty: the project's.
|
|
SOURCE_URL: ${SOURCE_URL:-}
|
|
# The domain people's handles are on; empty: DOMAIN's host.
|
|
ACCOUNT_DOMAIN: ${ACCOUNT_DOMAIN:-}
|
|
# The Jellyfin server suggested to people; empty: jellyfin. on the
|
|
# instance's own domain, if one answers there; `none`: none.
|
|
JELLYFIN_URL: ${JELLYFIN_URL:-}
|
|
# Where people's data and the backups are, which the app shows: `EU`, a
|
|
# country (`DE`) or a subdivision (`US-CA`); empty: it says nothing.
|
|
DATA_LOCATION: ${DATA_LOCATION:-}
|
|
BACKUP_LOCATION: ${BACKUP_LOCATION:-}
|
|
# How much room people's files may take, in MiB; empty: the defaults.
|
|
FILE_LIMIT_ANONYMOUS_MB: ${FILE_LIMIT_ANONYMOUS_MB:-}
|
|
FILE_LIMIT_IDENTIFIED_MB: ${FILE_LIMIT_IDENTIFIED_MB:-}
|
|
FILE_LIMIT_INSTANCE_MB: ${FILE_LIMIT_INSTANCE_MB:-}
|
|
# The git repositories of published studysets (ADR 0005, rule 3).
|
|
REPOSITORIES_DIR: /app/repositories
|
|
# The dictionary files it loads at start and rereads hourly, those that changed
|
|
# (see the server's README): those the dictionaries service keeps current, read
|
|
# only. To supply files yourself instead, remove that service and mount
|
|
# ./dictionaries here (`- ./dictionaries:/app/dictionaries:ro`).
|
|
volumes:
|
|
- dictionaries:/app/dictionaries:ro
|
|
# Every release of every published studyset: backed up beside the database
|
|
# (backup.sh), never deleted with `down -v`.
|
|
- repositories:/app/repositories
|
|
networks:
|
|
- default
|
|
- edge
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
logging: *logging
|
|
|
|
# Keeps the dictionary files current from EDRDG's files of the day, once they are a
|
|
# week old (EDRDG's licence asks it of servers showing its dictionaries at least
|
|
# monthly; the curator's README), and the languages of DICTIONARY_LANGUAGES: Chinese
|
|
# from MDBG's CC-CEDICT as often, the rest from Wiktionary's dump whenever kaikki.org
|
|
# makes a new one (about monthly, some 3 GB, converted as it downloads): in a
|
|
# container of its own, the curator never in the server's image or process. It only
|
|
# downloads and writes into the volume; the files are never backed up, as they can be
|
|
# had again.
|
|
dictionaries:
|
|
build:
|
|
context: ..
|
|
dockerfile: apps/learn/halkyon_learn_curator/Dockerfile
|
|
image: halkyon-learn-dictionaries
|
|
restart: unless-stopped
|
|
# Docker's stop signal reaches the curator through a small init.
|
|
init: true
|
|
# JMdict's conversion takes some 400 MB for a minute a week, Wiktionary's some
|
|
# 300 MB for ten minutes a month, KRDict's some 600 MB for half a minute.
|
|
mem_limit: 1g
|
|
# Unset: German, Greek, English, Korean and Chinese; empty: none.
|
|
command: ["--every", "7", "--languages", "${DICTIONARY_LANGUAGES-de,el,en,ko,zh}"]
|
|
volumes:
|
|
- dictionaries:/dictionaries
|
|
logging: *logging
|
|
|
|
postgres:
|
|
image: postgres:16
|
|
restart: unless-stopped
|
|
# The server's session is UTC anyway (the driver sets it); this makes the
|
|
# database's own default UTC too, for psql, COPY loads and future sidecars.
|
|
command: ["postgres", "-c", "timezone=UTC", "-c", "log_timezone=UTC"]
|
|
environment:
|
|
POSTGRES_USER: halkyon_learn
|
|
POSTGRES_DB: halkyon_learn
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in deploy/.env}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U halkyon_learn -d halkyon_learn"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 12
|
|
logging: *logging
|
|
|
|
networks:
|
|
edge:
|
|
name: halkyon-learn-edge
|
|
ipam:
|
|
config:
|
|
- subnet: 172.30.80.0/24
|
|
gateway: 172.30.80.1
|
|
# Containers without an address of their own (the server) get one from the
|
|
# upper half, so the fixed ones below it (Caddy .2, sidecars from .10) are
|
|
# never taken: the server started first once and took Caddy's (2026-10-14).
|
|
# (With a range alone, Docker would move the gateway to its first address.)
|
|
ip_range: 172.30.80.128/25
|
|
|
|
volumes:
|
|
caddy_data:
|
|
caddy_config:
|
|
dictionaries:
|
|
postgres_data:
|
|
repositories:
|