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:
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:
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:
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:
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:
or, where supported:
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
- Decide final app ID.
- Add Flatpak metadata files.
- Add a minimal
gcodescribe-desktop launcher.
- Force local desktop defaults through the launcher.
- Make state store explicitly file-based when configured.
- Add initial Flatpak manifest.
- Add Python dependency source generation.
- Add npm dependency source generation.
- Add Poppler/system tool modules.
- Build and install locally.
- Test persistence and first-run behavior.
- Test offline behavior.
- Test serial access with real hardware.
- Add GitHub Actions build workflow.
- Add tag/release workflow.
- Document install and troubleshooting steps in the README.
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
.flatpakbundles.Motivation
GCodeScribe should work both as:
Docker should remain the preferred deployment path for network/server/workshop setups. Flatpak should become the convenient local Linux desktop packaging path.
Goals
.flatpakbundle as a GitHub Release asset.Non-goals
Proposed repository structure
The exact app ID can still be changed, but it should be stable before publishing bundles.
Candidate:
Runtime model
Flatpak should run the same web application locally:
Initial MVP launcher can open the system browser with
xdg-open. A better desktop experience can later use GTK/WebKitGTK.Suggested launcher defaults:
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/datamaps to the app's persistent data area.Recommended layout:
Current storage behavior that should be preserved:
Implementation notes:
Possible setting:
or equivalent internal behavior:
Settings and secrets
This work depends on, or should be coordinated with, a central settings dialog/configuration layer.
Important behavior:
Possible technologies:
pydantic-settingsfor typed config and custom source precedence.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-generatorflatpak-builder-toolsThe 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:
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:
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:
Possibly needed:
- --share=networkOnly if online features or OctoPrint should work from the packaged app:
For direct serial/USB printer access, this needs careful testing:
- --device=allor, where supported:
- --device=usbCaution:
--device=usbis about raw USB access and may not solve every/dev/ttyUSB*or/dev/ttyACM*serial case.--device=allis broad and less ideal, but may be required for practical serial access.--filesystem=homeaccess.Desktop integration files
Add and validate:
.desktoplauncherThe
.desktop, icon, and metainfo files should use the chosen app ID consistently.Local build workflow
Example local build flow:
Build a bundle from a local repository:
GitHub Actions build workflow
Use
flatpak/flatpak-github-actions/flatpak-builderto build bundles in CI.Example PR/main workflow:
For release tags, create/upload the
.flatpakbundle as a release asset.Example release workflow outline:
Release process considerations
Before tagging a release:
pyproject.tomlversion.frontend/package.jsonversion if kept in sync..metainfo.xmlfile..flatpakinstalls on a clean Linux user account.Suggested release assets:
Optional later:
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:
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
.desktop, icon, and metainfo files are included..flatpakbundle for version tags/releases.Suggested implementation steps
gcodescribe-desktoplauncher.