serverpod_offline_sync v0.0.10 collects outbound changes in one consistent snapshot, so a sync no longer fails on writes made while it runs (upstream #84, #163), keeps checkpoints per author, so a device clock moving backwards no longer stalls sync or resends data, and keeps unique conflicts and foreign key defaults alike on every device. Its tables and its messages are unchanged: no migration, and apps on v0.0.8 keep syncing. It needs Serverpod >=4.0.4 <4.1.0 (v4.0.4 is fixes only), so the root overrides that held the workspace on one Serverpod go: each package pins v4.0.4 itself. The generated code is identical under the v4.0.4 CLI, which CI now uses. 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>
151 lines
8.4 KiB
Markdown
151 lines
8.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.4**: `dart install serverpod_cli@4.0.4`. 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/app_icons.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),
|
||
with the APK and the snap, and the APK also in the project's F-Droid repository
|
||
(`https://halkyon.app/fdroid/repo?fingerprint=E8082788756F6B7E682AA22FCEAD24B53E2A8B0912AD66D65E8C2D4F07BB94B0`,
|
||
added in the F-Droid app as a repository). The Snap Store (`halkyon-learn`) follows.
|
||
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** follow [semantic versioning](https://semver.org): `version` in
|
||
`halkyon_learn_flutter/pubspec.yaml`, `<major>.<minor>.<patch>+<build>`. Since the last
|
||
release, a commit typed `feat` raises the minor number, `fix` or `perf` the patch
|
||
number, and a breaking one (`feat!:` or a `BREAKING CHANGE:` line) the major number from
|
||
1.0 on, the minor one before; other commits release nothing by themselves. 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`, or `tool/release.sh minor` (or `major`, `patch`) to choose. It needs
|
||
`main` clean, pushed and green in CI, the app's key, the F-Droid repository's key and
|
||
a Forgejo token; its header says more. It shows the version and its commits and asks
|
||
first, then writes the version into the pubspec in a signed release commit with a
|
||
signed tag, builds the APK and the snap (`snap/snapcraft.yaml`, built in LXD; its icon
|
||
comes from `tool/app_icons.py`), checks both, signs the checksums, pushes, and
|
||
publishes on code.halkyon.app and in the F-Droid repository; the Snap Store once it is
|
||
switched on in the script. Besides Flutter and the Android SDK, it uses snapcraft with
|
||
LXD, a JDK (keytool), GnuPG, curl, jq, unzip, squashfs-tools, rsync and fdroidserver.
|
||
Release builds made without the app's key are signed with the debug key and can't
|
||
update a released app.
|
||
- **The F-Droid repository** is kept by `tool/fdroid_repo.sh` (fdroidserver v2.4.5,
|
||
e.g. `pipx install fdroidserver==2.4.5`); its settings and the app's listing are in
|
||
`tool/release/fdroid/`.
|
||
|
||
## 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.
|