Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a643c658d | ||
|
|
3a1d80ad65 | ||
|
|
137091b402 | ||
|
|
172948822a | ||
|
|
f2d7ad24ec | ||
|
|
c1bf97c9c2 | ||
|
|
2e5a4fd707 | ||
|
|
357bf16479 | ||
|
|
40f7aef501 | ||
|
|
8e85980468 | ||
|
|
fdc1441c32 | ||
|
|
5bea16bc01 | ||
|
|
6d70a1bfb9 | ||
|
|
6822bc3061 | ||
|
|
0e4ea86f22 | ||
|
|
b9fe183da1 | ||
|
|
e4251f4d78 | ||
|
|
edfa563fd7 | ||
|
|
a789cb6669 | ||
|
|
380d550351 | ||
|
|
f6205eb982 | ||
|
|
84f2309fba | ||
|
|
f00f4340b7 | ||
|
|
a5c6170ac0 |
@@ -7,17 +7,48 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 4 * * *"
|
# Weekly, Mondays 04:00 UTC.
|
||||||
|
- cron: "0 4 * * 1"
|
||||||
|
|
||||||
jobs:
|
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 }})
|
name: ${{ matrix.image }} (${{ matrix.distro }})
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.cosmic == 'true'
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
# To build another image, add images/<name>/{manifest.yaml,build.conf}
|
|
||||||
# and a matching matrix entry here.
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- image: fedora-cosmic
|
- image: fedora-cosmic
|
||||||
@@ -41,7 +72,7 @@ jobs:
|
|||||||
GITEA_URL: https://git.plabble.org
|
GITEA_URL: https://git.plabble.org
|
||||||
GITEA_REPO: Misthios/bootc-images
|
GITEA_REPO: Misthios/bootc-images
|
||||||
|
|
||||||
steps:
|
steps: &build_steps
|
||||||
- name: Install build tools
|
- name: Install build tools
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
@@ -118,3 +149,60 @@ jobs:
|
|||||||
buildid="$(cat .buildid)"
|
buildid="$(cat .buildid)"
|
||||||
pkgs="build/${IMAGE}-${DISTRO}-${ARCH}/packages-current.txt"
|
pkgs="build/${IMAGE}-${DISTRO}-${ARCH}/packages-current.txt"
|
||||||
./release.sh "${IMAGE}" "${DISTRO}" "${buildid}" "${pkgs}"
|
./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
|
||||||
|
|||||||
@@ -89,14 +89,16 @@ gpgcheck=0
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# Drop packages listed in a blocklist from the cloned upstream package
|
# Drop packages listed in a blocklist from the cloned upstream manifests.
|
||||||
# manifests. rpm-ostree refuses (fatally) to exclude a package that an included
|
# rpm-ostree refuses (fatally) to exclude a package that an included manifest
|
||||||
# manifest declares, so remove those lines at the source instead.
|
# 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() {
|
trim_upstream_packages() {
|
||||||
local root="$1"
|
local root="$1"
|
||||||
local blocklist="$2"
|
local blocklist="$2"
|
||||||
local f
|
local f
|
||||||
for f in "${root}"/packages/*.yaml; do
|
for f in "${root}"/*.yaml "${root}"/packages/*.yaml; do
|
||||||
[[ -f "${f}" ]] || continue
|
[[ -f "${f}" ]] || continue
|
||||||
awk '
|
awk '
|
||||||
NR == FNR {
|
NR == FNR {
|
||||||
@@ -111,7 +113,28 @@ trim_upstream_packages() {
|
|||||||
print
|
print
|
||||||
}
|
}
|
||||||
' "${blocklist}" "${f}" > "${f}.tmp"
|
' "${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
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,10 +160,18 @@ seed_kanidm_token() {
|
|||||||
if [[ -n "${KANIDM_UNIXD_TOKEN:-}" ]]; then
|
if [[ -n "${KANIDM_UNIXD_TOKEN:-}" ]]; then
|
||||||
printf '%s\n' "${KANIDM_UNIXD_TOKEN}" > "${dir}/kanidm-unixd-token"
|
printf '%s\n' "${KANIDM_UNIXD_TOKEN}" > "${dir}/kanidm-unixd-token"
|
||||||
chmod 0600 "${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'
|
cat > "${out}" <<'EOF'
|
||||||
# Generated by build.sh from the KANIDM_UNIXD_TOKEN secret.
|
# Generated by build.sh from the KANIDM_UNIXD_TOKEN secret.
|
||||||
add-files:
|
add-files:
|
||||||
- ["kanidm-unixd-token", "/etc/kanidm/unixd_token"]
|
- ["kanidm-unixd-token", "/etc/kanidm/unixd_token"]
|
||||||
|
- ["kanidm-unixd-token.conf", "/usr/lib/systemd/system/kanidm-unixd.service.d/10-token.conf"]
|
||||||
EOF
|
EOF
|
||||||
else
|
else
|
||||||
cat > "${out}" <<'EOF'
|
cat > "${out}" <<'EOF'
|
||||||
@@ -171,6 +202,16 @@ case "${BUILD_MODE}" in
|
|||||||
if [[ -d "${IMAGE_DIR}/files" ]]; then
|
if [[ -d "${IMAGE_DIR}/files" ]]; then
|
||||||
cp -a "${IMAGE_DIR}/files/." "${BUILD_DIR}/upstream/"
|
cp -a "${IMAGE_DIR}/files/." "${BUILD_DIR}/upstream/"
|
||||||
fi
|
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
|
if [[ "${TOKEN2}" == "1" ]]; then
|
||||||
setup_token2_repo "${BUILD_DIR}/upstream"
|
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
|
if compgen -G "${local_dir}/repos/${DISTRO}/*.repo" >/dev/null; then
|
||||||
cp "${local_dir}"/repos/"${DISTRO}"/*.repo "${local_dir}/"
|
cp "${local_dir}"/repos/"${DISTRO}"/*.repo "${local_dir}/"
|
||||||
fi
|
fi
|
||||||
|
if [[ -d "${REPO_ROOT}/images/shared/files" ]]; then
|
||||||
|
cp -a "${REPO_ROOT}/images/shared/files/." "${local_dir}/"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "${TOKEN2}" == "1" ]]; then
|
if [[ "${TOKEN2}" == "1" ]]; then
|
||||||
setup_token2_repo "${local_dir}"
|
setup_token2_repo "${local_dir}"
|
||||||
|
|||||||
Executable
+24
@@ -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}"
|
||||||
@@ -46,6 +46,8 @@ add-files:
|
|||||||
- ["kanidm-password-auth", "/usr/share/fedora-cosmic/authselect/password-auth"]
|
- ["kanidm-password-auth", "/usr/share/fedora-cosmic/authselect/password-auth"]
|
||||||
- ["kanidm-nsswitch.conf", "/usr/share/fedora-cosmic/authselect/nsswitch.conf"]
|
- ["kanidm-nsswitch.conf", "/usr/share/fedora-cosmic/authselect/nsswitch.conf"]
|
||||||
- ["50-kanidm.preset", "/usr/lib/systemd/system-preset/50-kanidm.preset"]
|
- ["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:
|
postprocess:
|
||||||
# The upstream fedora.yaml removes the Google Chrome repo from the Fedora
|
# 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
|
install -m 0644 /usr/share/fedora-cosmic/authselect/nsswitch.conf /etc/nsswitch.conf
|
||||||
fi
|
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
|
#!/usr/bin/env bash
|
||||||
set -xeuo pipefail
|
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.
|
# Lock down the Kanidm service account token if it was seeded at build time.
|
||||||
- |
|
- |
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Executable
+47
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
-1
@@ -1,6 +1,6 @@
|
|||||||
passwd: kanidm compat systemd
|
passwd: kanidm compat systemd
|
||||||
group: kanidm compat systemd
|
group: kanidm compat systemd
|
||||||
shadow: files
|
shadow: files systemd
|
||||||
hosts: files dns myhostname
|
hosts: files dns myhostname
|
||||||
services: files
|
services: files
|
||||||
netgroup: files
|
netgroup: files
|
||||||
@@ -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.
|
||||||
@@ -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)))
|
||||||
Executable
+78
@@ -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."
|
||||||
Executable
+50
@@ -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
@@ -156,3 +156,29 @@ curl -fsSL -X POST \
|
|||||||
"${API}/releases/${release_id}/assets?name=${ASSET_NAME}"
|
"${API}/releases/${release_id}/assets?name=${ASSET_NAME}"
|
||||||
|
|
||||||
echo "Published release ${RELEASE_TAG}."
|
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."
|
||||||
|
|||||||
Reference in New Issue
Block a user