Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ on:
- "tsconfig.base.json"
- ".npmrc"
- "!**/*.md"
- "!packaging/repositories/*-channel.json"
- "!scripts/*docs*"
- "!scripts/build-static-site.mjs"
- "!scripts/verify-static-site.mjs"
Expand Down Expand Up @@ -46,6 +47,7 @@ on:
- "tsconfig.base.json"
- ".npmrc"
- "!**/*.md"
- "!packaging/repositories/*-channel.json"
- "!scripts/*docs*"
- "!scripts/build-static-site.mjs"
- "!scripts/verify-static-site.mjs"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
paths:
- ".github/workflows/deploy-docs.yml"
- "apps/site/**"
- "packaging/repositories/apt-channel.json"
- "apps/docs/**"
- "assets/product-screenshot.png"
- "package.json"
Expand Down
86 changes: 86 additions & 0 deletions .github/workflows/publish-apt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: Publish APT Repository

on:
workflow_call:
inputs:
tag:
type: string
required: true
operation:
type: string
default: publish
workflow_dispatch:
inputs:
operation:
description: Publish a stable release, refresh expiry, or roll back to a retained revision
type: choice
options: [publish, refresh, rollback]
default: publish
tag:
description: Existing stable release tag for publish
type: string
revision:
description: Retained repository revision SHA-256 for rollback
type: string
schedule:
- cron: "37 5 * * 1"

permissions:
contents: read

concurrency:
group: apt-repository-production
cancel-in-progress: false

jobs:
publish:
if: >-
${{
vars.APT_REPOSITORY_ENABLED == 'true' &&
(github.ref == format('refs/heads/{0}', github.event.repository.default_branch) ||
(github.workflow == 'Release' && startsWith(github.ref, 'refs/tags/v')))
}}
runs-on: ubuntu-24.04
timeout-minutes: 30
environment: packages-production
steps:
- name: Checkout publisher
uses: actions/checkout@v7.0.0
with:
fetch-depth: 0
persist-credentials: false

- name: Set up Node for release verification
uses: actions/setup-node@v6.4.0
with:
node-version: 22.23.1

- name: Install repository tools
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends apt-utils dpkg-dev gnupg gpgv openssh-client python3

- name: Build, publish, and verify repository
env:
GH_TOKEN: ${{ github.token }}
OPERATION: ${{ inputs.operation || 'refresh' }}
RELEASE_TAG: ${{ inputs.tag }}
ROLLBACK_REVISION: ${{ inputs.revision }}
APT_REPOSITORY_URL: ${{ vars.APT_REPOSITORY_URL }}
APT_REPOSITORY_HOST: ${{ vars.APT_REPOSITORY_HOST }}
APT_REPOSITORY_ROOT: ${{ vars.APT_REPOSITORY_ROOT }}
APT_SSH_PORT: ${{ vars.APT_SSH_PORT || '22' }}
APT_SIGNING_FINGERPRINT: ${{ vars.APT_SIGNING_FINGERPRINT }}
APT_SSH_PRIVATE_KEY: ${{ secrets.APT_SSH_PRIVATE_KEY }}
APT_SSH_KNOWN_HOSTS: ${{ secrets.APT_SSH_KNOWN_HOSTS }}
APT_SIGNING_KEY: ${{ secrets.APT_SIGNING_KEY }}
APT_SIGNING_PASSPHRASE: ${{ secrets.APT_SIGNING_PASSPHRASE }}
run: bash scripts/publish-apt-workflow.sh

- name: Upload public verification and activation record
uses: actions/upload-artifact@v7.0.1
with:
name: loopwire-apt-publication-${{ github.run_id }}
path: dist/apt-publication
if-no-files-found: error
retention-days: 90
16 changes: 16 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ jobs:
needs: build-linux
runs-on: ubuntu-22.04
timeout-minutes: 45
outputs:
tag: ${{ steps.verified-tag.outputs.tag }}
steps:
- name: Checkout
uses: actions/checkout@v7.0.0
Expand Down Expand Up @@ -497,3 +499,17 @@ jobs:
${{ env.LOOPWIRE_RELEASE_EVIDENCE_ARCHIVE }}
if-no-files-found: error
retention-days: 90

- name: Export verified release tag
id: verified-tag
run: printf 'tag=%s\n' "$LOOPWIRE_RELEASE_TAG" >>"$GITHUB_OUTPUT"

