Compare commits

..
9 Commits
Author SHA1 Message Date
xavierk f06424f3b8 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	README.md
#	scripts/xbps-publish.sh
2026-09-15 21:19:33 +05:30
xavierk fae72bb07b Document XBPS cache-bypass refresh 2026-09-15 19:53:50 +05:30
xavierk 9d22525403 Fix XBPS repository verification 2026-09-15 19:48:08 +05:30
xavierk a3e6cc3b3c Fix Void package acceptance gaps 2026-09-15 19:43:00 +05:30
xavierk 95c75badd5 Route TUI controls through polkit 2026-09-15 15:44:10 +05:30
xavierk 70dbae65fb Clarify TUI monitoring activation 2026-09-15 15:40:29 +05:30
xavierk d119a09b1f Fix Void package lifecycle on host 2026-09-15 15:30:51 +05:30
xavierk fca0724fb4 docs: add Void installation and operations guide 2026-09-15 11:00:02 +05:30
xavierk 1c3037c2f8 Implement independent format gates for release workflow (issue #86)
Add XBPS build and sign steps to CI workflow
Add XBPS publication as independent gate (requires manual trigger)
Track format availability in release notes
Update release-footer.md with XBPS install instructions
Add --available/--withheld arguments to extract_changelog.py
Attach XBPS artifacts to Gitea release
Clean up XBPS signing key material after use
2026-09-15 10:50:38 +05:30
12 changed files with 100 additions and 29 deletions
+5
View File
@@ -1,5 +1,10 @@
## Agent skills
## Commit messages
Do not add `Co-authored-by: CommandCodeBot <noreply@commandcode.ai>` or other
CommandCodeBot attribution trailers to commits.
### Issue tracker
Issues are tracked in Gitea using the authenticated `tea` CLI. See `docs/agents/issue-tracker.md`.
+11
View File
@@ -95,6 +95,7 @@ install: check-python check-smartctl dist/fenris-*.whl
@echo "=== Installing runit service files (dormant — not enabled) ==="
@sudo install -d -m 0755 /etc/sv/fenris-collect/log
@sudo install -d -o root -g fenris -m 2770 /var/log/fenris-collect
@sudo install -m 0755 units/runit/fenris-collect/run /etc/sv/fenris-collect/run
@sudo install -m 0755 units/runit/fenris-collect/log/run /etc/sv/fenris-collect/log/run
@sudo touch /etc/sv/fenris-collect/down
@@ -111,10 +112,14 @@ install: check-python check-smartctl dist/fenris-*.whl
@echo "$(LIBEXEC_DIR)/fenris-collect" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(UNIT_DIR)/fenris-collect.timer" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(UNIT_DIR)/fenris-collect.service" | sudo tee -a $(MANIFEST) > /dev/null
@echo "/etc/sv/fenris-collect/run" | sudo tee -a $(MANIFEST) > /dev/null
@echo "/etc/sv/fenris-collect/log/run" | sudo tee -a $(MANIFEST) > /dev/null
@echo "/etc/sv/fenris-collect/down" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(POLKIT_DIR)/com.bongbetic.fenris.monitor.policy" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(VENV_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(DATA_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(CONF_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
@echo "/var/log/fenris-collect" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(MANIFEST)" | sudo tee -a $(MANIFEST) > /dev/null
@echo "=== Install complete ==="
@@ -150,6 +155,8 @@ upgrade: dist/fenris-*.whl
@sudo install -m 0644 units/fenris-collect.timer $(UNIT_DIR)/
@sudo install -m 0644 units/fenris-collect.service $(UNIT_DIR)/
@sudo install -d -m 0755 /etc/sv/fenris-collect/log
@sudo groupadd -f fenris
@sudo install -d -o root -g fenris -m 2770 /var/log/fenris-collect
@sudo install -m 0755 units/runit/fenris-collect/run /etc/sv/fenris-collect/run
@sudo install -m 0755 units/runit/fenris-collect/log/run /etc/sv/fenris-collect/log/run
@sudo install -m 0644 polkit/com.bongbetic.fenris.monitor.policy $(POLKIT_DIR)/
@@ -167,10 +174,14 @@ upgrade: dist/fenris-*.whl
@echo "$(LIBEXEC_DIR)/fenris-collect" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(UNIT_DIR)/fenris-collect.timer" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(UNIT_DIR)/fenris-collect.service" | sudo tee -a $(MANIFEST) > /dev/null
@echo "/etc/sv/fenris-collect/run" | sudo tee -a $(MANIFEST) > /dev/null
@echo "/etc/sv/fenris-collect/log/run" | sudo tee -a $(MANIFEST) > /dev/null
@echo "/etc/sv/fenris-collect/down" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(POLKIT_DIR)/com.bongbetic.fenris.monitor.policy" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(VENV_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(DATA_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(CONF_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
@echo "/var/log/fenris-collect" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(MANIFEST)" | sudo tee -a $(MANIFEST) > /dev/null
@echo "=== Restarting timer only if contents changed and active (IN-5) ==="
+8 -6
View File
@@ -68,16 +68,15 @@ TLS).
### Void Linux (XBPS)
Void x86_64 with glibc and runit is the native target. Its XBPS channel is
withheld until the host-acceptance gate passes; do not install proof artifacts
from it. Once a Fenris Release lists XBPS as available, add the permanent
signed repository, refresh its metadata, and install the package:
Void x86_64 with glibc and runit is the native target. Its signed XBPS channel
is available from the permanent repository below. Add it, refresh its
metadata, and install the released package:
```bash
sudo install -d -m 0755 /etc/xbps.d
echo 'repository=https://git.bongbetic.com/xavierk/Fenris-xbps/raw/branch/stable/x86_64' \
| sudo tee /etc/xbps.d/fenris.conf
sudo xbps-install -S fenris
sudo xbps-install -M -S fenris
```
XBPS requires remote repositories to be signed. On the first refresh it
@@ -89,9 +88,12 @@ available at
For later updates, always refresh first so XBPS fetches the current index:
```bash
sudo xbps-install -Syu
sudo xbps-install -M -Syu
```
The `-M` flag bypasses XBPS's on-disk repodata cache. It is required when
checking for a newly published package through Gitea's cached raw-file URL.
The runit service remains dormant after installation. `fenris monitor resume`
creates `/var/service/fenris-collect`; pause removes that link and records a
deliberate disable in the observation history.
+4 -4
View File
@@ -1,14 +1,14 @@
# 8. Native Void Linux support and XBPS delivery
Status: Accepted scope and hosting direction; implementation and acceptance proof pending.
Status: Accepted — implementation and host acceptance completed; evidence is recorded in [issue #87](https://git.bongbetic.com/xavierk/Fenris/issues/87).
Fenris will support Void Linux natively with runit and full application feature parity, while retaining its existing Debian/RPM and systemd support. This extends the platform boundary in [ADR 0003](0003-service-lifecycle-and-sanctioned-toggle.md) and the delivery scope in [ADR 0007](0007-package-delivery-amends-0004.md): requiring Void users to replace their init system would not meet the native-support goal.
Delivery will include a Fenris-maintained, signed XBPS repository that users configure once for subsequent installation and updates through XBPS, plus versioned release artifacts and notes on Gitea. All downloads must be served directly by Gitea itself; a separate static HTTP repository, even alongside Gitea, does not satisfy this requirement.
Use a dedicated public Gitea repository, provisionally `xavierk/Fenris-xbps`, with a permanent `stable` branch. Its raw-file URL serves the XBPS index, versioned packages, and package signatures as ordinary Git blobs without LFS. Keeping binaries in a separate repository avoids increasing application source-clone size. This accepts growth in distribution-repository Git history in exchange for publishing index and artifacts together through one branch update, without the generic registry's delete-and-upload index replacement gap. The proposed repository has not yet been created.
Use the dedicated public Gitea repository `xavierk/Fenris-xbps` with its permanent `stable` branch. Its raw-file URL serves the XBPS index, versioned packages, and package signatures as ordinary Git blobs without LFS. Keeping binaries in a separate repository avoids increasing application source-clone size. This accepts growth in distribution-repository Git history in exchange for publishing index and artifacts together through one branch update, without the generic registry's delete-and-upload index replacement gap.
Serialize publication, commit the signed index and its new artifacts together, and retain older versioned artifacts in the current tree so clients with cached older indexes can still download them. Native XBPS installation and update tests against the actual endpoint are required before release validation. Verify binary delivery limits and index freshness: the inspected Gitea raw endpoint advertised six-hour HTTP caching, so immediate update visibility has not been established.
Serialize publication, commit the signed index and its new artifacts together, and retain older versioned artifacts in the current tree so clients with cached older indexes can still download them. Native XBPS installation and update tests against the actual endpoint are required before release validation. The first release acceptance recorded in issue #87 verified direct artifact delivery, signed metadata, retained packages, and immediate discovery after an explicit memory-synchronized refresh. The Gitea raw endpoint advertises six-hour HTTP caching; users should use `xbps-install -M -S` when looking for updates so XBPS bypasses its on-disk repodata cache.
Immediate availability is required: after successful XBPS publication, an explicit repository refresh against the permanent URL must discover the newly published version without a cache-expiry wait or a URL change. This does not promise automatic installation on client machines. Acceptance must exercise a client that fetched the previous index before publication and verify that refresh retrieves the new index and its signed package afterward. Resolve and document actual client and intermediary cache behavior; if the selected Gitea route cannot meet this requirement, hold XBPS publication and revisit its delivery mechanics rather than silently accepting delayed availability.
@@ -18,4 +18,4 @@ The first supported Void target is x86_64 with glibc, matching the inspected dev
Package formats have independent publication gates: publish each validated format, and hold only formats that have not passed release validation. A failure or pending validation in XBPS must not prevent a validated Debian or RPM package from shipping, and vice versa. Release notes must identify available formats and those still withheld; publication must not imply validation of a missing format.
After successful native acceptance testing, leave the released XBPS package installed on this machine and monitoring the selected NVMe drive. Preserve the observation history collected during testing. Coordinate the reboot test with the user so it can occur at a suitable interruption point.
After successful native acceptance testing, leave the released XBPS package installed on this machine and monitoring the selected NVMe drive. Preserve the observation history collected during testing. Coordinate the reboot test with the user so it can occur at a suitable interruption point. Issue #87 records that this final state, including reboot persistence, was achieved for the first supported release.
+1 -1
View File
@@ -63,4 +63,4 @@ Musl, other architectures, additional init systems, official Void repository inc
## Further Notes
The design decisions are recorded in ADR 0008. Hosting feasibility is supported by Gitea routing/source inspection and an existing raw-file request, but the dedicated distribution repository and end-to-end XBPS proof do not yet exist. Current host inspection found Void x86_64/glibc with runit, polkit support and an NVMe controller; Fenris and smartmontools were absent. Verify these facts again before host changes.
The design decisions are recorded in ADR 0008. The dedicated distribution repository and end-to-end XBPS proof are complete; the host acceptance record is [issue #87](https://git.bongbetic.com/xavierk/Fenris/issues/87). The accepted target is Void x86_64/glibc with runit, with the released package installed and monitoring the selected NVMe drive after the coordinated reboot and lifecycle checks.
+5 -5
View File
@@ -68,8 +68,8 @@ Cache behavior:
- Conditional requests with old ETag return 200 (full content), not 304
xbps-install behavior:
- Always fetches fresh repodata with `-S` flag
- Respects ETag changes for immediate discovery
- `-M -S` fetches fresh repodata while bypassing the on-disk cache
- The ordinary `-S` path can reuse a cached repodata archive
- No 6-hour delay observed in practice
Binary size limits:
@@ -77,8 +77,9 @@ Binary size limits:
- Real packages expected to be <10MB (vendored pure-Python)
- Gitea serves any file size without LFS
**Caveat**: Clients using `xbps-install -Su` without `-S` may use cached repodata.
The `-S` flag forces a fresh fetch. Users should always use `-Syu` for updates.
**Caveat**: Clients using `xbps-install -Su` or `-Syu` without `-M` may use
cached repodata. Users should use `-M -Syu` for updates when immediate
publication visibility matters.
### 5. Publish index/artifacts together, preserve older artifacts, safe failure recovery
@@ -117,7 +118,6 @@ xavierk/Fenris-xbps (stable branch)
fenris-<version>_1.x86_64.xbps # Package archives
fenris-<version>_1.x86_64.xbps.sig2 # Package signatures
x86_64-repodata # Repository index (zstd-compressed tar)
x86_64-repodata.sig2 # Repository metadata signature
keys/
fenris-xbps-signing.pub # Public signing key
README.md
+1 -2
View File
@@ -69,8 +69,7 @@ print(f'Fenris migration: {n} step(s) applied') if n else None
touch /etc/sv/fenris-collect/down
fi
# Ensure log directory exists
mkdir -p /var/log/fenris-collect
chown fenris:fenris /var/log/fenris-collect 2>/dev/null || true
install -d -o root -g fenris -m 2770 /var/log/fenris-collect 2>/dev/null || true
fi
;;
abort-upgrade|abort-install|disappear)
+5 -2
View File
@@ -30,8 +30,7 @@ if [ "$1" -eq 1 ]; then
touch /etc/sv/fenris-collect/down
fi
# Ensure log directory exists
mkdir -p /var/log/fenris-collect
chown fenris:fenris /var/log/fenris-collect 2>/dev/null || true
install -d -o root -g fenris -m 2770 /var/log/fenris-collect 2>/dev/null || true
fi
elif [ "$1" -ge 2 ]; then
# Upgrade — snapshot, migration, init-system-aware reload
@@ -67,5 +66,9 @@ print(f'Fenris migration: {n} step(s) applied') if n else None
done
# Re-apply placement modes (store dir group access, issue #54)
systemd-tmpfiles --create || true
else
# runit: repair log access when upgrading from an older package
groupadd -f fenris
install -d -o root -g fenris -m 2770 /var/log/fenris-collect 2>/dev/null || true
fi
fi
+3 -4
View File
@@ -48,8 +48,8 @@ print(f'Fenris migration: {n} step(s) applied') if n else None
" 2>&1 || echo "Fenris: migration skipped (store not yet initialized)"
fi
# Ensure log directory exists on upgrade
mkdir -p /var/log/fenris-collect
chown fenris:fenris /var/log/fenris-collect 2>/dev/null || true
groupadd -f fenris
install -d -o root -g fenris -m 2770 /var/log/fenris-collect 2>/dev/null || true
else
# Fresh install — runit service setup
groupadd -f fenris
@@ -59,8 +59,7 @@ print(f'Fenris migration: {n} step(s) applied') if n else None
touch /etc/sv/fenris-collect/down
fi
# Ensure log directory exists
mkdir -p /var/log/fenris-collect
chown fenris:fenris /var/log/fenris-collect 2>/dev/null || true
install -d -o root -g fenris -m 2770 /var/log/fenris-collect 2>/dev/null || true
fi
;;
esac
+30 -4
View File
@@ -42,7 +42,7 @@ for arg in "$@"; do
echo " --publish Execute the full publication flow"
echo ""
echo "Environment:"
echo " XBPS_SIGNING_KEY Path to SSH RSA private key (default: ~/.ssh/id_rsa)"
echo " XBPS_SIGNING_KEY Path to SSH RSA private key (default: ~/.ssh/id_xbps)"
echo " SIGNED_BY Signature identity (default: Fenris Packaging <packaging@bongbetic.com>)"
exit 0
;;
@@ -98,7 +98,7 @@ XBPS_PATH="${SOURCE_DIR}/${XBPS_FILE}"
GIT_USER_NAME=$(git config user.name || true)
GIT_USER_EMAIL=$(git config user.email || true)
if [[ -z "$GIT_USER_NAME" || -z "$GIT_USER_EMAIL" ]]; then
if [[ -z "${GIT_USER_NAME}" || -z "${GIT_USER_EMAIL}" ]]; then
echo "ERROR: Configure git user.name and user.email in the source repository before publishing" >&2
exit 1
fi
@@ -154,8 +154,34 @@ echo ""
# ── Step 6: Verify publication ───────────────────────────────────────────
echo "--- Verify publication ---"
RAW_URL="${GITEA_URL}/${GITEA_OWNER}/${GITEA_REPO}/raw/branch/${GITEA_BRANCH}/${ARCH}/x86_64-repodata"
_run "curl -sI '${RAW_URL}' | head -5"
RAW_BASE="${GITEA_URL}/${GITEA_OWNER}/${GITEA_REPO}/raw/branch/${GITEA_BRANCH}/${ARCH}"
if $PUBLISH; then
# Compare every served byte with the artifact that was indexed and pushed.
# A successful HEAD request alone can still hide a stale or incomplete
# publication behind the raw endpoint's cache.
# Repository signatures are embedded in x86_64-repodata by xbps-rindex;
# only package signatures are separate .sig2 files.
for artifact in "${XBPS_FILE}" "${XBPS_FILE}.sig2" "x86_64-repodata"; do
case "${artifact}" in
"${XBPS_FILE}") local_path="${XBPS_PATH}" ;;
"${XBPS_FILE}.sig2") local_path="${XBPS_PATH}.sig2" ;;
*) local_path="${WORK_DIR}/${ARCH}/${artifact}" ;;
esac
downloaded="${WORK_DIR}/.${artifact}.download"
curl --fail --silent --show-error --location \
--output "${downloaded}" "${RAW_BASE}/${artifact}"
cmp -- "${local_path}" "${downloaded}"
rm -f "${downloaded}"
done
else
_run "curl --fail --silent --show-error --location --output ${WORK_DIR}/.${XBPS_FILE}.download ${RAW_BASE}/${XBPS_FILE}"
_run "cmp -- ${XBPS_PATH} ${WORK_DIR}/.${XBPS_FILE}.download"
_run "curl --fail --silent --show-error --location --output ${WORK_DIR}/.${XBPS_FILE}.sig2.download ${RAW_BASE}/${XBPS_FILE}.sig2"
_run "cmp -- ${XBPS_PATH}.sig2 ${WORK_DIR}/.${XBPS_FILE}.sig2.download"
_run "curl --fail --silent --show-error --location --output ${WORK_DIR}/.x86_64-repodata.download ${RAW_BASE}/x86_64-repodata"
_run "cmp -- ${WORK_DIR}/${ARCH}/x86_64-repodata ${WORK_DIR}/.x86_64-repodata.download"
_run "rm -f ${WORK_DIR}/.${XBPS_FILE}.download ${WORK_DIR}/.${XBPS_FILE}.sig2.download ${WORK_DIR}/.x86_64-repodata.download"
fi
echo ""
# ── Cleanup ──────────────────────────────────────────────────────────────
+23
View File
@@ -96,6 +96,29 @@ def _find_package(fmt: str) -> Path:
return candidate
def test_runit_logger_uses_accounts_shipped_by_package():
"""The runit logger must use the package's group-only identity.
The package declares a ``fenris`` group for store/log access, not a
``fenris`` service user. Starting the logger with ``fenris:fenris``
therefore leaves the diagnostics supervisor down on a real Void host;
Void's standard ``nobody`` account supplies the unprivileged uid.
"""
logger = (REPO_ROOT / "units" / "runit" / "fenris-collect" / "log" / "run").read_text()
sysusers = (REPO_ROOT / "packaging" / "sysusers.d" / "fenris.conf").read_text()
assert "g fenris -" in sysusers
assert "chpst -u nobody:fenris" in logger
def test_xbps_publisher_verifies_embedded_repository_signature():
"""Publication verification must match XBPS's repository layout."""
publisher = (REPO_ROOT / "scripts" / "xbps-publish.sh").read_text()
assert '"x86_64-repodata"' in publisher
assert '"x86_64-repodata.sig2"' not in publisher
# ---------------------------------------------------------------------------
# Matrix definitions
# ---------------------------------------------------------------------------
+4 -1
View File
@@ -5,5 +5,8 @@
# The logger writes to runit's log directory for diagnostics.
#
# Spec: §8.8 (actionable native diagnostics)
exec chpst -u fenris:fenris \
# The package creates the fenris group for shared diagnostics access; it does
# not create a service user. Use Void's standard unprivileged account while
# giving the logger the declared group identity.
exec chpst -u nobody:fenris \
svlogd -tt /var/log/fenris-collect/