Author SHA1 Message Date
Misthios 6a643c658d fedora-remote: keep Intel GPU firmware/VA-API for iGPU passthrough
Build containers / Compute changes (push) Successful in 2s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Successful in 9m26s
Build containers / Prune old releases and tags (push) Successful in 26s
The iGPU on the Proxmox host (UHD 630, 8086:3e91) is bound to vfio-pci and
will be passed through to this VM, so the guest's i915 needs the GuC/HuC
blobs (intel-gpu-firmware) and iHD VA-API decode (libva-intel-media-driver).
Both are upstream common.yaml entries that the trim list was dropping; iris
(mesa-dri-drivers) and i915 (kernel-modules) are already present.
2026-10-04 20:14:03 +02:00
Misthios 3a1d80ad65 fedora-remote: add Mesa EGL for Proxmox virtio-gpu acceleration
Build containers / Compute changes (push) Successful in 3s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Successful in 9m46s
Build containers / Prune old releases and tags (push) Successful in 25s
base-atomic ships mesa-dri-drivers (the virtio_gpu Gallium driver) but not
mesa-libEGL, and with recommends off nothing pulls it in. mutter/gnome-shell
only require libglvnd's EGL dispatcher, which then has no Mesa backend, so the
session falls back to llvmpipe software rendering. Add mesa-libEGL explicitly
so the host iGPU is used under Proxmox's VirGL/virtio-gpu display.
2026-10-04 18:55:10 +02:00
Misthios 137091b402 fedora-remote: install Flatpaks system-wide on first boot
Build containers / Compute changes (push) Successful in 3s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Successful in 8m28s
Build containers / Prune old releases and tags (push) Successful in 25s
Per-user Flatpaks are only visible to the account that logged in first (and
consumed space per user). Install them into the system installation instead, so
every user including Kanidm accounts sees Firefox.
2026-09-30 19:37:03 +02:00
Misthios 172948822a fedora-remote: fix GDM greeter so headless RDP renders
Build containers / Compute changes (push) Successful in 2s
Build containers / fedora-cosmic (rawhide) (push) Failing after 48s
Build containers / fedora-remote (44) (push) Successful in 10m30s
Build containers / fedora-cosmic (44) (push) Successful in 14m21s
Build containers / Prune old releases and tags (push) Successful in 26s
The minimal base (recommends off) was missing session infrastructure and one
maintained service actively broke the greeter, so RDP clients saw only a white
screen:

- add dbus-daemon: gdm-wayland-session runs it for the greeter session bus
- add systemd-pam: pam_systemd starts the per-user systemd manager
- nsswitch: shadow must include systemd so pam_unix can resolve GDM 50's
  dynamic gdm-greeter user (Fedora 44 ships 'files systemd')
- mask uresourced: its resource tuning makes user@<uid>.service fail with
  'Failed to spawn executor', leaving no systemd user bus for gnome-session
2026-09-30 19:00:29 +02:00
Misthios f2d7ad24ec fedora-remote: set GRD credentials with explicit arguments
Build containers / Compute changes (push) Successful in 2s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Successful in 8m48s
Build containers / Prune old releases and tags (push) Successful in 25s
grdctl's interactive set-credentials reads from a controlling terminal, which a
systemd service does not have, so piping the username/password in set nothing
and GRD denied every client with 'Credentials are not set'. Pass them as
arguments instead.
2026-09-30 18:39:02 +02:00
Misthios c1bf97c9c2 fedora-remote: open the RDP port in firewalld
Build containers / Compute changes (push) Successful in 3s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Successful in 8m56s
Build containers / Prune old releases and tags (push) Successful in 27s
firewalld only allows ssh by default, so GRD was listening on 3389 but the port
was still blocked. Allow the firewalld 'rdp' service in grd-firstboot.
2026-09-30 18:26:25 +02:00
Misthios 2e5a4fd707 fedora-remote: add openssl for grd-firstboot TLS, enable sshd
Build containers / Compute changes (push) Successful in 3s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Successful in 9m9s
Build containers / Prune old releases and tags (push) Successful in 27s
grd-firstboot aborts before enabling RDP because it shells out to openssl to
generate the TLS certificate, and the minimal image did not include the openssl
CLI. Add it. Also enable sshd so the headless host is reachable without RDP.
2026-09-30 17:26:58 +02:00
Misthios 357bf16479 prune: don't abort when a distro has no versioned tags
Build containers / Compute changes (push) Successful in 3s
Build containers / fedora-cosmic (rawhide) (push) Failing after 58s
Build containers / fedora-remote (44) (push) Successful in 10m43s
Build containers / fedora-cosmic (44) (push) Successful in 13m20s
Build containers / Prune old releases and tags (push) Successful in 27s
fedora-remote has no rawhide.* tags yet, so grep exited non-zero and, under
set -e/pipefail, killed prune-registry.sh before it reported anything. Tolerate
an empty match.
2026-09-28 17:59:50 +02:00
Misthios 40f7aef501 build: trim blocklisted packages from top-level manifests too
Build containers / Compute changes (push) Successful in 3s
Build containers / fedora-cosmic (rawhide) (push) Failing after 59s
Build containers / fedora-remote (44) (push) Successful in 11m18s
Build containers / fedora-cosmic (44) (push) Successful in 13m39s
Build containers / Prune old releases and tags (push) Failing after 25s
kernel-modules-extra is declared in the upstream top-level common.yaml, not
under packages/, so the headless fedora-remote image still requested it while
also listing it in exclude-packages, which made compose fail with
'Packages not found: kernel-modules-extra'. Scan top-level manifests as well,
and drop keys whose block is left empty (comments/blanks do not count).
2026-09-28 17:44:27 +02:00
Misthios 8e85980468 ci: use static matrices with change-based guards
Build containers / Compute changes (push) Successful in 3s
Build containers / fedora-cosmic (rawhide) (push) Failing after 1m10s
Build containers / fedora-remote (44) (push) Failing after 1m14s
Build containers / fedora-cosmic (44) (push) Successful in 13m5s
Build containers / Prune old releases and tags (push) Failing after 28s
Gitea does not expand a dynamic fromJson matrix (the job name stayed literal
and it queued with no matching runner). Instead, compute cosmic/remote booleans
in a changes job and gate static-matrix build jobs with job-level if.
2026-09-27 23:14:13 +02:00
Misthios fdc1441c32 ci: only rebuild images whose files changed
Build containers / Compute build matrix (push) Successful in 3s
Build containers / ${{ matrix.image }} (${{ matrix.distro }}) (push) Canceled after 0s
Build containers / Prune old releases and tags (push) Canceled after 0s
Add a changes job that diffs the pushed range (or PR base) and emits a matrix
via ci-matrix.sh. Shared files or build tooling changes rebuild all images.
Also fix the upstream package trim to drop top-level keys left empty (e.g.
packages-x86_64 when every entry is excluded), which broke fedora-remote.
2026-09-27 22:59:02 +02:00
Misthios 5bea16bc01 refactor: share common files between images; trim fedora-remote for a VM
Build containers / fedora-remote (44) (push) Failing after 36s
Build containers / fedora-cosmic (rawhide) (push) Failing after 51s
Build containers / fedora-cosmic (44) (push) Successful in 12m34s
Build containers / Prune old releases (push) Failing after 28s
- Move Kanidm/flatpak/finalize.d/sshd files to images/shared/{files,repos}
  and have build.sh overlay them for every image.
