Repository navigation
ci: add shellcheck, gitleaks and script smoke-test workflow - #3
Conversation
The repo is public with no CI, and both systemd units failed with status=203/EXEC (F-0024) after scripts moved into subdirectories, because ExecStart pointed at a path that no longer existed. Add .github/workflows/ci.yml running, on push and pull_request: shellcheck over every *.sh, a gitleaks secret scan, and a smoke job (bash -n plus scripts/test-smoke.sh). The smoke test guards the 203/EXEC failure class: it asserts each documented entrypoint exists, has a shebang, parses, and answers --help with exit 0 without sudo. To make entrypoints smoke-testable, add safe --help handling to all six shell entrypoints (no behavioural change otherwise), and clear the three pre-existing shellcheck info findings (array in nightly-launch; justified SC2086/SC2009 disables). Actions are pinned to full SHAs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Fleet-Item: F-0600 Co-Authored-By: Claude <noreply@anthropic.com>
fleet reviewreviewer: BLOCKI'm blocking this PR. The workflow, the Acceptance criteria:
Safety: this is a repo with no deploy, so there is nothing to roll out.
proofproofattempted
Approvals: 0/2 on 🤖 Generated with Claude Code |
The runner's shellcheck (ubuntu-24.04 apt, 0.9.x) flags SC2015 on `[[ -t 1 ]] && printf || true` in system-audit.sh, which local 0.11 does not, so the shellcheck job was red on the PR head. Rewrite it as an if, pin the runner image and print the shellcheck version. The smoke test now reads the symlink and ExecStart paths from the READMEs and fails (not warns) when a symlinked script is missing, has no shebang or is not committed as 100755: the actual 203/EXEC causes behind F-0024. A self-test proves it goes red on each failure mode. Push CI runs only on main to avoid duplicate runs, and actionlint lints the workflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Fleet-Item: F-0600 Co-Authored-By: Claude <noreply@anthropic.com>
fleet implementer, round 2Round 2 of F-0600, addressing all three review findings. Tests
🤖 Generated with Claude Code |
fleet reviewreviewer: PASSI'm passing this PR. The new head (bb6fa48) fixes the blocker from round 1 and both of my earlier minor findings. All four CI jobs are green on the pull_request run 36309575488 (merge commit dc913d8 = bb6fa48 into main): shellcheck, actionlint, gitleaks and the script smoke test. Acceptance criteria (assignment and issue #2):
Root cause: the item asks for CI. The 203/EXEC cause (a script moved away from the path behind the unit's Scope: minimal. Beyond the CI files, the changes are Safety: nothing to deploy (env: repo). Check I could not run: running
proofproofattempted
adversary: BLOCKBlocking. The CI workflow and the
proofattempted
Approvals: 1/2 on 🤖 Generated with Claude Code |
The adversary showed the smoke test stayed green on the exact F-0024 configuration: an ExecStart pointing straight at a moved repo path (%h/dev/fedora-workstation-scripts/...) was never parsed, and an ExecStart with no mode argument (main() exits 1 with usage) was never checked. test-smoke.sh now parses whole ExecStart lines and the unit they belong to. It fails on a missing or non-executable direct repo path, on an ExecStart that runs nothing from the repo, on a missing or unknown mode (declared with `# smoke-modes:` in the script and cross-checked against its case branches), on %i in a unit that is not a template, on a timer instance that is not a mode, and on a user unit that runs a need_root script without sudo. It also parses `ln -sf`, never runs a script that has no --help branch, and adds a timeout. The new check caught a real doc bug: fedora-maintenance README Option A passed %i in a non-template user unit, so the mode was empty (the F-0024 exit 1). It now passes `daily`. The self-test goes from 5 to 14 cases, one per failure mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Fleet-Item: F-0600 Co-Authored-By: Claude <noreply@anthropic.com>
fleet implementer, round 3Round 3 of F-0600 fixes the adversary's block: the regression test now catches every recorded F-0024 failure mode. The new check found a real documentation bug. Option A in Review findings also fixed:
Left out: the Node 20 checkout nit. I couldn't look up a v5 SHA ( Not run locally: Tests
🤖 Generated with Claude Code |
fleet reviewreviewer: PASSPassing round-3 head 80b7952. It fixes the adversary round-1 major block: the smoke test now catches both recorded F-0024 failure modes, a direct-repo-path ExecStart and a missing mode argument. All acceptance criteria met, CI green, scope tight, safe to deploy since env is repo/CI only. adversary: PASSPass. I couldn't break head 80b7952. It closes the major finding from the last adversary round. The regression test now covers all three F-0024 failure modes recorded in state/items/F-0024.yaml:
The live host unit (a user unit with Other checks:
Not verified:
Remaining gaps: two parser nits, both about README layouts the repo doesn't use today. Nothing currently in the repo slips through.
proofattempted
security: PASSPass on head 80b7952. The PR adds no exploitable issue and doesn't weaken any test, gate or scanner; it adds gates where there were none. CI workflow:
proofattempted
Approvals: 3/3 on 🤖 Generated with Claude Code |
Closes #2
Problem
fedora-workstation-scriptsis a public repo with no CI (issue #2 / F-0600). It also has no guard against the failure that took down its automation: thefedora-maintenanceandsystem-healthsystemd units failed withstatus=203/EXEC(F-0024) after the scripts were reorganised into per-tool subdirectories, because each unit'sExecStart=still pointed at the old flat path.203/EXECmeans "the executable could not be run" — file missing, no exec bit, or no valid interpreter.Root cause
No automation validated the scripts or caught a moved/renamed entrypoint. Nothing ran shellcheck, scanned for secrets (relevant for a public bug-bounty repo), or verified that each script was still runnable at its documented path.
Fix
Add
.github/workflows/ci.yml, running on push and pull_request with minimalcontents: readpermissions and all actions pinned to full commit SHAs (with# vX.Ycomments):*.sh(runner-provided shellcheck).gitleaks/gitleaks-actionscan for committed secrets (fetch-depth: 0).bash -non every script, thenscripts/test-smoke.sh.scripts/test-smoke.shis the F-0024 regression guard. For each documented entrypoint it asserts the script exists, starts with a shebang, parses (bash -n), and answers--helpwith exit 0 — reproducing the203/EXECfailure class (a moved/renamed/non-runnable entrypoint turns the job red). The executable bit is reported as a warning (CI cannot assert committed file mode; the READMEs documentchmod +x).To make the entrypoints smoke-testable without sudo and without side effects, each of the six shell entrypoints (
fedora-maintenance.sh,system-health-check.sh,soft-reboot.sh,system-audit.sh,nightly-launch.sh,install-tools.sh) gained a-h/--helphandler that prints usage and exits 0 before any privileged or destructive work (notablyinstall-tools.sh, which otherwise installs packages on any argument). Normal behaviour is otherwise unchanged.The three pre-existing shellcheck
infofindings are cleared so the job is green at default severity:nightly-launch.shnow uses an argument array (EXTRA_ARGS=("$@")), and the two intentional cases carry justified inline disables —SC2086ondnf -y remove $old_kernels(word splitting of a package list is required) andSC2009onps aux | grep(needs full command-line text to match attack patterns;pgrepmatches process names only).Tests
shellcheck <all 7 scripts>→ exit 0, no findings.bash -n <all 7 scripts>→ pass.gitleaks dir .→ no leaks found (~272 KB scanned).bash scripts/test-smoke.sh→ not run in sandbox (arbitrarybash <script>is refused by the fleet allowlist); it runs in the CI smoke job and is re-run byfleet gate. Every--helppath was traced by hand and exits 0 without root.actionlint/yamllint→ not installed in sandbox; the workflow YAML was reviewed manually and runs in CI.Risk / rollout
Low. Additive CI plus non-behavioural
--helphandlers and shellcheck-clean tweaks; no production runtime is touched.gitleaks/gitleaks-actionis free for public repos on personal accounts (aGITLEAKS_LICENSEis only required for GitHub organizations), which matchesMRegra/fedora-workstation-scripts.Follow-ups (not in this PR)
ExecStartpaths live in the user's~/.config, not in this repo); this PR only guards against the failure class recurring in the repo.orchestrator.py, etc.) are out of scope for this shell-focused slice — a follow-up could addruff/py_compileand a Python smoke check.git update-index --chmod=+x) would let the smoke test promote the exec-bit warning to a hard failure.🤖 Generated with Claude Code
Fleet item F-0600 · owner maya · risk low
Tests run by the implementer
shellcheck scripts/test-smoke.sh + all 6 entrypoints → exit 0, no findingsbash -n on all 7 scripts → passgitleaks dir . --no-banner → no leaks found (~272 KB scanned)bash scripts/test-smoke.sh → refused by allowlist (arbitrary bash <script>); runs in CI smoke job, --help paths traced by handactionlint / yamllint → not installed in sandbox; workflow YAML reviewed manually, runs in CI🤖 Generated with Claude Code