diff --git a/.github/workflows/artcraft-linux-publish.yml b/.github/workflows/artcraft-linux-publish.yml new file mode 100644 index 0000000000..103a4642b6 --- /dev/null +++ b/.github/workflows/artcraft-linux-publish.yml @@ -0,0 +1,193 @@ +name: ArtCraft Linux release +on: + push: + branches: [release, artcraft-release, artcraft-linux] + workflow_dispatch: +permissions: + contents: read +concurrency: + group: linux-release-${{ github.ref }} + cancel-in-progress: false +jobs: + packaging-check: + runs-on: ubuntu-22.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: storytold/craft-common-actions/actions/install-apt-packages@main + with: + packages: rpm jq binutils zsync + - run: packaging/linux/package-test.sh + build: + strategy: + fail-fast: false + matrix: + include: + - {arch: x86_64, runner: ubuntu-22.04, target: x86_64-unknown-linux-gnu, bundles: "deb,rpm,appimage"} + - {arch: aarch64, runner: ubuntu-22.04-arm, target: aarch64-unknown-linux-gnu, bundles: "deb,rpm,appimage"} + - {arch: riscv64, runner: ubuntu-24.04, target: riscv64gc-unknown-linux-gnu, bundles: "deb,rpm"} + runs-on: ${{ matrix.runner }} + timeout-minutes: 90 + env: + SQLX_OFFLINE: 'true' + VITE_ENVIRONMENT_TYPE: production + TAURI_FRONTEND_PATH: ${{ github.workspace }}/frontend + TAURI_APP_PATH: ${{ github.workspace }}/crates/desktop/artcraft + steps: + - uses: actions/checkout@v4 + - uses: storytold/craft-common-actions/actions/install-apt-packages@main + with: + packages: libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev libclang-dev libasound2-dev libxdo-dev libdbus-1-dev build-essential clang file jq perl cmake nasm rpm zsync gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-libav + - uses: storytold/craft-common-actions/actions/install-riscv64-cross@main + if: matrix.arch == 'riscv64' + - name: Remove conflicting host Pango development files + if: matrix.arch == 'riscv64' + # Ubuntu's Pango GIR differs by architecture despite being in a shared path. + # Only the target GTK development packages are needed for this cross build. + run: sudo apt-get remove -y libpango1.0-dev:amd64 + - name: Install RISC-V WebKitGTK dependencies + if: matrix.arch == 'riscv64' + uses: storytold/craft-common-actions/actions/install-apt-packages@main + with: + packages: libwebkit2gtk-4.1-dev:riscv64 libappindicator3-dev:riscv64 libssl-dev:riscv64 libxdo-dev:riscv64 libdbus-1-dev:riscv64 g++-riscv64-linux-gnu + - name: Configure RISC-V cross compilation + if: matrix.arch == 'riscv64' + run: | + cat >> "$GITHUB_ENV" <<'ENV' + CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc + CC_riscv64gc_unknown_linux_gnu=riscv64-linux-gnu-gcc + CXX_riscv64gc_unknown_linux_gnu=riscv64-linux-gnu-g++ + AR_riscv64gc_unknown_linux_gnu=riscv64-linux-gnu-ar + PKG_CONFIG_ALLOW_CROSS=1 + PKG_CONFIG_PATH_riscv64gc_unknown_linux_gnu=/usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig + PKG_CONFIG_LIBDIR_riscv64gc_unknown_linux_gnu=/usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig + ENV + echo "CMAKE_TOOLCHAIN_FILE_riscv64gc_unknown_linux_gnu=$GITHUB_WORKSPACE/packaging/linux/riscv64.cmake" >> "$GITHUB_ENV" + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + cache-dependency-path: frontend/package-lock.json + - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1 + with: + toolchain: stable + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: tauri-linux-${{ matrix.arch }} + cache-on-failure: true + - run: npm ci --ignore-scripts + working-directory: frontend + - run: ./node_modules/.bin/nx run artcraft:build + working-directory: frontend + - uses: tauri-apps/tauri-action@fce9c6108b31ea247710505d3aaaa893ee6768d4 # v0 + with: + projectPath: crates/desktop/artcraft + args: --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --config ${{ github.workspace }}/packaging/linux/tauri.conf.json + - name: Package and verify Linux outputs + env: + ARCH: ${{ matrix.arch }} + TARGET: ${{ matrix.target }} + run: packaging/linux/package.sh "$ARCH" "$TARGET" + - uses: actions/upload-artifact@v4 + with: + name: release-linux-${{ matrix.arch }} + path: dist/release/* + if-no-files-found: error + arch: + if: always() && !cancelled() + needs: build + runs-on: ubuntu-24.04 + strategy: + matrix: + arch: [x86_64, aarch64] + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: release-linux-${{ matrix.arch }} + path: dist/linux + - name: Find tarball + id: tarball + env: + ARCH: ${{ matrix.arch }} + run: | + shopt -s nullglob + files=(dist/linux/artcraft-*-linux-"$ARCH".tar.gz) + test "${#files[@]}" -eq 1 + echo "path=${files[0]}" >> "$GITHUB_OUTPUT" + - uses: storytold/craft-common-actions/actions/arch-package@9d6790582b4997aa2120f4413cdb55c0b6f77fae # extra-depends support (#161) + with: + tarball: ${{ steps.tarball.outputs.path }} + arch: ${{ matrix.arch }} + extra-depends: webkit2gtk-4.1 gtk3 openssl + - name: Verify Arch runtime dependencies + run: | + for package in dist/release/*.pkg.tar.zst; do + tar --zstd -xOf "$package" .PKGINFO | grep -Fx 'depend = webkit2gtk-4.1' + done + - uses: actions/upload-artifact@v4 + with: + name: release-arch-${{ matrix.arch }} + path: dist/release/*.pkg.tar.zst + if-no-files-found: error + flatpak: + if: always() && !cancelled() + needs: build + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - {arch: x86_64, runner: ubuntu-24.04} + - {arch: aarch64, runner: ubuntu-24.04-arm} + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: release-linux-${{ matrix.arch }} + path: dist/linux + - uses: storytold/craft-common-actions/actions/install-apt-packages@main + with: + packages: flatpak flatpak-builder elfutils librsvg2-common skopeo + - name: Allow Flatpak build sandboxes + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + - uses: storytold/craft-common-actions/actions/import-signing-key@main + with: + key: ${{ secrets.LINUX_SIGNING_KEY }} + - name: Build Flatpak and publish OCI + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: packaging/linux/flatpak.sh + - uses: actions/upload-artifact@v4 + with: + name: release-flatpak-${{ matrix.arch }} + path: | + dist/release/*.flatpak + dist/release/*.oci-manifest.json + if-no-files-found: error + release: + needs: [packaging-check, build, arch, flatpak] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - name: Read version + id: version + run: echo "value=$(jq -r .version crates/desktop/artcraft/tauri.conf.json)" >> "$GITHUB_OUTPUT" + - name: Require signing key + env: + SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }} + run: test -n "$SIGNING_KEY" + - uses: storytold/craft-common-actions/actions/draft-release@main + with: + version: ${{ steps.version.outputs.value }} + app-title: ArtCraft + tag-prefix: artcraft-v + retain: "true" + github-token: ${{ secrets.GITHUB_TOKEN }} + signing-key: ${{ secrets.LINUX_SIGNING_KEY }} diff --git a/.github/workflows/artcraft-macos-publish.yml b/.github/workflows/artcraft-macos-publish.yml index b71111407e..d40cda5e61 100644 --- a/.github/workflows/artcraft-macos-publish.yml +++ b/.github/workflows/artcraft-macos-publish.yml @@ -84,7 +84,7 @@ jobs: echo "Starting certificate import process..." # Create certificate file - echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 + echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 # Debug: Check if certificate file exists and has content echo "Certificate file info:" @@ -93,25 +93,25 @@ jobs: # Debug: Check certificate contents without password (just structure) echo "Certificate structure (may show error if password protected):" - openssl pkcs12 -info -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -noout || echo "Could not read certificate structure" + openssl pkcs12 -info -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -noout || echo "Could not read certificate structure" # Debug: Try to list what's in the p12 file echo "Attempting to list certificate contents:" security cms -D -i certificate.p12 || echo "Not a CMS file, trying PKCS12..." - openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -info -nodes 2>&1 | head -20 || echo "Failed to read PKCS12 contents" + openssl pkcs12 -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -info -nodes 2>&1 | head -20 || echo "Failed to read PKCS12 contents" # Debug: Extract certificate details to verify type and expiration echo "Extracting certificate details..." # Extract just the certificate portion and examine it - openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After)" || echo "Failed to parse certificate details" + openssl pkcs12 -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After)" || echo "Failed to parse certificate details" # Also try to see what type of certificate this is echo "Certificate type check:" - openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(CN=|OU=)" || echo "Could not determine certificate type" + openssl pkcs12 -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(CN=|OU=)" || echo "Could not determine certificate type" # Debug: Check if private key exists echo "Checking for private key..." - openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -nocerts -nodes 2>&1 | grep -E "(PRIVATE KEY|RSA KEY)" | head -5 || echo "No private key found" + openssl pkcs12 -in certificate.p12 -passin pass:"$APPLE_CERTIFICATE_PASSWORD" -nocerts -nodes 2>&1 | grep -E "(PRIVATE KEY|RSA KEY)" | head -5 || echo "No private key found" # Create temporary keychain security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain @@ -139,6 +139,7 @@ jobs: security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain # Add the keychain to the search list + # shellcheck disable=SC2046 security list-keychains -d user -s build.keychain $(security list-keychains -d user | sed 's/^[[:space:]]*"//;s/"$//') # List all certificates to debug @@ -181,11 +182,11 @@ jobs: security find-certificate -c "Learning Machines" build.keychain -p | openssl x509 -text -noout | grep -E "(Subject:|CN=)" || echo "Could not parse certificate" # Set the correct signing identity - echo "APPLE_SIGNING_IDENTITY=Developer ID Application: Learning Machines LLC (DJ6XS33FX8)" >> $GITHUB_ENV + echo "APPLE_SIGNING_IDENTITY=Developer ID Application: Learning Machines LLC (DJ6XS33FX8)" >> "$GITHUB_ENV" echo "Proceeding with identity: Developer ID Application: Learning Machines LLC (DJ6XS33FX8)" # Also set the team ID - echo "APPLE_DEVELOPMENT_TEAM=DJ6XS33FX8" >> $GITHUB_ENV + echo "APPLE_DEVELOPMENT_TEAM=DJ6XS33FX8" >> "$GITHUB_ENV" - name: Pre-build frontend to avoid Tauri build issues run: | @@ -200,6 +201,7 @@ jobs: npx nx run artcraft:build echo "Checking build output..." + # shellcheck disable=SC2012 ls -la apps/artcraft/dist/ | head -10 # Update Tauri config to skip the build (since we already built) diff --git a/.github/workflows/artcraft-windows-publish.yml b/.github/workflows/artcraft-windows-publish.yml index 3eab50b7b3..fa5847e49c 100644 --- a/.github/workflows/artcraft-windows-publish.yml +++ b/.github/workflows/artcraft-windows-publish.yml @@ -44,6 +44,7 @@ jobs: node-version: lts/* - name: install frontend dependencies + shell: pwsh run: | # Navigate to the frontend directory to install dependencies cd ./frontend @@ -63,6 +64,7 @@ jobs: npm list --depth=0 | findstr nx - name: Pre-build frontend to avoid Tauri build issues + shell: pwsh run: | # Build the frontend app from the correct directory cd ./frontend diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml new file mode 100644 index 0000000000..be3e879702 --- /dev/null +++ b/.github/workflows/freebsd.yml @@ -0,0 +1,24 @@ +# Builds and tests the workspace on FreeBSD, inside a FreeBSD VM on a Linux runner +# (GitHub has no FreeBSD runners). +name: FreeBSD + +# The VM build is slow, so it runs nightly on main and on pushes to `release`, not on pull requests. +on: + schedule: + - cron: "27 4 * * *" + push: + branches: [release] + workflow_dispatch: + +concurrency: + group: freebsd-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + freebsd: + # Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos. + uses: storytold/craft-common-actions/.github/workflows/freebsd.yml@main + diff --git a/.github/workflows/packaging-lint.yml b/.github/workflows/packaging-lint.yml new file mode 100644 index 0000000000..1c1561448b --- /dev/null +++ b/.github/workflows/packaging-lint.yml @@ -0,0 +1,23 @@ +# Quick static checks for the release pipeline (seconds, no Rust build): workflow syntax, +# packaging shell/PowerShell scripts, and the Linux desktop/AppStream/Flatpak metadata. +name: Packaging lint + +on: + push: + branches: [main, release] + paths: [".github/workflows/**", "packaging/**"] + pull_request: + paths: [".github/workflows/**", "packaging/**"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: packaging-lint-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + # Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos. + uses: storytold/craft-common-actions/.github/workflows/packaging-lint.yml@main diff --git a/README.md b/README.md index 390ad2748e..e7095a5377 100644 --- a/README.md +++ b/README.md @@ -130,13 +130,15 @@ Additional provider integrations are planned for Kling, Google, Runway, and Luma ## Downloads -- [Stable releases for Windows and macOS](https://getartcraft.com/) -- [Latest builds on GitHub](https://github.com/storytold/artcraft/releases) +- [Stable releases for Windows, macOS, and Linux](https://getartcraft.com/) +- [Latest builds on GitHub](https://github.com/storytold/artcraft/releases) (including AppImage, DEB, RPM, Arch, and Flatpak packages) +- [Linux packaging notes](./packaging/linux/README.md) - [Build from source](./_docs/dev_setup.md), including Linux ## Documentation - [Development setup](./_docs/dev_setup.md) +- [Linux packaging](./packaging/linux/README.md) - [Developer documentation](./_docs) - [Desktop build and development scripts](./script/artcraft) - [Roadmap](./ROADMAP.md) diff --git a/crates/schema/database/sqlite_tasks/build.rs b/crates/schema/database/sqlite_tasks/build.rs index 2296d83bc1..cccf56d85a 100644 --- a/crates/schema/database/sqlite_tasks/build.rs +++ b/crates/schema/database/sqlite_tasks/build.rs @@ -1,7 +1,20 @@ use std::env; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; pub fn main() { + println!("cargo:rerun-if-env-changed=DATABASE_URL"); + println!("cargo:rerun-if-env-changed=SQLX_OFFLINE"); + + if let Ok(url) = env::var("DATABASE_URL") { + println!("cargo:rustc-env=DATABASE_URL={}", url); + return; + } + + if env::var("SQLX_OFFLINE").as_deref() == Ok("true") || env::var("SQLX_OFFLINE").as_deref() == Ok("1") { + println!("cargo:rustc-env=SQLX_OFFLINE=true"); + return; + } + // NB: This should help JetBrains' RustRover from highlighting failing query macros, // but we do not want to interfere with the following: // @@ -14,27 +27,29 @@ pub fn main() { Some("unix") => unix_temp_database_pathing(), Some("windows") => windows_temp_database_pathing(), _ => { - println!("cargo:warning=Unsupported target family, using Unix temp database pathing."); - unix_temp_database_pathing(); + println!("cargo:rustc-env=SQLX_OFFLINE=true"); }, } } fn unix_temp_database_pathing() { - println!("cargo:rustc-env=DATABASE_URL=sqlite:/tmp/tasks.sqlite"); + println!("cargo:rerun-if-changed=/tmp/tasks.sqlite"); + if Path::new("/tmp/tasks.sqlite").exists() { + println!("cargo:rustc-env=DATABASE_URL=sqlite:/tmp/tasks.sqlite"); + } else { + println!("cargo:rustc-env=SQLX_OFFLINE=true"); + } } fn windows_temp_database_pathing() { if let Ok(local_app_data) = env::var("LOCALAPPDATA") { - let path = PathBuf::from(local_app_data); - let path = path.join("Temp\\tasks.sqlite"); - let path = path - .to_str() - .expect("path should be valid") - .to_string(); - println!("cargo:warning=LocalAppData path: {}", path); - println!("cargo:rustc-env=DATABASE_URL=sqlite:{}", path); - } else { - panic!("LOCALAPPDATA environment variable not set"); + let path = PathBuf::from(local_app_data).join("Temp\\tasks.sqlite"); + if path.exists() { + let path_str = path.to_str().expect("path should be valid"); + println!("cargo:warning=LocalAppData path: {}", path_str); + println!("cargo:rustc-env=DATABASE_URL=sqlite:{}", path_str); + return; + } } + println!("cargo:rustc-env=SQLX_OFFLINE=true"); } diff --git a/packaging/linux/README.md b/packaging/linux/README.md new file mode 100644 index 0000000000..2c52f8920c --- /dev/null +++ b/packaging/linux/README.md @@ -0,0 +1,36 @@ +# ArtCraft Studio Linux releases + +The Linux workflow builds every package before the release job signs and uploads them to the +existing `artcraft-v` draft, retaining Windows and macOS assets. The version comes from +`crates/desktop/artcraft/tauri.conf.json`. + +| Architecture | Outputs | Builder | +|--------------|---------------------------------------------------------------|----------------------------| +| x86_64 | AppImage, DEB, RPM, tar.gz, Arch pkg.tar.zst, Flatpak bundle, OCI | Ubuntu 22.04 native | +| aarch64 | AppImage, DEB, RPM, tar.gz, Arch pkg.tar.zst, Flatpak bundle, OCI | Ubuntu 22.04 ARM native | +| riscv64 | DEB, RPM, tar.gz | Ubuntu 24.04 cross compiler | + +Native AppImages also include zsync delta-update metadata. + +Flatpak uses native Ubuntu 24.04 runners and GNOME 51 for GTK3/WebKitGTK 4.1. Its build checks +for missing ELF dependencies inside the sandbox. RISC-V packages require Ubuntu 24.04-era +libraries; x86_64 and ARM64 use Ubuntu 22.04 as their build baseline. The suite does not publish +RISC-V AppImages, Arch packages or Flatpaks. + +`package.sh ` normalizes Tauri's DEB/RPM/AppImage filenames and +extracts the DEB's `/usr` tree into the suite's `bin/` and `share/` tarball layout. Shared Craft +Arch packaging consumes that tarball, with WebKitGTK, GTK3 and OpenSSL dependencies added. +`flatpak.sh` consumes the same tarball, builds the bundle, and exports the OCI layout. + +Release runs import `LINUX_SIGNING_KEY` for Flatpak commit signatures and OCI publishing. The final shared +`draft-release` action signs RPMs, DEBs, Arch packages, AppImages, tarballs, OCI manifests and +checksums. Only a stable version on `release` or `artcraft-release` moves the OCI stable architecture tags. +Like the macOS and Windows release workflows, Linux release packaging runs on release branches +(`release`, `artcraft-release`, `artcraft-linux`) and workflow dispatch rather than on pull requests. + +APT, RPM and pacman repositories are generated by `artcraft-services` from the release assets +and checksums. Its release catalog must be updated only after the signed release is published. +Installing a DEB manually is separate from publishing that DEB in the website's APT repository. + +A successful build proves artifact production and metadata/dependency checks. It does not replace +installation and interactive testing on physical RISC-V, ARM64 and x86_64 desktops. diff --git a/packaging/linux/ai.artcraft.app.metainfo.xml b/packaging/linux/ai.artcraft.app.metainfo.xml new file mode 100644 index 0000000000..affa90da6c --- /dev/null +++ b/packaging/linux/ai.artcraft.app.metainfo.xml @@ -0,0 +1,14 @@ + + + ai.artcraft.app + ArtCraft Studio + Create AI images and video + MIT + MIT OR Apache-2.0 +

ArtCraft Studio is a desktop workspace for generating AI images and video with connected AI providers.

+ ai.artcraft.app.desktop + https://getartcraft.com + ArtCraft + GraphicsAudioVideo + artcraft +
diff --git a/packaging/linux/flatpak.sh b/packaging/linux/flatpak.sh new file mode 100755 index 0000000000..82a856bfea --- /dev/null +++ b/packaging/linux/flatpak.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail +arch="$(uname -m)" +version="$(jq -r .version crates/desktop/artcraft/tauri.conf.json)" +base="artcraft-$version-linux-$arch" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT +mkdir -p "$work/stage" dist/release +tar -xzf "dist/linux/$base.tar.gz" --strip-components=1 -C "$work/stage" +cp packaging/linux/flatpak.yml "$work/manifest.yml" +flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo +sign=() +keys=() +if [[ -n "${SIGNING_FPR:-}" ]]; then + sign=(--gpg-sign="$SIGNING_FPR" --gpg-homedir="$GNUPGHOME") + gpg --batch --armor --export "$SIGNING_FPR" > "$work/public.asc" + keys=(--gpg-keys="$work/public.asc") +fi +flatpak-builder --user --install-deps-from=flathub --disable-rofiles-fuse --force-clean \ + --default-branch=stable --repo="$work/repo" "${sign[@]}" "$work/build" "$work/manifest.yml" +# Fail on missing WebKitGTK or other runtime libraries rather than shipping an unloadable binary. +# shellcheck disable=SC2016 # Expand these variables inside the Flatpak runtime shell. +flatpak build --runtime "$work/build" sh -c ' + dependencies=$(ldd /app/bin/artcraft) || exit 1 + printf "%s\n" "$dependencies" + case "$dependencies" in *"not found"*) exit 1 ;; esac +' +flatpak build-bundle --runtime-repo=https://dl.flathub.org/repo/flathub.flatpakrepo \ + "${keys[@]}" "$work/repo" "dist/release/$base.flatpak" ai.artcraft.app stable +flatpak build-bundle --oci "$work/repo" "dist/release/$base-oci" ai.artcraft.app stable +skopeo inspect "oci:dist/release/$base-oci" >/dev/null +if [[ -n "${SIGNING_FPR:-}" ]]; then + registry="ghcr.io/${GITHUB_REPOSITORY,,}" + creds="$GITHUB_ACTOR:$GITHUB_TOKEN" + skopeo copy --format oci --dest-creds "$creds" "oci:dist/release/$base-oci" "docker://$registry:$version-$arch" + if [[ "$version" != *-* && ( "$GITHUB_REF" == refs/heads/release || "$GITHUB_REF" == refs/heads/artcraft-release ) ]]; then + skopeo copy --format oci --dest-creds "$creds" "oci:dist/release/$base-oci" "docker://$registry:stable-$arch" + fi + skopeo inspect --raw --creds "$creds" "docker://$registry:$version-$arch" >"dist/release/$base.oci-manifest.json" +fi diff --git a/packaging/linux/flatpak.yml b/packaging/linux/flatpak.yml new file mode 100644 index 0000000000..f1b4581269 --- /dev/null +++ b/packaging/linux/flatpak.yml @@ -0,0 +1,25 @@ +app-id: ai.artcraft.app +runtime: org.gnome.Platform +runtime-version: '51' +sdk: org.gnome.Sdk +command: artcraft +finish-args: + - --socket=wayland + - --socket=fallback-x11 + - --socket=pulseaudio + - --share=ipc + - --share=network + - --device=dri + - --filesystem=xdg-pictures + - --filesystem=xdg-videos + - --filesystem=xdg-download +modules: + - name: artcraft + buildsystem: simple + build-commands: + - cp -a . /app/ + - install -Dm644 /app/share/icons/hicolor/128x128/apps/artcraft.png /app/share/icons/hicolor/128x128/apps/ai.artcraft.app.png + - sed -i 's/^Icon=.*/Icon=ai.artcraft.app/' /app/share/applications/ai.artcraft.app.desktop + sources: + - type: dir + path: stage diff --git a/packaging/linux/package-test.sh b/packaging/linux/package-test.sh new file mode 100755 index 0000000000..3b2b63af76 --- /dev/null +++ b/packaging/linux/package-test.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Integration fixture: real DEB/RPM archives, with no Rust compilation or signing key. +set -euo pipefail +script="$(realpath "$(dirname "$0")/package.sh")" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT +cd "$work" +mkdir -p crates/desktop/artcraft target/x86_64-unknown-linux-gnu/release/bundle/{deb,rpm,appimage} +printf '{"version":"0.42.0"}\n' > crates/desktop/artcraft/tauri.conf.json +mkdir -p stage/{DEBIAN,usr/bin,usr/share/applications} +cp /bin/true stage/usr/bin/artcraft +printf '[Desktop Entry]\nType=Application\nName=ArtCraft\nExec=artcraft\n' > stage/usr/share/applications/ArtCraft.desktop +cat > stage/DEBIAN/control <<'CONTROL' +Package: artcraft +Version: 0.42.0 +Architecture: amd64 +Maintainer: Fixture +Description: Packaging fixture +CONTROL +dpkg-deb --build stage target/x86_64-unknown-linux-gnu/release/bundle/deb/ArtCraft.deb +mkdir -p rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} +cat > rpmbuild/SPECS/artcraft.spec <<'SPEC' +Name: artcraft +Version: 0.42.0 +Release: 1 +Summary: Packaging fixture +License: MIT +%description +Packaging fixture +%install +mkdir -p %{buildroot}/usr/bin +cp /bin/true %{buildroot}/usr/bin/artcraft +%files +/usr/bin/artcraft +SPEC +rpmbuild --define "_topdir $work/rpmbuild" -bb rpmbuild/SPECS/artcraft.spec +find rpmbuild/RPMS -name '*.rpm' -exec cp {} target/x86_64-unknown-linux-gnu/release/bundle/rpm/ \; +printf 'appimage fixture\n' > target/x86_64-unknown-linux-gnu/release/bundle/appimage/ArtCraft.AppImage +"$script" x86_64 x86_64-unknown-linux-gnu +for format in deb rpm AppImage AppImage.zsync tar.gz; do test -s "dist/release/artcraft-0.42.0-linux-x86_64.$format"; done +mkdir unpacked +tar -xzf dist/release/artcraft-0.42.0-linux-x86_64.tar.gz -C unpacked +test -x unpacked/artcraft-0.42.0-linux-x86_64/bin/artcraft +test -f unpacked/artcraft-0.42.0-linux-x86_64/share/applications/ai.artcraft.app.desktop +rm target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm +if "$script" x86_64 x86_64-unknown-linux-gnu; then + echo 'Packaging accepted a missing RPM' >&2 + exit 1 +fi +echo 'Package integration checks passed' diff --git a/packaging/linux/package.sh b/packaging/linux/package.sh new file mode 100755 index 0000000000..3ff5dbb177 --- /dev/null +++ b/packaging/linux/package.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Normalize Tauri packages and derive the suite's bin/share tarball from its Debian package. +set -euo pipefail +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +arch="${1:?architecture required}" +target="${2:?Rust target required}" +version="$(jq -r .version crates/desktop/artcraft/tauri.conf.json)" +case "$arch" in x86_64|aarch64|riscv64) ;; *) exit 2 ;; esac +base="artcraft-$version-linux-$arch" +bundle="target/$target/release/bundle" +mkdir -p dist/release +for format in deb rpm AppImage; do + mapfile -t files < <(find "$bundle" -type f -name "*.$format") + if [[ "$arch" == riscv64 && "$format" == AppImage ]]; then continue; fi + [[ ${#files[@]} == 1 ]] || { echo "Expected one $format in $bundle, found ${#files[@]}" >&2; exit 1; } + cp "${files[0]}" "dist/release/$base.$format" +done +if [[ "$arch" != riscv64 ]]; then + zsyncmake -u "https://github.com/storytold/artcraft/releases/download/artcraft-v$version/$base.AppImage" \ + -o "dist/release/$base.AppImage.zsync" "dist/release/$base.AppImage" +fi +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT +dpkg-deb -x "dist/release/$base.deb" "$work/deb" +mkdir -p "$work/$base" +cp -a "$work/deb/usr/." "$work/$base/" +mapfile -t desktops < <(find "$work/$base/share/applications" -name '*.desktop') +[[ ${#desktops[@]} == 1 ]] || { echo 'Expected one desktop entry' >&2; exit 1; } +if [[ "${desktops[0]}" != "$work/$base/share/applications/ai.artcraft.app.desktop" ]]; then + mv "${desktops[0]}" "$work/$base/share/applications/ai.artcraft.app.desktop" +fi +install -Dm644 "$script_dir/ai.artcraft.app.metainfo.xml" "$work/$base/share/metainfo/ai.artcraft.app.metainfo.xml" +sed -i 's/^Name=.*/Name=ArtCraft Studio/' "$work/$base/share/applications/ai.artcraft.app.desktop" +test -x "$work/$base/bin/artcraft" +case "$arch" in + x86_64) machine='Advanced Micro Devices X86-64' ;; + aarch64) machine='AArch64' ;; + riscv64) machine='RISC-V' ;; +esac +readelf -h "$work/$base/bin/artcraft" | grep -F "$machine" +if [[ "$arch" == "$(uname -m)" ]]; then + dependencies="$(ldd "$work/$base/bin/artcraft")" + if grep -q 'not found' <<< "$dependencies"; then + printf '%s\n' "$dependencies" >&2 + exit 1 + fi +fi +# The tarball layout is consumed by the launcher, Arch packaging and Flatpak. +tar -czf "dist/release/$base.tar.gz" -C "$work" "$base" +case "$arch" in x86_64) deb_arch=amd64 ;; aarch64) deb_arch=arm64 ;; riscv64) deb_arch=riscv64 ;; esac +[[ "$(dpkg-deb -f "dist/release/$base.deb" Architecture)" == "$deb_arch" ]] +[[ "$(rpm -qp --qf '%{ARCH}' "dist/release/$base.rpm")" == "$arch" ]] +dpkg-deb --info "dist/release/$base.deb" +rpm -qp --requires "dist/release/$base.rpm" diff --git a/packaging/linux/riscv64.cmake b/packaging/linux/riscv64.cmake new file mode 100644 index 0000000000..e969fbf384 --- /dev/null +++ b/packaging/linux/riscv64.cmake @@ -0,0 +1,7 @@ +# boring-sys2 has built-in ARM toolchains but no RISC-V toolchain. Set the target +# explicitly so BoringSSL does not select the x86_64 runner's assembly sources. +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR riscv64) +set(CMAKE_C_COMPILER riscv64-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER riscv64-linux-gnu-g++) +set(OPENSSL_NO_ASM ON CACHE BOOL "Use BoringSSL's portable RISC-V implementation") diff --git a/packaging/linux/tauri.conf.json b/packaging/linux/tauri.conf.json new file mode 100644 index 0000000000..7c3a6ba023 --- /dev/null +++ b/packaging/linux/tauri.conf.json @@ -0,0 +1,15 @@ +{ + "build": { + "beforeBuildCommand": "" + }, + "bundle": { + "category": "GraphicsAndDesign", + "shortDescription": "AI image and video creation", + "longDescription": "ArtCraft Studio is a desktop workspace for generating AI images and video.", + "linux": { + "appimage": { + "bundleMediaFramework": true + } + } + } +} diff --git a/packaging/windows/README.md b/packaging/windows/README.md new file mode 100644 index 0000000000..3ec4d78d2f --- /dev/null +++ b/packaging/windows/README.md @@ -0,0 +1,3 @@ +# Windows Packaging + +ArtCraft Studio Windows release packaging assets and configuration.