Package an existing interactive course generated by the book-to-course skill (a folder with index.html, assets/ and course/data.js, or its .zip) into a self-contained Linux AppImage — one file that carries its own WebView engine (Electron), so it runs on any desktop distro without installing anything, keeps progress in a file and can run code exercises' tests. Checks the build tools on the machine (appimagetool, Electron, AppImage runtime), tells the user exactly what is missing and where to ...
Installs into .claude/skills of the current project.
Are you the author of Course To Appimage?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/sebastianhaba-course-to-appimage)
---
name: course-to-appimage
description: Package an existing interactive course generated by the book-to-course skill (a folder with index.html, assets/ and course/data.js, or its .zip) into a self-contained Linux AppImage — one file that carries its own WebView engine (Electron), so it runs on any desktop distro without installing anything, keeps progress in a file and can run code exercises' tests. Checks the build tools on the machine (appimagetool, Electron, AppImage runtime), tells the user exactly what is missing and where to get it, and can download it for them. Use this whenever the user wants a course "na Linuxa", "jako aplikację na pulpit/desktop", "w menu aplikacji", "zrób z kursu AppImage", "course to appimage", wants a course app they can copy to another Linux computer, wants to open their generated course like a normal program instead of start.sh, or wants to rebuild/update the course app after adding chapters — even if they don't say "AppImage" explicitly. Do NOT use for building a course from a book (that is book-to-course), for Android (course-to-apk), Windows/macOS installers, Flatpak/Snap/.deb packaging, or general Linux/Electron app development.
---
# Course → Linux AppImage
Wraps a finished book-to-course course into one executable file for Linux, self-contained like the APK: copy it to any
desktop Linux computer and start it — no Python, no WebKitGTK, nothing to install. Inside: the official Electron build
(Chromium + Node), a small `main.js`, the course, an icon and the Noto Color Emoji font (the player's UI uses emoji, and
many distros ship no emoji font). `main.js` does what the course's `serve.py` does — serves
the page on 127.0.0.1, saves progress to a file and runs code exercises' tests — so the "Run tests" button works on
the desktop (unlike the phone app). Nothing is compiled; the AppImage is ~105 MB + the course (that's Chromium; unused
Chromium translations are left out) + ~9 MB for the emoji font.
Why Electron and not the system WebView: Linux has no WebView every computer is guaranteed to have, and bundling this
machine's WebKitGTK would tie the file to this distro's glibc. Official Electron builds run on any current desktop distro.
The wrapper and the scripts are fixed and tested — **your job is the conversation around them**: find the course, get
the toolchain in place with the user's consent, build, and explain how to start and install the app.
| script (in `scripts/`) | purpose |
|---|---|
| `toolchain.py [--json]` | what is installed, what is missing, where to download it; exit 0 = ready |
| `setup_toolchain.py --dry-run` | list exactly what would be downloaded (name, size, URL) — downloads nothing |
| `setup_toolchain.py --download` | download only the missing parts into `~/.local/share/course-to-appimage` (no sudo, SHA-256 verified) |
| `build_appimage.py COURSE_DIR [--install]` | build the AppImage into `<COURSE_DIR>-appimage/`; `--install` adds it to the app menu |
| `build_appimage.py COURSE_DIR --uninstall` | remove the menu entry, icon and `~/Applications` copy (progress stays) |
All scripts are Python 3 standard library only (Python is needed on the build machine, not in the app).
## Workflow
### 1. Find the course
A course folder has `index.html`, `assets/app.js` and `course/data.js`. Look where the user points, in the current
directory, or among recent book-to-course output. If they give a `.zip` (from `pack_course.py`), unzip it next to the zip
first — it contains one top folder named after the course; that folder is COURSE_DIR. If `content/` exists but
`course/data.js` is missing or older than the lessons, the course was not rebuilt — run book-to-course's
`build_course.py COURSE_DIR` first (when that skill is available), otherwise ask the user to.
### 2. Check the toolchain
```bash
python3 scripts/toolchain.py
```
Needed: **appimagetool** (packs the file), **Electron 30+** (the engine, ~115 MB official zip — an unpacked folder or
the zip itself, found in Downloads, `~/.cache/electron`, `$ELECTRON_DIST` or the skill's own folder) and the **Noto Color
Emoji** font (taken from the system's fonts when installed — usually it is — otherwise a pinned ~10 MB download). The **runtime** file
is optional. It checks that appimagetool actually starts. If it prints **READY**, go to step 4 (if only the runtime is
missing, mention in one line that the build will fetch it from GitHub — don't stop for it).
### 3. Something is missing → ask, don't assume
Tell the user, in their language, what is missing — use the report's lines, they already contain the official download
links (GitHub releases of AppImage/appimagetool, electron/electron and AppImage/type2-runtime, googlefonts/noto-emoji)
and package-manager commands. Run `setup_toolchain.py --dry-run` so you can quote the real sizes, versions and URLs, then ask them to
choose (AskUserQuestion works well here):
- **"I'll install it myself"** — give a short checklist: what to download, from where, and where to put it
(appimagetool: `chmod +x`, e.g. as `~/.local/bin/appimagetool`; Electron: the `electron-vX.Y.Z-linux-x64.zip` — `arm64`
on ARM — can simply stay in Downloads, the build unpacks it). Warn that the distro's `electron` package is not
suitable (built against this system's libraries). Then **stop and wait** — end your turn. Do not poll, do not build,
do not download anything. When they write that it's done, rerun `toolchain.py`; if something is still missing or
"found but unusable", say exactly what and wait again.
- **"Download it for me"** — run `python3 scripts/setup_toolchain.py --download` (~140 MB when everything is missing;
official GitHub releases — MIT, the font OFL — checksums verified; installs only into `~/.local/share/course-to-appimage`,
no sudo). It is a one-time download — later builds reuse it.
Why the care: downloading over 100 MB and running a binary are the user's decisions, not yours, and a user who said
they'd install it themselves is mid-installation — building or downloading behind their back collides with that.
### 4. Build
```bash
python3 scripts/build_appimage.py "<COURSE_DIR>"
```
Takes a few seconds. Defaults that are right for almost everyone — mention them, don't ask:
- **Output** `<COURSE_DIR>-appimage/<course-id>-<arch>.AppImage`, next to the course folder; `appimage.json` there
remembers the app id, name, icon colour and an auto-incremented version.
- **App id** `net.booktocourse.<course_id>`, **name** = course title. Override with `--app-id` / `--name` only when asked —
the app id names the learner's data folder, so changing it later starts with empty progress.
- Built for this machine's architecture (x86_64 or aarch64 — Electron has no 32-bit Linux build).
Offer `--install` (copies the AppImage to `~/Applications`, adds a menu entry with icon under *Education*, no sudo) —
ask in the same message as the hand-over if the user hasn't said; it's quick to do afterwards by rerunning with `--install`.
Once installed, every later build refreshes the installed copy automatically.
The build ends with a self-test (it starts the AppImage with `--paths`, which loads Electron and main.js). If the build
or the self-test fails, the output says what failed; see `references/troubleshooting.md`.
### 5. Hand it over
Tell the user in a few lines: where the AppImage is (absolute path), its size, and how to start it:
- double-click it in the file manager, or `./<file>.AppImage` in a terminal. **On another computer**: copy the one file
(USB stick, cloud drive, network); if it lost the executable bit, `chmod +x` / Properties → "Allow executing as program".
- with `--install`: from the application menu (Education), with its own icon.
Also say, briefly:
- **Progress** lives in `~/.local/share/<app-id>/progress/progress.json` of whoever runs the app (not in the course
folder's `progress/` — the page's footer wording comes from the folder version). It survives updates: rebuild after
adding chapters and start the new file.
- **Code exercises** (if the course has them) work fully, unlike on the phone: the AppImage is an ordinary program on the
computer, so "Run tests" runs the exercise's command (e.g. `go test`) with the compiler installed there — exactly like
`start.sh`. The learner edits the files in their own editor; editable copies live in `~/.local/share/<app-id>/exercises/`,
and each exercise in the app shows that full path, an "Open folder" button and a ready `cd …` command. The language
toolchain (e.g. Go) must be installed on that computer — without it the page says the program is not installed.
Bundling compilers is not done: hundreds of MB per language. Lessons, quizzes, tests and flashcards need nothing.
- **Requirements of the target**: a desktop Linux (x86_64/arm64 as built) — the usual desktop libraries (GTK 3, NSS, X11/
Wayland, ALSA) that every GNOME/KDE/XFCE/Cinnamon install has, as for any Electron app. A bare server/container without
a desktop lacks them.
## What the app does (so you can answer questions)
- `AppRun` starts the bundled Electron; where unprivileged user namespaces are blocked (e.g. Ubuntu 24.04's AppArmor
default) or when run as root, it adds `--no-sandbox`, because an AppImage cannot carry Chromium's setuid sandbox helper.
The window only shows the bundled course; links to other sites open in the user's own browser.
- `main.js` serves the course on 127.0.0.1 with the same API as `serve.py` (`/api/ping`, `/api/progress`,
`/api/run-tests`; host check; the test command comes from `exercise.json`, only the folder name from the page), on a
port derived from the app id so the page's localStorage (theme) survives restarts.
- In exercise blocks it replaces the player's relative "exercises/<dir>/" and "cd exercises/<dir>" (meant for the course
folder) with the real path, adds an "Open folder" button (`/api/open-exercise`) and makes the copy button copy the fixed command.
- `AppRun` points fontconfig at a config that includes the system's `/etc/fonts/fonts.conf` plus the bundled emoji font
as a fallback; the system's own emoji font still wins where installed.
- On start it copies new exercises and refreshes each exercise's `exercise.json` and test files; the learner's own files
are never overwritten. `_solution/` folders are not packed, and the starter files are taken from `course/data.js`
(what the page shows as start files), because the course folder's `exercises/` may hold someone's solved versions. It adds common toolchain dirs (`~/go/bin`,
`/usr/local/go/bin`, `~/.cargo/bin`, …) to PATH, since a desktop session's PATH often lacks them.
- One instance per app (a second start focuses the window). Ctrl +/−/0 zoom, Ctrl+R reload, F11 full screen; window
size and zoom are remembered. Chromium's data lives in `~/.local/share/<app-id>/electron`.
- Options: `--paths`, `--open-exercises`.
To change the wrapper, edit `assets/app/main.js` (Electron main process, Node standard library only — there is no npm
step) or `assets/appdir/AppRun`.