BlueGit

golden-age paperdoll-renderer

Merge feature/release-aur (v0.1.0 release + AUR)

timfa Grok Bot · pushed by timfa

Authored 2026-11-07 · 6 days ago

Hash
4df9cdbe4368b354da813995bf9fbb9cb598eb4e
Pipeline
Failed
6 files changed +241 −28
README.md +4 −0
@@ -193,6 +193,10 @@ let layer = LayerMaps::from_atlas(&body, "body", "clip000-walk", 0, 60, 330)?;
193 193
194 194 Highres is exactly 8× on both axes; batch targets are currently **1–256 pixels per axis** (highres max 2048). Larger project settings give a clear render error, not silently reduced supersampling. At most 500000 PNGs / 16 GiB of estimated uncompressed target data per run; output requires hard links. Production renders all glTF/GLB meshes with triangle primitives (see *Multiple meshes and skins*), base color factor × 8-bit albedo textures per material, and vertex colors. An FBX body goes through the normalized scene (`fbx_scene`) in the same batch: all animation stacks × frames × 60 angles, the same fixed camera, centre pivot and fixed zoom per body, and the same four maps. Equipment hangs on the animated bones of that FBX skeleton. Static bodies render one bind-pose sample per angle (see *Static bodies*). Live placement remains a lit, simplified view.
195 195
196 ## Releases and packages
197
198 A pushed `vX.Y.Z` tag (protected pattern `v*`) runs the tag pipeline: tests, then the AppImage, Debian/Ubuntu `.deb`, Arch `.pkg.tar.zst` and Windows zip, a package check with `SHA256SUMS`, and the release job, which publishes those files as the release of that existing tag on BlueGit (`https://git.timfalken.com/golden-age/paperdoll-renderer/-/releases`). It then updates the AUR package `paperdoll-renderer-bin` from the release's `.deb` (`yay -S paperdoll-renderer-bin`).
199
196 200 ## Open with
197 201
198 202 - **Windows:** run the unpacked `.exe` once to add the editor to *Open with* for the current account. Then choose **Open with → Paperdoll Renderer** on a `.ppd` (or select the `.exe` via Browse).
@@ -193,6 +193,10 @@ let layer = LayerMaps::from_atlas(&body, "body", "clip000-walk", 0, 60, 330)?;
193
193
194Highres is exactly 8× on both axes; batch targets are currently **1–256 pixels per axis** (highres max 2048). Larger project settings give a clear render error, not silently reduced supersampling. At most 500000 PNGs / 16 GiB of estimated uncompressed target data per run; output requires hard links. Production renders all glTF/GLB meshes with triangle primitives (see *Multiple meshes and skins*), base color factor × 8-bit albedo textures per material, and vertex colors. An FBX body goes through the normalized scene (`fbx_scene`) in the same batch: all animation stacks × frames × 60 angles, the same fixed camera, centre pivot and fixed zoom per body, and the same four maps. Equipment hangs on the animated bones of that FBX skeleton. Static bodies render one bind-pose sample per angle (see *Static bodies*). Live placement remains a lit, simplified view.
194Highres is exactly 8× on both axes; batch targets are currently **1–256 pixels per axis** (highres max 2048). Larger project settings give a clear render error, not silently reduced supersampling. At most 500000 PNGs / 16 GiB of estimated uncompressed target data per run; output requires hard links. Production renders all glTF/GLB meshes with triangle primitives (see *Multiple meshes and skins*), base color factor × 8-bit albedo textures per material, and vertex colors. An FBX body goes through the normalized scene (`fbx_scene`) in the same batch: all animation stacks × frames × 60 angles, the same fixed camera, centre pivot and fixed zoom per body, and the same four maps. Equipment hangs on the animated bones of that FBX skeleton. Static bodies render one bind-pose sample per angle (see *Static bodies*). Live placement remains a lit, simplified view.
195
195
196## Releases and packages
197
198A pushed `vX.Y.Z` tag (protected pattern `v*`) runs the tag pipeline: tests, then the AppImage, Debian/Ubuntu `.deb`, Arch `.pkg.tar.zst` and Windows zip, a package check with `SHA256SUMS`, and the release job, which publishes those files as the release of that existing tag on BlueGit (`https://git.timfalken.com/golden-age/paperdoll-renderer/-/releases`). It then updates the AUR package `paperdoll-renderer-bin` from the release's `.deb` (`yay -S paperdoll-renderer-bin`).
199
196## Open with
200## Open with
197
201
198- **Windows:** run the unpacked `.exe` once to add the editor to *Open with* for the current account. Then choose **Open with → Paperdoll Renderer** on a `.ppd` (or select the `.exe` via Browse).
202- **Windows:** run the unpacked `.exe` once to add the editor to *Open with* for the current account. Then choose **Open with → Paperdoll Renderer** on a `.ppd` (or select the `.exe` via Browse).
blue-ci-cd.yml +29 −10
@@ -2,27 +2,28 @@
2 2 #
3 3 # Syntax checked on 6 November 2026 against GET /api/v1/ci/spec,
4 4 # timfa/bluegit docs/CI.md, and the public pipelines of timfa/posprint,
5 # timfa/minecart-trains and timfa/click-tween. legacy-cycles publishes
6 # releases only; its pipeline file is not public and was not copied.
5 # timfa/minecart-trains and timfa/click-tween. The AUR job follows
6 # timfa/legacy-cycles (aur-ssh-setup.sh, publish-aur.sh).
7 7 #
8 8 # Requested graph:
9 9 # merge request and master/main push -> test
10 10 # tag push, after tests succeed -> AppImage, Ubuntu package, Arch package,
11 # Windows build, artifact check, then one release publish
11 # Windows build, artifact check, one release publish, then the AUR
12 12 #
13 # Verified release limit, rechecked against api_routes.go on master: no route
14 # attaches artifacts to an existing tag or release. POST /repos/$CI_PROJECT_PATH/releases
15 # creates the git tag (409 if it exists). replace=true is 403 on a tag pipeline.
16 # scripts/ci/release.sh does not call that route and does not send replace.
17 # The release job exits 3 until BlueGit adds the attach route in design.md.
18 # This file does not create a tag. No release has been started here.
13 # Release (design.md, v0.1.0 decision, rechecked 7 November 2026 against the
14 # live OpenAPI): the release job publishes onto the tag that started this
15 # pipeline with POST /repos/$CI_PROJECT_PATH/releases/$CI_COMMIT_TAG/assets,
16 # BlueGit's create-only route for an existing tag (409 if a release exists;
17 # never creates, moves or deletes the tag). replace is never sent and the
18 # tag-creating release route is not called. Tags are pushed by hand on a green
19 # master; publish:aur then updates the AUR package paperdoll-renderer-bin.
19 20 #
20 21 # posprint notes two runner facts used below: jobs start in the checkout
21 22 # (CI_PROJECT_DIR is not set), and artifact paths are exact files.
22 23 # Windows is cross-compiled with MinGW, the same reason posprint gives:
23 24 # no Windows runner on BlueGit.
24 25
25 stages: [test, package, verify, release]
26 stages: [test, package, verify, release, publish]
26 27
27 28 variables:
28 29 CARGO_TERM_COLOR: always
@@ -164,4 +165,22 @@ release:
164 165 needs: [verify:packages, package:appimage, package:ubuntu, package:arch, package:windows]
165 166 resource_group: release
166 167 script:
168 - apk add --no-cache jq
167 169 - bash scripts/ci/release.sh
170
171 # AUR (approach of timfa/legacy-cycles): after the release, publish
172 # paperdoll-renderer-bin from this tag's .deb release asset. Protected
173 # variables (AUR_SSH_PRIVATE_KEY_B64, AUR_GIT_AUTHOR_NAME, AUR_GIT_AUTHOR_EMAIL)
174 # reach only protected refs; v* is a protected tag pattern.
175 # allow_failure: an AUR outage does not turn the release red.
176 publish:aur:
177 stage: publish
178 image: archlinux/archlinux:base-devel
179 extends: .on_tag
180 needs: [release]
181 allow_failure: true
182 resource_group: aur
183 script:
184 - pacman -Syu --noconfirm --needed git openssh curl coreutils shadow util-linux
185 - bash scripts/ci/aur-ssh-setup.sh
186 - bash scripts/ci/publish-aur.sh paperdoll-renderer-bin
@@ -2,27 +2,28 @@
2#
2#
3# Syntax checked on 6 November 2026 against GET /api/v1/ci/spec,
3# Syntax checked on 6 November 2026 against GET /api/v1/ci/spec,
4# timfa/bluegit docs/CI.md, and the public pipelines of timfa/posprint,
4# timfa/bluegit docs/CI.md, and the public pipelines of timfa/posprint,
5# timfa/minecart-trains and timfa/click-tween. legacy-cycles publishes
5# timfa/minecart-trains and timfa/click-tween. The AUR job follows
6# releases only; its pipeline file is not public and was not copied.
6# timfa/legacy-cycles (aur-ssh-setup.sh, publish-aur.sh).
7#
7#
8# Requested graph:
8# Requested graph:
9# merge request and master/main push -> test
9# merge request and master/main push -> test
10# tag push, after tests succeed -> AppImage, Ubuntu package, Arch package,
10# tag push, after tests succeed -> AppImage, Ubuntu package, Arch package,
11# Windows build, artifact check, then one release publish
11# Windows build, artifact check, one release publish, then the AUR
12#
12#
13# Verified release limit, rechecked against api_routes.go on master: no route
13# Release (design.md, v0.1.0 decision, rechecked 7 November 2026 against the
14# attaches artifacts to an existing tag or release. POST /repos/$CI_PROJECT_PATH/releases
14# live OpenAPI): the release job publishes onto the tag that started this
15# creates the git tag (409 if it exists). replace=true is 403 on a tag pipeline.
15# pipeline with POST /repos/$CI_PROJECT_PATH/releases/$CI_COMMIT_TAG/assets,
16# scripts/ci/release.sh does not call that route and does not send replace.
16# BlueGit's create-only route for an existing tag (409 if a release exists;
17# The release job exits 3 until BlueGit adds the attach route in design.md.
17# never creates, moves or deletes the tag). replace is never sent and the
18# This file does not create a tag. No release has been started here.
18# tag-creating release route is not called. Tags are pushed by hand on a green
19# master; publish:aur then updates the AUR package paperdoll-renderer-bin.
19#
20#
20# posprint notes two runner facts used below: jobs start in the checkout
21# posprint notes two runner facts used below: jobs start in the checkout
21# (CI_PROJECT_DIR is not set), and artifact paths are exact files.
22# (CI_PROJECT_DIR is not set), and artifact paths are exact files.
22# Windows is cross-compiled with MinGW, the same reason posprint gives:
23# Windows is cross-compiled with MinGW, the same reason posprint gives:
23# no Windows runner on BlueGit.
24# no Windows runner on BlueGit.
24
25
25stages: [test, package, verify, release]
26stages: [test, package, verify, release, publish]
26
27
27variables:
28variables:
28 CARGO_TERM_COLOR: always
29 CARGO_TERM_COLOR: always
@@ -164,4 +165,22 @@ release:
164 needs: [verify:packages, package:appimage, package:ubuntu, package:arch, package:windows]
165 needs: [verify:packages, package:appimage, package:ubuntu, package:arch, package:windows]
165 resource_group: release
166 resource_group: release
166 script:
167 script:
168 - apk add --no-cache jq
167 - bash scripts/ci/release.sh
169 - bash scripts/ci/release.sh
170
171# AUR (approach of timfa/legacy-cycles): after the release, publish
172# paperdoll-renderer-bin from this tag's .deb release asset. Protected
173# variables (AUR_SSH_PRIVATE_KEY_B64, AUR_GIT_AUTHOR_NAME, AUR_GIT_AUTHOR_EMAIL)
174# reach only protected refs; v* is a protected tag pattern.
175# allow_failure: an AUR outage does not turn the release red.
176publish:aur:
177 stage: publish
178 image: archlinux/archlinux:base-devel
179 extends: .on_tag
180 needs: [release]
181 allow_failure: true
182 resource_group: aur
183 script:
184 - pacman -Syu --noconfirm --needed git openssh curl coreutils shadow util-linux
185 - bash scripts/ci/aur-ssh-setup.sh
186 - bash scripts/ci/publish-aur.sh paperdoll-renderer-bin
packaging/aur/paperdoll-renderer-bin/PKGBUILD added +44 −0
@@ -0,0 +1,44 @@
1 # Maintainer: Tim Falken <tim@timfalken.com>
2 # CI (scripts/ci/publish-aur.sh) sets pkgver, _tag and sha256sums.
3 pkgname=paperdoll-renderer-bin
4 pkgver=0.1.0
5 pkgrel=1
6 _tag=v0.1.0
7 pkgdesc='Paperdoll Renderer: sprite/paperdoll renders from glTF/FBX bodies with a live preview (prebuilt binary)'
8 arch=('x86_64')
9 url='https://git.timfalken.com/golden-age/paperdoll-renderer'
10 license=('MIT' 'custom:Sketchfab-Standard')
11 depends=('gcc-libs' 'glibc' 'libxcb' 'libx11' 'libxkbcommon' 'libxkbcommon-x11' 'libglvnd'
12 'shared-mime-info' 'desktop-file-utils' 'xdg-desktop-portal' 'zenity')
13 provides=('paperdoll-renderer')
14 conflicts=('paperdoll-renderer')
15 options=('!strip' '!debug')
16 source=("paperdoll-renderer-${_tag}-amd64.deb::${url}/-/releases/${_tag}/assets/paperdoll-renderer-${_tag}-amd64.deb")
17 sha256sums=('SKIP')
18 noextract=("paperdoll-renderer-${_tag}-amd64.deb")
19
20 prepare() {
21 mkdir -p "$srcdir/deb"
22 cd "$srcdir/deb"
23 bsdtar -xf "$srcdir/paperdoll-renderer-${_tag}-amd64.deb"
24 mkdir -p root
25 bsdtar -xf data.tar.* -C root
26 }
27
28 package() {
29 local r="$srcdir/deb/root/usr"
30 install -Dm755 "$r/bin/paperdoll-renderer" "$pkgdir/usr/bin/paperdoll-renderer"
31 install -Dm644 "$r/share/applications/paperdoll-renderer.desktop" \
32 "$pkgdir/usr/share/applications/paperdoll-renderer.desktop"
33 install -Dm644 "$r/share/mime/packages/paperdoll-project.xml" \
34 "$pkgdir/usr/share/mime/packages/paperdoll-project.xml"
35 install -Dm644 "$r/share/metainfo/paperdoll-renderer.metainfo.xml" \
36 "$pkgdir/usr/share/metainfo/paperdoll-renderer.metainfo.xml"
37 local icon
38 for icon in "$r"/share/icons/hicolor/*/apps/paperdoll-renderer.*; do
39 install -Dm644 "$icon" "$pkgdir/usr/share/icons/hicolor/${icon#"$r/share/icons/hicolor/"}"
40 done
41 install -Dm644 "$r/share/licenses/paperdoll-renderer/LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
42 install -Dm644 "$r/share/licenses/paperdoll-renderer/THIRD_PARTY_NOTICES.md" \
43 "$pkgdir/usr/share/licenses/$pkgname/THIRD_PARTY_NOTICES.md"
44 }
@@ -0,0 +1,44 @@
1# Maintainer: Tim Falken <tim@timfalken.com>
2# CI (scripts/ci/publish-aur.sh) sets pkgver, _tag and sha256sums.
3pkgname=paperdoll-renderer-bin
4pkgver=0.1.0
5pkgrel=1
6_tag=v0.1.0
7pkgdesc='Paperdoll Renderer: sprite/paperdoll renders from glTF/FBX bodies with a live preview (prebuilt binary)'
8arch=('x86_64')
9url='https://git.timfalken.com/golden-age/paperdoll-renderer'
10license=('MIT' 'custom:Sketchfab-Standard')
11depends=('gcc-libs' 'glibc' 'libxcb' 'libx11' 'libxkbcommon' 'libxkbcommon-x11' 'libglvnd'
12 'shared-mime-info' 'desktop-file-utils' 'xdg-desktop-portal' 'zenity')
13provides=('paperdoll-renderer')
14conflicts=('paperdoll-renderer')
15options=('!strip' '!debug')
16source=("paperdoll-renderer-${_tag}-amd64.deb::${url}/-/releases/${_tag}/assets/paperdoll-renderer-${_tag}-amd64.deb")
17sha256sums=('SKIP')
18noextract=("paperdoll-renderer-${_tag}-amd64.deb")
19
20prepare() {
21 mkdir -p "$srcdir/deb"
22 cd "$srcdir/deb"
23 bsdtar -xf "$srcdir/paperdoll-renderer-${_tag}-amd64.deb"
24 mkdir -p root
25 bsdtar -xf data.tar.* -C root
26}
27
28package() {
29 local r="$srcdir/deb/root/usr"
30 install -Dm755 "$r/bin/paperdoll-renderer" "$pkgdir/usr/bin/paperdoll-renderer"
31 install -Dm644 "$r/share/applications/paperdoll-renderer.desktop" \
32 "$pkgdir/usr/share/applications/paperdoll-renderer.desktop"
33 install -Dm644 "$r/share/mime/packages/paperdoll-project.xml" \
34 "$pkgdir/usr/share/mime/packages/paperdoll-project.xml"
35 install -Dm644 "$r/share/metainfo/paperdoll-renderer.metainfo.xml" \
36 "$pkgdir/usr/share/metainfo/paperdoll-renderer.metainfo.xml"
37 local icon
38 for icon in "$r"/share/icons/hicolor/*/apps/paperdoll-renderer.*; do
39 install -Dm644 "$icon" "$pkgdir/usr/share/icons/hicolor/${icon#"$r/share/icons/hicolor/"}"
40 done
41 install -Dm644 "$r/share/licenses/paperdoll-renderer/LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
42 install -Dm644 "$r/share/licenses/paperdoll-renderer/THIRD_PARTY_NOTICES.md" \
43 "$pkgdir/usr/share/licenses/$pkgname/THIRD_PARTY_NOTICES.md"
44}
scripts/ci/aur-ssh-setup.sh added +78 −0
@@ -0,0 +1,78 @@
1 #!/usr/bin/env bash
2 # Write ~/.ssh/id_ed25519 for AUR pushes from CI or local scripts.
3 #
4 # Supported CI variables (pick one):
5 # AUR_SSH_PRIVATE_KEY_B64 — base64 of the private key (Variable type, one line; Masked OK)
6 # or File type whose contents are that base64 string
7 # AUR_SSH_PRIVATE_KEY — raw OpenSSH private key (File type preferred), or raw key text
8 # File contents may also be base64 (auto-detected)
9 #
10 # Always normalizes CRLF → LF (Windows pastes into GitLab often break OpenSSH otherwise).
11 set -euo pipefail
12
13 KEY_PATH="${1:-$HOME/.ssh/id_ed25519}"
14
15 install -d -m 700 "$(dirname "$KEY_PATH")"
16
17 is_raw_ssh_key() {
18 grep -qE 'BEGIN (OPENSSH |RSA |EC )?PRIVATE KEY' "$1" 2>/dev/null
19 }
20
21 normalize_key_file() {
22 local tmp
23 tmp="$(mktemp)"
24 tr -d '\r' < "$KEY_PATH" > "$tmp"
25 if [[ -s "$tmp" ]] && [[ "$(tail -c1 "$tmp" | wc -l)" -eq 0 ]]; then
26 printf '\n' >> "$tmp"
27 fi
28 mv "$tmp" "$KEY_PATH"
29 chmod 600 "$KEY_PATH"
30 }
31
32 decode_b64_to_key() {
33 local src="$1"
34 if [[ -f "$src" ]]; then
35 tr -d '\r\n[:space:]' < "$src" | base64 -d > "$KEY_PATH"
36 else
37 printf '%s' "$src" | tr -d '\r\n[:space:]' | base64 -d > "$KEY_PATH"
38 fi
39 }
40
41 if [[ -n "${AUR_SSH_PRIVATE_KEY_B64:-}" ]]; then
42 decode_b64_to_key "$AUR_SSH_PRIVATE_KEY_B64"
43 elif [[ -n "${AUR_SSH_PRIVATE_KEY:-}" && -f "${AUR_SSH_PRIVATE_KEY}" ]]; then
44 if is_raw_ssh_key "$AUR_SSH_PRIVATE_KEY"; then
45 cp "$AUR_SSH_PRIVATE_KEY" "$KEY_PATH"
46 else
47 decode_b64_to_key "$AUR_SSH_PRIVATE_KEY"
48 fi
49 elif [[ -n "${AUR_SSH_PRIVATE_KEY:-}" ]]; then
50 if printf '%s' "$AUR_SSH_PRIVATE_KEY" | grep -qE 'BEGIN (OPENSSH |RSA |EC )?PRIVATE KEY'; then
51 printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > "$KEY_PATH"
52 else
53 decode_b64_to_key "$AUR_SSH_PRIVATE_KEY"
54 fi
55 elif [[ -f "$HOME/.ssh/id_ed25519" ]]; then
56 cp "$HOME/.ssh/id_ed25519" "$KEY_PATH"
57 else
58 echo "No AUR SSH key found. Set AUR_SSH_PRIVATE_KEY_B64 or AUR_SSH_PRIVATE_KEY." >&2
59 exit 1
60 fi
61
62 normalize_key_file
63
64 if ! is_raw_ssh_key "$KEY_PATH"; then
65 echo "AUR SSH key is not a valid OpenSSH private key after setup." >&2
66 echo "Use either:" >&2
67 echo " - AUR_SSH_PRIVATE_KEY as File with raw key (BEGIN ... PRIVATE KEY)" >&2
68 echo " - AUR_SSH_PRIVATE_KEY_B64 as Variable with one-line base64 of that key" >&2
69 echo "Tip: generate base64 from a LF-normalized key (see docs/CI.md)." >&2
70 exit 1
71 fi
72
73 if ! ssh-keygen -y -f "$KEY_PATH" >/dev/null 2>&1; then
74 echo "AUR SSH key failed ssh-keygen validation (often CRLF or truncated paste)." >&2
75 exit 1
76 fi
77
78 ssh-keyscan aur.archlinux.org >> "$HOME/.ssh/known_hosts" 2>/dev/null || true
@@ -0,0 +1,78 @@
1#!/usr/bin/env bash
2# Write ~/.ssh/id_ed25519 for AUR pushes from CI or local scripts.
3#
4# Supported CI variables (pick one):
5# AUR_SSH_PRIVATE_KEY_B64 — base64 of the private key (Variable type, one line; Masked OK)
6# or File type whose contents are that base64 string
7# AUR_SSH_PRIVATE_KEY — raw OpenSSH private key (File type preferred), or raw key text
8# File contents may also be base64 (auto-detected)
9#
10# Always normalizes CRLF → LF (Windows pastes into GitLab often break OpenSSH otherwise).
11set -euo pipefail
12
13KEY_PATH="${1:-$HOME/.ssh/id_ed25519}"
14
15install -d -m 700 "$(dirname "$KEY_PATH")"
16
17is_raw_ssh_key() {
18 grep -qE 'BEGIN (OPENSSH |RSA |EC )?PRIVATE KEY' "$1" 2>/dev/null
19}
20
21normalize_key_file() {
22 local tmp
23 tmp="$(mktemp)"
24 tr -d '\r' < "$KEY_PATH" > "$tmp"
25 if [[ -s "$tmp" ]] && [[ "$(tail -c1 "$tmp" | wc -l)" -eq 0 ]]; then
26 printf '\n' >> "$tmp"
27 fi
28 mv "$tmp" "$KEY_PATH"
29 chmod 600 "$KEY_PATH"
30}
31
32decode_b64_to_key() {
33 local src="$1"
34 if [[ -f "$src" ]]; then
35 tr -d '\r\n[:space:]' < "$src" | base64 -d > "$KEY_PATH"
36 else
37 printf '%s' "$src" | tr -d '\r\n[:space:]' | base64 -d > "$KEY_PATH"
38 fi
39}
40
41if [[ -n "${AUR_SSH_PRIVATE_KEY_B64:-}" ]]; then
42 decode_b64_to_key "$AUR_SSH_PRIVATE_KEY_B64"
43elif [[ -n "${AUR_SSH_PRIVATE_KEY:-}" && -f "${AUR_SSH_PRIVATE_KEY}" ]]; then
44 if is_raw_ssh_key "$AUR_SSH_PRIVATE_KEY"; then
45 cp "$AUR_SSH_PRIVATE_KEY" "$KEY_PATH"
46 else
47 decode_b64_to_key "$AUR_SSH_PRIVATE_KEY"
48 fi
49elif [[ -n "${AUR_SSH_PRIVATE_KEY:-}" ]]; then
50 if printf '%s' "$AUR_SSH_PRIVATE_KEY" | grep -qE 'BEGIN (OPENSSH |RSA |EC )?PRIVATE KEY'; then
51 printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > "$KEY_PATH"
52 else
53 decode_b64_to_key "$AUR_SSH_PRIVATE_KEY"
54 fi
55elif [[ -f "$HOME/.ssh/id_ed25519" ]]; then
56 cp "$HOME/.ssh/id_ed25519" "$KEY_PATH"
57else
58 echo "No AUR SSH key found. Set AUR_SSH_PRIVATE_KEY_B64 or AUR_SSH_PRIVATE_KEY." >&2
59 exit 1
60fi
61
62normalize_key_file
63
64if ! is_raw_ssh_key "$KEY_PATH"; then
65 echo "AUR SSH key is not a valid OpenSSH private key after setup." >&2
66 echo "Use either:" >&2
67 echo " - AUR_SSH_PRIVATE_KEY as File with raw key (BEGIN ... PRIVATE KEY)" >&2
68 echo " - AUR_SSH_PRIVATE_KEY_B64 as Variable with one-line base64 of that key" >&2
69 echo "Tip: generate base64 from a LF-normalized key (see docs/CI.md)." >&2
70 exit 1
71fi
72
73if ! ssh-keygen -y -f "$KEY_PATH" >/dev/null 2>&1; then
74 echo "AUR SSH key failed ssh-keygen validation (often CRLF or truncated paste)." >&2
75 exit 1
76fi
77
78ssh-keyscan aur.archlinux.org >> "$HOME/.ssh/known_hosts" 2>/dev/null || true
scripts/ci/publish-aur.sh added +48 −0
@@ -0,0 +1,48 @@
1 #!/usr/bin/env bash
2 # publish-aur.sh PKGNAME — adapted from timfa/legacy-cycles.
3 # Runs in the tag pipeline after `release`: downloads this tag's .deb release
4 # asset, sets pkgver/_tag/sha256sums in packaging/aur/PKGNAME/PKGBUILD,
5 # regenerates .SRCINFO and pushes to aur.archlinux.org (the first push creates
6 # the AUR package). Uses AUR_GIT_AUTHOR_NAME / AUR_GIT_AUTHOR_EMAIL; the SSH
7 # key comes from aur-ssh-setup.sh (AUR_SSH_PRIVATE_KEY_B64).
8 set -euo pipefail
9 pkg="$1"
10 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
11 SRC="$ROOT/packaging/aur/$pkg"
12 : "${CI_COMMIT_TAG:?}" "${CI_PROJECT_URL:?}" "${AUR_GIT_AUTHOR_NAME:?}" "${AUR_GIT_AUTHOR_EMAIL:?}"
13 tag="$CI_COMMIT_TAG"
14 [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "not a release tag: $tag" >&2; exit 1; }
15 ver="${tag#v}"
16 url="$CI_PROJECT_URL/-/releases/$tag/assets/paperdoll-renderer-${tag}-amd64.deb"
17
18 WORKDIR="$(mktemp -d)"; export WORKDIR
19 trap 'rm -rf "$WORKDIR"' EXIT
20 curl -fsSL "$url" -o "$WORKDIR/asset.deb"
21 sha="$(sha256sum "$WORKDIR/asset.deb" | awk '{print $1}')"
22 echo "$pkg $ver $url $sha"
23
24 dest="$WORKDIR/$pkg"
25 remote="ssh://aur@aur.archlinux.org/${pkg}.git"
26 if ! git clone "$remote" "$dest"; then
27 echo "AUR repo $pkg does not exist yet; the first push creates it"
28 mkdir -p "$dest"; git -C "$dest" init -q -b master; git -C "$dest" remote add origin "$remote"
29 fi
30 git -C "$dest" symbolic-ref HEAD refs/heads/master
31 find "$dest" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
32 cp -a "$SRC/." "$dest/"
33 cd "$dest"
34 sed -i "s/^pkgver=.*/pkgver=${ver}/; s/^pkgrel=.*/pkgrel=1/; s/^_tag=.*/_tag=${tag}/; s/^sha256sums=.*/sha256sums=('${sha}')/" PKGBUILD
35
36 useradd -m aurbuild 2>/dev/null || true
37 chmod 755 "$WORKDIR"; chown -R aurbuild "$dest"
38 runuser -u aurbuild -- makepkg --printsrcinfo > "$WORKDIR/srcinfo"
39 mv "$WORKDIR/srcinfo" .SRCINFO
40 chown -R root:root "$dest"
41 git config --global --add safe.directory '*'
42 git config user.name "$AUR_GIT_AUTHOR_NAME"
43 git config user.email "$AUR_GIT_AUTHOR_EMAIL"
44 git add -A
45 if git diff --cached --quiet; then echo "AUR $pkg already up to date ($ver)"; exit 0; fi
46 git commit -qm "$ver"
47 git push -u origin HEAD:master
48 echo "AUR updated: $pkg $ver"
@@ -0,0 +1,48 @@
1#!/usr/bin/env bash
2# publish-aur.sh PKGNAME — adapted from timfa/legacy-cycles.
3# Runs in the tag pipeline after `release`: downloads this tag's .deb release
4# asset, sets pkgver/_tag/sha256sums in packaging/aur/PKGNAME/PKGBUILD,
5# regenerates .SRCINFO and pushes to aur.archlinux.org (the first push creates
6# the AUR package). Uses AUR_GIT_AUTHOR_NAME / AUR_GIT_AUTHOR_EMAIL; the SSH
7# key comes from aur-ssh-setup.sh (AUR_SSH_PRIVATE_KEY_B64).
8set -euo pipefail
9pkg="$1"
10ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
11SRC="$ROOT/packaging/aur/$pkg"
12: "${CI_COMMIT_TAG:?}" "${CI_PROJECT_URL:?}" "${AUR_GIT_AUTHOR_NAME:?}" "${AUR_GIT_AUTHOR_EMAIL:?}"
13tag="$CI_COMMIT_TAG"
14[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "not a release tag: $tag" >&2; exit 1; }
15ver="${tag#v}"
16url="$CI_PROJECT_URL/-/releases/$tag/assets/paperdoll-renderer-${tag}-amd64.deb"
17
18WORKDIR="$(mktemp -d)"; export WORKDIR
19trap 'rm -rf "$WORKDIR"' EXIT
20curl -fsSL "$url" -o "$WORKDIR/asset.deb"
21sha="$(sha256sum "$WORKDIR/asset.deb" | awk '{print $1}')"
22echo "$pkg $ver $url $sha"
23
24dest="$WORKDIR/$pkg"
25remote="ssh://aur@aur.archlinux.org/${pkg}.git"
26if ! git clone "$remote" "$dest"; then
27 echo "AUR repo $pkg does not exist yet; the first push creates it"
28 mkdir -p "$dest"; git -C "$dest" init -q -b master; git -C "$dest" remote add origin "$remote"
29fi
30git -C "$dest" symbolic-ref HEAD refs/heads/master
31find "$dest" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
32cp -a "$SRC/." "$dest/"
33cd "$dest"
34sed -i "s/^pkgver=.*/pkgver=${ver}/; s/^pkgrel=.*/pkgrel=1/; s/^_tag=.*/_tag=${tag}/; s/^sha256sums=.*/sha256sums=('${sha}')/" PKGBUILD
35
36useradd -m aurbuild 2>/dev/null || true
37chmod 755 "$WORKDIR"; chown -R aurbuild "$dest"
38runuser -u aurbuild -- makepkg --printsrcinfo > "$WORKDIR/srcinfo"
39mv "$WORKDIR/srcinfo" .SRCINFO
40chown -R root:root "$dest"
41git config --global --add safe.directory '*'
42git config user.name "$AUR_GIT_AUTHOR_NAME"
43git config user.email "$AUR_GIT_AUTHOR_EMAIL"
44git add -A
45if git diff --cached --quiet; then echo "AUR $pkg already up to date ($ver)"; exit 0; fi
46git commit -qm "$ver"
47git push -u origin HEAD:master
48echo "AUR updated: $pkg $ver"
scripts/ci/release.sh +38 −18
@@ -1,17 +1,12 @@
1 1 #!/usr/bin/env bash
2 # Tag-pipeline publish step.
2 # Tag-pipeline publish step (design.md, "v0.1.0 release" decision).
3 3 #
4 # Inspected BlueGit master on 6 November 2026 (api_routes.go, ops_ci.go
5 # opPublishRelease, release_replace.go checkReplace, gitrepo.Tag):
6 # there is no route that attaches artifacts to an existing git tag or an
7 # existing release. POST /api/v1/repos/{owner}/{repo}/releases always creates
8 # the annotated tag. An existing tag returns HTTP 409 unless replace=true,
9 # and replace=true is HTTP 403 on a tag pipeline.
10 #
11 # This script therefore does not call that endpoint and does not send
12 # replace. It exits 3 until BlueGit grows an attach route. It does not
13 # invent a second tag and it does not switch the pipeline onto a branch
14 # release. The required server change is recorded in design.md section 15.
4 # Publishes the release for the tag that started this pipeline with
5 # POST $CI_API_URL/repos/{owner}/{repo}/releases/{tag}/assets — BlueGit's
6 # create-only route for an existing tag: an existing release is HTTP 409, it
7 # never creates, moves or deletes the tag, and the assets are artifacts of
8 # successful jobs of this same pipeline. `replace` is never sent and the
9 # tag-creating route POST .../releases is never called.
15 10 set -euo pipefail
16 11
17 12 : "${CI_API_URL:?}" "${CI_JOB_TOKEN:?}" "${CI_COMMIT_SHA:?}" "${CI_PROJECT_PATH:?}" "${CI_COMMIT_TAG:?}"
@@ -19,10 +14,35 @@ set -euo pipefail
19 14 root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
20 15 cd "$root"
21 16 version="$(bash scripts/ci/package-version.sh)"
17 tag="$CI_COMMIT_TAG"
18 [[ "$tag" == "v$version" ]] || { echo "tag $tag does not match package version $version" >&2; exit 1; }
19
20 b="paperdoll-renderer-${tag}"
21 asset() { jq -n --arg j "$1" --arg p "$2" --arg n "$3" '{job:$j,path:$p,name:$n}'; }
22 assets="$(jq -s . \
23 <(asset package:appimage dist/paperdoll-renderer-x86_64.AppImage "${b}-x86_64.AppImage") \
24 <(asset package:ubuntu dist/paperdoll-renderer_amd64.deb "${b}-amd64.deb") \
25 <(asset package:arch dist/paperdoll-renderer-x86_64.pkg.tar.zst "${b}-x86_64.pkg.tar.zst") \
26 <(asset package:windows dist/paperdoll-renderer-windows-x86_64.zip "${b}-windows-x86_64.zip") \
27 <(asset verify:packages dist/SHA256SUMS "${b}-SHA256SUMS.txt"))"
28 desc="Paperdoll Renderer ${version}.
29
30 * Linux AppImage (portable).
31 * Debian/Ubuntu .deb: /usr/bin/paperdoll-renderer, desktop entry, icons, .ppd MIME type.
32 * Arch .pkg.tar.zst, built from packaging/arch/PKGBUILD. AUR: paperdoll-renderer-bin.
33 * Windows zip (x86_64, MinGW build).
34 * SHA256SUMS of the package files.
35
36 The default starter bodies are \"Animated walking Male Base Mesh\" and \"Animated Female Base Mesh\" by assetfactory (Sketchfab Standard licence); see THIRD_PARTY_NOTICES.md."
37 body="$(jq -n --arg n "Paperdoll Renderer ${tag}" --arg b "$desc" --argjson a "$assets" '{name:$n,body:$b,assets:$a}')"
22 38
23 echo "release-blocked: no-attach-endpoint" >&2
24 echo "tag ${CI_COMMIT_TAG} version ${version} commit ${CI_COMMIT_SHA} project ${CI_PROJECT_PATH}" >&2
25 echo "BlueGit has no request that publishes these package artifacts onto the tag that already started this pipeline." >&2
26 echo "The create-release route would make a new annotated tag. This script does not call it." >&2
27 echo "The overwrite flag is not sent. No second tag is created." >&2
28 exit 3
39 echo "Release $tag on $CI_COMMIT_SHA (existing tag, create-only)"
40 resp="$(mktemp)"
41 code="$(curl -sS -o "$resp" -w '%{http_code}' -X POST \
42 "$CI_API_URL/repos/$CI_PROJECT_PATH/releases/$tag/assets" \
43 -H "Authorization: Bearer $CI_JOB_TOKEN" -H "Content-Type: application/json" -d "$body")" || code=000
44 cat "$resp"; echo
45 if [[ "$code" != 2* ]]; then
46 echo "release API returned HTTP $code (see response above)" >&2
47 exit 1
48 fi
@@ -1,17 +1,12 @@
1#!/usr/bin/env bash
1#!/usr/bin/env bash
2# Tag-pipeline publish step.
2# Tag-pipeline publish step (design.md, "v0.1.0 release" decision).
3#
3#
4# Inspected BlueGit master on 6 November 2026 (api_routes.go, ops_ci.go
4# Publishes the release for the tag that started this pipeline with
5# opPublishRelease, release_replace.go checkReplace, gitrepo.Tag):
5# POST $CI_API_URL/repos/{owner}/{repo}/releases/{tag}/assets — BlueGit's
6# there is no route that attaches artifacts to an existing git tag or an
6# create-only route for an existing tag: an existing release is HTTP 409, it
7# existing release. POST /api/v1/repos/{owner}/{repo}/releases always creates
7# never creates, moves or deletes the tag, and the assets are artifacts of
8# the annotated tag. An existing tag returns HTTP 409 unless replace=true,
8# successful jobs of this same pipeline. `replace` is never sent and the
9# and replace=true is HTTP 403 on a tag pipeline.
9# tag-creating route POST .../releases is never called.
10#
11# This script therefore does not call that endpoint and does not send
12# replace. It exits 3 until BlueGit grows an attach route. It does not
13# invent a second tag and it does not switch the pipeline onto a branch
14# release. The required server change is recorded in design.md section 15.
15set -euo pipefail
10set -euo pipefail
16
11
17: "${CI_API_URL:?}" "${CI_JOB_TOKEN:?}" "${CI_COMMIT_SHA:?}" "${CI_PROJECT_PATH:?}" "${CI_COMMIT_TAG:?}"
12: "${CI_API_URL:?}" "${CI_JOB_TOKEN:?}" "${CI_COMMIT_SHA:?}" "${CI_PROJECT_PATH:?}" "${CI_COMMIT_TAG:?}"
@@ -19,10 +14,35 @@ set -euo pipefail
19root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
14root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
20cd "$root"
15cd "$root"
21version="$(bash scripts/ci/package-version.sh)"
16version="$(bash scripts/ci/package-version.sh)"
17tag="$CI_COMMIT_TAG"
18[[ "$tag" == "v$version" ]] || { echo "tag $tag does not match package version $version" >&2; exit 1; }
19
20b="paperdoll-renderer-${tag}"
21asset() { jq -n --arg j "$1" --arg p "$2" --arg n "$3" '{job:$j,path:$p,name:$n}'; }
22assets="$(jq -s . \
23 <(asset package:appimage dist/paperdoll-renderer-x86_64.AppImage "${b}-x86_64.AppImage") \
24 <(asset package:ubuntu dist/paperdoll-renderer_amd64.deb "${b}-amd64.deb") \
25 <(asset package:arch dist/paperdoll-renderer-x86_64.pkg.tar.zst "${b}-x86_64.pkg.tar.zst") \
26 <(asset package:windows dist/paperdoll-renderer-windows-x86_64.zip "${b}-windows-x86_64.zip") \
27 <(asset verify:packages dist/SHA256SUMS "${b}-SHA256SUMS.txt"))"
28desc="Paperdoll Renderer ${version}.
29
30* Linux AppImage (portable).
31* Debian/Ubuntu .deb: /usr/bin/paperdoll-renderer, desktop entry, icons, .ppd MIME type.
32* Arch .pkg.tar.zst, built from packaging/arch/PKGBUILD. AUR: paperdoll-renderer-bin.
33* Windows zip (x86_64, MinGW build).
34* SHA256SUMS of the package files.
35
36The default starter bodies are \"Animated walking Male Base Mesh\" and \"Animated Female Base Mesh\" by assetfactory (Sketchfab Standard licence); see THIRD_PARTY_NOTICES.md."
37body="$(jq -n --arg n "Paperdoll Renderer ${tag}" --arg b "$desc" --argjson a "$assets" '{name:$n,body:$b,assets:$a}')"
22
38
23echo "release-blocked: no-attach-endpoint" >&2
39echo "Release $tag on $CI_COMMIT_SHA (existing tag, create-only)"
24echo "tag ${CI_COMMIT_TAG} version ${version} commit ${CI_COMMIT_SHA} project ${CI_PROJECT_PATH}" >&2
40resp="$(mktemp)"
25echo "BlueGit has no request that publishes these package artifacts onto the tag that already started this pipeline." >&2
41code="$(curl -sS -o "$resp" -w '%{http_code}' -X POST \
26echo "The create-release route would make a new annotated tag. This script does not call it." >&2
42 "$CI_API_URL/repos/$CI_PROJECT_PATH/releases/$tag/assets" \
27echo "The overwrite flag is not sent. No second tag is created." >&2
43 -H "Authorization: Bearer $CI_JOB_TOKEN" -H "Content-Type: application/json" -d "$body")" || code=000
28exit 3
44cat "$resp"; echo
45if [[ "$code" != 2* ]]; then
46 echo "release API returned HTTP $code (see response above)" >&2
47 exit 1
48fi