halkyon/deploy/.env.example
Tizian 「ティツィアーン」 1c67310c93
feat(train): OpenFreeMap's dark style in a dark theme, and MAP_DARK_STYLE_URL for a provider's
The maps follow the theme where the source names a dark style, which
only the instance's own map did: OpenFreeMap, the default source, had
none, so a dark phone showed Liberty's bright map. The default now
hands OpenFreeMap's Dark style beside Liberty, and an operator setting
MAP_STYLE_URL may set MAP_DARK_STYLE_URL (HTTPS, else none) beside it,
passed on by compose.train.yaml and named in .env.example.
ADR 0030, D65.

Whats-New: The map is dark in a dark theme.
Whats-New-De: Die Karte ist im dunklen Design dunkel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-05 08:58:33 +01:00

216 lines
13 KiB
Shell

# Copy to deploy/.env, restrict it (chmod 600), and fill in. deploy/.env is git-ignored
# and never goes into an image. How it reaches the server is decided when the server is
# set up (e.g. rendered from Proton Pass on the deploying machine and copied over SSH,
# so the server itself never holds Proton credentials).
# Halkyon Learn's domain, as TRAIN_DOMAIN and TODO_DOMAIN are Train's and Todo's (named
# DOMAIN until 2 Nov 2026): `http://localhost` for the local rehearsal;
# `learn.halkyon.eu` on its server.
LEARN_DOMAIN=http://localhost
# The domain people's handles are on (`raisondetredev@halkyon.eu`) and their profile
# pages (`https://halkyon.eu/@raisondetredev`, ADR 0011), when it isn't LEARN_DOMAIN's
# host: point its DNS (A and AAAA) at this server, and Caddy serves the profile pages
# there, nothing else. Unset: LEARN_DOMAIN's host. Decide before anyone follows a
# handle: it can never change once the fediverse knows it.
#ACCOUNT_DOMAIN=halkyon.eu
# The instance's other services (optional, ADR 0016), such as Halkyon Todo: Halkyon
# Learn holds the instance's register of accounts, so an address, a SimpleLogin account
# and a handle are one person in every service, and deleting the account deletes it in
# all. The register learns when someone signs in to which service (the privacy page says
# so).
# INSTANCE_SECRET is the secret the services share, carried by every call between them:
# the same value in each service's environment, never given to a device. Empty: no
# service calls another. Generate: openssl rand -hex 32
#INSTANCE_SECRET=
# The web servers of the other services as this server reaches them on the instance's
# own network (never through the internet), separated by commas or spaces
# (`http://todo-server:8082`): told when an account goes, so each deletes that person's
# data too, and told again hourly until each answers. Each of them sets, in its own
# environment, the same INSTANCE_SECRET and ACCOUNT_REGISTER_URL, this server's web
# server as it reaches it (`http://server:8082`). Learn never takes ACCOUNT_REGISTER_URL:
# it doesn't start with one, as it holds the register. Train's and Todo's compose files
# name their own servers (INSTANCE_MEMBER_TRAIN, INSTANCE_MEMBER_TODO): this is for others.
#INSTANCE_MEMBERS=
# The instance's assistants, which an operator grants to people (Halkyon Learn's
# ADR 0021): in a file of their own, deploy/assistants.env, which goes to the server
# alone (assistants.env.example says how). Without it, no assistant.
# The compose files of what runs, the instance's own first (compose.learn.yaml: Caddy,
# PostgreSQL, Learn's server with the register), then the sidecars switched on beside it
# (sidecars/README.md): compose.jellyfin.yaml, a Jellyfin people watch and listen to
# learn from; compose.languagetool.yaml, spelling and grammar checked; compose.train.yaml,
# Halkyon Train's server with its BRouter; compose.todo.yaml, Halkyon Todo's server.
# Always set: no file has Compose's default name (compose.yaml until 2 Nov 2026), so
# without it `docker compose` finds none (e.g. compose.learn.yaml:compose.jellyfin.yaml).
COMPOSE_FILE=compose.learn.yaml
# With the Jellyfin sidecar: the domain it answers at, which the instance then suggests
# to its people (the app's Jellyfin sign-in fills it in).
#JELLYFIN_DOMAIN=jellyfin.example.org
# With Halkyon Train beside the instance (compose.train.yaml, sidecars/README.md,
# Train): the domain its apps reach it at, its database's password, its own session
# pepper (never change it once people sign in), INSTANCE_SECRET above set, and the
# region of BRouter's segments, `west,south,east,north` in whole degrees, multiples of
# 5 (empty: Europe's, -25,35,40,70, about 3.3 GB). Generate each secret:
# openssl rand -hex 32. Its compose file names its server to Learn's register.
#TRAIN_DOMAIN=train.example.org
#TRAIN_POSTGRES_PASSWORD=
#TRAIN_SESSION_KEY_HASH_PEPPER=
#BROUTER_REGION=
# The Loop's firmware through the instance (Train's ADR 0030, D40): `off` to stop
# relaying it; empty: on.
#LOOP_FIRMWARE_RELAY=
# Workouts' weather from Open-Meteo (Train's ADR 0036): `off` to stop asking; empty: on,
# on Open-Meteo's free API, which is for non-commercial use alone. An instance with ads
# or subscriptions needs a paid plan: its key.
#WEATHER=
#OPEN_METEO_API_KEY=
# Train's map (ADR 0030, D22): a MapLibre style over HTTPS, with its dark one for a
# dark theme (D65; empty: the light one in both), its name, credit and page; empty:
# OpenFreeMap's, Liberty and Dark.
#MAP_STYLE_URL=
#MAP_DARK_STYLE_URL=
#MAP_SOURCE_NAME=
#MAP_ATTRIBUTION=
#MAP_ABOUT_URL=
# With Train's own map (compose.maps.yaml, sidecars/README.md, Train's map; ADR 0030,
# D50), after compose.train.yaml in COMPOSE_FILE: the secret its tiles' keys are made
# with (openssl rand -hex 32), and the region cut, `west,south,east,north` in degrees
# (empty: Europe's, -25,35,45,75). MAP_STYLE_URL is then not used.
#MAP_KEY_SECRET=
#MAP_REGION=
# With Train's place search (compose.search.yaml, sidecars/README.md, Train's search;
# ADR 0030, D50), after compose.train.yaml in COMPOSE_FILE: the region of Photon's
# database, a path under GraphHopper's downloads (empty: `europe`; `europe/germany`
# for Germany alone), Java's heap and the container's memory (empty: 6g and 8g).
#PHOTON_REGION=
#PHOTON_HEAP=
#PHOTON_MEMORY=
# With Halkyon Todo beside the instance (compose.todo.yaml, sidecars/README.md, Todo):
# the domain its apps reach it at, its database's password and its own session pepper
# (never change it once people sign in), INSTANCE_SECRET above set. Generate each
# secret: openssl rand -hex 32. Its compose file names its server to Learn's register.
#TODO_DOMAIN=todo.example.org
#TODO_POSTGRES_PASSWORD=
#TODO_SESSION_KEY_HASH_PEPPER=
# The password of the instance's PostgreSQL superuser, halkyon_learn, which Learn's
# server signs in as, and backups and deploys dump as (Train's and Todo's servers have
# roles of their own), as TRAIN_POSTGRES_PASSWORD and TODO_POSTGRES_PASSWORD are their
# roles' (named POSTGRES_PASSWORD until 2 Nov 2026). Set once, when the database is
# made: PostgreSQL keeps the password it was made with, whatever this says later.
# Generate: openssl rand -base64 32
LEARN_POSTGRES_PASSWORD=
# Learn's pepper for hashing session keys (server-side sessions), as Train's and Todo's
# are TRAIN_ and TODO_SESSION_KEY_HASH_PEPPER (named SESSION_KEY_HASH_PEPPER until
# 2 Nov 2026). NEVER change its value once sessions exist: everyone would be signed out
# of Learn, and anonymous accounts can't sign back in. Rotation only via
# fallbackSessionKeyHashPeppers. Generate: openssl rand -base64 32
LEARN_SESSION_KEY_HASH_PEPPER=
# Email sign-in (optional, ADR 0010): people sign in with a code mailed to their address,
# of which the instance keeps only a keyed hash. Off unless the pepper and all of the SMTP
# service's settings are set; the app then doesn't offer it.
# The pepper: NEVER change it once anybody signs in with an address, as every address's
# hash would be lost, and with it those people's way in.
# Generate: openssl rand -hex 32
EMAIL_SIGN_IN_PEPPER=
# The SMTP service codes go through, over TLS only (STARTTLS, or TLS directly on 465).
# Proton (a paid plan with an address on your own domain): smtp.protonmail.ch, 587, the
# address as the user, an SMTP token made for it in Proton Mail's settings (IMAP/SMTP) as
# the password, and the same address as EMAIL_FROM. Some hosts block outgoing 25 and 465;
# 587 gets through.
EMAIL_SMTP_HOST=
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USER=
EMAIL_SMTP_PASSWORD=
# The address codes come from (shown as "Halkyon Learn"); its domain needs SPF, DKIM and
# DMARC records for the service, or mail lands in spam.
EMAIL_FROM=
# Password sign-in (ADR 0017): an address, a password that never leaves the device, and
# a code from an authenticator app. Off unless all three keys and email sign-in are set
# up (resets go by mail); each key at least 32 characters, or the server doesn't start.
# They stay outside the database, so a copy of it alone gives none of this away:
# TOTP_KEY seals the authenticators' secrets: changing it makes every authenticator
# useless (people then reset theirs, 72 hours, by mail).
# RECOVERY_CODE_KEY keys the recovery codes' hashes: changing it voids every recovery
# code (people make new ones once signed in).
# SIGN_IN_DECOY_KEY makes the answers for addresses nobody has look like an account's:
# changing it lets someone who watched those answers tell which addresses are unused.
# SimpleLogin needs it alone (below), for handles nobody has.
# Generate each: openssl rand -hex 32
TOTP_KEY=
RECOVERY_CODE_KEY=
SIGN_IN_DECOY_KEY=
# SimpleLogin (ADR 0020): people sign in with their SimpleLogin or Proton account, of
# which the instance keeps only SimpleLogin's ID for them (no address, no name). Off
# unless both are set: the client ID and secret of this instance's own SimpleLogin app
# (SimpleLogin → Settings → Developer → New website), registered with the callback
# /simplelogin-callback on the server's web address (on learn.halkyon.eu's server
# https://learn.halkyon.eu/simplelogin-callback), its forwarding page back to the apps.
# Takes SIGN_IN_DECOY_KEY (above), or the server doesn't start: a broken link is mended
# with the handle and the recovery phrase, a handle nobody has answered as an account's.
SIMPLELOGIN_CLIENT_ID=
SIMPLELOGIN_CLIENT_SECRET=
# Another callback than this server's own page, as the app registered it (https only).
#SIMPLELOGIN_CALLBACK_URL=https://learn.example.org/simplelogin-callback
# The ways in the app offers, of those set up above: simplelogin, password, or both
# (separated by spaces or commas). Empty: every way set up. An unknown name stops the
# server as it starts.
#SIGN_IN_WAYS=simplelogin
# Who may make an account (Learn's ADR 0021): open (empty), approval (new accounts wait
# until an operator approves them in the app, at most 10 at once, deleted unapproved
# after 14 days) or closed (nobody new). The first operator is made by SQL once signed
# up: docs/adr/0021-private-instance-and-assistant.md, D32. Anything else stops the
# server as it starts.
#SIGN_UP=approval
# Where the source of what this instance runs is, shown in the app's About dialog: both
# licences (AGPL for the app, SSPL for the server) require offering it to the people who
# use the instance. Empty: the project's repository, which is right for unmodified code.
# Running modified code, set it to where your modified source is published.
SOURCE_URL=
# The languages besides Japanese whose dictionaries this instance keeps (the dictionaries
# service): Chinese from CC-CEDICT, weekly (some 4 MB), in simplified and traditional
# characters; Korean from KRDict, about monthly (some 84 MB); their characters from
# Unihan, weekly (some 8 MB); the rest from Wiktionary (German and Greek with English
# meanings, English with German ones; for Korean the forms of its words and the
# readings of its Hanja), whose whole dump (some 3 GB) it downloads whenever kaikki.org
# makes a new one, about monthly, and converts in ten minutes. Unset: de,el,en,ko,zh.
# Empty: none. Files of a language taken out are removed, and the server unloads them.
#DICTIONARY_LANGUAGES=de,el,en,ko,zh
# The Jellyfin server the app suggests to this instance's people, who watch and listen
# to learn from it (Halkyon Learn's Watch screen). Empty: `https://jellyfin.` on the
# domain registered for this instance (learn.example.org → jellyfin.example.org), if a
# Jellyfin server answers there; `none`: no suggestion.
JELLYFIN_URL=
# Where this instance keeps people's data, and its backups, which the app shows as a flag
# and in words when someone chooses an instance: `EU`, a country's code (ISO 3166-1
# alpha-2, `DE`, `CH`) or a subdivision's (ISO 3166-2, `DE-BY`, `US-CA`); for the backups
# also `none`. As precise as you know: the app adds "(EU)" to a member country and the
# country to a subdivision ("Germany (EU)", "California, United States"). Where the disks
# are, not which law governs you as the operator: claim only that. Empty: the app says
# nothing.
DATA_LOCATION=
BACKUP_LOCATION=
# How much room people's images and sounds may take on this instance, in MiB. Empty: the
# defaults, 500 for a person and 15000 for everyone together (0: no limit for everyone
# together). Every file is in the database, so also in each of the 3 dumps
# this server keeps (and a fourth while a deploy writes its own): files take five times
# their size on the disk. One person may get more than the default; as the operator,
# in deploy/ (their id is their account's, "authUserId"):
# docker compose exec postgres psql -U halkyon_learn -d halkyon_learn -c "INSERT INTO
# file_allowance (\"authUserId\", \"maxBytes\", note) VALUES ('<id>', 2147483648, 'asked by mail')"
FILE_LIMIT_IDENTIFIED_MB=
FILE_LIMIT_INSTANCE_MB=