publish-apt:
name: Publish signed APT channel
needs: publish-release
if: ${{ vars.APT_REPOSITORY_ENABLED == 'true' && !contains(needs.publish-release.outputs.tag, '-') }}
uses: ./.github/workflows/publish-apt.yml
with:
tag: ${{ needs.publish-release.outputs.tag }}
operation: publish
secrets: inherit
2 changes: 2 additions & 0 deletions .github/workflows/web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
paths:
- ".github/workflows/web.yml"
- "apps/site/**"
- "packaging/repositories/apt-channel.json"
- "apps/docs/**"
- "assets/product-screenshot.png"
- "README.md"
Expand All @@ -30,6 +31,7 @@ on:
paths:
- ".github/workflows/web.yml"
- "apps/site/**"
- "packaging/repositories/apt-channel.json"
- "apps/docs/**"
- "assets/product-screenshot.png"
- "README.md"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/workflow-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- "scripts/ci-impact.rb"
- "scripts/test-ci-impact.rb"
- "scripts/test-ci-workflow-paths.rb"
- "scripts/test-apt-workflow.rb"
- "scripts/*native-package-proof-snapshot.mjs"
- "scripts/verify-github-workflows.sh"
- "scripts/verify-requirements.sh"
Expand All @@ -21,6 +22,7 @@ on:
- "scripts/ci-impact.rb"
- "scripts/test-ci-impact.rb"
- "scripts/test-ci-workflow-paths.rb"
- "scripts/test-apt-workflow.rb"
- "scripts/*native-package-proof-snapshot.mjs"
- "scripts/verify-github-workflows.sh"
- "scripts/verify-requirements.sh"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ node_modules/
dist/
dist-ssr/
coverage/
__pycache__/
.vitepress/cache/
.vitepress/dist/
.astro/
Expand Down
5 changes: 3 additions & 2 deletions .planning/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ gsd_state_version: 1.0
milestone: v0.5
milestone_name: GitHub Operator Setup
status: Ready for Review
last_updated: "2026-09-05T11:55:11.036Z"
last_updated: "2026-09-05T14:08:23Z"
last_activity: 2026-09-05
progress:
total_phases: 1
Expand All @@ -27,7 +27,7 @@ See: .planning/PROJECT.md (updated 2026-07-03)
Phase: 19 of 19 complete
Plan: 19.1 — Hardened GitHub Actions Setup
Status: Ready for review in PR #9
Last activity: 2026-09-05 - completed quick task 260905-i4l: CI input scopes and full release-validation evidence
Last activity: 2026-09-05 - completed quick task 260905-kyo: signed APT repository development and clean-guest proof


## Blockers / Concerns
Expand Down Expand Up @@ -93,6 +93,7 @@ Last activity: 2026-09-05 - completed quick task 260905-i4l: CI input scopes and
| 260905-fld | Default platform installer and homepage tabs; native install/reinstall proof | 2026-09-05 | c415386 | [260905-fld-homepage-platform-installer](./quick/260905-fld-homepage-platform-installer/) |
| 260905-hia | Minimal landing identity, GSAP reactions and screenshot proofs in PR #40 | 2026-09-05 | 6e84a9a | [260905-hia-landing-identity](./quick/260905-hia-landing-identity/) |
| 260905-i4l | Scope CI to affected files while retaining release validation | 2026-09-05 | f9bc0d8 | [260905-i4l-scope-ci](./quick/260905-i4l-scope-ci/) |
| 260905-kyo | Signed APT repository development and clean-guest lifecycle proof | 2026-09-05 | 639cbbb | [260905-kyo-signed-apt](./quick/260905-kyo-signed-apt/) |

## Accumulated Context

Expand Down
50 changes: 50 additions & 0 deletions .planning/quick/260905-kyo-signed-apt/260905-kyo-PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
status: implementing
issue: 35
---

# Signed APT repository development

Goal: complete the development checklist in #35, verify it, and open one dedicated PR containing `resolves #35`.
The overall goal continues with #36 and then #37 after this PR is opened. Production accounts, keys, credentials,
and the first public activation remain the separately listed human operational work.

## Contract and decisions

- Target Ubuntu 24.04 and Debian 13, amd64, using the existing tested native package payload and recipes.
- Reuse existing Python/Bash/Git/OpenSSH tooling and distro APT/GnuPG utilities; no new application dependencies.
- Project-owned HTTPS publication uses a POSIX server over SSH. This provides a verifiable same-filesystem atomic
InRelease replacement; Bunny's documented PUT interface does not establish the required publication guarantee.
- Suites are ubuntu-24.04 and debian-13, component main. Preserve immutable pool and by-hash URLs indefinitely in
the first implementation. InRelease is the per-suite commit point; no cross-suite instantaneous transaction claim.
- Keep the server HTTP document root separate from private publication state and retained snapshots. Serialize
writes, compare the expected current revision, reject immutable collisions, and recover interrupted promotion.
- OpenPGP repository signatures are independent of the existing OpenSSL release checksum signatures. Verify both.
- Metadata is valid for 30 days; provide protected scheduled refresh of the same package set and explicit rollback
that produces fresh signed metadata. Rollback requires an explicit package downgrade on already upgraded clients.
- Homepage repository commands activate only through validated channel configuration after human public proof;
until then retain the functional existing installation options. Implement and test the activated UI in fixtures.

