feat(packaging/flatpak,decky): Steam Deck client flatpak + plugin deploy + CI
apple / swift (push) Successful in 53s
android / android (push) Successful in 3m48s
ci / web (push) Successful in 29s
ci / docs-site (push) Successful in 34s
ci / rust (push) Successful in 2m21s
ci / bench (push) Successful in 1m36s
decky / build-publish (push) Successful in 31s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
flatpak / build-publish (push) Failing after 4s
deb / build-publish (push) Successful in 2m38s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m9s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m42s
docker / deploy-docs (push) Successful in 16s
apple / swift (push) Successful in 53s
android / android (push) Successful in 3m48s
ci / web (push) Successful in 29s
ci / docs-site (push) Successful in 34s
ci / rust (push) Successful in 2m21s
ci / bench (push) Successful in 1m36s
decky / build-publish (push) Successful in 31s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
flatpak / build-publish (push) Failing after 4s
deb / build-publish (push) Successful in 2m38s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m9s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m42s
docker / deploy-docs (push) Successful in 16s
Ship the punktfunk Linux client to the Steam Deck as a Flatpak — the only viable
SteamOS install path, since /usr is read-only and lacks libadwaita/SDL3 — and
publish both it and the Decky plugin through Gitea. Built and validated live on a
Steam Deck (SteamOS 3.7): bundle installs user-scope, all libs resolve, libavcodec
resolves to the codecs-extra HEVC build, devices=all for DualSense hidraw.
packaging/flatpak (new):
- io.unom.Punktfunk.yml on GNOME 50 / freedesktop-sdk 25.08. rust-stable//25.08
(rustc 1.96 — the GTK4 chain needs >=1.92; the EOL GNOME-48/24.08 rust-stable at
1.89 could not build it) + llvm20 (libclang for bindgen in ffmpeg-sys-next/sdl3-sys).
HEVC libavcodec comes from the runtime's auto codecs-extra extension point (no
app-side codec declaration). Bundled SDL3 3.4.10 (matches sdl3-sys 0.6.6+SDL-3.4.10).
finish-args: wayland/fallback-x11, --device=all (GPU/VAAPI + evdev + hidraw — flatpak
cannot bind /dev/hidrawN char devices via --filesystem), pulseaudio, network,
~/.config/punktfunk.
- metainfo.xml, desktop, square SVG icon, build-flatpak.sh (offline cargo-sources;
on-Deck org.flatpak.Builder or CI), README.
clients/decky:
- add LICENSE (MIT), fix package.json license (BSD-3-Clause -> Apache-2.0 OR MIT),
add scripts/{package.sh,deploy.sh} (the plugins dir is root-owned: stage to /tmp,
sudo install, restart plugin_loader), align the launcher fallback to the real
flatpak app id io.unom.Punktfunk, rewrite the install section.
.gitea/workflows:
- flatpak.yml: privileged Fedora container builds the bundle and publishes to the
Gitea generic registry (+ release attachment on tags).
- decky.yml: pnpm build -> store-layout zip -> registry (stable latest/ URL for
Decky "install from URL").
docs: packaging/README + packaging/flatpak/README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
# Build the punktfunk Decky Loader plugin (Gaming-Mode QAM launcher) into a distribution zip
|
||||
# and publish it to Gitea's GENERIC package registry, giving Decky's "install from URL" a
|
||||
# stable link. On tags the zip is ALSO attached to the Gitea release.
|
||||
#
|
||||
# PUT/GET https://git.unom.io/api/packages/unom/generic/punktfunk-decky/<version>/punktfunk.zip
|
||||
#
|
||||
# The plugin backend is PURE PYTHON (clients/decky/main.py — no compiled binary), so we do NOT
|
||||
# need the Decky CLI (which requires Docker + rust-nightly only to compile native backends).
|
||||
# We build the frontend with pnpm and assemble the store-layout zip by hand:
|
||||
#
|
||||
# punktfunk.zip
|
||||
# punktfunk/ <- single top-level dir == plugin.json "name"
|
||||
# plugin.json [required]
|
||||
# package.json [required]
|
||||
# main.py [required: python backend]
|
||||
# dist/index.js [required: rollup output]
|
||||
# README.md (recommended)
|
||||
# LICENSE [required by the plugin store]
|
||||
#
|
||||
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope (shared with deb/rpm/docker).
|
||||
name: decky
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: git.unom.io
|
||||
OWNER: unom
|
||||
PACKAGE: punktfunk-decky # generic-registry package name
|
||||
PLUGIN: punktfunk # plugin.json "name" == zip top-level dir
|
||||
|
||||
jobs:
|
||||
build-publish:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
container:
|
||||
image: node:22-bookworm # node + corepack(pnpm); matches the @decky toolchain
|
||||
defaults:
|
||||
run:
|
||||
working-directory: clients/decky
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: pnpm
|
||||
run: |
|
||||
corepack enable
|
||||
# The repo's pnpm-lock.yaml + package.json devDeps target pnpm 9 (the version the
|
||||
# @decky toolchain and the local build use). Pin it so --frozen-lockfile holds.
|
||||
corepack prepare pnpm@9 --activate
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run build # rollup -> clients/decky/dist/index.js
|
||||
|
||||
- name: Version
|
||||
# Tag v1.2.3 -> 1.2.3; main push -> 0.0.1-ciN.g<sha>. Used only for the registry
|
||||
# version path + the zip name (the plugin.json version is the source of truth Decky
|
||||
# reads after install).
|
||||
working-directory: ${{ gitea.workspace }}
|
||||
run: |
|
||||
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}" ;;
|
||||
*) V="0.0.1-ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;;
|
||||
esac
|
||||
echo "VERSION=$V" >> "$GITHUB_ENV"
|
||||
echo "decky version $V"
|
||||
|
||||
- name: Assemble store-layout zip
|
||||
working-directory: ${{ gitea.workspace }}
|
||||
run: |
|
||||
apt-get update && apt-get install -y --no-install-recommends zip >/dev/null
|
||||
STAGE="$RUNNER_TEMP/decky"
|
||||
DEST="$STAGE/$PLUGIN"
|
||||
rm -rf "$STAGE"; mkdir -p "$DEST/dist"
|
||||
cp clients/decky/plugin.json "$DEST/"
|
||||
cp clients/decky/package.json "$DEST/"
|
||||
cp clients/decky/main.py "$DEST/"
|
||||
cp clients/decky/dist/index.js "$DEST/dist/"
|
||||
cp clients/decky/README.md "$DEST/"
|
||||
# Store requires a LICENSE in the plugin root; the project is MIT OR Apache-2.0.
|
||||
cp LICENSE-MIT "$DEST/LICENSE"
|
||||
( cd "$STAGE" && zip -r "$RUNNER_TEMP/punktfunk.zip" "$PLUGIN" )
|
||||
ls -lh "$RUNNER_TEMP/punktfunk.zip"
|
||||
unzip -l "$RUNNER_TEMP/punktfunk.zip"
|
||||
|
||||
- name: Publish to the Gitea generic registry
|
||||
working-directory: ${{ gitea.workspace }}
|
||||
env:
|
||||
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
BASE="https://$REGISTRY/api/packages/$OWNER/generic/$PACKAGE"
|
||||
# 1) Immutable, versioned URL.
|
||||
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$RUNNER_TEMP/punktfunk.zip" \
|
||||
"$BASE/$VERSION/punktfunk.zip"
|
||||
echo "published $BASE/$VERSION/punktfunk.zip"
|
||||
# 2) Stable `latest/punktfunk.zip` — this is the link to paste into Decky's
|
||||
# "install from URL". The generic registry rejects re-uploading an existing
|
||||
# version/file (409), so delete the prior `latest` first (ignore 404 on run #1).
|
||||
curl -fsS -o /dev/null --user "enricobuehler:$TOKEN" -X DELETE \
|
||||
"$BASE/latest/punktfunk.zip" || true
|
||||
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$RUNNER_TEMP/punktfunk.zip" \
|
||||
"$BASE/latest/punktfunk.zip"
|
||||
echo "install-from-URL link: $BASE/latest/punktfunk.zip"
|
||||
|
||||
- name: Attach zip to the Gitea release (tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/')
|
||||
working-directory: ${{ gitea.workspace }}
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
API="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}"
|
||||
ID=$(curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
|
||||
-d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\"}" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])' \
|
||||
|| curl -sf "$API/releases/tags/$GITHUB_REF_NAME" -H "Authorization: token $TOKEN" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
|
||||
curl -sf -X POST "$API/releases/$ID/assets?name=punktfunk-${VERSION}.zip" \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@$RUNNER_TEMP/punktfunk.zip" >/dev/null
|
||||
echo "attached punktfunk-${VERSION}.zip to release $GITHUB_REF_NAME"
|
||||
@@ -0,0 +1,129 @@
|
||||
# Build the native punktfunk Linux CLIENT as a single-file Flatpak bundle and publish it to
|
||||
# Gitea's GENERIC package registry, so the Steam Deck (and any flatpak distro) installs it
|
||||
# the SteamOS-native, update-survivable way: `flatpak install --user <downloaded>.flatpak`.
|
||||
# (The HOST stays an RPM/deb — it needs unsandboxed /dev/uinput + zero-copy NVENC; only the
|
||||
# CLIENT is sandbox-friendly. See packaging/README.md and packaging/flatpak/README.md.)
|
||||
#
|
||||
# Gitea has NO flatpak/ostree registry, so the bundle lives in the generic registry:
|
||||
# PUT https://git.unom.io/api/packages/unom/generic/punktfunk-client-flatpak/<version>/<file>
|
||||
# GET https://git.unom.io/api/packages/unom/generic/punktfunk-client-flatpak/<version>/<file>
|
||||
# On tags the bundle is ALSO attached to the Gitea release (mirrors release.yml's DMG).
|
||||
#
|
||||
# PRIVILEGED-BUILD CONSTRAINT: flatpak-builder runs bubblewrap, which needs user namespaces.
|
||||
# In a Gitea/act_runner Docker executor that means the job container must be --privileged
|
||||
# (the same runner already runs `docker build` in docker.yml, so its Docker daemon allows it).
|
||||
# If your runner CANNOT grant --privileged, this job will fail at `flatpak-builder` with
|
||||
# "Creating new namespace failed: Operation not permitted" — see the fallback in
|
||||
# packaging/flatpak/README.md (build on the Deck via org.flatpak.Builder, or on a Linux box,
|
||||
# then upload with the curl line below).
|
||||
#
|
||||
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope (shared with deb/rpm/docker).
|
||||
name: flatpak
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: git.unom.io
|
||||
OWNER: unom
|
||||
APP_ID: io.unom.Punktfunk
|
||||
MANIFEST: packaging/flatpak/io.unom.Punktfunk.yml
|
||||
PACKAGE: punktfunk-client-flatpak # generic-registry package name
|
||||
|
||||
jobs:
|
||||
build-publish:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120
|
||||
container:
|
||||
# Fedora ships a recent flatpak + flatpak-builder + the kernel userns support.
|
||||
# --privileged is required for bubblewrap inside the Docker executor (see header).
|
||||
image: fedora:43
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Tooling
|
||||
run: |
|
||||
dnf -y install flatpak flatpak-builder git python3 python3-aiohttp python3-toml curl jq
|
||||
# Flathub provides the GNOME runtime/SDK + the rust-stable + ffmpeg-full extensions.
|
||||
flatpak remote-add --user --if-not-exists flathub \
|
||||
https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
git config --global --add safe.directory "$PWD"
|
||||
|
||||
- name: Version
|
||||
# Tag v1.2.3 -> 1.2.3; a main push -> 0.0.1-ciN.g<sha> (sorts before a real release,
|
||||
# increases by run number — newest main build always wins). The generic registry
|
||||
# version string allows letters/dots/hyphens.
|
||||
run: |
|
||||
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}" ;;
|
||||
*) V="0.0.1-ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;;
|
||||
esac
|
||||
echo "VERSION=$V" >> "$GITHUB_ENV"
|
||||
echo "BUNDLE=punktfunk-client-${V}.flatpak" >> "$GITHUB_ENV"
|
||||
echo "flatpak version $V"
|
||||
|
||||
- name: Generate offline cargo sources
|
||||
# flatpak builds with no network; vendor every crate from Cargo.lock into
|
||||
# cargo-sources.json next to the manifest (referenced by the manifest's
|
||||
# punktfunk-client module).
|
||||
run: |
|
||||
curl -fsSL -o /tmp/flatpak-cargo-generator.py \
|
||||
https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py
|
||||
python3 /tmp/flatpak-cargo-generator.py Cargo.lock \
|
||||
-o packaging/flatpak/cargo-sources.json
|
||||
|
||||
- name: Build the flatpak (install deps from Flathub, offline build)
|
||||
run: |
|
||||
# --install-deps-from=flathub pulls everything the manifest declares: the GNOME 50
|
||||
# runtime/SDK + the rust-stable (//25.08, rustc 1.96) and llvm20 SDK extensions, plus
|
||||
# the runtime's auto codecs-extra (HEVC libavcodec). --disable-rofiles-fuse is the
|
||||
# container-safe path (no FUSE).
|
||||
flatpak-builder --user --force-clean --disable-rofiles-fuse \
|
||||
--install-deps-from=flathub \
|
||||
--repo="$PWD/repo" \
|
||||
"$PWD/build-dir" "$MANIFEST"
|
||||
|
||||
- name: Export single-file bundle
|
||||
run: |
|
||||
flatpak build-bundle "$PWD/repo" "$BUNDLE" "$APP_ID"
|
||||
ls -lh "$BUNDLE"
|
||||
|
||||
- name: Publish to the Gitea generic registry
|
||||
env:
|
||||
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
BASE="https://$REGISTRY/api/packages/$OWNER/generic/$PACKAGE"
|
||||
# 1) Immutable, versioned URL.
|
||||
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$BUNDLE" \
|
||||
"$BASE/$VERSION/$BUNDLE"
|
||||
echo "published $BASE/$VERSION/$BUNDLE"
|
||||
# 2) Stable `latest/punktfunk-client.flatpak` alias for the Decky fallback + scripts.
|
||||
# The generic registry rejects re-uploading an existing version/file (409), so
|
||||
# delete the prior `latest` file first (ignore 404 on the first ever run).
|
||||
curl -fsS -o /dev/null --user "enricobuehler:$TOKEN" -X DELETE \
|
||||
"$BASE/latest/punktfunk-client.flatpak" || true
|
||||
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$BUNDLE" \
|
||||
"$BASE/latest/punktfunk-client.flatpak"
|
||||
echo "published $BASE/latest/punktfunk-client.flatpak"
|
||||
|
||||
- name: Attach bundle to the Gitea release (tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/')
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
API="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}"
|
||||
ID=$(curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
|
||||
-d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\"}" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])' \
|
||||
|| curl -sf "$API/releases/tags/$GITHUB_REF_NAME" -H "Authorization: token $TOKEN" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
|
||||
curl -sf -X POST "$API/releases/$ID/assets?name=$BUNDLE" \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@$BUNDLE" >/dev/null
|
||||
echo "attached $BUNDLE to release $GITHUB_REF_NAME"
|
||||
Reference in New Issue
Block a user