Skip to content

Add Flatpak packaging and release build workflow #7

Description

@Friedjof

Summary

Add Flatpak packaging for GCodeScribe as an additional distribution target next to the existing Docker setup.

The goal is to keep the current application architecture mostly intact: the FastAPI backend, built React frontend, existing profile/job storage, and Docker deployment should continue to work. The Flatpak build should add a desktop-friendly launcher, persistent local app data paths, a reproducible Flatpak manifest, and a GitHub Actions release workflow that can produce installable .flatpak bundles.

Motivation

GCodeScribe should work both as:

  • a Docker-hosted LAN/server app,
  • and as a local Linux desktop app for a laptop connected directly to a plotter/printer.

Docker should remain the preferred deployment path for network/server/workshop setups. Flatpak should become the convenient local Linux desktop packaging path.

Goals

  • Keep the existing Docker workflow working unchanged.
  • Add a Flatpak manifest and desktop packaging files.
  • Add a small desktop launcher that starts the local backend and opens the UI.
  • Persist all important state across application restarts.
  • Avoid Redis as a hard requirement for the local desktop build.
  • Keep profiles, calibration data, generated jobs, gallery assets, and job metadata durable.
  • Bundle Python dependencies reproducibly.
  • Bundle/build the React frontend reproducibly.
  • Include required system tools such as Poppler utilities.
  • Support a release workflow that creates a .flatpak bundle as a GitHub Release asset.
  • Keep online features optional and gracefully degraded when not configured.

Non-goals

  • Do not replace Docker or Docker Compose.
  • Do not run Docker inside Flatpak.
  • Do not rewrite the app as a native desktop app.
  • Do not make Redis required for local desktop use.
  • Do not migrate existing profile/job metadata unnecessarily if the current file-based layout is already reliable.
  • Do not require users to manually edit environment variables for normal desktop usage once the settings UI exists.

Proposed repository structure

packaging/
└── flatpak/
    ├── info.noweck.gcodescribe.yml
    ├── info.noweck.gcodescribe.desktop
    ├── info.noweck.gcodescribe.metainfo.xml
    ├── info.noweck.gcodescribe.svg
    ├── gcodescribe-desktop
    ├── python3-requirements.json
    └── npm-sources.json

The exact app ID can still be changed, but it should be stable before publishing bundles.

Candidate:

info.noweck.gcodescribe

Runtime model

Flatpak should run the same web application locally:

gcodescribe-desktop
├── set desktop/local defaults
├── start gcodescribe-web on 127.0.0.1:<port>
├── open the local UI in a WebView or browser
└── stop backend process when the app exits

Initial MVP launcher can open the system browser with xdg-open. A better desktop experience can later use GTK/WebKitGTK.

Suggested launcher defaults:

PLOTTER_HOST=127.0.0.1
PLOTTER_PORT=<fixed or discovered local port>
PLOTTER_DATA_DIR=/var/data/gcodescribe
REDIS_URL=
GCODESCRIBE_PACKAGING=flatpak

Persistent data model

Be careful that no important data is written to temporary paths.

For the local desktop package, use Flatpak's app-local persistent storage. Inside the sandbox, /var/data maps to the app's persistent data area.

Recommended layout:

/var/data/gcodescribe/
├── calibration.json
├── profiles/
│   ├── active.json
│   └── prof-*.json
├── state/
│   └── *.json
├── jobs/
├── gallery/
└── settings/

Current storage behavior that should be preserved:

  • Calibration/profile state is already file-based.
  • The active profile is stored separately.
  • Job metadata is stored as JSON sidecars next to generated G-code files.
  • Runtime state can already fall back to a file-based store when Redis is unavailable.

Implementation notes:

  • Add an explicit local/file state-store mode so the Flatpak build does not first try Redis on every launch.
  • Keep Redis optional for Docker/server deployments.
  • Use SQLite only where it adds value, for example structured UI settings or future gallery/job metadata indexing.
  • Keep profile and job sidecar JSON files unless there is a strong reason to migrate them.

Possible setting:

STATE_STORE=file

or equivalent internal behavior:

if GCODESCRIBE_PACKAGING == "flatpak":
    use FileStateStore directly

Settings and secrets