- fedora-remote: aggressive hardware trim (no firmware, no kernel-modules-extra,
  no non-QEMU guest agents), keep + enable qemu-guest-agent (Proxmox).
- Fixes the missing package-list (release step) for fedora-remote.
2026-09-27 22:44:10 +02:00
Misthios 6d70a1bfb9 fedora-remote: minimal headless GNOME Remote Desktop host with Kanidm
Build containers / fedora-cosmic (rawhide) (push) Failing after 54s
Build containers / fedora-cosmic (44) (push) Successful in 14m8s
Build containers / fedora-remote (44) (push) Failing after 15m36s
Build containers / Prune old releases (push) Failing after 26s
New image built from upstream base-atomic (no desktop), adding only gnome-shell
+ gdm + gnome-remote-desktop + pipewire/portal + foot + Flatpak + Kanidm.
recommends=false to stay lean. Configures GNOME Remote Desktop headless remote
login on first boot (TLS + credentials + enable). Firefox preinstalled as a
per-user Flatpak. Add it to the CI matrix and prune both images.
2026-09-27 22:26:48 +02:00
Misthios 6822bc3061 fedora-cosmic: harden sshd for Kanidm key-only auth
Build containers / fedora-cosmic (rawhide) (push) Failing after 54s
Build containers / fedora-cosmic (44) (push) Successful in 12m1s
Build containers / Prune old releases (push) Successful in 22s
Disable password/root/GSSAPI/Kerberos SSH auth now that public keys are
served by Kanidm.
2026-09-27 22:11:55 +02:00
Misthios 0e4ea86f22 fedora-cosmic: enable Kanidm SSH public key auth
Build containers / fedora-cosmic (rawhide) (push) Failing after 46s
Build containers / Prune old releases (push) Canceled after 0s
Build containers / fedora-cosmic (44) (push) Canceled after 4m17s
Ship an sshd_config.d drop-in so sshd fetches authorized keys from Kanidm via
kanidm_ssh_authorizedkeys. Named 10-* to take precedence over systemd-userdbd.
2026-09-27 22:09:56 +02:00
Misthios b9fe183da1 fedora-cosmic: mark login/nss domains permissive for Kanidm
Build containers / fedora-cosmic (rawhide) (push) Failing after 51s
Build containers / fedora-cosmic (44) (push) Successful in 11m55s
Build containers / Prune old releases (push) Successful in 25s
Enforcing SELinux blocks Kanidm user resolution/authentication for sshd and
friends, but no AVC is emitted (even with dontaudit disabled), so the exact
allow could not be pinned. Keep the CIL allows and mark the login/nss domains
permissive so Kanidm logins work. Revisit when the denial can be isolated.
2026-09-27 21:18:05 +02:00
Misthios e4251f4d78 ci: fix prune-registry registry ref case and protect signatures
Build containers / fedora-cosmic (rawhide) (push) Failing after 45s
Build containers / fedora-cosmic (44) (push) Successful in 11m0s
Build containers / Prune old releases (push) Successful in 29s
The registry path is lowercase, so the capitalized owner made skopeo inspect
fail and every cosign .sig tag was deleted. Lowercase the owner and, if no
digest resolves, keep all signature tags instead of deleting them.
2026-09-27 20:28:40 +02:00
Misthios edfa563fd7 ci: make prune-registry.sh executable
Build containers / fedora-cosmic (rawhide) (push) Failing after 46s
Build containers / fedora-cosmic (44) (push) Successful in 11m15s
Build containers / Prune old releases (push) Successful in 29s
2026-09-27 20:15:34 +02:00
Misthios a789cb6669 ci: prune old container registry tags too
Build containers / fedora-cosmic (rawhide) (push) Failing after 47s
Build containers / fedora-cosmic (44) (push) Successful in 11m4s
Build containers / Prune old releases (push) Failing after 21s
Add prune-registry.sh: keeps the moving tag and newest versioned tag per
distro plus their cosign signature tags, deletes the rest via the Gitea
packages API. Wire it into the prune job (uses REGISTRY_TOKEN).
2026-09-27 20:03:12 +02:00
Misthios 380d550351 fedora-cosmic: make Kanidm logins work despite a local uid-1000 account
Build containers / fedora-cosmic (rawhide) (push) Failing after 50s
Build containers / fedora-cosmic (44) (push) Successful in 11m0s
Build containers / Prune old releases (push) Successful in 20s
- uid_attr_map/gid_attr_map = name so NSS/PAM see 'misthios', not the SPN
- allow_local_account_override = [misthios] so Kanidm takes over an existing
  local account of the same name (Kanidm otherwise ignores its own entry,
  leaving logins to fall back to the local account and fail)
