feat: the Linux app as a Snap
snap/snapcraft.yaml builds the Linux app as a strictly confined Snap on core24 with the GNOME extension (GTK, WebKitGTK, GStreamer and libsecret come from its runtime; files and the browser go through the desktop's portals), connecting to learn.halkyon.eu, the project's instance. It builds what is committed, with Flutter pinned as in CI. - The session: libsecret's keyring over D-Bus needs the password-manager-service plug, which the Store never connects by itself, so unless it is connected the app keeps the session in an encrypted file whose key comes from the Secret portal (snap/local/secret-backend). - network-manager-observe lets Serverpod hear that the network went and end the sync's connection at once; until the Store grants it, it too is connected by hand. - The listing points to the project, not the instance: the website learn.halkyon.app, contact and issues support@halkyon.app. - The icon is the app's with rounded corners (tool/snap_icon.py), for the Store and the dock alike. - The app's version is 0.1.0, build 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
88b82cda4b
commit
6da776e6ba
7 changed files with 190 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -24,3 +24,6 @@ pubspec_overrides.yaml
|
|||
.scloud/
|
||||
# Deployment secrets (see deploy/.env.example)
|
||||
deploy/.env
|
||||
|
||||
# Snaps built by `snapcraft pack` in the repository root.
|
||||
/*.snap
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.0.0+1
|
||||
version: 0.1.0+1
|
||||
|
||||
environment:
|
||||
sdk: '^3.13.0'
|
||||
|
|
|
|||
9
snap/gui/halkyon-learn.desktop
Normal file
9
snap/gui/halkyon-learn.desktop
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Halkyon Learn
|
||||
Comment=Learn with flashcards, studied offline and synced between your devices
|
||||
Exec=halkyon-learn
|
||||
Icon=${SNAP}/meta/gui/halkyon-learn.png
|
||||
Terminal=false
|
||||
Categories=Education;
|
||||
StartupWMClass=app.halkyon.learn
|
||||
BIN
snap/gui/halkyon-learn.png
Normal file
BIN
snap/gui/halkyon-learn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
12
snap/local/secret-backend
Executable file
12
snap/local/secret-backend
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/sh
|
||||
# Starts the app with a way to keep the session that a Snap can use.
|
||||
#
|
||||
# Serverpod keeps the session with libsecret, whose usual way, the desktop's
|
||||
# keyring over D-Bus, needs the password-manager-service plug, which the Snap
|
||||
# Store never connects by itself. So unless someone connected it by hand,
|
||||
# libsecret keeps an encrypted file in the Snap's own folder, whose key comes
|
||||
# from the desktop's Secret portal.
|
||||
if ! snapctl is-connected password-manager-service; then
|
||||
export SECRET_BACKEND=file
|
||||
fi
|
||||
exec "$@"
|
||||
118
snap/snapcraft.yaml
Normal file
118
snap/snapcraft.yaml
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# 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 learning app: studysets of flashcards with
|
||||
rich text, notes, learning paths and PDFs, reviewed on an FSRS schedule. It works
|
||||
offline and syncs between your devices.
|
||||
|
||||
It starts with an anonymous account; signing in with GitHub later keeps
|
||||
everything. It connects to learn.halkyon.eu, the project's instance.
|
||||
license: AGPL-3.0-or-later
|
||||
source-code: https://code.halkyon.app/project-halkyon/halkyon_learn
|
||||
# The project's, not an instance's: the app works with any instance.
|
||||
website: https://learn.halkyon.app
|
||||
contact: support@halkyon.app
|
||||
# Reports by mail until people can open an account on the issue tracker.
|
||||
issues: support@halkyon.app
|
||||
# The app's icon with rounded corners, made by tool/snap_icon.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.
|
||||
build-packages:
|
||||
- libgstreamer1.0-dev
|
||||
- libgstreamer-plugins-base1.0-dev
|
||||
override-build: |
|
||||
set -eu
|
||||
export PATH="$CRAFT_STAGE/usr/bin:$PATH"
|
||||
cd 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/"
|
||||
|
||||
secret-backend:
|
||||
plugin: dump
|
||||
source: snap/local
|
||||
organize:
|
||||
secret-backend: bin/secret-backend
|
||||
47
tool/snap_icon.py
Executable file
47
tool/snap_icon.py
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Makes the snap's icon from the app's: the same picture with rounded corners.
|
||||
|
||||
The app's icon is a white square, which the Snap Store and the desktop's dock
|
||||
show as it is, so the snap's icon rounds its corners off to transparency. The
|
||||
radius, 142.222 of 512 pixels (about 28 %), is the one other desktop apps'
|
||||
white rounded tiles use. The mask is drawn at four times the size and scaled
|
||||
down, so the corners' edges are smooth.
|
||||
|
||||
Run from the repository root with Python 3 and Pillow (python3-pil on Ubuntu):
|
||||
|
||||
python3 tool/snap_icon.py
|
||||
|
||||
It rewrites snap/gui/halkyon-learn.png (512 x 512 pixels, the Store's size),
|
||||
used by both the Store listing and the desktop file.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
SOURCE = Path("halkyon_learn_flutter/assets/icon/halkyon_icon.png")
|
||||
TARGET = Path("snap/gui/halkyon-learn.png")
|
||||
SIZE = 512
|
||||
RADIUS = 142.222
|
||||
SUPERSAMPLING = 4
|
||||
|
||||
|
||||
def main() -> None:
|
||||
icon = Image.open(SOURCE).convert("RGBA").resize((SIZE, SIZE), Image.LANCZOS)
|
||||
|
||||
large = SIZE * SUPERSAMPLING
|
||||
mask = Image.new("L", (large, large), 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle(
|
||||
(0, 0, large - 1, large - 1), radius=RADIUS * SUPERSAMPLING, fill=255
|
||||
)
|
||||
mask = mask.resize((SIZE, SIZE), Image.LANCZOS)
|
||||
|
||||
# Keeps what the icon itself makes transparent, and takes the corners away.
|
||||
transparent = Image.new("L", (SIZE, SIZE), 0)
|
||||
icon.putalpha(Image.composite(icon.getchannel("A"), transparent, mask))
|
||||
icon.save(TARGET, optimize=True)
|
||||
print(f"Wrote {TARGET} ({TARGET.stat().st_size} bytes).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue