ci: add GitLab CI pipeline and rootfs smoke tests
- tests/: offline tree validation plus rootfs smoke tests that assert the hardening guarantees (kptr_restrict, default-deny firewall, key-only ssh, no snapd/telemetry, btrfs tooling). - ci/: GitLab pipeline - validate, build matrix (server/workstation/ cloud), smoke tests, publish on tags.
This commit is contained in:
99
ci/.gitlab-ci.yml
Normal file
99
ci/.gitlab-ci.yml
Normal file
@@ -0,0 +1,99 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — GitLab CI pipeline
|
||||
#
|
||||
# Mirrors the local build flow (scripts/ + Makefile) in CI:
|
||||
# validate → build (matrix over editions) → test → publish
|
||||
#
|
||||
# The build image is Debian bookworm with the host deps installed, matching
|
||||
# what scripts/check-host-deps.sh expects locally.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
stages:
|
||||
- validate
|
||||
- build
|
||||
- test
|
||||
- publish
|
||||
|
||||
variables:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
VERSION: "0.1.0"
|
||||
|
||||
# ── validate ────────────────────────────────────────────────────────────────
|
||||
validate:
|
||||
stage: validate
|
||||
image: debian:bookworm
|
||||
script:
|
||||
- apt-get update -qq && apt-get install -y -qq make bash
|
||||
- make check
|
||||
|
||||
# ── build (one job per edition) ─────────────────────────────────────────────
|
||||
.build:
|
||||
stage: build
|
||||
image: debian:bookworm
|
||||
before_script:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y -qq debootstrap squashfs-tools grub2-common xorriso cpio curl git make bash
|
||||
script:
|
||||
- make iso-${EDITION}
|
||||
artifacts:
|
||||
name: "arcline-${EDITION}-${VERSION}"
|
||||
paths:
|
||||
- build/artifacts/
|
||||
expire_in: 2 weeks
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
when: never
|
||||
- when: always
|
||||
|
||||
build-server:
|
||||
extends: .build
|
||||
variables:
|
||||
EDITION: server
|
||||
|
||||
build-workstation:
|
||||
extends: .build
|
||||
variables:
|
||||
EDITION: workstation
|
||||
|
||||
build-cloud:
|
||||
extends: .build
|
||||
variables:
|
||||
EDITION: cloud
|
||||
|
||||
# ── test ────────────────────────────────────────────────────────────────────
|
||||
test:
|
||||
stage: test
|
||||
image: debian:bookworm
|
||||
needs: [build-server, build-workstation, build-cloud]
|
||||
before_script:
|
||||
- apt-get update -qq && apt-get install -y -qq bash make xz-utils
|
||||
script:
|
||||
# extract the rootfs artifacts so the smoke tests can inspect them
|
||||
- for f in build/artifacts/arcline-*.tar.xz; do
|
||||
[ -e "$f" ] || continue;
|
||||
e=$(basename "$f" | sed -E 's/arcline-([a-z]+)-.*/\1/');
|
||||
mkdir -p "build/rootfs/$e";
|
||||
tar -xJf "$f" -C "build/rootfs/$e";
|
||||
done
|
||||
- make test
|
||||
artifacts:
|
||||
reports:
|
||||
junit: build/logs/*.xml
|
||||
when: always
|
||||
|
||||
# ── publish (tagged releases only) ──────────────────────────────────────────
|
||||
publish:
|
||||
stage: publish
|
||||
image: alpine:latest
|
||||
needs: [test]
|
||||
before_script:
|
||||
- apk add --no-cache curl jq
|
||||
script:
|
||||
- echo "Publishing release $VERSION (edit this step to push to your release server / GitLab Packages)"
|
||||
- ls -la build/artifacts/ || true
|
||||
artifacts:
|
||||
name: "arcline-${VERSION}"
|
||||
paths:
|
||||
- build/artifacts/
|
||||
expire_in: 1 year
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG'
|
||||
42
tests/run-tests.sh
Executable file
42
tests/run-tests.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — test runner
|
||||
#
|
||||
# tests/run-tests.sh # validate tree + test built rootfs(s)
|
||||
# tests/run-tests.sh server # test a specific edition
|
||||
#
|
||||
# Verifies:
|
||||
# 1. the whole build tree is syntactically valid (validate.sh)
|
||||
# 2. any built rootfs in build/rootfs/<edition> satisfies the hardening
|
||||
# and packaging guarantees (tests/smoke/verify-rootfs.sh)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/../scripts/common.sh"
|
||||
|
||||
fail=0
|
||||
|
||||
log "step 1/2 — tree validation"
|
||||
"$ROOT/scripts/validate.sh" || fail=1
|
||||
|
||||
log "step 2/2 — rootfs smoke tests"
|
||||
editions=("${EDITIONS[@]}")
|
||||
[[ $# -gt 0 ]] && editions=("$@")
|
||||
|
||||
for e in "${editions[@]}"; do
|
||||
rootfs="$ROOTFS_DIR/$e"
|
||||
if [[ ! -d "$rootfs" ]]; then
|
||||
warn "no rootfs for '$e' at $rootfs — skipping smoke test"
|
||||
continue
|
||||
fi
|
||||
log "smoke testing rootfs: $e"
|
||||
if ! "$ROOT/tests/smoke/verify-rootfs.sh" "$rootfs"; then
|
||||
warn "smoke test failed for '$e'"
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $fail -eq 0 ]]; then
|
||||
log "tests passed ✓"
|
||||
else
|
||||
die "tests failed"
|
||||
fi
|
||||
78
tests/smoke/verify-rootfs.sh
Executable file
78
tests/smoke/verify-rootfs.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Arcline OS — rootfs smoke test
|
||||
#
|
||||
# tests/smoke/verify-rootfs.sh <rootfs>
|
||||
#
|
||||
# Asserts the packaging + hardening guarantees of a built rootfs. Runs
|
||||
# read-only against an extracted rootfs tree (no chroot needed). A failed
|
||||
# assertion exits nonzero with a message.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
ROOTFS="${1:?usage: verify-rootfs.sh <rootfs>}"
|
||||
[[ -d "$ROOTFS" ]] || { echo "FAIL: rootfs not found: $ROOTFS"; exit 1; }
|
||||
|
||||
pass=0; fail=0
|
||||
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; pass=$((pass+1)); }
|
||||
bad() { printf ' \033[31m✗\033[0m %s\n' "$*"; fail=$((fail+1)); }
|
||||
|
||||
have_file() { [[ -f "$ROOTFS/$1" ]]; }
|
||||
have_dir() { [[ -d "$ROOTFS/$1" ]]; }
|
||||
|
||||
echo "─ hardening ───────────────────────────────────────────"
|
||||
if have_file "etc/sysctl.d/10-arcline-hardening.conf"; then
|
||||
grep -q 'kernel.kptr_restrict=2' "$ROOTFS/etc/sysctl.d/10-arcline-hardening.conf" \
|
||||
&& ok "sysctl hardening present (kptr_restrict=2)" || bad "sysctl file missing kptr_restrict=2"
|
||||
else
|
||||
bad "missing etc/sysctl.d/10-arcline-hardening.conf"
|
||||
fi
|
||||
|
||||
if have_file "etc/nftables.conf"; then
|
||||
grep -q 'policy drop' "$ROOTFS/etc/nftables.conf" \
|
||||
&& ok "nftables default-deny present" || bad "nftables policy is not drop"
|
||||
else
|
||||
bad "missing etc/nftables.conf"
|
||||
fi
|
||||
|
||||
if have_file "etc/ssh/sshd_config.d/10-arcline-hardening.conf"; then
|
||||
grep -q 'PasswordAuthentication no' "$ROOTFS/etc/ssh/sshd_config.d/10-arcline-hardening.conf" \
|
||||
&& ok "ssh hardening present" || bad "ssh password auth not disabled"
|
||||
else
|
||||
bad "missing ssh hardening drop-in"
|
||||
fi
|
||||
|
||||
have_file "etc/systemd/journald.conf.d/10-arcline.conf" \
|
||||
&& ok "journald persistent config present" || bad "missing journald config"
|
||||
have_file "etc/modprobe.d/arcline-hardening.conf" \
|
||||
&& ok "module blacklist present" || bad "missing modprobe blacklist"
|
||||
|
||||
echo "─ telemetry (must be absent) ──────────────────────────"
|
||||
if have_dir "var/lib/ubuntu-report" || have_dir "var/lib/popularity-contest"; then
|
||||
bad "telemetry package artifacts found (ubuntu-report / popularity-contest)"
|
||||
else
|
||||
ok "no distro telemetry artifacts"
|
||||
fi
|
||||
if [[ -d "$ROOTFS/usr/lib/snapd" ]]; then
|
||||
bad "snapd found (Arcline is snap-free)"
|
||||
else
|
||||
ok "no snapd"
|
||||
fi
|
||||
|
||||
echo "─ core subsystems ─────────────────────────────────────"
|
||||
if have_file "usr/local/sbin/arcline-snapshot"; then
|
||||
ok "btrfs snapshot tooling present"
|
||||
else
|
||||
bad "missing arcline-snapshot"
|
||||
fi
|
||||
have_file "usr/local/sbin/arcline-rollback" \
|
||||
&& ok "btrfs rollback tooling present" || bad "missing arcline-rollback"
|
||||
have_file "usr/lib/systemd/system/arcline-snapshot.timer" \
|
||||
&& ok "snapshot timer unit present" || bad "missing arcline-snapshot.timer"
|
||||
|
||||
grep -qi 'arcline' "$ROOTFS/etc/arcline-release" 2>/dev/null \
|
||||
&& ok "arcline-release present" || bad "missing /etc/arcline-release"
|
||||
|
||||
echo "─ summary ─────────────────────────────────────────────"
|
||||
echo " $pass passed, $fail failed"
|
||||
[[ $fail -eq 0 ]]
|
||||
Reference in New Issue
Block a user