2026-09-30 15:13:34 +02:00
|
|
|
|
# Halkyon Learn
|
|
|
|
|
|
|
docs: the README's aims, developer setup and releases
The README opens with what the app aims at: creating, sharing and
collaborating on study material, FSRS for retention, study material
organised with its external references, offline-first, and a transparent
stack anyone can host.
"Setting up" lists what developers need: Flutter as CI pins it, with
Flutter's own guide per platform; the Serverpod CLI; the Linux packages,
with what each plugin needs them for; Developer Mode on Windows, and why;
no Docker for development; Python for the scripts in tool/.
"Releases" says where releases are published, how to check a download
(the signed checksums, the APK certificate's fingerprint), how versions
are numbered, and how a maintainer makes one with tool/release.sh.
AGENTS.md: Windows needs Developer Mode for Flutter's plugin links too,
not only for pdfrx's build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-05 17:00:27 +02:00
|
|
|
|
Halkyon Learn is for creating, sharing and collaborating on study material. It schedules
|
|
|
|
|
|
flashcards with [FSRS](https://github.com/open-spaced-repetition) to strengthen
|
|
|
|
|
|
retention, and organises study material together with the external references it draws
|
|
|
|
|
|
on. It is offline-first: everything works on the device, without a connection. And the
|
|
|
|
|
|
whole stack is transparent: the app, the server and everything needed to host an
|
|
|
|
|
|
instance are published, so anyone can see how an instance works and run their own.
|
2026-09-30 15:13:34 +02:00
|
|
|
|
|
|
|
|
|
|
- **Offline first.** Each device keeps its own database and syncs in the background
|
|
|
|
|
|
(`serverpod_offline_sync`). Edits made on two devices while offline are never lost:
|
|
|
|
|
|
card content is stored as append-only revisions, and a side changed differently on two
|
|
|
|
|
|
devices is shown as a conflict until the person picks a version
|
|
|
|
|
|
([ADR 0001](docs/adr/0001-offline-sync.md), [ADR 0003](docs/adr/0003-card-revisions.md)).
|
|
|
|
|
|
- **No sign-up needed.** The app starts with an anonymous account; signing in with GitHub
|
|
|
|
|
|
later keeps everything. A GitHub sign-in stores only the numeric GitHub ID.
|
|
|
|
|
|
- **No end-to-end encryption.** Content is readable by the operator of the instance, as on
|
|
|
|
|
|
Mastodon; whoever trusts no operator can run their own
|
|
|
|
|
|
([ADR 0002](docs/adr/0002-no-end-to-end-encryption.md)).
|
|
|
|
|
|
|
|
|
|
|
|
Targets: Linux, Windows, macOS, Android and iOS. There is no web build.
|
|
|
|
|
|
|
|
|
|
|
|
## Packages
|
|
|
|
|
|
|
|
|
|
|
|
| Package | What it is | Licence |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `halkyon_learn_server` | The [Serverpod](https://serverpod.dev) server: endpoints, models, migrations | SSPL-1.0 |
|
|
|
|
|
|
| `halkyon_learn_client` | The client, generated from the server's models | AGPL-3.0-or-later |
|
|
|
|
|
|
| `halkyon_learn_flutter` | The app (`app.halkyon.learn`) | AGPL-3.0-or-later |
|
|
|
|
|
|
|
docs: the README's aims, developer setup and releases
The README opens with what the app aims at: creating, sharing and
collaborating on study material, FSRS for retention, study material
organised with its external references, offline-first, and a transparent
stack anyone can host.
"Setting up" lists what developers need: Flutter as CI pins it, with
Flutter's own guide per platform; the Serverpod CLI; the Linux packages,
with what each plugin needs them for; Developer Mode on Windows, and why;
no Docker for development; Python for the scripts in tool/.
"Releases" says where releases are published, how to check a download
(the signed checksums, the APK certificate's fingerprint), how versions
are numbered, and how a maintainer makes one with tool/release.sh.
AGENTS.md: Windows needs Developer Mode for Flutter's plugin links too,
not only for pdfrx's build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-05 17:00:27 +02:00
|
|
|
|
## Setting up
|
|
|
|
|
|
|
|
|
|
|
|
- **Flutter v3.47.5**, the version CI uses (`FLUTTER_VERSION` in
|
|
|
|
|
|
`.forgejo/workflows/ci.yaml`). Set up each platform you build for with Flutter's own
|
|
|
|
|
|
guides: [Linux](https://docs.flutter.dev/platform-integration/linux/setup),
|
|
|
|
|
|
[Windows](https://docs.flutter.dev/platform-integration/windows/setup),
|
|
|
|
|
|
[Android](https://docs.flutter.dev/platform-integration/android/setup),
|
|
|
|
|
|
[macOS](https://docs.flutter.dev/platform-integration/macos/setup),
|
|
|
|
|
|
[iOS](https://docs.flutter.dev/platform-integration/ios/setup).
|
|
|
|
|
|
- **The Serverpod CLI v4.0.3**: `dart install serverpod_cli@4.0.3`. Pass `--no-analytics`
|
|
|
|
|
|
to every call; otherwise it reports the commands run to Serverpod.
|
|
|
|
|
|
- **Linux**, on top of Flutter's packages, the libraries of three plugins: WebKitGTK
|
|
|
|
|
|
(pulled in by the sign-in package), libsecret (keeps the session) and GStreamer
|
|
|
|
|
|
(plays sounds). On Debian or Ubuntu, Flutter's and these together:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev \
|
|
|
|
|
|
libwebkit2gtk-4.1-dev libsecret-1-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **Windows** needs Developer Mode (Settings › System › For developers): building makes
|
|
|
|
|
|
symbolic links, as Flutter links each plugin's native code into the build and the PDF
|
|
|
|
|
|
engine's build does too, and Windows lets ordinary users make them only in Developer
|
|
|
|
|
|
Mode.
|
|
|
|
|
|
- **No Docker** for development: `serverpod start` and the server's tests run an embedded
|
|
|
|
|
|
PostgreSQL. Docker Compose is only for running an instance (below).
|
|
|
|
|
|
- **For the scripts in `tool/`**: Python 3 (`tool/snap_icon.py` also needs Pillow,
|
|
|
|
|
|
`python3-pil`), and for releases what [Releases](#releases) lists.
|
|
|
|
|
|
|
|
|
|
|
|
The rich-text editor comes from our fork of AppFlowy Editor
|
2026-09-30 21:49:04 +02:00
|
|
|
|
([project-halkyon/appflowy-editor](https://code.halkyon.app/project-halkyon/appflowy-editor)),
|
|
|
|
|
|
which `flutter pub get` fetches by itself.
|
2026-09-30 15:13:34 +02:00
|
|
|
|
|
docs: the README's aims, developer setup and releases
The README opens with what the app aims at: creating, sharing and
collaborating on study material, FSRS for retention, study material
organised with its external references, offline-first, and a transparent
stack anyone can host.
"Setting up" lists what developers need: Flutter as CI pins it, with
Flutter's own guide per platform; the Serverpod CLI; the Linux packages,
with what each plugin needs them for; Developer Mode on Windows, and why;
no Docker for development; Python for the scripts in tool/.
"Releases" says where releases are published, how to check a download
(the signed checksums, the APK certificate's fingerprint), how versions
are numbered, and how a maintainer makes one with tool/release.sh.
AGENTS.md: Windows needs Developer Mode for Flutter's plugin links too,
not only for pdfrx's build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-05 17:00:27 +02:00
|
|
|
|
## Running it locally
|
|
|
|
|
|
|
2026-09-30 15:13:34 +02:00
|
|
|
|
The server's secrets live in `halkyon_learn_server/config/passwords.yaml`, which is never
|
|
|
|
|
|
committed. In a fresh clone, create it with a `development` and a `test` section, each
|
|
|
|
|
|
holding `database`, `serverSideSessionKeyHashPepper` and `serviceSecret` (for example from
|
|
|
|
|
|
`openssl rand -base64 32`). GitHub sign-in is optional: add `githubClientId` and
|
|
|
|
|
|
`githubClientSecret` of your own GitHub OAuth app to `development`, registered with the
|
|
|
|
|
|
callback URLs `app.halkyon.learn://github-callback` and `http://127.0.0.1/github-callback`.
|
|
|
|
|
|
|
|
|
|
|
|
Then start the server, its embedded database and the app:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
cd halkyon_learn_server
|
|
|
|
|
|
serverpod --no-analytics start
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`serverpod start` applies pending migrations and reloads on changes. The app talks to the
|
|
|
|
|
|
server named by `--dart-define=SERVER_URL=…`, or else to the one in
|
|
|
|
|
|
`halkyon_learn_flutter/assets/config.json` (`http://localhost:8080`). Without
|
|
|
|
|
|
`--no-analytics`, the Serverpod CLI reports usage to Serverpod.
|
|
|
|
|
|
|
|
|
|
|
|
## Tests
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
tool/check.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
runs the format check, the analyzer with the strict lints of `leancode_lint`, the server's
|
|
|
|
|
|
tests (against an embedded PostgreSQL, no Docker needed) and the app's tests. To run it
|
ci: check every push on sahaquiel and deploy a green main
Pushes to main were deployed by hand. Now Forgejo Actions runs tool/check.sh on every
push, and a green main deploys learn.halkyon.eu by itself.
The runner (forgejo-runner v13.1.0) runs on sahaquiel as the unprivileged user `runner`,
with jobs directly on the host: containers would take Docker access, which is root on the
production server, and Forgejo warns that host jobs have no isolation from the runner's
user. So its systemd unit (deploy/runner/forgejo-runner.service) provides it: /home and
the Docker socket invisible, Docker's container networks unreachable, no capabilities,
two cores and 3 GB at most, lowest priority. It is registered to this repository only,
with the UUID and token of Forgejo's "Create new runner" (the old `register` is
deprecated); the token reaches it as a systemd credential. The configuration
(deploy/runner/config.yml) was dry-run against v13.1.0, the unit rates 1.8 in
`systemd-analyze security`. CI runs on pushes only: a pull request from a fork would run
its code on the production server.
The deploy job calls deploy/redeploy.sh as `deploy`, through a key that authorized_keys
pins to that script and to connections from sahaquiel itself. The script deploys the
commit CI checked while it is still main's tip, as a fast-forward, only if every new
commit is signed by a key in deploy/allowed-signers (as the deployed checkout lists
them), after dumping the database (the last ten are kept), and fails unless the server
answers afterwards; a failure puts the checkout back. Tested against the local stack:
refusals of a malformed argument, a stale commit, a forged unsigned commit and a
non-fast-forward, a real deploy with its dump, a repeat, and a failed build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 21:49:04 +02:00
|
|
|
|
before every push: `ln -s ../../tool/check.sh .git/hooks/pre-push`. CI runs it on every
|
|
|
|
|
|
push too (`.forgejo/workflows/ci.yaml`).
|
2026-09-30 15:13:34 +02:00
|
|
|
|
|
|
|
|
|
|
The sync tests need a running server (`serverpod start`); run them in
|
|
|
|
|
|
`halkyon_learn_flutter` with `flutter test -t requires-server`.
|
|
|
|
|
|
|
|
|
|
|
|
## Running an instance
|
|
|
|
|
|
|
|
|
|
|
|
`deploy/` holds a Docker Compose setup: Caddy (HTTPS), the server and PostgreSQL. Copy
|
|
|
|
|
|
`deploy/.env.example` to `deploy/.env` and fill it in; it explains every value. The
|
|
|
|
|
|
licences require offering the source of what the instance runs to the people who use it.
|
|
|
|
|
|
|
ci: check every push on sahaquiel and deploy a green main
Pushes to main were deployed by hand. Now Forgejo Actions runs tool/check.sh on every
push, and a green main deploys learn.halkyon.eu by itself.
The runner (forgejo-runner v13.1.0) runs on sahaquiel as the unprivileged user `runner`,
with jobs directly on the host: containers would take Docker access, which is root on the
production server, and Forgejo warns that host jobs have no isolation from the runner's
user. So its systemd unit (deploy/runner/forgejo-runner.service) provides it: /home and
the Docker socket invisible, Docker's container networks unreachable, no capabilities,
two cores and 3 GB at most, lowest priority. It is registered to this repository only,
with the UUID and token of Forgejo's "Create new runner" (the old `register` is
deprecated); the token reaches it as a systemd credential. The configuration
(deploy/runner/config.yml) was dry-run against v13.1.0, the unit rates 1.8 in
`systemd-analyze security`. CI runs on pushes only: a pull request from a fork would run
its code on the production server.
The deploy job calls deploy/redeploy.sh as `deploy`, through a key that authorized_keys
pins to that script and to connections from sahaquiel itself. The script deploys the
commit CI checked while it is still main's tip, as a fast-forward, only if every new
commit is signed by a key in deploy/allowed-signers (as the deployed checkout lists
them), after dumping the database (the last ten are kept), and fails unless the server
answers afterwards; a failure puts the checkout back. Tested against the local stack:
refusals of a malformed argument, a stale commit, a forged unsigned commit and a
non-fast-forward, a real deploy with its dump, a repeat, and a failed build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 21:49:04 +02:00
|
|
|
|
learn.halkyon.eu itself deploys from `main` once CI is green: `deploy/redeploy.sh`, run by
|
|
|
|
|
|
the CI runner described in `deploy/runner/`, deploys only commits signed by a key in
|
2026-10-03 16:45:40 +02:00
|
|
|
|
`deploy/allowed-signers`. It backs up its database every hour, off-site and encrypted;
|
|
|
|
|
|
[`deploy/backups.md`](deploy/backups.md) says how to set that up and how to restore.
|
ci: check every push on sahaquiel and deploy a green main
Pushes to main were deployed by hand. Now Forgejo Actions runs tool/check.sh on every
push, and a green main deploys learn.halkyon.eu by itself.
The runner (forgejo-runner v13.1.0) runs on sahaquiel as the unprivileged user `runner`,
with jobs directly on the host: containers would take Docker access, which is root on the
production server, and Forgejo warns that host jobs have no isolation from the runner's
user. So its systemd unit (deploy/runner/forgejo-runner.service) provides it: /home and
the Docker socket invisible, Docker's container networks unreachable, no capabilities,
two cores and 3 GB at most, lowest priority. It is registered to this repository only,
with the UUID and token of Forgejo's "Create new runner" (the old `register` is
deprecated); the token reaches it as a systemd credential. The configuration
(deploy/runner/config.yml) was dry-run against v13.1.0, the unit rates 1.8 in
`systemd-analyze security`. CI runs on pushes only: a pull request from a fork would run
its code on the production server.
The deploy job calls deploy/redeploy.sh as `deploy`, through a key that authorized_keys
pins to that script and to connections from sahaquiel itself. The script deploys the
commit CI checked while it is still main's tip, as a fast-forward, only if every new
commit is signed by a key in deploy/allowed-signers (as the deployed checkout lists
them), after dumping the database (the last ten are kept), and fails unless the server
answers afterwards; a failure puts the checkout back. Tested against the local stack:
refusals of a malformed argument, a stale commit, a forged unsigned commit and a
non-fast-forward, a real deploy with its dump, a repeat, and a failed build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 21:49:04 +02:00
|
|
|
|
|
docs: the README's aims, developer setup and releases
The README opens with what the app aims at: creating, sharing and
collaborating on study material, FSRS for retention, study material
organised with its external references, offline-first, and a transparent
stack anyone can host.
"Setting up" lists what developers need: Flutter as CI pins it, with
Flutter's own guide per platform; the Serverpod CLI; the Linux packages,
with what each plugin needs them for; Developer Mode on Windows, and why;
no Docker for development; Python for the scripts in tool/.
"Releases" says where releases are published, how to check a download
(the signed checksums, the APK certificate's fingerprint), how versions
are numbered, and how a maintainer makes one with tool/release.sh.
AGENTS.md: Windows needs Developer Mode for Flutter's plugin links too,
not only for pdfrx's build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-05 17:00:27 +02:00
|
|
|
|
## Releases
|
|
|
|
|
|
|
|
|
|
|
|
Releases are published on [code.halkyon.app](https://code.halkyon.app/project-halkyon/halkyon_learn/releases)
|
|
|
|
|
|
and, for Linux, in the Snap Store (`halkyon-learn`). Their builds connect to
|
|
|
|
|
|
learn.halkyon.eu, the project's instance.
|
|
|
|
|
|
|
|
|
|
|
|
- **Checking a download:** `SHA256SUMS` lists the checksum of every file, signed with the
|
|
|
|
|
|
maintainer's GPG key (`SHA256SUMS.asc`). The APK is signed with the app's key, whose
|
|
|
|
|
|
certificate has the SHA-256
|
|
|
|
|
|
`27:E2:43:FC:BD:C8:26:FF:98:AB:7D:BB:10:43:6C:98:C3:BE:A2:4B:2A:01:5E:94:E4:CF:C7:D1:FC:2A:00:6C`
|
|
|
|
|
|
(`apksigner verify --print-certs` shows it).
|
|
|
|
|
|
- **Versions** are `version` in `halkyon_learn_flutter/pubspec.yaml`,
|
|
|
|
|
|
`<major>.<minor>.<patch>+<build>`. Every release raises the build number: Android only
|
|
|
|
|
|
installs a higher one over an older, and the server tells apps below the build it
|
|
|
|
|
|
supports to update.
|
|
|
|
|
|
- **Making one** is for maintainers, on their own machine and never in CI:
|
|
|
|
|
|
`tool/release.sh vX.Y.Z`. Its header says what it needs (the signed tag, the app's
|
|
|
|
|
|
key, a Forgejo token, `snapcraft login`). It builds the APK and the snap
|
|
|
|
|
|
(`snap/snapcraft.yaml`, built in LXD; its icon comes from `tool/snap_icon.py`), checks
|
|
|
|
|
|
both, signs the checksums and publishes everything. Besides Flutter and the Android
|
|
|
|
|
|
SDK, it uses snapcraft with LXD, a JDK (keytool), GnuPG, curl, jq, unzip and
|
|
|
|
|
|
squashfs-tools. Release builds made without the app's key are signed with the debug
|
|
|
|
|
|
key and can't update a released app.
|
|
|
|
|
|
|
2026-09-30 15:13:34 +02:00
|
|
|
|
## More
|
|
|
|
|
|
|
|
|
|
|
|
- [`AGENTS.md`](AGENTS.md): how the code is organised and the rules it follows.
|
|
|
|
|
|
- [`docs/adr/`](docs/adr/): the architecture decisions and their reasons.
|
|
|
|
|
|
- [`CONTRIBUTING.md`](CONTRIBUTING.md): licences of contributions and the sign-off.
|
|
|
|
|
|
- [`LICENSE.md`](LICENSE.md): which licence covers what.
|