This work depends on, or should be coordinated with, a central settings dialog/configuration layer.

Important behavior:

  • Environment variables remain supported.
  • Environment values are startup defaults.
  • UI settings can persist user overrides.
  • Secrets must be redacted in API responses and logs.
  • API keys should ideally use a system keyring where available.

Possible technologies:

  • pydantic-settings for typed config and custom source precedence.
  • SQLite or JSON/TOML for persisted non-secret settings.
  • Python keyring / Freedesktop Secret Service for API keys.

Dependencies to bundle/build

Python

Use Flatpak's Python dependency workflow instead of installing from the network during the build.

Relevant tools:

  • flatpak-pip-generator
  • flatpak-builder-tools

The app currently uses Python dependencies such as FastAPI, uvicorn, vpype, OpenCV, NumPy, ReportLab, pyserial, OpenAI client, etc. These need to be represented in the Flatpak manifest as fixed sources with hashes.

Frontend

The React/Vite frontend must be built as part of the Flatpak build, similarly to the Docker build:

npm ci
npm run build

For Flatpak, npm dependencies should be vendored/declared reproducibly, e.g. via generated npm sources.

System tools

The Docker image currently installs Poppler tools. The Flatpak build must also provide the required binaries:

pdftocairo
pdftoppm
pdfinfo

Office document support needs a separate decision because bundling LibreOffice can make the Flatpak significantly larger. For the first Flatpak iteration, Office support may be optional or documented as not included until a dedicated solution exists.

Sandbox permissions to review carefully

Flatpak permissions are static and should be kept as narrow as possible.

Likely needed:

finish-args:
  - --share=ipc
  - --socket=wayland
  - --socket=fallback-x11

Possibly needed:

  - --share=network

Only if online features or OctoPrint should work from the packaged app:

  • OctoPrint over LAN
  • OpenStreetMap / Overpass
  • OpenAI image generation

For direct serial/USB printer access, this needs careful testing:

  - --device=all

or, where supported:

  - --device=usb

Caution:

  • --device=usb is about raw USB access and may not solve every /dev/ttyUSB* or /dev/ttyACM* serial case.
  • --device=all is broad and less ideal, but may be required for practical serial access.
  • Avoid broad --filesystem=home access.
  • Prefer app-local data and file chooser/import/export flows.
  • If document import/export needs host files, prefer portals or limited XDG directories.

Desktop integration files

Add and validate:

  • .desktop launcher
  • AppStream/MetaInfo file
  • app icon
  • screenshots later if publishing through an app store

The .desktop, icon, and metainfo files should use the chosen app ID consistently.

Local build workflow

Example local build flow:

flatpak install flathub org.gnome.Platform//<runtime-version> org.gnome.Sdk//<runtime-version>
flatpak-builder --user --install --force-clean build-dir packaging/flatpak/info.noweck.gcodescribe.yml
flatpak run info.noweck.gcodescribe

Build a bundle from a local repository:

flatpak-builder --force-clean --repo=repo build-dir packaging/flatpak/info.noweck.gcodescribe.yml
flatpak build-bundle repo GCodeScribe.flatpak info.noweck.gcodescribe
flatpak install --user GCodeScribe.flatpak

GitHub Actions build workflow

Use flatpak/flatpak-github-actions/flatpak-builder to build bundles in CI.

Example PR/main workflow:

name: Flatpak

on:
  pull_request:
  push:
    branches: [main]

jobs:
  flatpak:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48
      options: --privileged
    steps:
      - uses: actions/checkout@v4
      - uses: flatpak/flatpak-github-actions/flatpak-builder@v6
        with:
          bundle: GCodeScribe.flatpak
          manifest-path: packaging/flatpak/info.noweck.gcodescribe.yml
          cache-key: flatpak-builder-${{ github.sha }}
          upload-artifact: true

For release tags, create/upload the .flatpak bundle as a release asset.

Example release workflow outline:

name: Release Flatpak

on:
  push:
    tags:
      - "v*"

permissions:
  contents: write