2026-09-27 19:46:36 +02:00
Misthios f6205eb982 ci: add prune job so failing variants still get cleaned up
Build containers / fedora-cosmic (rawhide) (push) Failing after 46s
Build containers / fedora-cosmic (44) (push) Successful in 10m56s
Build containers / Prune old releases (push) Successful in 20s
2026-09-27 19:14:02 +02:00
Misthios 84f2309fba ci: build weekly and prune old releases
Build containers / fedora-cosmic (rawhide) (push) Failing after 48s
Build containers / fedora-cosmic (44) (push) Successful in 11m5s
Schedule moves to Mondays 04:00 UTC. release.sh now deletes older releases
for the same image/distro (and their assets and tags) after publishing, so
only the newest is kept. Add prune-releases.sh for a one-off cleanup.
2026-09-27 19:02:06 +02:00
Misthios f00f4340b7 fedora-cosmic: ship SELinux policy for kanidm-unixd sockets
Build containers / fedora-cosmic (rawhide) (push) Failing after 50s
Build containers / fedora-cosmic (44) (push) Successful in 11m3s
nss/pam consumers (sshd, the display manager, systemd-userdbd, polkit, ...)
were denied write access to /run/kanidm-unixd/* (var_run_t), so Kanidm users
could not be resolved or authenticated and logins failed with 'invalid user'.
Add the audit2allow-derived CIL and load it at build time (semodule noreload).
2026-09-27 18:49:12 +02:00
Misthios a5c6170ac0 fedora-cosmic: pass Kanidm token via systemd credential
Build containers / fedora-cosmic (rawhide) (push) Failing after 48s
Build containers / fedora-cosmic (44) (push) Successful in 10m50s
kanidm-unixd runs with DynamicUser=yes, so it cannot read the root-only
/etc/kanidm/unixd_token. With service_account_token_path set in
/etc/kanidm/unixd the daemon failed with PermissionDenied and no Kanidm user
resolved. Drop that setting and inject the token with LoadCredential +
KANIDM_SERVICE_ACCOUNT_TOKEN_PATH through a generated drop-in.

Verified in a QEMU VM: kanidm-unixd active, 'kanidm-unix status' online,
'getent passwd job' resolves.
2026-09-27 17:59:36 +02:00
32 changed files with 730 additions and 33 deletions
+93 -5
View File
@@ -7,17 +7,48 @@ on:
push:
branches: ["main"]
schedule:
- cron: "0 4 * * *"
# Weekly, Mondays 04:00 UTC.
- cron: "0 4 * * 1"
jobs:
build:
# Work out which images actually changed, so a push only rebuilds those.
changes:
name: Compute changes
runs-on: job-v2
outputs:
cosmic: ${{ steps.changes.outputs.cosmic }}
remote: ${{ steps.changes.outputs.remote }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect changed images
id: changes
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" == "push" \
&& -n "${{ github.event.before }}" \
&& "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]]; then
changed="$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" || true)"
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
changed="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" || true)"
else
changed=""
fi
eval "$(printf '%s\n' "${changed}" | ./ci-changes.sh)"
echo "cosmic=${cosmic}" >> "$GITHUB_OUTPUT"
echo "remote=${remote}" >> "$GITHUB_OUTPUT"
build-cosmic:
name: ${{ matrix.image }} (${{ matrix.distro }})
needs: changes
if: needs.changes.outputs.cosmic == 'true'
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
# To build another image, add images/<name>/{manifest.yaml,build.conf}
# and a matching matrix entry here.
matrix:
include:
- image: fedora-cosmic
@@ -41,7 +72,7 @@ jobs:
GITEA_URL: https://git.plabble.org
GITEA_REPO: Misthios/bootc-images
steps:
steps: &build_steps
- name: Install build tools
run: |
set -xeuo pipefail
@@ -118,3 +149,60 @@ jobs:
buildid="$(cat .buildid)"
pkgs="build/${IMAGE}-${DISTRO}-${ARCH}/packages-current.txt"
./release.sh "${IMAGE}" "${DISTRO}" "${buildid}" "${pkgs}"
build-remote:
name: fedora-remote (44)
needs: changes
if: needs.changes.outputs.remote == 'true'
runs-on: job-v2
container:
image: "quay.io/fedora-ostree-desktops/buildroot:44"
options: "--security-opt=label=disable --privileged --user 0:0 --device=/dev/fuse --volume /:/run/host:rw"
env:
IMAGE: fedora-remote
DISTRO: "44"
ARCH: x86_64
REGISTRY: git.plabble.org/misthios
GITEA_URL: https://git.plabble.org
GITEA_REPO: Misthios/bootc-images
steps: *build_steps
prune:
name: Prune old releases and tags
runs-on: job-v2
needs: [changes, build-cosmic, build-remote]
# Run even if some builds were skipped or failed.
if: ${{ always() && github.event_name != 'pull_request' }}
container:
image: "quay.io/fedora-ostree-desktops/buildroot:44"
options: "--security-opt=label=disable --privileged --user 0:0"
env:
GITEA_URL: https://git.plabble.org
GITEA_REPO: Misthios/bootc-images
steps:
- name: Install tools
run: dnf install -y nodejs jq curl skopeo
- name: Checkout
uses: actions/checkout@v4
- name: Prune old releases
env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
for image in fedora-cosmic fedora-remote; do
./prune-releases.sh "${image}"
done
- name: Prune old registry tags
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -xeuo pipefail
authfile=/tmp/prune-auth.json
echo "${REGISTRY_TOKEN}" | skopeo login -u "${REGISTRY_USERNAME}" \
--password-stdin --authfile "${authfile}" git.plabble.org
for image in fedora-cosmic fedora-remote; do
AUTHFILE="${authfile}" ./prune-registry.sh "${image}" Misthios 44 rawhide
done
+49 -5
View File
@@ -89,14 +89,16 @@ gpgcheck=0
EOF
}
# Drop packages listed in a blocklist from the cloned upstream package
# manifests. rpm-ostree refuses (fatally) to exclude a package that an included
# manifest declares, so remove those lines at the source instead.
# Drop packages listed in a blocklist from the cloned upstream manifests.
# rpm-ostree refuses (fatally) to exclude a package that an included manifest
# declares, so remove those lines at the source instead. Packages are declared
# both in packages/*.yaml and in the top-level variant manifests (e.g. the base
# kernel list lives in common.yaml), so scan both.
trim_upstream_packages() {
local root="$1"
local blocklist="$2"
local f
for f in "${root}"/packages/*.yaml; do
for f in "${root}"/*.yaml "${root}"/packages/*.yaml; do
[[ -f "${f}" ]] || continue
awk '
NR == FNR {
@@ -111,7 +113,28 @@ trim_upstream_packages() {
print
}
' "${blocklist}" "${f}" > "${f}.tmp"
mv "${f}.tmp" "${f}"
# Drop top-level keys left with no list items or nested keys (e.g. if every
# entry under packages-x86_64: was removed). Comments and blank lines do not
# count as content, so a key followed only by comments is still dropped.
awk '
{ lines[NR] = $0 }
END {
for (i = 1; i <= NR; i++) {
if (lines[i] ~ /^[A-Za-z0-9_.-]+:$/) {
keep = 0
for (j = i + 1; j <= NR; j++) {
if (lines[j] ~ /^[A-Za-z0-9_.-]+:/) break
if (lines[j] ~ /^[[:space:]]+-[[:space:]]/) { keep = 1; break }
if (lines[j] ~ /^[[:space:]]+[A-Za-z0-9_.-]+:/) { keep = 1; break }
}
if (!keep) continue
}
print lines[i]
}
}
' "${f}.tmp" > "${f}.tmp2"
mv "${f}.tmp2" "${f}"
rm -f "${f}.tmp"
done
}
@@ -137,10 +160,18 @@ seed_kanidm_token() {
if [[ -n "${KANIDM_UNIXD_TOKEN:-}" ]]; then
printf '%s\n' "${KANIDM_UNIXD_TOKEN}" > "${dir}/kanidm-unixd-token"
chmod 0600 "${dir}/kanidm-unixd-token"
# kanidm-unixd is DynamicUser=yes, so it cannot read the root-only token
# file directly; hand it over as a systemd credential instead.
cat > "${dir}/kanidm-unixd-token.conf" <<'EOF'
[Service]
LoadCredential=unixd_token:/etc/kanidm/unixd_token
Environment=KANIDM_SERVICE_ACCOUNT_TOKEN_PATH=%d/unixd_token
EOF
cat > "${out}" <<'EOF'
# Generated by build.sh from the KANIDM_UNIXD_TOKEN secret.
add-files:
- ["kanidm-unixd-token", "/etc/kanidm/unixd_token"]
- ["kanidm-unixd-token.conf", "/usr/lib/systemd/system/kanidm-unixd.service.d/10-token.conf"]
EOF
else
cat > "${out}" <<'EOF'
@@ -171,6 +202,16 @@ case "${BUILD_MODE}" in
if [[ -d "${IMAGE_DIR}/files" ]]; then
cp -a "${IMAGE_DIR}/files/." "${BUILD_DIR}/upstream/"
fi
# Shared overlay (files/repos used by multiple images).
if [[ -d "${REPO_ROOT}/images/shared/files" ]]; then
cp -a "${REPO_ROOT}/images/shared/files/." "${BUILD_DIR}/upstream/"
fi
if compgen -G "${REPO_ROOT}/images/shared/repos/*.repo" >/dev/null; then
cp "${REPO_ROOT}"/images/shared/repos/*.repo "${BUILD_DIR}/upstream/"
fi
if compgen -G "${REPO_ROOT}/images/shared/repos/${DISTRO}/*.repo" >/dev/null; then
cp "${REPO_ROOT}"/images/shared/repos/"${DISTRO}"/*.repo "${BUILD_DIR}/upstream/"
fi
if [[ "${TOKEN2}" == "1" ]]; then
setup_token2_repo "${BUILD_DIR}/upstream"
@@ -201,6 +242,9 @@ case "${BUILD_MODE}" in
if compgen -G "${local_dir}/repos/${DISTRO}/*.repo" >/dev/null; then
cp "${local_dir}"/repos/"${DISTRO}"/*.repo "${local_dir}/"
fi
if [[ -d "${REPO_ROOT}/images/shared/files" ]]; then
cp -a "${REPO_ROOT}/images/shared/files/." "${local_dir}/"
fi
if [[ "${TOKEN2}" == "1" ]]; then
setup_token2_repo "${local_dir}"
Executable
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Given changed paths on stdin, print `cosmic=<bool>` and `remote=<bool>` so the
# workflow can skip images that didn't change. Shared files or build tooling
# changes rebuild everything; empty input (schedule/manual) rebuilds everything.
set -euo pipefail
changed="$(cat || true)"
all=0
[[ -z "${changed}" ]] && all=1
grep -qE '^(build\.sh|release\.sh|prune-releases\.sh|prune-registry\.sh|ci-changes\.sh|\.gitea/)' <<< "${changed}" && all=1
grep -qE '^images/shared/' <<< "${changed}" && all=1
cosmic=false
remote=false
if [[ "${all}" == 1 ]]; then
cosmic=true
remote=true
fi
grep -qE '^images/fedora-cosmic/' <<< "${changed}" && cosmic=true
grep -qE '^images/fedora-remote/' <<< "${changed}" && remote=true
echo "cosmic=${cosmic}"
echo "remote=${remote}"
+20 -2
View File
@@ -46,6 +46,8 @@ add-files:
- ["kanidm-password-auth", "/usr/share/fedora-cosmic/authselect/password-auth"]
- ["kanidm-nsswitch.conf", "/usr/share/fedora-cosmic/authselect/nsswitch.conf"]
- ["50-kanidm.preset", "/usr/lib/systemd/system-preset/50-kanidm.preset"]
- ["kanidm-unixd.cil", "/usr/share/fedora-cosmic/kanidm-unixd.cil"]
- ["10-kanidm.conf", "/etc/ssh/sshd_config.d/10-kanidm.conf"]
postprocess:
# The upstream fedora.yaml removes the Google Chrome repo from the Fedora
@@ -88,11 +90,27 @@ postprocess:
install -m 0644 /usr/share/fedora-cosmic/authselect/nsswitch.conf /etc/nsswitch.conf
fi
# Kanidm does not ship an SELinux policy yet; let the unixd daemons run.
# Kanidm has no SELinux policy; install ours so nss/pam consumers (sshd,
# the display manager, systemd-userdbd, ...) can reach the kanidm-unixd
# sockets and resolve Kanidm users.
- |
#!/usr/bin/env bash
set -xeuo pipefail
semanage permissive -a unconfined_service_t || true
semodule -n -i /usr/share/fedora-cosmic/kanidm-unixd.cil
# Kanidm ships no SELinux policy and its nss/pam path is blocked under
# enforcing for the login domains in a way we could not pin to a single
# allow (no AVC is emitted, even with dontaudit off). Keep the CIL allows
# above and mark the login/nss consumers permissive so Kanidm users can
# resolve and log in. Tradeoff: these domains are not confined.
- |
#!/usr/bin/env bash
set -xeuo pipefail
for domain in sshd_t sshd_session_t sshd_auth_t chkpwd_t \
systemd_userdbd_t local_login_t xdm_t polkit_t accountsd_t \
unconfined_service_t; do
semanage permissive -a "${domain}" || true
done
# Lock down the Kanidm service account token if it was seeded at build time.
- |
-20
View File
@@ -1,20 +0,0 @@
version = "2"
# Bind cached credentials to the local TPM when one is available.
hsm_type = "tpm_if_possible"
default_shell = "/bin/bash"
home_prefix = "/home/"
home_attr = "uuid"
home_alias = "name"
use_etc_skel = true
selinux = true
[kanidm]
# Members of this Kanidm POSIX group are allowed to log in via PAM.
pam_allowed_login_groups = ["unix_users"]
# Token for the Kanidm service account used to resolve identities. Provision it
# at /etc/kanidm/unixd_token (a single line) after install, or remove this line
# if the server permits anonymous reads.
service_account_token_path = "/etc/kanidm/unixd_token"
+10
View File
@@ -0,0 +1,10 @@
# Build configuration for images/fedora-remote. Sourced by build.sh.
BUILD_MODE=upstream
UPSTREAM_REPO="https://gitlab.com/fedora/ostree/ci-test.git"
# Stable only; this is a small remote-access host.
declare -A UPSTREAM_REFS=(
[44]=f44
)
+120
View File
@@ -0,0 +1,120 @@
# Customizations for the minimal headless remote host.
packages:
# Fedora integration normally provided by the upstream fedora.yaml (which we
# opt out of to avoid the Firefox RPM).
- fedora-release
- fedora-release-ostree-desktop
- fedora-flathub-remote
# GNOME Remote Desktop headless remote login. gdm pulls gnome-session,
# gnome-settings-daemon, accountsservice, dconf and gnome-keyring-pam.
- gdm
- gnome-shell
- gnome-remote-desktop
- pipewire
- wireplumber
- xdg-desktop-portal-gnome
# Terminal (foot has no Flathub build) and Flatpak.
- foot
- flatpak
# Proxmox guest integration.
- qemu-guest-agent
# Proxmox virtio-gpu display (VirGL): render on the host iGPU instead of
# software (llvmpipe). mesa-dri-drivers ships the virtio_gpu Gallium driver
# and comes from base-atomic; mesa-libEGL is the piece base-atomic lacks - it
# provides the Mesa EGL vendor, without which libglvnd's EGL dispatcher has no
# backend and mutter/gnome-shell fall back to software rendering. Listed
# explicitly (recommends are off) so the GL stack survives upstream changes.
- mesa-dri-drivers
- mesa-libEGL
# grd-firstboot generates the RDP TLS certificate with the openssl CLI.
- openssl
# GDM/gnome-session session infrastructure. The greeter needs the reference
# dbus-daemon for its session bus, and pam_systemd (systemd-pam) to start the
# per-user systemd manager; without them the greeter dies and RDP clients just
# get a white screen (base-atomic does not pull these in with recommends off).
- dbus-daemon
- systemd-pam
# Kanidm Unix authentication (kanidm-unixd-clients pulls kanidm-clients).
- kanidm-unixd-clients
# Firefox is shipped as a per-user Flatpak, not an RPM.
exclude-packages:
- firefox
add-files:
# System-wide Flatpaks on first boot (Firefox), visible to every user.
- ["flatpak-system-firstboot", "/usr/libexec/flatpak-system-firstboot"]
- ["flatpak-system-firstboot.service", "/usr/lib/systemd/system/flatpak-system-firstboot.service"]
- ["50-flatpak-system-firstboot.preset", "/usr/lib/systemd/system-preset/50-flatpak-system-firstboot.preset"]
- ["flatpaks.list", "/usr/share/flatpak/flatpaks.list"]
# Kanidm client config + authselect profile sources.
- ["kanidm-config", "/etc/kanidm/config"]
- ["kanidm-unixd", "/etc/kanidm/unixd"]
- ["kanidm-system-auth", "/usr/share/fedora-remote/authselect/system-auth"]
- ["kanidm-password-auth", "/usr/share/fedora-remote/authselect/password-auth"]
- ["kanidm-nsswitch.conf", "/usr/share/fedora-remote/authselect/nsswitch.conf"]
- ["50-kanidm.preset", "/usr/lib/systemd/system-preset/50-kanidm.preset"]
- ["kanidm-unixd.cil", "/usr/share/fedora-remote/kanidm-unixd.cil"]
- ["10-kanidm.conf", "/etc/ssh/sshd_config.d/10-kanidm.conf"]
# GNOME Remote Desktop first-boot configuration.
- ["grd-firstboot", "/usr/libexec/grd-firstboot"]
- ["grd-firstboot.service", "/usr/lib/systemd/system/grd-firstboot.service"]
- ["50-grd.preset", "/usr/lib/systemd/system-preset/50-grd.preset"]
postprocess:
# Kanidm PAM/nsswitch via an authselect custom profile (direct-file fallback).
- |
#!/usr/bin/env bash
set -xeuo pipefail
if ! (
set -euo pipefail
authselect create-profile kanidm -b local
install -m 0644 /usr/share/fedora-remote/authselect/system-auth \
/etc/authselect/custom/kanidm/system-auth
install -m 0644 /usr/share/fedora-remote/authselect/password-auth \
/etc/authselect/custom/kanidm/password-auth
install -m 0644 /usr/share/fedora-remote/authselect/nsswitch.conf \
/etc/authselect/custom/kanidm/nsswitch.conf
authselect select custom/kanidm --force --nobackup
); then
echo "authselect setup failed, installing PAM/nsswitch directly" >&2
rm -f /etc/pam.d/system-auth /etc/pam.d/password-auth /etc/nsswitch.conf
install -m 0644 /usr/share/fedora-remote/authselect/system-auth /etc/pam.d/system-auth
install -m 0644 /usr/share/fedora-remote/authselect/password-auth /etc/pam.d/password-auth
install -m 0644 /usr/share/fedora-remote/authselect/nsswitch.conf /etc/nsswitch.conf
fi
# uresourced's resource tuning breaks the GDM greeter's systemd user manager
# here: user@<uid>.service fails with "Failed to spawn executor" (result
# 'resources'), so the greeter never registers and RDP clients get a white
# screen. It is optional, so mask it.
- |
#!/usr/bin/env bash
set -xeuo pipefail
ln -sf /dev/null /etc/systemd/system/uresourced.service
# Kanidm SELinux policy (same approach as fedora-cosmic).
- |
#!/usr/bin/env bash
set -xeuo pipefail
semodule -n -i /usr/share/fedora-remote/kanidm-unixd.cil || true
for domain in sshd_t sshd_session_t sshd_auth_t chkpwd_t \
systemd_userdbd_t local_login_t xdm_t polkit_t accountsd_t \
unconfined_service_t; do
semanage permissive -a "${domain}" || true
done
# Make helper scripts executable.
- |
#!/usr/bin/env bash
set -xeuo pipefail
chmod 0755 /usr/libexec/flatpak-system-firstboot /usr/libexec/grd-firstboot
systemctl --user --global preset-all
@@ -0,0 +1 @@
enable flatpak-system-firstboot.service
+8
View File
@@ -0,0 +1,8 @@
# GNOME Remote Desktop headless remote login + Proxmox guest agent.
enable gdm.service
enable gnome-remote-desktop.service
enable grd-firstboot.service
enable qemu-guest-agent.service
# Headless host: SSH (Kanidm key auth via 10-kanidm.conf) is the other way in.
enable sshd.service
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Install the Flatpaks listed in /usr/share/flatpak/flatpaks.list into the
# system-wide installation on first boot, so every user (including Kanidm
# accounts) sees them.
set -euo pipefail
LIST="/usr/share/flatpak/flatpaks.list"
MARKER="/var/lib/flatpak/.system-firstboot-done"
[[ -f "${LIST}" ]] || exit 0
[[ -f "${MARKER}" ]] && exit 0
# Make Flathub available system-wide.
flatpak remote-add --system --if-not-exists flathub \
https://flathub.org/repo/flathub.flatpakrepo
mapfile -t apps < <(
sed -e 's/#.*//' -e 's/[[:space:]]//g' "${LIST}" | grep -v '^$' || true
)
if [[ ${#apps[@]} -gt 0 ]]; then
flatpak install --system --noninteractive --assumeyes "${apps[@]}"
fi
install -d "$(dirname "${MARKER}")"
touch "${MARKER}"
@@ -0,0 +1,14 @@
[Unit]
Description=Install system-wide Flatpak applications on first boot
Documentation=https://docs.flatpak.org/en/latest/flatpak-command-reference.html
ConditionPathExists=!/var/lib/flatpak/.system-firstboot-done
After=network-online.target flatpak-add-fedora-repos.service
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/libexec/flatpak-system-firstboot
[Install]
WantedBy=multi-user.target
+3
View File
@@ -0,0 +1,3 @@
# Flatpaks installed system-wide on first boot (visible to all users).
# foot is installed as an RPM (no Flathub build), so only Firefox here.
org.mozilla.firefox
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# One-time configuration of GNOME Remote Desktop (system / headless remote
# login). Runs on first boot because grdctl talks to the running daemon.
#
# Optional /etc/gnome-remote-desktop/rdp.env:
# GRD_SYSTEM_USER=rdp
# GRD_SYSTEM_PASSWORD=...
# If unset, remote login is enabled but no greeter credential is configured
# (set one with: printf '%s\n%s\n' USER PASS | grdctl --system rdp set-credentials).
set -euo pipefail
GRD_USER=gnome-remote-desktop
STATE="/var/lib/${GRD_USER}"
TLS_DIR="${STATE}/.local/share/gnome-remote-desktop"
MARKER="${STATE}/.configured"
[[ -f "${MARKER}" ]] && exit 0
install -d -o "${GRD_USER}" -g "${GRD_USER}" "${TLS_DIR}"
if [[ ! -f "${TLS_DIR}/tls.key" ]]; then
sudo -u "${GRD_USER}" openssl req -new -newkey rsa:4096 -days 720 -nodes -x509 \
-subj "/CN=${GRD_CERT_CN:-fedora-remote}" \
-out "${TLS_DIR}/tls.crt" -keyout "${TLS_DIR}/tls.key"
fi
grdctl --system rdp set-tls-key "${TLS_DIR}/tls.key"
grdctl --system rdp set-tls-cert "${TLS_DIR}/tls.crt"
# Pass username/password as arguments: grdctl's interactive prompt reads from a
# controlling terminal, which a system service does not have, so piping them in
# silently sets nothing (GRD then logs "Credentials are not set, denying client").
if [[ -n "${GRD_SYSTEM_PASSWORD:-}" ]]; then
grdctl --system rdp set-credentials \
"${GRD_SYSTEM_USER:-rdp}" "${GRD_SYSTEM_PASSWORD}"
fi
grdctl --system rdp enable
systemctl restart gnome-remote-desktop.service || true
# Open the RDP port in firewalld (best effort; firewalld may not be installed).
if command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-service=rdp >/dev/null 2>&1 || true
firewall-cmd --reload >/dev/null 2>&1 || true
fi
touch "${MARKER}"
@@ -0,0 +1,15 @@
[Unit]
Description=Configure GNOME Remote Desktop on first boot
Documentation=https://github.com/GNOME/gnome-remote-desktop/blob/main/docs/configuration.md
After=network-online.target gnome-remote-desktop.service
Wants=network-online.target
ConditionPathExists=!/var/lib/gnome-remote-desktop/.configured
[Service]
Type=oneshot
EnvironmentFile=-/etc/gnome-remote-desktop/rdp.env
ExecStart=/usr/libexec/grd-firstboot
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
+29
View File
@@ -0,0 +1,29 @@
# Minimal headless remote host: GNOME Remote Desktop (RDP over Wayland) with
# Kanidm Unix authentication. Built on the upstream base-atomic (no desktop)
# manifest, not the full GNOME/silverblue set.
metadata:
summary: Fedora headless remote host (GNOME Remote Desktop + Kanidm)
variables:
# Opt out of the upstream fedora.yaml so we don't pull the Firefox RPM.
distro: "fedora-remote"
variant: "remote"
# Keep it small: no weak/optional dependencies.
recommends: false
default_target: graphical.target
ref: fedora-remote/${releasever_ref}/${basearch}/remote
repos:
# Kanidm packages (OBS network:idm).
- network_idm
include:
- base-atomic.yaml
- custom.yaml
# Generated by build.sh.
- hardware-exclude.yaml
- kanidm-token.yaml
+46
View File
@@ -0,0 +1,46 @@
# Aggressive trim for a headless Proxmox VM. Almost all physical hardware is
# absent, so drop the firmware and non-virtio drivers for it. Keep
# qemu-guest-agent (Proxmox), the virtio stack (kernel / kernel-modules), and
# the Intel GPU bits below for the passthrough iGPU.
# Firmware for hardware this VM does not have (kept off virtio).
# intel-gpu-firmware is intentionally NOT listed: the host's UHD 630 is passed
# through (vfio-pci), so the guest's i915 needs the GuC/HuC blobs.
amd-gpu-firmware
amd-ucode-firmware
alsa-sof-firmware
atheros-firmware
brcmfmac-firmware
cirrus-audio-firmware
intel-audio-firmware
intel-lpmd
intel-vsc-firmware
iwlegacy-firmware
iwlwifi-dvm-firmware
iwlwifi-mvm-firmware
libertas-firmware
linux-firmware
mt7xxx-firmware
nvidia-gpu-firmware
nxpwireless-firmware
qcom-wwan-firmware
realtek-firmware
tiwilink-firmware
# Kept for the passthrough Intel iGPU (not for virtio): VA-API decode via iHD.
# iris (Mesa) and i915 (kernel-modules) already come from upstream common.yaml.
# Extra kernel modules are not needed in a VM
kernel-modules-extra
# x86 platform tools not needed under QEMU/KVM
mcelog
microcode_ctl
thermald
# Guest agents for hypervisors other than the one in use
hyperv-daemons
open-vm-tools-desktop
spice-vdagent
spice-webdavd
virtualbox-guest-additions
+16
View File
@@ -0,0 +1,16 @@
# Fetch authorized SSH public keys from Kanidm (uploaded to the account).
# Name this 10-* so it is read before systemd-userdbd's AuthorizedKeysCommand
# drop-in, since sshd honours the first directive it sees.
PubkeyAuthentication yes
UsePAM yes
AuthorizedKeysCommand /usr/bin/kanidm_ssh_authorizedkeys %u
AuthorizedKeysCommandUser nobody
# Hardening: key-only auth through Kanidm. Make sure you have uploaded an SSH
# public key to your account before relying on this, or you can lock yourself
# out of SSH.
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
GSSAPIAuthentication no
KerberosAuthentication no
@@ -1,6 +1,6 @@
passwd: kanidm compat systemd
group: kanidm compat systemd
shadow: files
shadow: files systemd
hosts: files dns myhostname
services: files
netgroup: files
+33
View File
@@ -0,0 +1,33 @@
version = "2"
# Bind cached credentials to the local TPM when one is available.
hsm_type = "tpm_if_possible"
default_shell = "/bin/bash"
home_prefix = "/home/"
home_attr = "uuid"
home_alias = "name"
use_etc_skel = true
selinux = true
# Present the short login name ("misthios"), not the SPN, to NSS/PAM. With the
# default (spn) the passwd entry name is "misthios@auth.plabble.org", which
# confuses logins.
uid_attr_map = "name"
gid_attr_map = "name"
[kanidm]
# Members of this Kanidm POSIX group are allowed to log in via PAM.
pam_allowed_login_groups = ["unix_users"]
# A host almost always already has a local account at uid 1000. Kanidm ignores
# its own entry when a local account with the same name exists, so logins would
# use the local account (and the Kanidm password would fail). Let Kanidm take
# over these local accounts. Add more names as needed.
allow_local_account_override = ["misthios"]
# NOTE: kanidm-unixd runs with DynamicUser=yes and cannot read a root-only file,
# so the service account token (when seeded) is passed as a systemd credential
# via the build.sh-generated drop-in
# /usr/lib/systemd/system/kanidm-unixd.service.d/10-token.conf.
# Do not set service_account_token_path here.
+21
View File
@@ -0,0 +1,21 @@
; Kanidm ships no SELinux policy. Without these allows, nss/pam consumers
; (sshd, the display manager, systemd-userdbd, ...) are denied write access to
; the kanidm-unixd sockets in /run/kanidm-unixd (labeled var_run_t), so Kanidm
; users cannot be resolved or authenticated and logins fail (sshd reports
; "invalid user"). This is the allow set produced by:
; grep avc: /var/log/audit/audit.log | audit2allow
(allow accountsd_t var_run_t (sock_file (write)))
(allow auditd_t var_run_t (sock_file (write)))
(allow chkpwd_t var_run_t (sock_file (write)))
(allow local_login_t var_run_t (sock_file (write)))
(allow policykit_t var_run_t (sock_file (write)))
(allow ssh_keygen_t var_run_t (sock_file (write)))
(allow sshd_auth_t var_run_t (sock_file (write)))
(allow sshd_keygen_t var_run_t (sock_file (write)))
(allow sshd_session_t var_run_t (sock_file (write)))
(allow sshd_t var_run_t (sock_file (write)))
(allow systemd_bootc_generator_t var_run_t (sock_file (write)))
(allow systemd_selinux_autorelabel_generator_t var_run_t (sock_file (write)))
(allow systemd_userdbd_t var_run_t (sock_file (write)))
(allow xdm_t var_run_t (sock_file (write)))
(allow init_t unconfined_service_t (unix_stream_socket (connectto)))
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# Prune old container image versions (tags) for an image in the Gitea registry.
#
# Keeps the moving tag (e.g. "44") and the newest versioned tag per distro,
# plus the cosign signature tag (sha256-<digest>.sig) for each kept image.
#
# Usage:
# REGISTRY_TOKEN=<write:package> REGISTRY_USERNAME=<user> \
# ./prune-registry.sh [image] [owner] [distro...]
set -euo pipefail
IMAGE="${1:-fedora-cosmic}"; shift || true
OWNER="${1:-Misthios}"; shift || true
DISTROS=("$@"); [[ ${#DISTROS[@]} -gt 0 ]] || DISTROS=(44 rawhide)
GITEA_URL="${GITEA_URL:-https://git.plabble.org}"
REGISTRY_HOST="${REGISTRY_HOST:-$(printf '%s' "${GITEA_URL}" | sed -E 's#https?://##')}"
TOKEN="${REGISTRY_TOKEN:?REGISTRY_TOKEN (write:package) is required}"
AUTHFILE="${AUTHFILE:-${HOME}/.docker/config.json}"
API="${GITEA_URL%/}/api/v1/packages/${OWNER}/container/${IMAGE}"
# Registry paths are lowercase (the Gitea API owner is not).
IMAGE_REF="${REGISTRY_HOST}/$(printf '%s' "${OWNER}" | tr '[:upper:]' '[:lower:]')/${IMAGE}"
# Collect all versions (paginated).
versions=""
page=1
while :; do
page_json="$(curl -fsSL -H "Authorization: token ${TOKEN}" \
"${API}?limit=50&page=${page}")" || break
[[ "$(jq 'length' <<< "${page_json}")" -eq 0 ]] && break
versions+="$(jq -r '.[].version' <<< "${page_json}")"$'\n'
page=$((page + 1))
[[ "${page}" -gt 40 ]] && break
done
# Decide what to keep.
declare -A keep=()
for d in "${DISTROS[@]}"; do
keep["${d}"]=1
# A distro that has no versioned tags yet is fine; don't let grep's non-zero
# exit status abort the script under set -e/pipefail.
newest="$(grep -E "^${d}\.[0-9]+\.[0-9]+$" <<< "${versions}" \
| sort -t. -k2,2n -k3,3n | tail -1 || true)"
[[ -n "${newest}" ]] && keep["${newest}"]=1
done
# Keep the cosign signature of each kept image tag. If no digest could be
# resolved, keep every signature tag rather than risk deleting a needed one.
resolved=0
for tag in "${!keep[@]}"; do
[[ "${tag}" == sha256-* ]] && continue
digest="$(skopeo inspect --authfile "${AUTHFILE}" --format '{{.Digest}}' \
"docker://${IMAGE_REF}:${tag}" 2>/dev/null || true)"
if [[ -n "${digest}" ]]; then
keep["sha256-${digest#sha256:}.sig"]=1
resolved=1
fi
done
if [[ "${resolved}" -eq 0 ]]; then
echo "WARNING: could not resolve any image digest; keeping all signature tags"
while IFS= read -r v; do
[[ "${v}" == sha256-* ]] && keep["${v}"]=1
done <<< "${versions}"
fi
# Delete everything else.
while IFS= read -r v; do
[[ -z "${v}" ]] && continue
if [[ -n "${keep[${v}]:-}" ]]; then
echo "keep ${v}"
else
echo "remove ${v}"
curl -fsSL -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/${v}" >/dev/null || echo " (failed to delete ${v})"
fi
done <<< "${versions}"
echo "Pruned ${IMAGE} registry versions."
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Delete older Gitea releases (including their assets and git tags) for an
# image, keeping only the newest release per distro.
#
# Usage: RELEASE_TOKEN=<token-with-write:repository> ./prune-releases.sh [image]
#
# The token defaults to $RELEASE_TOKEN; GITEA_URL/GITEA_REPO match the workflow.
set -euo pipefail
IMAGE="${1:-fedora-cosmic}"
GITEA_URL="${GITEA_URL:-https://git.plabble.org}"
GITEA_REPO="${GITEA_REPO:-Misthios/bootc-images}"
TOKEN="${RELEASE_TOKEN:-${GITHUB_TOKEN:-}}"
[[ -n "${TOKEN}" ]] || { echo "RELEASE_TOKEN (write:repository) is required" >&2; exit 1; }
API="${GITEA_URL%/}/api/v1/repos/${GITEA_REPO}"
# Collect matching releases (API returns newest first).
all=""
page=1
while :; do
page_json="$(curl -fsSL -H "Authorization: token ${TOKEN}" \
"${API}/releases?limit=50&page=${page}")"
[[ "$(jq 'length' <<< "${page_json}")" -eq 0 ]] && break
all+="$(jq -r --arg p "${IMAGE}-" \
'.[] | select(.tag_name | startswith($p)) | "\(.tag_name)\t\(.id)"' \
<<< "${page_json}")"$'\n'
page=$((page + 1))
[[ "${page}" -gt 40 ]] && break
done
# Tag shape: "<image>-<distro>.<date>.<run>" -> prefix "<image>-<distro>".
# Keep the first (newest) release seen per prefix, delete the rest.
seen=""
while IFS=$'\t' read -r tag id; do
[[ -z "${tag}" ]] && continue
prefix="${tag%.*.*}"
if grep -qxF "${prefix}" <<< "${seen}"; then
echo "removing ${tag}"
curl -fsSL -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/releases/${id}" >/dev/null || true
curl -fsSL -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/tags/${tag}" >/dev/null || true
else
seen+="${prefix}"$'\n'
echo "keeping ${tag}"
fi
done <<< "${all}"
echo "Pruned ${IMAGE} releases (kept newest per distro)."
+26
View File
@@ -156,3 +156,29 @@ curl -fsSL -X POST \
"${API}/releases/${release_id}/assets?name=${ASSET_NAME}"
echo "Published release ${RELEASE_TAG}."
# --- prune older releases (and their assets/tags) for this image/distro -----
echo "Pruning older ${IMAGE} ${DISTRO} releases ..."
old_releases=""
page=1
while :; do
page_json="$(curl -fsSL -H "Authorization: token ${TOKEN}" \
"${API}/releases?limit=50&page=${page}")"
[[ "$(jq 'length' <<< "${page_json}")" -eq 0 ]] && break
old_releases+="$(jq -r --arg prefix "${IMAGE}-${DISTRO}." --arg keep "${release_id}" \
'.[] | select(.tag_name | startswith($prefix)) | select((.id | tostring) != $keep) | "\(.id)\t\(.tag_name)"' \
<<< "${page_json}")"$'\n'
page=$((page + 1))
[[ "${page}" -gt 20 ]] && break
done
while IFS=$'\t' read -r old_id old_tag; do
[[ -z "${old_id}" ]] && continue
echo " removing release ${old_tag}"
curl -fsSL -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/releases/${old_id}" >/dev/null || true
curl -fsSL -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/tags/${old_tag}" >/dev/null || true
done <<< "${old_releases}"
echo "Done."