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:
|
||||
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
|
||||
|
||||
@@ -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
@@ -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-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.
|
||||
- |
|
||||
|
||||
@@ -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
|
||||
group: kanidm compat systemd
|
||||
shadow: files
|
||||
shadow: files systemd
|
||||
hosts: files dns myhostname
|
||||
services: 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}"
|
||||
|
||||
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