Commit graph halkyon/deploy/backups.md
Author SHA1 Message Date
a8f7c26e69
build(train): Train's server and BRouter deployable beside the instance (ADR 0030, D46)
deploy/compose.train.yaml adds Train's server at train.halkyon.eu, a
member of Learn's register, with its database halkyon_train in the
instance's PostgreSQL, and BRouter beside it alone (v1.7.10, checked by
SHA-256, no log), its segments of the operator's region kept current
monthly. Nothing changes until deploy/.env names the compose file:
redeploy.sh and backup.sh take Train in only then, and the operator's
steps are in sidecars/README.md. restic now forgets snapshots grouped by
host alone, so adding Train's dump leaves no group behind for good.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:53:01 +01:00
3e24bf8d9d
docs(deploy): deploy's manual key is pinned to redeploy.sh too
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-29 00:17:11 +01:00
4e33133733
feat(base,app)!: GitHub signs nobody in any more (ADR 0020, D36)
Every account signs in with SimpleLogin or a password now, so GitHub
goes from the module, the shared app package, Learn and Todo, ahead of
ADR 0017's GITHUB_SIGN_IN_ENDS.

- halkyon_base: no `gitHubSignIn` endpoint, GitHub identity provider or
  GitHub refusals; no `githubClientId`/`githubClientSecret`, no
  `GITHUB_SIGN_IN_ENDS` and no `forgetGitHubAccounts`; the register
  loses `account-with-github`. `SignInWays.gitHub`, the basics'
  `gitHubClientId` and `gitHubSignInEnds`, and `PasswordRefusal`'s
  `signInAgain` and `noAddress` go. A first password takes a code
  mailed to the person's own address; an account without one sets none
  (`wrongCredentials`).
- Learn's server: `instance.info()` without `gitHubClientId`, the
  pruning without GitHub, and `minimumAppBuild` 24, as builds up to 23
  (v0.20.0) require `SignInWays.gitHub`: the server deploys with the
  release of build 24.
- halkyon_app: no GitHub button or problems on the start screen, no
  password offered after a GitHub sign-in, and `ServerpodAuthGateway`
  without the browser half only GitHub used (`(client, base:)`). The
  account section offers a first password only to an account with an
  address. `LoopbackWebAuth` and the apps' schemes stay, SimpleLogin's
  ways back.
- Learn's Profile lists no GitHub; the texts go (English, German).
- Serverpod's `serverpod_auth_idp_github_account` stays, as its
  migrations make it; a row goes with its account, and ADR 0020's D36
  gives operators the SQL to delete those left.
- deploy/: compose.yaml and .env.example without GitHub. The READMEs,
  AGENTS.md files and ADR 0017 amended.

BREAKING CHANGE: builds up to 23 can't read the instance's answers
about an account's ways in; minimumAppBuild is 24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:16:39 +01:00
65c5cd740d
feat(accounts): one account across an instance's services (ADR 0016)
halkyon_base runs as the instance's register or as one of its members.
The register keeps the ways in, handles and profiles; members ask it
over HTTP with the instance's shared secret (instanceSecret), and open
sessions of their own for its accounts, under the same account ID.

- AccountsMode from the environment: INSTANCE_MEMBERS on the register,
  ACCOUNT_REGISTER_URL on a member. Learn always holds the register
  (Explore joins the profiles); Todo is a member where it is set.
- AccountRegister: LocalRegister, RemoteRegister, an in-memory one in
  tests. Calls between services on the web server, refused without the
  secret (constant-time compare); a server needing it doesn't start
  without it.
- Deleting an account deletes it everywhere: notices per member kept in
  the deletion's transaction (account_deletion_notice), told at once and
  hourly until confirmed; members prune daily whom the register no longer
  knows, never while it knows none of them or can't be reached.
