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>
139 lines
7.4 KiB
Markdown
139 lines
7.4 KiB
Markdown
# Halkyon Learn
|
||
|
||
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.
|
||
|
||
- **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 |
|
||
|
||
## 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
|
||
([project-halkyon/appflowy-editor](https://code.halkyon.app/project-halkyon/appflowy-editor)),
|
||
which `flutter pub get` fetches by itself.
|
||
|
||
## Running it locally
|
||
|
||
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
|
||
before every push: `ln -s ../../tool/check.sh .git/hooks/pre-push`. CI runs it on every
|
||
push too (`.forgejo/workflows/ci.yaml`).
|
||
|
||
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.
|
||
|
||
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
|
||
`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.
|
||
|
||
## 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.
|
||
|
||
## 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.
|