# 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). # `http://localhost` for the local rehearsal; `learn.halkyon.eu` on its server. 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 DOMAIN's host: # point its DNS (A and AAAA) at this server, and Caddy serves the profile pages there, # nothing else. Unset: 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. #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. # Sidecars switched on beside the instance (sidecars/README.md), each by its compose file # after the instance's own: 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. Unset: the instance alone. #COMPOSE_FILE=compose.yaml:compose.jellyfin.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 server is told of accounts gone without INSTANCE_MEMBERS; # setting it, name Train's too (`http://train-server:8082/`). #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= # Train's map (ADR 0030, D22): a MapLibre style over HTTPS, with its name, credit and # page; empty: OpenFreeMap's. #MAP_STYLE_URL= #MAP_SOURCE_NAME= #MAP_ATTRIBUTION= #MAP_ABOUT_URL= # Postgres password, shared by the postgres and server services. # Generate: openssl rand -base64 32 POSTGRES_PASSWORD= # Pepper for hashing session keys (server-side sessions). NEVER change it once # sessions exist: everyone would be signed out, and anonymous accounts can't sign # back in. Rotation only via fallbackSessionKeyHashPeppers. # Generate: openssl rand -base64 32 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 ('', 2147483648, 'asked by mail')" FILE_LIMIT_IDENTIFIED_MB= FILE_LIMIT_INSTANCE_MB=