## Tasks and ownership

1. Generator and trust verification (`apt_protocol`): scripts/apt-repository.py and tests; Packages/Release/InRelease,
exact release-package validation, immutable inventory, prior-version retention, version ordering, fresh rollback,
tamper/path/key/architecture failure tests using real signing and APT tools.
2. Publication (`apt_publisher`): scripts/publish-package-repository.py and tests; local and SSH transports, locking,
compare-and-swap, immutable snapshots, atomic per-suite promotion/recovery, dry-run and cache configuration.
3. Lifecycle (`apt_guest_surface`): explicit APT mode in the existing VM runner, guest lifecycle script and independent
evidence verifier; fresh matching guests install/reinstall/upgrade/rollback/remove through HTTPS APT and perform
real GUI/provider/linkage checks. Preserve historical native proof; label synthetic package revisions as fixtures.
4. Integration (root): scoped bootstrap, release/refresh/rollback workflows, CI inputs, configuration contract,
gated homepage/install documentation, regression coverage, review, final validation and PR delivery.

## Required evidence

- Every development checkbox in #35 maps to implemented files and an executed check in the summary.
- Repository signatures and actual APT reject wrong keys, altered metadata/packages and incomplete publication.
- Retry, concurrent publication, downgrade and rollback rules are exercised, including actual SSH transport.
- Clean Ubuntu and Debian KVM guests consume the served HTTPS repository, with version/origin/signature and real
installed GUI/provider checks recorded for all applicable lifecycle transitions.
- Workflow syntax/contract checks, docs/build checks, focused tests and the full local validation pass.
- PR targets the current default branch, has a reviewable diff and `resolves #35`, and explicitly lists the human
production setup/activation still required. Do not claim public production installation was verified without it.
83 changes: 83 additions & 0 deletions .planning/quick/260905-kyo-signed-apt/260905-kyo-SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
status: complete
issue: 35
---

# Signed APT repository development

Issue: https://github.com/sandwichfarm/loopwire/issues/35

## Result

The development work for the Ubuntu 24.04 and Debian 13 amd64 APT channel is complete. The checked-in channel remains
`pending` until the separately listed human operations provision a production HTTPS/SSH origin, create the signing
identity, configure the protected environment, and perform the first public verification. Existing homepage install
commands remain usable; a complete reviewed activation record switches only the Ubuntu and Debian panels to
`sudo apt install loopwire` and exposes the repository-scoped bootstrap command.

## Development checklist evidence

