ADR 0044's step 6a (D42, D43, D45): accounts for programs, such as AI assistants, that an identified person makes from the Profile. halkyon_base keeps them on the instance's register, which its members ask: an agent is an account (agent_account: its owner, its name, its public keys, set by itself once) with one token (agent_token: 32 random bytes, kept as their SHA-256 alone, shown once). agents.create, mine, newToken, revoke, delete and setKeys serve its owner; agentSignIn.signIn exchanges the token for an ordinary server-side session of a day, with the scopes identified and agent, at most 60 sign-ins an hour from a network. Revoking a token or making a new one ends the agent's sessions; a token unused for 90 days lapses in every service's pruning. Up to 5 agents a person, where the operator didn't switch them off (AGENTS=off; said on the privacy page and in instance.basics). An agent makes no agents, creates or owns no shared space, is invited and raised as a Viewer alone, is never handed a space on, is named no operator and granted no assistants; each refusal has its test. Deleting a person deletes their agents first; an agent's public profile names whose agent it is. In the apps, every Profile has an Agents card (halkyon_app's AgentsSection): made with a name and a handle, its token shown once to copy, a new token, the token taken back, deleted. Learn's Share sheet marks an agent among the members and offers it no role above Viewer. The migrations, the module's and each host's (Learn, Meditate, Todo, Train), are additive alone: CREATE TABLE "agent_account" with a unique index on "agentId" (one row an agent account) and one on "ownerId" (a person's agents, and deleting them with the person), and CREATE TABLE "agent_token" with unique indexes on "agentId" (one token an agent) and "tokenHash" (the sign-in finds the token by its hash). No existing table is touched, so the server before the deploy keeps working on the migrated database. Whats-New: Make agents in your Profile: accounts for programs such as AI assistants, which sign in with a token and see only what you share with them. Whats-New-De: Leg in deinem Profil Agenten an: Konten für Programme wie KI-Assistenten, die sich mit einem Token anmelden und nur sehen, was du mit ihnen teilst. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
234 lines
14 KiB
Shell
234 lines
14 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
|
|
# Learn's server's public address, a host alone, where it isn't learn.halkyon.eu
|
|
# (`learn.halkyon.li`): the address its SimpleLogin callback, profile pages and privacy
|
|
# page are on. Unset: learn.halkyon.eu.
|
|
#LEARN_PUBLIC_HOST=learn.halkyon.li
|
|
|
|
# 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;
|
|
# compose.todo-search.yaml, Todo on compose.search.yaml's Photon, after both.
|
|
# 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 (Train's ADR 0036) and Todo's forecasts (ADR 0033, D40) from
|
|
# Open-Meteo: `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=
|
|
|
|
# With Halkyon Meditate beside the instance (compose.meditate.yaml, sidecars/README.md,
|
|
# Meditate): as Todo's. Its library (compose.meditate-library.yaml, optional): the
|
|
# folder on the host with library.yaml, mounted read-only.
|
|
#MEDITATE_DOMAIN=meditate.example.org
|
|
#MEDITATE_POSTGRES_PASSWORD=
|
|
#MEDITATE_SESSION_KEY_HASH_PEPPER=
|
|
#MEDITATE_LIBRARY=/data/meditate-library
|
|
|
|
# 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
|
|
# Whether people may make agents in their Profile (ADR 0044, D45): accounts for programs,
|
|
# such as AI assistants, signing in with a token, Viewers of what they're invited to.
|
|
# on (empty) or off; anything else stops the server as it starts.
|
|
#AGENTS=off
|
|
|
|
# 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=
|