Signed release pipeline (macOS/Windows/Linux/Web); README community note

Ports photocraft's release workflow + packaging (signing, notarization,
MSI/DMG/deb/rpm/AppImage/web), adds `cargo xtask version`. File-type
associations omitted (route B); app keeps its own .desktop. Fresh WiX
UpgradeCode. README 'about' note updated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Brandon ThomasandClaude Opus 4.8 committed 2026-10-02 12:06:14 -04:00
1 parent 75e4e3ced0
commit 453f97b549
24 files changed
+1633 -3

No files matched your search

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- AppStream metadata (software centres, Flathub). Template: package.sh and the Flatpak
manifest substitute @VERSION@ and @DATE@ from Cargo.toml and the build date. -->
<component type="desktop-application">
<id>ai.storyteller.lightcraft</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT OR Apache-2.0</project_license>
<name>LightCraft</name>
<summary>Organise photos and develop raw files, non-destructively</summary>
<developer id="ai.storyteller">
<name>Learning Machines LLC</name>
</developer>
<description>
<p>
LightCraft is a photo library and raw developer. Organise photos and develop raw files, non-destructively
</p>
</description>
<launchable type="desktop-id">ai.storyteller.lightcraft.desktop</launchable>
<icon type="stock">ai.storyteller.lightcraft</icon>
<!-- Only url types known to appstream 0.15 (Ubuntu 22.04, where CI validates): no vcs-browser. -->
<url type="homepage">https://getartcraft.com/apps/lightcraft</url>
<url type="bugtracker">https://github.com/storytold/lightcraft/issues</url>
<url type="help">https://github.com/storytold/lightcraft</url>
<url type="contact">https://discord.gg/artcraft</url>
<categories>
<category>Graphics</category>
<category>RasterGraphics</category>
<category>Photography</category>
</categories>
<keywords>
<keyword>photo</keyword>
<keyword>image editor</keyword>
<keyword>psd</keyword>
<keyword>layers</keyword>
</keywords>
<screenshots>
<screenshot type="default">
<caption>Editing The Great Wave off Kanagawa with type layers and adjustment layers</caption>
<image>https://raw.githubusercontent.com/storytold/lightcraft/main/docs/images/lightcraft-demo.jpg</image>
</screenshot>
</screenshots>
<branding>
<color type="primary" scheme_preference="light">#9fd0ff</color>
<color type="primary" scheme_preference="dark">#1f64ba</color>
</branding>
<requires>
<display_length compare="ge">760</display_length>
</requires>
<supports>
<control>keyboard</control>
<control>pointing</control>
<control>tablet</control>
</supports>
<provides>
<binary>lightcraft</binary>
</provides>
<content_rating type="oars-1.1"/>
<releases>
<release version="@VERSION@" date="@DATE@"/>
</releases>
</component>
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
</mime-info>
@@ -0,0 +1,56 @@
# Flatpak manifest for LightCraft, ready for a Flathub submission.
#
# Flatpak builds run offline, so the crate sources must be vendored first. From the repo root:
#
# curl -fsSLO https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py
# python3 flatpak-cargo-generator.py Cargo.lock -o packaging/linux/flatpak/cargo-sources.json
# flatpak-builder --user --install --force-clean build-flatpak packaging/linux/flatpak/ai.storyteller.lightcraft.yml
# flatpak run ai.storyteller.lightcraft
#
# For Flathub, replace the `dir` source with a `git` source pinned to a release tag and commit.
id: ai.storyteller.lightcraft
runtime: org.freedesktop.Platform
runtime-version: '25.08'
sdk: org.freedesktop.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
command: lightcraft
finish-args:
- --share=ipc
- --socket=wayland
- --socket=fallback-x11
- --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.
- --filesystem=xdg-pictures
build-options:
append-path: /usr/lib/sdk/rust-stable/bin
modules:
- name: lightcraft
buildsystem: simple
build-options:
env:
CARGO_HOME: /run/build/lightcraft/cargo
CARGO_NET_OFFLINE: 'true'
build-commands:
- cargo --offline build --release --locked -p lightcraft
- install -Dm755 target/release/lightcraft /app/bin/lightcraft
- install -Dm644 packaging/linux/ai.storyteller.lightcraft.desktop /app/share/applications/ai.storyteller.lightcraft.desktop
- install -Dm644 packaging/linux/ai.storyteller.lightcraft.mime.xml /app/share/mime/packages/ai.storyteller.lightcraft.xml
- mkdir -p /app/share/icons && cp -R assets/app-icon/hicolor /app/share/icons/
- >-
version=$(awk '/^\[/{p=($0=="[workspace.package]");next} p&&$1=="version"{gsub(/[" ]/,"",$3);print $3;exit}' Cargo.toml);
install -d /app/share/metainfo;
sed -e "s/@VERSION@/$version/" -e "s/@DATE@/$(date -u +%Y-%m-%d)/"
packaging/linux/ai.storyteller.lightcraft.metainfo.xml.in > /app/share/metainfo/ai.storyteller.lightcraft.metainfo.xml
sources:
- type: dir
path: ../../..
skip:
- target
- dist
- corpus
- build-flatpak
- .flatpak-builder
- cargo-sources.json
+70
View File
@@ -0,0 +1,70 @@
# .deb and .rpm packages (nfpm: https://nfpm.goreleaser.com). Run by package.sh, which stages
# the file tree, substitutes its path for ${STAGE} (nfpm doesn't expand env vars in `src`) and
# exports VERSION and NFPM_ARCH.
#
# nfpm package -f packaging/linux/nfpm.yaml -p deb -t dist/release/
name: lightcraft
arch: ${NFPM_ARCH}
platform: linux
version: ${VERSION}
# semver: 0.2.0-rc.1 becomes 0.2.0~rc.1 in deb/rpm, so pre-releases sort before the release.
version_schema: semver
release: 1
section: graphics
priority: optional
maintainer: ${LIGHTCRAFT_MAINTAINER}
vendor: Learning Machines LLC
homepage: https://getartcraft.com/apps/lightcraft
license: MIT OR Apache-2.0
description: |-
Open-source, native image editor that works the way Photoshop users expect.
Layers, masks, adjustment layers, layer styles, type and brushes; opens and
saves layered PSD/PSB files; 8/16/32-bit documents; GPU compositing (wgpu).
Includes lightcraft-cli, a headless converter and command runner.
# The binaries link only glibc and libgcc_s directly. Windowing (X11/Wayland, xkbcommon) and the
# GPU (Vulkan, EGL/GL) are loaded at runtime, so they are dependencies/recommendations here
# rather than ELF NEEDED entries.
overrides:
deb:
depends:
- libc6 (>= 2.35)
- libgcc-s1
- libxkbcommon0
- libvulkan1 | libegl1
recommends:
- libwayland-client0
- libx11-6
- libx11-xcb1
- libxcursor1
- libxi6
- libxrandr2
- mesa-vulkan-drivers
- xdg-desktop-portal
rpm:
depends:
- libxkbcommon
- (vulkan-loader or libglvnd-egl)
recommends:
- libwayland-client
- libX11
- libXcursor
- libXi
- libXrandr
- mesa-vulkan-drivers
- xdg-desktop-portal
contents:
- src: ${STAGE}/usr/bin/lightcraft
dst: /usr/bin/lightcraft
file_info: { mode: 0755 }
- src: ${STAGE}/usr/bin/lightcraft-cli
dst: /usr/bin/lightcraft-cli
file_info: { mode: 0755 }
- src: ${STAGE}/usr/share/
dst: /usr/share/
type: tree
scripts:
postinstall: packaging/linux/postinst.sh
postremove: packaging/linux/postinst.sh
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
# Build and package LightCraft for Linux (<arch> is x86_64 or aarch64):
#
# $DIST/lightcraft-<version>-linux-<arch>.AppImage any distro with glibc >= the build host's
# $DIST/lightcraft-<version>-linux-<arch>.deb Debian, Ubuntu, Mint, Pop!_OS, ...
# $DIST/lightcraft-<version>-linux-<arch>.rpm Fedora, openSUSE, RHEL, ...
# $DIST/lightcraft-<version>-linux-<arch>.tar.gz plain FHS-style tree (bin/, share/)
#
# Usage: packaging/linux/package.sh [--skip-build] [--formats "appimage deb rpm tar"]
#
# Needs: cargo; nfpm for deb/rpm (https://nfpm.goreleaser.com); appimagetool for the AppImage
# (downloaded into $CARGO_TARGET_DIR if missing). Build on an old distro (CI: Ubuntu 22.04,
# glibc 2.35) so the binaries run on newer ones. Optional: desktop-file-validate, appstreamcli.
set -euo pipefail
# shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
HERE="$ROOT/packaging/linux"
APP_ID=ai.storyteller.lightcraft
SKIP_BUILD=0
FORMATS="appimage deb rpm tar"
while [ $# -gt 0 ]; do
case "$1" in
--skip-build) SKIP_BUILD=1; shift ;;
--formats) FORMATS="$2"; shift 2 ;;
-h | --help) sed -n '2,15p' "$0"; exit 0 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) DEB_ARCH=amd64 ;;
aarch64 | arm64) ARCH=aarch64; DEB_ARCH=arm64 ;;
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
esac
export LIGHTCRAFT_MAINTAINER="${LIGHTCRAFT_MAINTAINER:-LightCraft maintainers <[email protected]>}"
BASENAME="lightcraft-$VERSION-linux-$ARCH"
echo "==> LightCraft $VERSION for Linux $ARCH ($FORMATS)"
if [ "$SKIP_BUILD" = 0 ]; then
(cd "$ROOT" && cargo build --release --locked -p lightcraft -p lightcraft-cli)
fi
BIN="$CARGO_TARGET_DIR/release"
WORK="$CARGO_TARGET_DIR/linux-package"
STAGE="$WORK/root"
rm -rf "$WORK"
# ---- stage an FHS tree (shared by every format) -------------------------------------------------
install -Dm755 "$BIN/lightcraft" "$STAGE/usr/bin/lightcraft"
install -Dm755 "$BIN/lightcraft-cli" "$STAGE/usr/bin/lightcraft-cli"
strip "$STAGE/usr/bin/lightcraft" "$STAGE/usr/bin/lightcraft-cli" 2>/dev/null || true
install -Dm644 "$HERE/$APP_ID.desktop" "$STAGE/usr/share/applications/$APP_ID.desktop"
install -Dm644 "$HERE/$APP_ID.mime.xml" "$STAGE/usr/share/mime/packages/$APP_ID.xml"
mkdir -p "$STAGE/usr/share/metainfo"
sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$LIGHTCRAFT_BUILD_DATE/g" \
"$HERE/$APP_ID.metainfo.xml.in" >"$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml"
mkdir -p "$STAGE/usr/share/icons"
cp -R "$ROOT/assets/app-icon/hicolor" "$STAGE/usr/share/icons/"
mkdir -p "$STAGE/usr/share/doc/lightcraft"
copy_docs "$STAGE/usr/share/doc/lightcraft"
if command -v desktop-file-validate >/dev/null; then
desktop-file-validate "$STAGE/usr/share/applications/$APP_ID.desktop"
fi
if command -v appstreamcli >/dev/null; then
appstreamcli validate --no-net --explain "$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml"
fi
has() { case " $FORMATS " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
# ---- .tar.gz ------------------------------------------------------------------------------------
if has tar; then
mkdir -p "$WORK/tar"
cp -R "$STAGE/usr" "$WORK/tar/$BASENAME"
tar -C "$WORK/tar" -czf "$DIST/$BASENAME.tar.gz" "$BASENAME"
echo "wrote $DIST/$BASENAME.tar.gz"
fi
# ---- .deb / .rpm --------------------------------------------------------------------------------
if has deb || has rpm; then
command -v nfpm >/dev/null || { echo "error: nfpm not found (https://nfpm.goreleaser.com/install/)" >&2; exit 1; }
export VERSION
export NFPM_ARCH="$DEB_ARCH"
# nfpm expands env vars in fields like `version` and `arch`, but not in `contents[].src`.
sed "s|\${STAGE}|$STAGE|g" "$HERE/nfpm.yaml" >"$WORK/nfpm.yaml"
for fmt in deb rpm; do
if has "$fmt"; then (cd "$ROOT" && nfpm package -f "$WORK/nfpm.yaml" -p "$fmt" -t "$DIST/$BASENAME.$fmt"); fi
done
fi
# ---- AppImage -----------------------------------------------------------------------------------
if has appimage; then
APPDIR="$WORK/LightCraft.AppDir"
cp -R "$STAGE" "$APPDIR"
mv "$APPDIR/usr/share/doc" "$WORK/doc-unused"
ln -s usr/bin/lightcraft "$APPDIR/AppRun"
cp "$HERE/$APP_ID.desktop" "$APPDIR/$APP_ID.desktop"
cp "$ROOT/assets/app-icon/hicolor/256x256/apps/$APP_ID.png" "$APPDIR/$APP_ID.png"
ln -s "$APP_ID.png" "$APPDIR/.DirIcon"
TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}"
if [ -z "$TOOL" ]; then
TOOL="$CARGO_TARGET_DIR/appimagetool-$ARCH.AppImage"
if [ ! -x "$TOOL" ]; then
curl -fsSL -o "$TOOL" "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$ARCH.AppImage"
chmod +x "$TOOL"
fi
fi
OUT="$DIST/$BASENAME.AppImage"
# Extract-and-run: works without FUSE (containers, CI). The output embeds the static runtime,
# so users don't need libfuse2 either.
ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream "$APPDIR" "$OUT"
echo "wrote $OUT"
fi
"$STAGE/usr/bin/lightcraft-cli" --version
echo "==> done"
ls -lh "$DIST"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# deb/rpm post-install and post-remove: refresh the desktop, MIME and icon caches.
# Every tool is optional; missing ones are skipped.
if command -v update-desktop-database >/dev/null 2>&1; then
update-desktop-database -q /usr/share/applications || true
fi
if command -v update-mime-database >/dev/null 2>&1; then
update-mime-database /usr/share/mime || true
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor || true
fi
exit 0