- Deploy: INSTANCE_SECRET and INSTANCE_MEMBERS in compose and .env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-22 18:11:07 +02:00
23e4645d55
feat(instance): data locations as precise as a country or a state
halkyon_shared's 96f2d14: an operator's DATA_LOCATION and
BACKUP_LOCATION may name a subdivision (ISO 3166-2, US-CA, DE-BY)
besides the EU or a country, and the app adds "(EU)" to a member
country and the country to a subdivision ("Germany (EU)", "California,
United States") under the most specific flag there is. deploy/.env and
the backups' guide say so; a Storage Box is named by its country now.
The package registers its notices (the flags, CLDR's subdivision
names); Learn's own CLDR notice for the world map stays, its
registration renamed to say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-22 18:06:16 +02:00
642c1c7e9f
feat(instance): an instance says where it keeps people's data
All checks were successful
CI / check (push) Successful in 17m19s
CI / deploy (push) Has been skipped
Its operator sets DATA_LOCATION and BACKUP_LOCATION in deploy/.env (a
country's code or EU, the backups also none), and instance.info() hands
them on. The first start's tile and Switch account show the flag of
where the data is and the words, as labels ("Data: European Union",
"Backups: Switzerland"), the countries by the world map's CLDR names:
where the disks are, never which law governs. The flags come from the
flag package, which draws flag-icons; its MIT notice joins the licence
list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-18 21:26:09 +02:00
de6af3efab
feat: a round in the order cards come, shared daily limits, and a start screen
Studying:
- A round is one list in the order its cards come: those done, the one
  shown, those to come. A card due again within the day goes back in
  after 4 more cards when rated Bad, at the end otherwise, so the pills
  above the card follow it; the buttons say "in 4 cards", "at the end",
  "now" (round_order.dart).
- The studied studyset's daily limits hold for everything inside it
  together, as Anki's do for subdecks: twenty topics start 20 new cards
  a day in all, not 20 each.
- The study header's studyset name shows whole on hover or long press.
- The assistant's button sits at the lower left: in the Library across
  from Create studyset, in a studyset above its bar, and higher while
  studying, above the buttons that answer the card.

A studyset:
- Progress also counts the learning paths' steps done and lists the
  PDFs not read to their end, begun first, each opening the reader,
  across its topics (MaterialProgressCubit).
- Next up starts with studying for the exam while one is ahead: its
  reviews due and the new cards its pace leaves for the day.
- Learning paths tagged with a level count towards its automatic exam
  plan and offer it, as cards do.
- A step's first heading equal to its title is dropped on import and
  hidden in paths imported before.

Signing out, or a session the server ended, leads to a start screen
(GitHub, email, without an account, another instance) rather than a new
anonymous account; only the very first start makes one by itself.
Settings names the kind of account and its instance.

Also: the Watch screen reads the cards once for held words and their
progress; its ±10 s buttons tick as the navigation bar does; a map card
keeps its view as its caption changes and brings a picked region
towards the middle; an empty or failed library can be pulled to sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-17 19:46:34 +02:00
28d22f3e94
chore: the SSH aliases and CI's runner label keep the servers' names
Some checks failed
CI / check (push) Successful in 15m47s
CI / deploy (push) Failing after 1m9s
The servers' names are no secret, so the settings that carry them stay
as they were, and nothing on a server or a laptop needs changing: CI
runs on `sahaquiel` again, the release uploads to `ramiel-fdroid`, and
deploys by hand go through `sahaquiel-deploy`. Prose still names a
server by the domain it serves, which says what a reader needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:33:51 +02:00
93b0c0a6b0
chore: the servers are named by the domain they serve
Files called the servers by their own host names, which are the
operator's and say nothing a reader needs. They now say which domain a
server serves: learn.halkyon.eu's server (the instance and its CI
runner), the halkyon.app server (Forgejo and the F-Droid repository).

Two names are settings rather than words. CI's runner label becomes
`instance` (`runs-on: instance`, `labels: ["instance:host"]`): the
runner reads its installed copy of deploy/runner/config.yml, so jobs
wait in the queue until that copy is updated and the runner restarted.
The SSH aliases the scripts and docs use become `learn-deploy` and
`fdroid-upload`. AGENTS.md says to keep it so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:33:50 +02:00
f635b0816a
feat(publish): releases in git (public tier, stage 2)
A person with an identity and a public profile publishes a studyset with
everything inside it as its next release (ADR 0005, rule 3): the server
reads their synced rows and writes them through the git command as a
commit on main and the annotated tag vN, in a bare repository of the
studyset's own, named by a random public id made at the first publish.

A release is a directory per studyset with a file per card, note, path,
step and PDF (its id, position, tag names and payload as synced, keys
sorted and indented) and the files the content names, without what is
only the author's (a card's report, a PDF's deadline). Publishing is
refused without a licence allowing passing it on in every studyset ("no
derivatives" allowed), with revisions to settle, with files not uploaded
yet, when nothing changed, or for a name that won't do; the name is
fixed from the first release, as is the person's handle.

In the app, Share in a studyset's action row opens the Publish sheet;
publishing syncs first, and settles by itself the revisions that merge
cleanly, listing only real conflicts. The server's image gets git, the
repositories a volume of their own, and the hourly backup a git bundle of
each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-10 09:46:38 +02:00
6c20c44cf5
docs(deploy): the Storage Box needs external reachability
A Storage Box answers only inside Hetzner's network unless External
Reachability is on, for the box and for the sub-account. FTP and SFTP on
port 22 can't be turned off, so the sub-account turns off Samba and
WebDAV only. A host key of another type is checked against Hetzner's
list too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-05 13:00:37 +02:00
999a3ce0b0
feat(deploy): hourly off-site backups with restic
All checks were successful
CI / check (push) Successful in 9m0s
CI / deploy (push) Successful in 7s
learn.halkyon.eu dumps its database every hour (people's files are in it
too) and restic stores the dump encrypted in a Hetzner Storage Box,
keeping 24 hourly, 7 daily, 4 weekly and 3 monthly snapshots. The box's
own snapshots, removable only in Hetzner's Console, protect the backups
from a taken-over server. A systemd timer runs it as deploy; once a day
it also prunes and checks the repository. Failures go to the journal.

Hourly, because a restore costs people who started after the backup
what their device held: the app no longer finds its session and starts
anew. deploy/backups.md describes the setup, the restore and what a
restore loses; both were rehearsed on the local Compose stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 16:45:40 +02:00