halkyon/snap/snapcraft.yaml
Tizian 「ティツィアーン」 4f7b123ad4
docs(auth): what building the apps' side settled (ADR 0017)
ADR 0017's step-3 section: codes by mail sign nobody in, the ways in
for accounts without a working password, changes while signed in,
sessions ended in every service, the apps' screens (the QR package and
the alternatives considered), the keys in the keyring, anonymous
people's keys waiting for step 4, and libsodium on devices (results
copied before they leave an isolate; a C compiler and make for every
build). The module's and halkyon_app's READMEs and the AGENTS files say
the same; the snap's build installs make for sodium's build hook.

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

182 lines
7.6 KiB
YAML

# The Linux app as a Snap, built in LXD on a developer's machine, in the repository
# root:
#
# snapcraft pack
#
# It builds what is committed (the source is this repository's HEAD), never the
# working tree with its build caches, and always for learn.halkyon.eu. Flutter is
# pinned like the local and CI one (FLUTTER_VERSION in .forgejo/workflows/ci.yaml):
# snapcraft's own Flutter plugin can only take a channel and can't pass
# --dart-define, so the build is spelled out below.
name: halkyon-learn
title: Halkyon Learn
base: core24
adopt-info: halkyon-learn # the version, from the app's pubspec.yaml
summary: Learn with flashcards, studied offline and synced between your devices
description: |
Halkyon Learn is a spaced-repetition app for your own material: studysets of
flashcards, notes, learning paths and PDFs, reviewed on an FSRS schedule. Every
studyset is made by its learners: build yours for any subject, or copy one that
someone published. It works offline and syncs between your devices.
Learn from what you watch: play films and shows from your Jellyfin server,
and every subtitle line is split into dictionary words, with readings above
and the translation below. Tap a word, and it is a flashcard.
Dictionaries for Japanese, Chinese, Korean, German, Greek and English; kanji,
hanzi and Hangul written by hand, each stroke checked; exam plans for the
JLPT, HSK and Korean levels, with a daily pace to your exam's date; and
studysets published to Explore and copied, later releases coming in as
merges that keep your own edits.
It starts with an anonymous account; signing in with GitHub or an email
address later keeps everything. It connects to learn.halkyon.eu, the project's
instance, or to any other: everything an instance needs is in the repository,
to run your own. The app is AGPL-3.0 and the server SSPL-1.0, so all of its
code is public.
Questions and reports: support@halkyon.app
license: AGPL-3.0-or-later
source-code: https://code.halkyon.app/project-halkyon/halkyon
# The project's, not an instance's: the app works with any instance.
website: https://halkyon.app/learn
# No contact or issues: the Store refuses an upload whose links aren't http(s) URLs
# ("Links must be http(s):// URLs", checked with snapcraft v9.1.4 on 2026-10-14),
# though snapcraft's reference allows mail addresses there (canonical/snapcraft#6062
# asks for them). Reports come by mail until people can open an account on the issue
# tracker, so the address is in the description.
# The app's icon with rounded corners, made by tool/app_icons.py; also the desktop file's.
icon: snap/gui/halkyon-learn.png
grade: stable
confinement: strict
platforms:
amd64:
lint:
ignore:
# PDFium is loaded by name while the app runs, which the linter can't see.
- library:
- bin/lib/libpdfium.so
apps:
halkyon-learn:
command: bin/halkyon_learn_flutter
# How the session is kept: see snap/local/secret-backend.
command-chain: [bin/secret-backend]
# GTK, WebKitGTK, GStreamer and libsecret come from the GNOME runtime; files
# and the browser go through the desktop's portals.
extensions: [gnome]
plugs:
- network
# GitHub sign-in answers on a loopback address the app listens on.
- network-bind
- audio-playback
# Never connected by itself; whoever connects it by hand gets the keyring.
- password-manager-service
# Lets Serverpod hear from NetworkManager that the network went, and end
# the sync's connection at once rather than after 40 s of silence. Not
# connected by itself until the Store grants it on a forum request;
# without it the app logs the refusal once and works on.
- network-manager-observe
parts:
flutter:
plugin: nil
source: https://github.com/flutter/flutter.git
source-tag: '3.47.5'
source-depth: 1
build-packages:
- clang
- cmake
- curl
- git
- libgtk-3-dev
- lld
- llvm-18
- ninja-build
- pkg-config
- unzip
- xz-utils
- zip
override-build: |
mkdir -p "$CRAFT_PART_INSTALL/usr/libexec" "$CRAFT_PART_INSTALL/usr/bin"
cp -r "$CRAFT_PART_SRC" "$CRAFT_PART_INSTALL/usr/libexec/flutter"
ln -s ../libexec/flutter/bin/flutter "$CRAFT_PART_INSTALL/usr/bin/flutter"
ln -s ../libexec/flutter/bin/dart "$CRAFT_PART_INSTALL/usr/bin/dart"
"$CRAFT_PART_INSTALL/usr/bin/flutter" --disable-analytics
"$CRAFT_PART_INSTALL/usr/bin/flutter" precache --linux
# Needed to build, not in the Snap.
override-prime: ''
halkyon-learn:
after: [flutter]
plugin: nil
source: .
source-type: git
# The sound player links GStreamer, which the GNOME SDK lacks; the GNOME
# runtime brings the same GStreamer (1.24) to run. The video player
# (media_kit, for Jellyfin) links libmpv, which neither has: the Snap
# brings Ubuntu's libmpv2 with what it needs (ffmpeg's libraries), or the
# app wouldn't start. sodium's build hook compiles libsodium with its
# configure script and make (ADR 0017), with the clang above.
build-packages:
- libgstreamer1.0-dev
- libgstreamer-plugins-base1.0-dev
- libmpv-dev
- make
stage-packages:
- libmpv2
override-build: |
set -eu
export PATH="$CRAFT_STAGE/usr/bin:$PATH"
cd apps/learn/halkyon_learn_flutter
craftctl set version="$(sed -n 's/^version: *\([^+]*\).*/\1/p' pubspec.yaml)"
flutter pub get --enforce-lockfile
flutter build linux --release \
--dart-define=SERVER_URL=https://learn.halkyon.eu/
mkdir -p "$CRAFT_PART_INSTALL/bin"
cp -r build/linux/x64/release/bundle/. "$CRAFT_PART_INSTALL/bin/"
# ffmpeg's speech recognition (libsphinxbase) links libblas.so.3 and
# liblapack.so.3, which Ubuntu keeps in blas/ and lapack/ and links into
# the library folder from its packages' install scripts
# (update-alternatives), which staging never runs: without these links
# the app doesn't start. The linter doesn't notice, as the build
# machine has them.
lib="$CRAFT_PART_INSTALL/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR"
mkdir -p "$lib"
ln -sf blas/libblas.so.3 "$lib/libblas.so.3"
ln -sf lapack/liblapack.so.3 "$lib/liblapack.so.3"
secret-backend:
plugin: dump
source: snap/local
organize:
secret-backend: bin/secret-backend
# Staged packages bring their whole dependency tree, also what the GNOME
# runtime and the base hold already: libmpv's ffmpeg brought Ubuntu's older
# librsvg, cairo, pango, harfbuzz and gdk-pixbuf, which came first on the
# library path and hid the runtime's newer ones from its GTK (its SVG
# loader failed for a function only the runtime's librsvg has). So every
# file the runtime or the base has at the same path goes, as the GNOME
# extension's reference shows, but only under usr/ and never a directory:
# its example removes every path the base has, and core24's bin and lib are
# links, so it would take the app's bin/ with them. blas/ and lapack/ stay:
# the runtime has them too, but the links into them are the app's own.
cleanup:
after: [halkyon-learn, secret-backend]
plugin: nil
build-snaps: [core24, gnome-46-2404]
override-prime: |
set -eu
for snap in core24 gnome-46-2404; do
cd "/snap/$snap/current/usr"
find . \( -type f -o -type l \) -print0 |
while IFS= read -r -d '' path; do
case "$path" in */blas/* | */lapack/*) continue ;; esac
target="$CRAFT_PRIME/usr/$path"
if [ -L "$target" ] || [ -f "$target" ]; then
rm -f "$target"
fi
done
done