1. **Layout/configuration:** `apt-repository.py` defines separate `ubuntu-24.04` and `debian-13` suites under
`main/binary-amd64`, suite-specific pool paths, retained SHA-256 by-hash indexes, stable dpkg version ordering,
30-day signed metadata, indefinite v1 immutable retention, and a strict manifest. The operator runbook specifies
every variable, secret, path, permission, cache, monitoring, and key-rotation boundary.
2. **Generation:** build verifies the existing OpenSSL-signed release manifest and exact internal deb identity before
preserving those package bytes. It creates Packages/Packages.gz, Release, OpenPGP clear-signed InRelease, exported
fingerprint key, by-hash objects, and the independently validated inventory. Verify checks the entire trust chain.
3. **Publication/rollback:** the local/SSH publisher validates before writes, requires pinned host trust, locks the
POSIX origin, uses revision compare-and-swap, rejects immutable collisions, retains private snapshots, promotes
immutable objects before metadata, and atomically replaces each suite's InRelease. Durable journals resume every
interruption point; expired recovery is explicit and demands immediate refresh. Rollback re-signs selected
package sets with fresh dates. The Nginx example serves only `ROOT/public`, revalidates metadata and long-caches
immutable URLs.
4. **Protected automation:** Publish APT Repository supports release-triggered publish, operator publish/refresh/
rollback, and weekly expiry refresh. `APT_REPOSITORY_ENABLED=true` and `packages-production` gate writes. Stable
release publication waits for the existing GitHub Release/evidence gates, re-downloads and verifies public release
assets, then verifies every HTTPS-served byte before producing a reviewable activation record. Preflight rejects
unsafe configuration before key or origin access.
5. **Bootstrap:** the repeat-safe helper supports Ubuntu 24.04 and Debian 13 amd64, downloads only HTTPS key material,
pins the full fingerprint, uses `/etc/apt/keyrings` and a deb822 source with `Signed-By`, preserves unrelated
sources, rejects symlink escapes, supports no-network dry-run and safe removal, and never uses `apt-key` or insecure
APT options. User docs cover install, updates, repair, explicit downgrade, source removal, trust and key changes.
6. **Regression tests:** the dedicated suite runs real GPG/OpenSSL/dpkg/APT checks plus an actual disposable SSH
server. It rejects unsigned or tampered metadata, modified packages, wrong signers, bad suite/package identity,
downgrades outside explicit rollback, unsafe files, stale CAS, concurrent access and origin drift. It exercises 22
publisher cases, all resumable checkpoints, permissions under umask 077, expired-journal recovery, public HTTPS
tampering, bootstrap containment and a real Zstandard deb on the pinned Debian 13 toolchain.
7. **Matching guests:** clean checksum-pinned Ubuntu 24.04 and Debian 13 KVM guests installed from a guest-only HTTPS
repository using the real scoped bootstrap. Each performed install, reinstall, a synthetic `+aptfixture1` upgrade,
explicit downgrade/rollback, removal and source removal. The verifier binds repository origin, versions, signed
package hashes, every installed `/usr` file, providers/backend detector, GUI linkage and a real X11 application
window. The synthetic version reuses the authenticated v0.1.0 payload and is lifecycle evidence, not a release.
8. **Docs/UI:** homepage, install guide, support matrix, release guide, user APT guide, maintainer runbook, release
notes, and navigation are updated. Pending and verified-fixture browser tests prove the fallback and activated
states. Production activation remains the human step that supplies verified public values; Loopwire is never
described as part of a distribution's default repository.

## Verification

- `pnpm check` passed after the final review change: all project verification, types, 295 workspace tests, 22 Rust
tests, builds, static-site validation and the dedicated APT suite.
- `pnpm verify:apt` passed: 13 generator, 22 publisher (including actual SSH), 11 bootstrap, 9 public HTTPS, 5 workflow
preflight, 17 proof-verifier, and 4 homepage channel cases.
- Generator tests passed with real APT on both Debian 13 and Ubuntu 24.04; wrong-key, unsigned/tampered metadata and
modified-package downloads were rejected.
- Ubuntu and Debian lifecycle proof directories each contain 96 evidence files from commit `7849a1b`, revalidated
successfully with the final portable verifier at `639cbbb`.
- Pending production build browser tests passed all existing install/copy/keyboard/responsive/motion/fallback cases.
The verified fixture passed those same checks and additionally rendered both short APT commands, their separate
setup links, and the complete URL/fingerprint setup command. The checked-in record was restored to pending.
- Workflow contracts, actionlint, ShellCheck, Python/Node/Ruby/Bash syntax, docs/build checks and whitespace checks
passed. Final review's Zstandard portability finding was reproduced, fixed with `dpkg-deb --fsys-tarfile`, covered
by a real compressed package, and approved on re-review.

## Human operations still open

No production host/account, TLS certificate, SSH credential, OpenPGP identity, GitHub environment value, or public
repository was created or changed. No production publication was triggered. The five Human operational tasks in
issue #35 remain unchecked. The first public run must attach the public URL/fingerprint/revision and clean-client
evidence, then its reviewed `apt-channel.json` can activate the website through a separate commit.

Power-loss behavior and network filesystems were not tested; the publication contract explicitly requires local
POSIX filesystem locking/fsync/atomic-rename semantics. Ubuntu/Debian guests used a disposable local CA and repository
key; fixture trust cannot generate a production activation record.
2 changes: 2 additions & 0 deletions apps/docs/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default defineConfig({
text: "Guide",
items: [
{ text: "Install", link: "/guide/install" },
{ text: "APT Repository", link: "/guide/apt-repository" },
{ text: "Basic Usage", link: "/guide/basic-usage" },
{ text: "Configurations", link: "/guide/configurations" },
{ text: "Audio Backends", link: "/guide/backends" },
Expand All @@ -60,6 +61,7 @@ export default defineConfig({
{ text: "GitHub Actions Setup", link: "/developer/github-actions-setup" },
{ text: "VM Matrix", link: "/developer/vm-matrix" },
{ text: "Release", link: "/developer/release" },
{ text: "APT Repository Operations", link: "/developer/apt-repository" },
{ text: "Release Notes", link: "/developer/release-notes" }
]
},
Expand Down
Loading