Skip to content

ci(workstation): strengthen keyboard policy assertions #310

ci(workstation): strengthen keyboard policy assertions

ci(workstation): strengthen keyboard policy assertions #310

name: workstation-scripts
on:
pull_request:
paths:
- 'profiles/linux-dev/workstation-v0/**'
- 'docs/workstation/**'
- '.github/workflows/workstation-scripts.yml'
push:
branches:
- main
paths:
- 'profiles/linux-dev/workstation-v0/**'
- 'docs/workstation/**'
- '.github/workflows/workstation-scripts.yml'
jobs:
shellcheck-and-syntax:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Shellcheck (workstation-v0)
run: |
set -euo pipefail
# Policy: ShellCheck warnings are treated as failures for workstation-v0 scripts.
files=$(git ls-files 'profiles/linux-dev/workstation-v0/**/*.sh' \
'profiles/linux-dev/workstation-v0/**/sourceos' \
'profiles/linux-dev/workstation-v0/**/install-sourceos-cli.sh' || true)
if [ -z "$files" ]; then
echo "No scripts found."
exit 0
fi
fail=0
while IFS= read -r f; do
shellcheck -S warning "$f" || fail=1
done <<<"$files"
exit $fail
- name: Bash syntax check (bash -n)
run: |
set -euo pipefail
files=$(git ls-files 'profiles/linux-dev/workstation-v0/**/*.sh' \
'profiles/linux-dev/workstation-v0/**/sourceos' \
'profiles/linux-dev/workstation-v0/**/install-sourceos-cli.sh' || true)
if [ -z "$files" ]; then
echo "No scripts found."
exit 0
fi
while IFS= read -r f; do
bash -n "$f"
done <<<"$files"
- name: Smoke: patch-shell apply is idempotent

Check failure on line 60 in .github/workflows/workstation-scripts.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/workstation-scripts.yml

Invalid workflow file