jobs:
  flatpak:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48
      options: --privileged
    steps:
      - uses: actions/checkout@v4
      - uses: flatpak/flatpak-github-actions/flatpak-builder@v6
        with:
          bundle: GCodeScribe-${{ github.ref_name }}.flatpak
          manifest-path: packaging/flatpak/info.noweck.gcodescribe.yml
          cache-key: flatpak-builder-${{ github.ref_name }}
          upload-artifact: true

      - name: Upload Flatpak to GitHub Release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1 || gh release create "${GITHUB_REF_NAME}" --title "${GITHUB_REF_NAME}" --notes "Release ${GITHUB_REF_NAME}"
          gh release upload "${GITHUB_REF_NAME}" GCodeScribe-${GITHUB_REF_NAME}.flatpak --clobber

Release process considerations

Before tagging a release:

  • Update pyproject.toml version.
  • Update frontend/package.json version if kept in sync.
  • Add/update AppStream release metadata in the .metainfo.xml file.
  • Ensure Docker image build still works.
  • Ensure Flatpak build works locally and in CI.
  • Ensure generated .flatpak installs on a clean Linux user account.
  • Test first-run setup/settings.
  • Test data persistence across app restarts.
  • Test direct serial access if enabled.
  • Test behavior without network.
  • Test behavior without Redis.

Suggested release assets:

GCodeScribe-vX.Y.Z.flatpak
checksums.txt

Optional later:

GCodeScribe-vX.Y.Z-x86_64.flatpak
GCodeScribe-vX.Y.Z-aarch64.flatpak

Things to be careful about

Persistent data

Do not write important state into temporary build/runtime paths. App data must survive closing and reopening the app.

Verify that the following survive restarts:

  • active profile
  • calibration values
  • paper calibration
  • job metadata
  • profile fingerprints
  • generated jobs
  • gallery assets
  • settings
  • selected backend
  • serial/OctoPrint settings

Profile/job safety

Do not break the current profile fingerprint model. Existing jobs must remain tied to the profile they were generated with. Stale, missing, archived, or foreign profiles should still prevent unsafe printing.

Secrets

Do not log API keys. Do not return raw secrets through /api/settings. Do not store secrets in normal exported settings unless explicitly requested and clearly warned.

Network behavior

The app should still be useful without internet access. Online-only features should show clear disabled/error states instead of failing silently.

USB/serial behavior

Serial access from sandboxed desktop packages is the riskiest part. Test with real devices:

  • /dev/ttyUSB0
  • /dev/ttyACM0
  • /dev/serial/by-id/...

Document any required host permissions or overrides.

Runtime size

Bundling OpenCV, Poppler, vpype, and optional LibreOffice can become large. Keep the first Flatpak version focused and consider making Office support optional later.

Reproducibility

Avoid build steps that download unpinned dependencies. Python and npm dependencies should be represented as fixed sources with hashes where possible.

Useful references

Flatpak documentation:

Flatpak GitHub Actions:

Storage/settings/secrets:

Desktop wrapper options:

GitHub release upload:

Serial access:

Acceptance criteria

  • Flatpak manifest exists and builds locally.
  • .desktop, icon, and metainfo files are included.
  • A Flatpak launcher starts the local backend and opens the UI.
  • Important app data persists across restarts.
  • Redis is not required for local desktop use.
  • Python dependencies are bundled reproducibly.
  • Frontend is built reproducibly.
  • Required PDF conversion tools are available inside the Flatpak.
  • Docker workflow remains unchanged.
  • GitHub Actions builds a Flatpak bundle on PR/main.
  • GitHub Actions uploads a .flatpak bundle for version tags/releases.
  • Installation from the generated bundle is documented.
  • Known limitations, especially USB/serial and online features, are documented.

Suggested implementation steps

  1. Decide final app ID.
  2. Add Flatpak metadata files.
  3. Add a minimal gcodescribe-desktop launcher.
  4. Force local desktop defaults through the launcher.
  5. Make state store explicitly file-based when configured.
  6. Add initial Flatpak manifest.
  7. Add Python dependency source generation.
  8. Add npm dependency source generation.
  9. Add Poppler/system tool modules.
  10. Build and install locally.
  11. Test persistence and first-run behavior.
  12. Test offline behavior.
  13. Test serial access with real hardware.
  14. Add GitHub Actions build workflow.
  15. Add tag/release workflow.
  16. Document install and troubleshooting steps in the README.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions