Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# SPDX-FileCopyrightText: 2025-2026 VectoDE
#
# SPDX-License-Identifier: MIT

# The website: built from the module metadata by scripts/build-site.sh and held to Lighthouse 100 in every category
# on every pull request and push to main; deployed to GitHub Pages only from main. The page is never committed, so a
# new module is on the site with the next deploy.
name: Pages

on:
pull_request:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages-${{ github.ref }}
# A deploy that has started finishes; pull request runs make way for newer commits.
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
build:
name: Build and Lighthouse
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build
# Also fails when the README tables differ from data/systems.conf.
run: scripts/build-site.sh _site
- name: Lighthouse
uses: treosh/lighthouse-ci-action@3e7e23fb74242897f95c0ba9cabad3d0227b9b18 # 12.6.2
with:
configPath: site/lighthouserc.json
uploadArtifacts: true
temporaryPublicStorage: false
- name: Upload the site for Pages
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: _site

deploy:
name: Deploy
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
with:
egress-policy: audit
- id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@

# Local Claude Code data (worktrees, settings)
.claude/

# Website build output (scripts/build-site.sh)
_site/
2 changes: 1 addition & 1 deletion .sonarcloud.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# SPDX-License-Identifier: MIT

# SonarQube Cloud automatic analysis reads this file: https://sonarcloud.io/project/overview?id=VectoDE_Linux-Server-Setup-Script
sonar.sources=lss,lib,libexec,modules,scripts,install-configure-script.bash
sonar.sources=lss,lib,libexec,modules,scripts,site,install-configure-script.bash
sonar.tests=tests

# Accepted findings, each with its reason.
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ now or later.
its results for Prometheus through the node exporter.
- The services of the backup, monitoring and network modules run with a systemd sandbox drop-in; a service that
listens on the WireGuard address starts after the interface.
- Website on GitHub Pages: lss explained as a film that plays while scrolling, generated from the module metadata,
the profiles and `data/systems.conf` on every deploy, with a strict Content Security Policy, a still version for
reduced motion and Lighthouse 100 checked in CI.

### Removed

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ row is the cost of the whole set, including the web server or PHP it needs.
time and the peak memory go. Later runs reuse the build.

The integration jobs print these figures as `RESOURCES` lines, so every new module and every new system adds its own.
The tables "Supported systems" and "System requirements" above come from `data/systems.conf`, which the website reads
too; CI fails when they differ.

## Quick start

Expand Down Expand Up @@ -499,6 +501,7 @@ well, where an alert rule picks it up.
- [Troubleshooting](docs/troubleshooting.md): locked out, failed modules, common messages
- [Assurance case](docs/assurance.md): threat model, trust boundaries and how lss counters common weaknesses
- [Writing a module](docs/module-guide.md)
- [Website](docs/website.md): the scroll film on GitHub Pages, built from the module files
- [Changelog](CHANGELOG.md)

## Contributing and security
Expand Down
1 change: 1 addition & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ path = [
".github/CODEOWNERS",
".github/allowed_signers",
"templates/**",
"site/lighthouserc.json",
"tests/fixtures/os-release/**",
]
SPDX-FileCopyrightText = "2025-2026 VectoDE"
Expand Down
28 changes: 28 additions & 0 deletions data/systems.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2025-2026 VectoDE
#
# SPDX-License-Identifier: MIT

# Supported systems and what lss needs to run on them. One source for the tables "Supported systems" and "System
# requirements" in README.md and for the website: scripts/build-site.sh renders the website from it, and
# `scripts/build-site.sh --check` (run in CI) fails when the README tables say something else.
#
# Fields are separated by "|". Text in backticks is shown as code.
#
# system|<distribution>|<releases>|<releases tested in CI>
# arch|<architectures and how CI covers them>
# requirement|<setup>|<RAM>|<free disk>|<install time>
#
# The requirement figures come from the RESOURCES lines of the integration jobs (tests/integration/run.sh): the
# largest value across the tested systems, rounded up.

system|Debian|12, 13|12, 13
system|Ubuntu|22.04, 24.04, 26.04|22.04, 24.04, 26.04
system|Rocky Linux, AlmaLinux, RHEL|9, 10|Rocky 9, AlmaLinux 10

arch|x86_64 and arm64 (aarch64). CI runs on x86_64: on every pull request it installs, verifies, installs again and removes every module on each tested system.

requirement|Profile `minimal-secure`|1 GB; 2 GB on the RHEL family|4 GB|3 to 5 min plus AIDE
requirement|`minimal-secure` with `crowdsec`|1.5 GB; 2 GB on the RHEL family|4.5 GB|about 30 s more
requirement|`minimal-secure` with `clamav`|2.5 GB|4.5 GB|about 1 min more
requirement|Profile `office`|1.5 GB; 2 GB on the RHEL family|10 GB|9 to 16 min
requirement|Profile `devops`|1 GB; 1.5 GB on the RHEL family|7 GB|6 to 11 min
89 changes: 89 additions & 0 deletions docs/website.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Website

The website explains lss as a film that plays while you scroll: every scene sticks to the screen while its lines
appear, its list rolls past or its items change, with a caption at the bottom. It is one static page on GitHub
Pages, built from the repository on every deploy.

## What it is built from

`scripts/build-site.sh` writes the page and reads everything it shows from the repository:

| On the page | Source |
| ------------------------------------- | ------------------------------------------------------------------------ |
| One scene per module group | `MOD_NAME`, `MOD_GROUP`, `MOD_DESCRIPTION`, `MOD_CONFLICTS` and `MOD_OPTIONS` of every module (sourced in a subshell, like `lss info`) |
| The order of an example run | `MOD_REQUIRES` |
| Profiles | `profiles/*.conf` (`# Profile:` line and `LSS_MODULES`) |
| Guard time and thresholds | the defaults in `MOD_OPTIONS` of `guard` |
| `lss --help` | the `usage` function of `lss` |
| The menu | `ui_menu` in `lib/ui.sh` |
| Supported systems, requirements | `data/systems.conf` |
| Version | `VERSION` |

A new module, group or profile is on the site with the next deploy; nobody edits the page. A group the generator
does not know yet gets a scene with a plain caption after the known groups. The captions of the known groups, the
security model and the verified install are written in the generator.

`data/systems.conf` is also the source of the tables "Supported systems" and "System requirements" in the README:
`scripts/build-site.sh --check` (part of every build, so part of CI) fails when they say something else. Change
both together.

## Build and preview

```bash
scripts/build-site.sh _site # writes _site/index.html, film.js, favicon.svg and robots.txt
python3 -m http.server -d _site 8000 # any static server will do
```

`_site/` is not committed. The page is generated in the Pages workflow (`.github/workflows/pages.yml`), so it
cannot fall behind the modules, and a pull request shows the result of its own changes.

## Motion, keyboard and screen readers

Every word of every scene is in the HTML from the start. `site/film.js` only moves and marks it: it sets the scene
counter, shows lines, rolls the stage and highlights the current item. The film needs motion allowed
(`prefers-reduced-motion: no-preference`), scripting and a screen at least 30rem tall. Otherwise the same scenes
are a still sequence of full-screen frames, with nothing hidden.

The chapter list in the header jumps to every scene, a skip link leads to the film, and focus is always visible.
Decoration (scene numbers on the splice line, the counter, the progress bar) is hidden from screen readers; the
headings, captions, lists and tables are plain HTML.

Colour has one meaning: yellow marks what lss is doing right now, red what it refuses or what failed. The palette
is checked against WCAG AA in light and dark mode. The fonts are the system's own monospace and sans-serif.

## Content Security Policy and headers

The page loads nothing from other servers: no fonts, no analytics, no cookies. Its policy, set by a meta tag:

```text
default-src 'none'; script-src 'self'; style-src 'sha256-<hash of the inlined stylesheet>'; img-src 'self';
connect-src 'self'; base-uri 'none'; form-action 'none'
```

The page itself makes no requests after loading; `connect-src 'self'` is there for tools that fetch `robots.txt`
of the same site from within the page, such as Lighthouse.

The stylesheet is inlined to save a request and allowed by its SHA-256, which the generator computes on every
build; there are no style attributes and no inline scripts. GitHub Pages cannot set response headers. A host that
can should send these as well (a meta tag cannot carry `frame-ancestors`):

```text
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'sha256-...'; img-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Permissions-Policy: camera=(), microphone=(), geolocation=()
Cross-Origin-Opener-Policy: same-origin
```

## Checks

The Pages workflow builds the page and runs Lighthouse (three runs, mobile settings) on every pull request and push
to `main`. It fails below 100 in any of performance, accessibility, best practices and SEO. The unit tests in
`tests/unit/site.bats` check that every module, group and profile is on the page, that the policy hash matches the
stylesheet and that the page has no external resources.

## Deploy

Only a push to `main` deploys, through `actions/upload-pages-artifact` and `actions/deploy-pages` with
`pages: write` and `id-token: write` for that job alone. GitHub Pages has to be enabled once in the repository
settings with the source "GitHub Actions".
Loading
Loading