You have an error in your yaml syntax on line 60
run: |
set -euo pipefail
p='profiles/linux-dev/workstation-v0/bin/patch-shell.sh'
bash -n "$p"
tmp=$(mktemp)
printf '# rc\n' > "$tmp"
SOURCEOS_RC_FILES="$tmp" bash "$p" apply
SOURCEOS_RC_FILES="$tmp" bash "$p" apply
test "$(grep -cF '# >>> sourceos workstation-v0 >>>' "$tmp")" -eq 1
test "$(grep -cF '# <<< sourceos workstation-v0 <<<' "$tmp")" -eq 1
grep -F 'export PATH="$HOME/.local/bin:$PATH"' "$tmp" >/dev/null
- name: Smoke: patch-shell revert removes marker block
run: |
set -euo pipefail
p='profiles/linux-dev/workstation-v0/bin/patch-shell.sh'
tmp=$(mktemp)
printf '# rc\n' > "$tmp"
SOURCEOS_RC_FILES="$tmp" bash "$p" apply
SOURCEOS_RC_FILES="$tmp" bash "$p" revert
! grep -qF '# >>> sourceos workstation-v0 >>>' "$tmp"
! grep -qF '# <<< sourceos workstation-v0 <<<' "$tmp"
- name: Smoke: patch-shell JSON contract parses
run: |
set -euo pipefail
p='profiles/linux-dev/workstation-v0/bin/patch-shell.sh'
tmp=$(mktemp)
printf '# rc\n' > "$tmp"
out=$(SOURCEOS_RC_FILES="$tmp" bash "$p" dry-run --json)
python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); assert j["kind"]=="sourceos.fix.shell"; assert j["mode"]=="dry-run"; assert "summary" in j; assert isinstance(j["results"], list); print("ok")' <<<"$out"
- name: Smoke: patch-fish apply/revert
run: |
set -euo pipefail
p='profiles/linux-dev/workstation-v0/bin/patch-fish.sh'
bash -n "$p"
tmp=$(mktemp)
printf '# fish\n' > "$tmp"
SOURCEOS_FISH_CONFIG="$tmp" bash "$p" apply
SOURCEOS_FISH_CONFIG="$tmp" bash "$p" revert
! grep -qF '# >>> sourceos workstation-v0 (fish) >>>' "$tmp"
! grep -qF '# <<< sourceos workstation-v0 (fish) <<<' "$tmp"
- name: Smoke: patch-fish JSON contract parses
run: |
set -euo pipefail
p='profiles/linux-dev/workstation-v0/bin/patch-fish.sh'
tmp=$(mktemp)
printf '# fish\n' > "$tmp"
out=$(SOURCEOS_FISH_CONFIG="$tmp" bash "$p" dry-run --json)
python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); assert j["kind"]=="sourceos.fix.fish"; assert j["mode"]=="dry-run"; assert "result" in j; print("ok")' <<<"$out"
- name: Smoke: patch-all apply/revert
run: |
set -euo pipefail
p='profiles/linux-dev/workstation-v0/bin/patch-all.sh'
bash -n "$p"
rc_tmp=$(mktemp)
fish_tmp=$(mktemp)
printf '# rc\n' > "$rc_tmp"
printf '# fish\n' > "$fish_tmp"
SOURCEOS_RC_FILES="$rc_tmp" SOURCEOS_FISH_CONFIG="$fish_tmp" bash "$p" apply
SOURCEOS_RC_FILES="$rc_tmp" SOURCEOS_FISH_CONFIG="$fish_tmp" bash "$p" revert
! grep -qF '# >>> sourceos workstation-v0 >>>' "$rc_tmp"
! grep -qF '# <<< sourceos workstation-v0 <<<' "$rc_tmp"
! grep -qF '# >>> sourceos workstation-v0 (fish) >>>' "$fish_tmp"
! grep -qF '# <<< sourceos workstation-v0 (fish) <<<' "$fish_tmp"
- name: Smoke: patch-all JSON contract parses
run: |
set -euo pipefail
p='profiles/linux-dev/workstation-v0/bin/patch-all.sh'
rc_tmp=$(mktemp)
fish_tmp=$(mktemp)
printf '# rc\n' > "$rc_tmp"
printf '# fish\n' > "$fish_tmp"
out=$(SOURCEOS_RC_FILES="$rc_tmp" SOURCEOS_FISH_CONFIG="$fish_tmp" bash "$p" dry-run --json)
python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); assert j["kind"]=="sourceos.fix.all"; assert j["mode"]=="dry-run"; assert "results" in j and "shell" in j["results"] and "fish" in j["results"]; print("ok")' <<<"$out"
- name: Smoke: sourceos help exits cleanly
run: |
set -euo pipefail
f='profiles/linux-dev/workstation-v0/bin/sourceos'
bash -n "$f"
SOURCEOS_PROFILE_DIR=profiles/linux-dev/workstation-v0 bash "$f" --help >/dev/null
- name: Smoke: sourceos fix all dry-run exits cleanly
run: |
set -euo pipefail
f='profiles/linux-dev/workstation-v0/bin/sourceos'
rc_tmp=$(mktemp)
fish_tmp=$(mktemp)
printf '# rc\n' > "$rc_tmp"
printf '# fish\n' > "$fish_tmp"
SOURCEOS_PROFILE_DIR=profiles/linux-dev/workstation-v0 SOURCEOS_RC_FILES="$rc_tmp" SOURCEOS_FISH_CONFIG="$fish_tmp" bash "$f" fix all dry-run >/dev/null
- name: Smoke: sourceos fix all JSON contract parses
run: |
set -euo pipefail
f='profiles/linux-dev/workstation-v0/bin/sourceos'
rc_tmp=$(mktemp)
fish_tmp=$(mktemp)
printf '# rc\n' > "$rc_tmp"
printf '# fish\n' > "$fish_tmp"
out=$(SOURCEOS_PROFILE_DIR=profiles/linux-dev/workstation-v0 SOURCEOS_RC_FILES="$rc_tmp" SOURCEOS_FISH_CONFIG="$fish_tmp" bash "$f" fix all dry-run --json)
python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); assert j["kind"]=="sourceos.fix.all"; assert j["mode"]=="dry-run"; assert "results" in j; print("ok")' <<<"$out"
- name: Smoke: sourceos fix shell dry-run exits cleanly
run: |
set -euo pipefail
f='profiles/linux-dev/workstation-v0/bin/sourceos'
tmp=$(mktemp)
printf '# rc\n' > "$tmp"
SOURCEOS_PROFILE_DIR=profiles/linux-dev/workstation-v0 SOURCEOS_RC_FILES="$tmp" bash "$f" fix shell dry-run >/dev/null
- name: Smoke: sourceos fix fish dry-run exits cleanly
run: |
set -euo pipefail
f='profiles/linux-dev/workstation-v0/bin/sourceos'
tmp=$(mktemp)
printf '# fish\n' > "$tmp"
SOURCEOS_PROFILE_DIR=profiles/linux-dev/workstation-v0 SOURCEOS_FISH_CONFIG="$tmp" bash "$f" fix fish dry-run >/dev/null
- name: Smoke: doctor JSON contract parses
run: |
set -euo pipefail
export PATH="$(pwd)/profiles/linux-dev/workstation-v0/bin:$PATH"
d='profiles/linux-dev/workstation-v0/doctor.sh'
set +e
out=$(SOURCEOS_PROFILE_DIR=profiles/linux-dev/workstation-v0 bash "$d" --json)
rc=$?
set -e
if [ "$rc" -ne 0 ] && [ "$rc" -ne 2 ]; then
echo "Unexpected exit code: $rc" >&2
exit 1
fi
python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); assert j["kind"]=="sourceos.doctor"; assert j["profile"]=="linux-dev/workstation-v0"; assert "summary" in j; assert isinstance(j["results"], list); print("ok")' <<<"$out"
- name: Smoke: sourceos doctor JSON contract parses
run: |
set -euo pipefail
f='profiles/linux-dev/workstation-v0/bin/sourceos'
export PATH="$(pwd)/profiles/linux-dev/workstation-v0/bin:$PATH"
set +e
out=$(SOURCEOS_PROFILE_DIR=profiles/linux-dev/workstation-v0 bash "$f" doctor --json)
rc=$?
set -e
if [ "$rc" -ne 0 ] && [ "$rc" -ne 2 ]; then
echo "Unexpected exit code: $rc" >&2
exit 1
fi
python3 -c 'import json,sys; j=json.loads(sys.stdin.read()); assert j["kind"]=="sourceos.doctor"; assert j["profile"]=="linux-dev/workstation-v0"; print("ok")' <<<"$out"
- name: Smoke: sourceos doctor report file writes
run: |
set -euo pipefail
f='profiles/linux-dev/workstation-v0/bin/sourceos'
export PATH="$(pwd)/profiles/linux-dev/workstation-v0/bin:$PATH"
out_json=$(mktemp)
set +e
SOURCEOS_PROFILE_DIR=profiles/linux-dev/workstation-v0 bash "$f" doctor --write "$out_json" >/dev/null
rc=$?
set -e
if [ "$rc" -ne 0 ] && [ "$rc" -ne 2 ]; then
echo "Unexpected exit code: $rc" >&2
exit 1
fi
test -s "$out_json"
python3 -c 'import json,sys; j=json.load(open(sys.argv[1])); assert j["kind"]=="sourceos.doctor"; print("ok")' "$out_json"
- name: Smoke: sourceos status JSON parses
run: |
set -euo pipefail
f='profiles/linux-dev/workstation-v0/bin/sourceos'
bash -n "$f"
# `sourceos status --json` may return exit 0 (all deps present) or 2 (missing deps).
# We only require the JSON output to parse and contain expected keys.
set +e
out=$(SOURCEOS_PROFILE_DIR=profiles/linux-dev/workstation-v0 bash "$f" status --json)
rc=$?
set -e
if [ "$rc" -ne 0 ] && [ "$rc" -ne 2 ]; then
echo "Unexpected exit code: $rc" >&2
exit 1
fi
python3 -c 'import json,sys; j=json.loads(sys.stdin.read());
req=["profile","ok","gnome","required_missing","optional_missing","warnings"];
miss=[k for k in req if k not in j];
assert not miss, "missing keys: "+",".join(miss);
print("ok")' <<<"$out"
- name: Drift guard: forbid legacy launcher strings
run: |
set -euo pipefail
# Prevent drift back to non-open launcher references.
if grep -RIn --exclude-dir=.git -E 'Albert|albert toggle|SOURCEOS_ALLOW_THIRDPARTY_REPOS' docs/workstation profiles/linux-dev/workstation-v0; then
echo "Found forbidden legacy launcher reference." >&2
exit 1
fi