mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-15 03:36:48 -04:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb17fff4ff | ||
|
|
d8ec1587d3 | ||
|
|
fc9060ae7b | ||
|
|
0c7a9e5b06 |
No files matched your search
@@ -50,4 +50,13 @@ jobs:
|
||||
desktop-file-validate packaging/linux/ai.storyteller.photocraft.desktop
|
||||
sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.photocraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.photocraft.metainfo.xml"
|
||||
appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.photocraft.metainfo.xml"
|
||||
python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.photocraft.yml')); assert m['id'] == 'ai.storyteller.photocraft' and m['command'] == 'photocraft'"
|
||||
python3 - <<'EOF'
|
||||
import yaml
|
||||
src = yaml.safe_load(open("packaging/linux/flatpak/ai.storyteller.photocraft.yml"))
|
||||
bundle = yaml.safe_load(open("packaging/linux/flatpak/ai.storyteller.photocraft.bundle.yml"))
|
||||
for m in (src, bundle):
|
||||
assert m["id"] == "ai.storyteller.photocraft" and m["command"] == "photocraft", m["id"]
|
||||
for key in ("runtime", "runtime-version", "sdk", "finish-args"):
|
||||
assert src[key] == bundle[key], f"Flatpak manifests disagree on {key}: {src[key]} vs {bundle[key]}"
|
||||
print("Flatpak manifests ok")
|
||||
EOF
|
||||
@@ -201,6 +201,44 @@ jobs:
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
flatpak:
|
||||
name: Flatpak ${{ matrix.arch }}
|
||||
needs: [version, linux]
|
||||
# Repackages the Linux job's tarball (the same binaries as the AppImage/deb/rpm); no Rust
|
||||
# build here. 24.04 for a newer flatpak-builder (1.4) than 22.04 ships.
|
||||
runs-on: ${{ matrix.runner }}
|
||||
# No `environment: release`: this job signs nothing, so it gets no release secrets.
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { arch: x86_64, runner: ubuntu-24.04 }
|
||||
- { arch: aarch64, runner: ubuntu-24.04-arm }
|
||||
env:
|
||||
PHOTOCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
PHOTOCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-linux-${{ matrix.arch }}
|
||||
path: dist/linux
|
||||
- name: Install flatpak-builder
|
||||
run: |
|
||||
sudo apt-get update
|
||||
# librsvg2-common: the SVG pixbuf loader. flatpak-builder runs the host's
|
||||
# `appstreamcli compose`, which renders the scalable icon and fails without it.
|
||||
sudo apt-get install -y --no-install-recommends flatpak flatpak-builder librsvg2-common
|
||||
# Ubuntu 24.04's AppArmor blocks the unprivileged user namespaces bubblewrap needs.
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
- name: Build the Flatpak bundle
|
||||
run: packaging/linux/flatpak-bundle.sh "dist/linux/photocraft-$PHOTOCRAFT_VERSION-linux-${{ matrix.arch }}.tar.gz"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-flatpak-${{ matrix.arch }}
|
||||
path: dist/release/*.flatpak
|
||||
if-no-files-found: error
|
||||
|
||||
web:
|
||||
name: Web (wasm)
|
||||
needs: version
|
||||
@@ -236,7 +274,7 @@ jobs:
|
||||
|
||||
release:
|
||||
name: Draft GitHub Release
|
||||
needs: [version, macos, windows, linux, web]
|
||||
needs: [version, macos, windows, linux, flatpak, web]
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
permissions:
|
||||
|
||||
@@ -257,7 +257,14 @@ cargo test --workspace # the test suite
|
||||
|
||||
New contributors and AI agents: start with [`AGENTS.md`](AGENTS.md), then [`docs/`](docs/).
|
||||
|
||||
Installers for macOS, Windows, Linux and the web are attached to each [GitHub release](https://github.com/storytold/photocraft/releases). Maintainers: [`docs/releasing.md`](docs/releasing.md) explains how releases are built, signed and published.
|
||||
Installers for macOS, Windows, Linux and the web are attached to each [GitHub release](https://github.com/storytold/photocraft/releases). On Linux you can pick an AppImage, a `.deb`, an `.rpm`, a tarball or a Flatpak bundle. The bundle needs the freedesktop runtime from [Flathub](https://flathub.org/setup), which `flatpak` offers to install along with it:
|
||||
|
||||
```sh
|
||||
flatpak install --user photocraft-<version>-linux-x86_64.flatpak # or -linux-aarch64
|
||||
flatpak run ai.storyteller.photocraft
|
||||
```
|
||||
|
||||
Maintainers: [`docs/releasing.md`](docs/releasing.md) explains how releases are built, signed and published.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Status:** PhotoCraft is in early alpha. The core editing workflow is here, and we're working toward full Photoshop parity, milestone by milestone (see [`docs/roadmap.md`](docs/roadmap.md)). Progress is measured, not guessed: `cargo xtask parity` checks every item in Photoshop's menu tree against the live command registry and writes [`docs/parity.md`](docs/parity.md). Expect rough edges, and please file issues. You can also tell us what broke on [Discord](https://discord.gg/artcraft).
|
||||
|
||||
@@ -340,6 +340,10 @@ fn system_font_dirs() -> Vec<std::path::PathBuf> {
|
||||
} else {
|
||||
v.push(PathBuf::from("/usr/share/fonts"));
|
||||
v.push(PathBuf::from("/usr/local/share/fonts"));
|
||||
// Inside a Flatpak sandbox the host's system and per-user fonts are mounted here
|
||||
// (`/usr/share/fonts` is the runtime's own small set). Missing dirs are skipped.
|
||||
v.push(PathBuf::from("/run/host/fonts"));
|
||||
v.push(PathBuf::from("/run/host/user-fonts"));
|
||||
if let Some(d) = std::env::var_os("XDG_DATA_HOME") {
|
||||
v.push(PathBuf::from(d).join("fonts"));
|
||||
}
|
||||
@@ -350,3 +354,21 @@ fn system_font_dirs() -> Vec<std::path::PathBuf> {
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn scans_flatpak_host_fonts() {
|
||||
let dirs = super::system_font_dirs();
|
||||
assert!(dirs.iter().any(|d| d.ends_with("run/host/fonts")));
|
||||
assert!(dirs.iter().any(|d| d.ends_with("run/host/user-fonts")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_font_dirs_are_skipped() {
|
||||
let mut files = Vec::new();
|
||||
super::collect_font_files(std::path::Path::new("/nonexistent/photocraft/fonts"), 0, &mut files);
|
||||
assert!(files.is_empty());
|
||||
}
|
||||
}
|
||||
+32
-8
@@ -44,8 +44,8 @@ in the dialog.
|
||||
| macOS 11+ (universal: Apple silicon + Intel) | `photocraft-<v>-macos-universal.dmg`, `photocraft-cli-<v>-macos-universal.zip` | `macos-15` |
|
||||
| Windows 10+ x64 | `photocraft-<v>-windows-x64.msi`, `photocraft-<v>-windows-x64-portable.zip` | `windows-latest` |
|
||||
| Windows 10+ x86 (32-bit) | `photocraft-<v>-windows-x86.msi`, `photocraft-<v>-windows-x86-portable.zip` | `windows-latest` |
|
||||
| Linux x86_64 | `photocraft-<v>-linux-x86_64.{AppImage,deb,rpm,tar.gz}` | `ubuntu-22.04` |
|
||||
| Linux aarch64 | `photocraft-<v>-linux-aarch64.{AppImage,deb,rpm,tar.gz}` | `ubuntu-22.04-arm` |
|
||||
| Linux x86_64 | `photocraft-<v>-linux-x86_64.{AppImage,deb,rpm,tar.gz,flatpak}` | `ubuntu-22.04` (Flatpak: `ubuntu-24.04`) |
|
||||
| Linux aarch64 | `photocraft-<v>-linux-aarch64.{AppImage,deb,rpm,tar.gz,flatpak}` | `ubuntu-22.04-arm` (Flatpak: `ubuntu-24.04-arm`) |
|
||||
| Web | `photocraft-web-<v>.zip` (static site; see [`packaging/web/README.md`](../packaging/web/README.md)) | `ubuntu-latest` |
|
||||
|
||||
Every binary reports its version, the commit and the build date: `photocraft --version`,
|
||||
@@ -128,11 +128,34 @@ Why these formats:
|
||||
(`nfpm.yaml`). It doesn't need cargo-deb and cargo-generate-rpm metadata in our crates, and
|
||||
it can build both formats on Ubuntu.
|
||||
- **.tar.gz** is for people who manage their own `/opt` or `~/.local`.
|
||||
- **Flatpak** is the route into Flathub, GNOME Software and KDE Discover.
|
||||
`packaging/linux/flatpak/ai.storyteller.photocraft.yml` is ready for a Flathub submission.
|
||||
CI only validates it. A real build needs vendored crate sources (`cargo-sources.json` from
|
||||
`flatpak-cargo-generator.py`) and adds 20+ minutes per architecture. Build it by hand with
|
||||
the commands in the manifest's header.
|
||||
- **Flatpak bundle** (`.flatpak`) is a single file that installs into Flatpak, sandboxed and
|
||||
updatable by installing a newer bundle, for people who prefer Flatpak to AppImage.
|
||||
`packaging/linux/flatpak-bundle.sh` repackages the job's `.tar.gz` with
|
||||
`packaging/linux/flatpak/ai.storyteller.photocraft.bundle.yml` on the freedesktop 26.08
|
||||
runtime. It does no Rust build and needs no network inside `flatpak-builder`, so the bundle
|
||||
holds the same binaries as the other formats. A separate `flatpak` job per architecture
|
||||
(`ubuntu-24.04` and `ubuntu-24.04-arm`, for flatpak-builder 1.4) downloads the Linux job's
|
||||
artifact, runs the script, installs the bundle and runs `photocraft-cli --version` inside the
|
||||
sandbox as a smoke test. The bundle names Flathub as its runtime repo, so users install
|
||||
it with:
|
||||
|
||||
```sh
|
||||
flatpak install --user photocraft-<v>-linux-x86_64.flatpak # pulls org.freedesktop.Platform//26.08 from Flathub if missing
|
||||
flatpak run ai.storyteller.photocraft
|
||||
```
|
||||
|
||||
Sandbox permissions (justified in the manifest): Wayland with X11 fallback, IPC (X11
|
||||
shared memory), `dri` for the GPU, and read/write access to Pictures and Documents. Every
|
||||
other file goes through the file-chooser and document portals. There's no network, so the
|
||||
`--control` server is only reachable from inside the sandbox until the user runs
|
||||
`flatpak override --user --share=network ai.storyteller.photocraft`. Host fonts are read from
|
||||
`/run/host/fonts` and `/run/host/user-fonts`. Locally (on Linux, with `flatpak` and
|
||||
`flatpak-builder`): `packaging/linux/package.sh --formats tar && packaging/linux/flatpak-bundle.sh`.
|
||||
- **Flathub**: `packaging/linux/flatpak/ai.storyteller.photocraft.yml` builds from source and
|
||||
is ready for a Flathub submission (it keeps the same runtime and `finish-args` as the bundle
|
||||
manifest, which packaging-lint checks). CI doesn't build it. A real build needs vendored crate
|
||||
sources (`cargo-sources.json` from `flatpak-cargo-generator.py`) and adds 20+ minutes per
|
||||
architecture. Build it by hand with the commands in the manifest's header.
|
||||
|
||||
All Linux binaries are built on Ubuntu 22.04 and need **glibc ≥ 2.35**: Ubuntu 22.04+,
|
||||
Debian 12+, Fedora 36+, RHEL 10, openSUSE Tumbleweed. They link only glibc and libgcc_s. X11,
|
||||
@@ -192,4 +215,5 @@ The outputs are committed, so packaging never needs those tools.
|
||||
|
||||
`.github/workflows/packaging-lint.yml` runs in seconds on any change to `packaging/`, the
|
||||
workflows or the icons. It runs actionlint, shellcheck, a PowerShell parse, xmllint,
|
||||
`desktop-file-validate`, `appstreamcli validate`, and a YAML check of the Flatpak manifest.
|
||||
`desktop-file-validate`, `appstreamcli validate`, and a YAML check that the two Flatpak
|
||||
manifests agree on the runtime and permissions.
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
# Repackage a PhotoCraft Linux tarball as a single-file Flatpak bundle:
|
||||
#
|
||||
# $DIST/photocraft-<version>-linux-<arch>.flatpak
|
||||
#
|
||||
# Usage: packaging/linux/flatpak-bundle.sh [--no-test] [TARBALL]
|
||||
#
|
||||
# TARBALL defaults to $DIST/photocraft-<version>-linux-<arch>.tar.gz from package.sh. The bundle
|
||||
# is built for the host architecture (x86_64 or aarch64), which must match the tarball's.
|
||||
# Needs flatpak, flatpak-builder and the SVG pixbuf loader (librsvg2-common) for the host's
|
||||
# `appstreamcli compose`; the freedesktop runtime and SDK named in the manifest are
|
||||
# installed per-user from Flathub. Unless --no-test, the bundle is then installed per-user and
|
||||
# `photocraft-cli --version` is run inside the sandbox as a smoke test.
|
||||
# Manifest: packaging/linux/flatpak/ai.storyteller.photocraft.bundle.yml.
|
||||
set -euo pipefail
|
||||
# shellcheck source=../env.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
|
||||
HERE="$ROOT/packaging/linux"
|
||||
APP_ID=ai.storyteller.photocraft
|
||||
FLATHUB=https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
|
||||
TEST=1
|
||||
TARBALL=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--no-test) TEST=0; shift ;;
|
||||
-h | --help) sed -n '2,14p' "$0"; exit 0 ;;
|
||||
-*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
*) TARBALL="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
x86_64) ;;
|
||||
aarch64 | arm64) ARCH=aarch64 ;;
|
||||
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
|
||||
esac
|
||||
BASENAME="photocraft-$VERSION-linux-$ARCH"
|
||||
TARBALL="${TARBALL:-$DIST/$BASENAME.tar.gz}"
|
||||
[ -f "$TARBALL" ] || { echo "error: $TARBALL not found (run packaging/linux/package.sh --formats tar first)" >&2; exit 1; }
|
||||
for tool in flatpak flatpak-builder; do
|
||||
command -v "$tool" >/dev/null || { echo "error: $tool not found (apt install flatpak flatpak-builder librsvg2-common)" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "==> PhotoCraft $VERSION Flatpak bundle for $ARCH from $(basename "$TARBALL")"
|
||||
|
||||
WORK="$CARGO_TARGET_DIR/flatpak-bundle"
|
||||
rm -rf "$WORK"
|
||||
mkdir -p "$WORK/stage"
|
||||
tar -xzf "$TARBALL" -C "$WORK/stage" --strip-components=1
|
||||
[ -x "$WORK/stage/bin/photocraft" ] || { echo "error: $TARBALL has no bin/photocraft" >&2; exit 1; }
|
||||
cp "$HERE/flatpak/$APP_ID.bundle.yml" "$WORK/$APP_ID.yml"
|
||||
|
||||
flatpak remote-add --user --if-not-exists flathub "$FLATHUB"
|
||||
# --disable-rofiles-fuse: no FUSE in containers and CI runners.
|
||||
flatpak-builder --user --install-deps-from=flathub --disable-rofiles-fuse --force-clean \
|
||||
--default-branch=stable --repo="$WORK/repo" --state-dir="$WORK/state" "$WORK/build" "$WORK/$APP_ID.yml"
|
||||
|
||||
OUT="$DIST/$BASENAME.flatpak"
|
||||
# --runtime-repo lets `flatpak install` fetch the freedesktop runtime from Flathub if missing.
|
||||
flatpak build-bundle --runtime-repo="$FLATHUB" "$WORK/repo" "$OUT" "$APP_ID" stable
|
||||
echo "wrote $OUT"
|
||||
|
||||
if [ "$TEST" = 1 ]; then
|
||||
flatpak install --user -y --noninteractive --reinstall "$OUT"
|
||||
flatpak info --user "$APP_ID"
|
||||
flatpak run --command=photocraft-cli "$APP_ID" --version
|
||||
flatpak run --command=sh "$APP_ID" -c 'ls /app/share/applications /app/share/metainfo /app/share/mime/packages /app/share/icons/hicolor/scalable/apps'
|
||||
fi
|
||||
echo "==> done"
|
||||
ls -lh "$OUT"
|
||||
@@ -0,0 +1,50 @@
|
||||
# Flatpak manifest for the single-file bundle attached to every GitHub release
|
||||
# (photocraft-<version>-linux-<arch>.flatpak). Build it with packaging/linux/flatpak-bundle.sh.
|
||||
#
|
||||
# It repackages the release's own Linux tarball (photocraft-<version>-linux-<arch>.tar.gz,
|
||||
# built on Ubuntu 22.04 by package.sh) instead of compiling Rust inside flatpak-builder: no
|
||||
# network and no vendored crate sources during the build, a few seconds instead of 20+ minutes,
|
||||
# and byte-for-byte the same binaries as the AppImage/deb/rpm. The script unpacks the tarball
|
||||
# into `stage/` next to a copy of this file. The binaries need glibc >= 2.35 and load X11,
|
||||
# Wayland, xkbcommon and Vulkan at runtime; the freedesktop runtime has all of them, and the
|
||||
# GPU driver comes from its org.freedesktop.Platform.GL extension.
|
||||
#
|
||||
# ai.storyteller.photocraft.yml (same directory) is the from-source manifest for Flathub. Keep
|
||||
# runtime-version and finish-args identical in both (packaging-lint checks this).
|
||||
id: ai.storyteller.photocraft
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: '26.08'
|
||||
sdk: org.freedesktop.Sdk
|
||||
command: photocraft
|
||||
finish-args:
|
||||
# Display: Wayland first; X11 only when no Wayland compositor is running.
|
||||
- --socket=wayland
|
||||
- --socket=fallback-x11
|
||||
# X11 shared memory (MIT-SHM). Without it the X11 fallback is slow or fails.
|
||||
- --share=ipc
|
||||
# GPU access for the wgpu compositor (Vulkan, or GL as a fallback).
|
||||
- --device=dri
|
||||
# Open/Save dialogs go through the file-chooser portal (rfd's xdg-portal backend) and
|
||||
# drag-and-drop through the document portal, so picked files work anywhere without broad
|
||||
# filesystem access. Pictures and Documents are granted so files passed by path (the command
|
||||
# line, Open Recent, "Save" next to the original) work in the usual places.
|
||||
- --filesystem=xdg-pictures
|
||||
- --filesystem=xdg-documents
|
||||
# Deliberately not granted: network (the optional --control server listens on 127.0.0.1 in
|
||||
# the sandbox; `flatpak override --user --share=network ai.storyteller.photocraft` exposes it to
|
||||
# host agents) and home/host filesystem access.
|
||||
modules:
|
||||
- name: photocraft
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- install -Dm755 bin/photocraft /app/bin/photocraft
|
||||
- install -Dm755 bin/photocraft-cli /app/bin/photocraft-cli
|
||||
- install -Dm644 share/applications/ai.storyteller.photocraft.desktop /app/share/applications/ai.storyteller.photocraft.desktop
|
||||
# .pcraft, .psb and .qoi; the runtime's shared-mime-info already knows PSD and the rest.
|
||||
- install -Dm644 share/mime/packages/ai.storyteller.photocraft.xml /app/share/mime/packages/ai.storyteller.photocraft.xml
|
||||
# Already has the release's version and date substituted by package.sh.
|
||||
- install -Dm644 share/metainfo/ai.storyteller.photocraft.metainfo.xml /app/share/metainfo/ai.storyteller.photocraft.metainfo.xml
|
||||
- mkdir -p /app/share/icons /app/share/doc && cp -R share/icons/hicolor /app/share/icons/ && cp -R share/doc/photocraft /app/share/doc/
|
||||
sources:
|
||||
- type: dir
|
||||
path: stage
|
||||
@@ -8,22 +8,32 @@
|
||||
# flatpak run ai.storyteller.photocraft
|
||||
#
|
||||
# For Flathub, replace the `dir` source with a `git` source pinned to a release tag and commit.
|
||||
# The single-file .flatpak attached to each release comes from ai.storyteller.photocraft.bundle.yml,
|
||||
# which repackages the prebuilt release binaries. Keep runtime-version and finish-args identical.
|
||||
id: ai.storyteller.photocraft
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: '25.08'
|
||||
runtime-version: '26.08'
|
||||
sdk: org.freedesktop.Sdk
|
||||
sdk-extensions:
|
||||
- org.freedesktop.Sdk.Extension.rust-stable
|
||||
command: photocraft
|
||||
finish-args:
|
||||
- --share=ipc
|
||||
# Display: Wayland first; X11 only when no Wayland compositor is running.
|
||||
- --socket=wayland
|
||||
- --socket=fallback-x11
|
||||
# X11 shared memory (MIT-SHM). Without it the X11 fallback is slow or fails.
|
||||
- --share=ipc
|
||||
# GPU access for the wgpu compositor (Vulkan, or GL as a fallback).
|
||||
- --device=dri
|
||||
# Open/Save dialogs use the file-chooser portal (rfd), and drag-and-drop uses the document
|
||||
# portal, so broad filesystem access isn't needed. Pictures is granted for opening files by
|
||||
# path from the command line.
|
||||
# Open/Save dialogs go through the file-chooser portal (rfd's xdg-portal backend) and
|
||||
# drag-and-drop through the document portal, so picked files work anywhere without broad
|
||||
# filesystem access. Pictures and Documents are granted so files passed by path (the command
|
||||
# line, Open Recent, "Save" next to the original) work in the usual places.
|
||||
- --filesystem=xdg-pictures
|
||||
- --filesystem=xdg-documents
|
||||
# Deliberately not granted: network (the optional --control server listens on 127.0.0.1 in
|
||||
# the sandbox; `flatpak override --user --share=network ai.storyteller.photocraft` exposes it to
|
||||
# host agents) and home/host filesystem access.
|
||||
build-options:
|
||||
append-path: /usr/lib/sdk/rust-stable/bin
|
||||
modules:
|
||||
|
||||
Reference in new issue
Block a user