Skip to content

lss 2.0 stage 7: Ansible collection, bootstrap and lss update - #25

Draft
VectoDE wants to merge 13 commits into
feat/lss-5from
feat/lss-7
Draft

VectoDE wants to merge 13 commits into
feat/lss-5from
feat/lss-7

Conversation

@VectoDE

@VectoDE VectoDE commented Oct 15, 2026

Copy link
Copy Markdown
Owner

Stage 7 of the 2.0 rebuild (#9): lss reaches servers only as a verified release.

  • bootstrap.sh: installs a release to /opt/lss/<version> behind /opt/lss/current (atomic link switches, /opt/lss/previous kept for rollback) only after the SHA-256 matches SHA256SUMS and the GitHub build attestation of both verifies for release.yml on refs/heads/main (pinned, checksum-checked GitHub CLI; no token needed).
  • lss update [VERSION] / --rollback / --dry-run: update, downgrade and rollback through the bootstrap.sh of the running version; a rollback re-checks the file list of the target version.
  • Ansible collection vectode.lss: role vectode.lss.lss (bootstrap, /etc/lss/lss.conf, modules) and module vectode.lss.lss (install/remove, check mode, change detection).
  • Release workflow (modelled on Self-Updater): version from labels, signed commit and tag via the deploy key, build of archive, bootstrap.sh, collection, CycloneDX SBOM and SHA256SUMS, one provenance attestation for all, GitHub release. Pre-release input (rc.1) for a test release.
  • distribution.yml: builds two test releases with the release build script, attests them for real, and on all 7 systems x amd64/arm64 installs, updates, rolls back, downgrades and refuses tampered/mis-signed releases; ansible-lint (production profile) and the collection over SSH on the same matrix.
  • 1.x script removed; docs/upgrading.md (incl. upgrade from 1.x), docs/releasing.md (owner setup: environment release, deploy key, signing key, tag ruleset in scripts/release/tag-ruleset.json, test release).

Owner steps (not done here): environment, secrets, deploy key and the tag ruleset as described in docs/releasing.md; the test release 2.0.0-rc.1 from main.

Refs #9

🤖 Generated with Claude Code

VectoDE and others added 13 commits October 15, 2026 17:54
Servers so far got lss from a git clone, with nothing proving where the code
came from. bootstrap.sh installs a release only after the SHA-256 of the
archive matches SHA256SUMS and the GitHub build attestation of both verifies
for the release workflow on main (gh attestation verify with a pinned and
checksum-checked GitHub CLI, so no account or token is needed). Versions live
in /opt/lss/<version>; /opt/lss/current, /opt/lss/previous and
/usr/local/sbin/lss are switched with rename(2), the new version must answer
`lss version` or the links go back, and a file list per version lets a
rollback refuse a tree that changed after it was installed.

`lss update [VERSION]` runs the bootstrap.sh of the running version with the
lock held: the latest release (never older than the active one), a named
version (also a downgrade, with a warning) or --rollback without a download.
lss runs main only when executed, so the unit tests can source it.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Fleets are set up with Ansible more often than by hand. The role
vectode.lss.lss copies bootstrap.sh to the target, so lss is installed only
from a verified release, writes /etc/lss/lss.conf (0600) and installs or
removes modules; the module vectode.lss.lss runs lss install or remove, reports
a change only when lss changed something, and supports check mode through
lss --dry-run. The collection is released with lss at the same version and
installs exactly that version by default.

tests/integration/ansible.sh runs the role over SSH against the systemd test
containers: install, a second run without changes, an update of lss through
the role, and a removal in check mode and for real. ansible-core and
ansible-lint are pinned with hashes in tests/ansible/requirements.txt.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
The release workflow follows Self-Updater: a push to main picks the version
from the pull request labels (scripts/release/prepare.sh; a pre-release
VERSION such as 2.0.0-dev releases that version), a job without repository
code commits and tags with the release SSH key and pushes with the deploy
key, and scripts/release/build.sh builds the archive, bootstrap.sh, the
Ansible collection, a CycloneDX SBOM of everything lss downloads by checksum
and SHA256SUMS from the tagged commit. One attestation from
actions/attest-build-provenance covers all of them and is attached as
lss-<version>.intoto.jsonl. A pre-release name (rc.1) allows a test release
without touching the changelog.

distribution.yml tests that path on every pull request with real
signatures: it builds two test releases with the same script, attests them
with its own OIDC identity and, on every supported system and both
architectures, installs, updates, rolls back and downgrades them with
bootstrap.sh and lss update, and refuses a tampered archive, a wrong
checksum, another signer and a changed rollback target. The tag ruleset that
lets only the deploy key create tags is prepared in
scripts/release/tag-ruleset.json for the owner to apply.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
2.0 replaces install-configure-script.bash, so it goes now, with the
upgrade path in docs/upgrading.md: what bootstrap.sh and lss update check,
the installed layout, update, downgrade and rollback, the move from a 2.0
development checkout, and a mapping of every 1.x option and leftover file
to the modules that replace it. docs/releasing.md describes the release
workflow and the one-time setup only the owner can do: the environment
"release", the deploy key, the signing key, the tag ruleset and the test
release. README, SECURITY, CONTRIBUTING, the architecture and the changelog
point to the new installation; SonarQube Cloud analyses bootstrap.sh and the
collection instead of the removed script.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
"KEY: value" in an unquoted description is a mapping in YAML, so
ansible-lint failed to load the argument spec of the role.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
ansible-lint cannot resolve vectode.lss.lss from the source tree in
offline mode, so the lint job now builds and installs the collection
first and points ANSIBLE_COLLECTIONS_PATH at it.

SonarCloud asked for named locals instead of positional parameters in
the new shell functions, and flagged /etc/lss as world-readable: the
role creates it with 0750, since only root reads the settings.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Under set -e, "(exit N)" in the EXIT trap ended the trap at once, so a
failed update or Ansible test printed no diagnostics and left the
container running. target_stop now takes the status as an argument and
also shows the SSH journal, which the Ansible test needs.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
The coverage run executes the unit tests in the kcov image. There,
BASH_SOURCE is unset in "bash -c", which lss with set -u turns into an
error, so update.bats now runs small driver scripts. The image has no
git, so the prepare.sh tests skip there like the build test without jq;
the plain unit test job still runs them.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
sshd in the minimal rocky-9 image denies root by its PAM account
configuration, so the Ansible test could not connect. When the first
SSH login fails, the test now prints the PAM stack and lets sshd skip
PAM in the container; lss and the role are unchanged.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
The rocky-9 update test ran `lss update` right after a dry run and was
refused with "Another lss run is in progress": a helper of the run
before still held the inherited lock descriptor for a moment. curl and
gh in bootstrap.sh no longer inherit fd 9, and lss and bootstrap.sh
wait up to five seconds for the lock instead of giving up at once.

Refs #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant