The previous run went green and produced an installer with no system-audio
capture in it — the exact silent failure this module exists to prevent.
Two causes. electron-rebuild takes -w as one comma-separated list, not a
repeated flag; passing it twice made argv.w an array and the CLI threw
'argv.w.split is not a function', which also broke uiohook-napi's rebuild that
had been working. And pnpm 10 refuses to run a dependency's build script unless
it is listed in onlyBuiltDependencies, so node-gyp never ran for it at all —
the log said 'Ignored build scripts' and nothing else complained.
Neither surfaced because desktop's postinstall ends in , which
exists so contributors without build tools can install. That is reasonable
locally and dangerous in CI, so the workflow now asserts a compiled .node
exists and fails loudly when it does not, instead of trusting an exit code that
was designed to lie.
Windows arm64 doubled the slowest job in the matrix — two Electron
distributions downloaded, native modules compiled twice — and nobody here runs
Windows on ARM. Linux arm64 went for the same reason.
Removing arm64 also retires the fpm step: electron-builder's bundled fpm is
x86_64-only and failed on the arm64 runner, which is the only reason a native
fpm was installed (ruby, ruby-dev, build-essential and a gem with native
extensions, on every run). On an x86_64 runner the bundled one works.
Matrix is now Windows x64 and Linux x64.
Nobody in this group runs macOS, and on a private repository the macOS runner
bills at 10x — it was the most expensive platform in the matrix, compiled on
every tag for zero users.
This edits a file inherited from upstream, so it will conflict on merges. That
is the accepted cost: the alternative is paying for a macOS build indefinitely.
release.yml builds four platforms and publishes a release, which is right for a
release and slow for 'give me an exe to test'. Wall time is set by the Windows
job, which compiles native modules and downloads Electron twice because it
targets x64 and arm64 together — and Windows-on-ARM is not something this group
runs.
This path builds one architecture, caches the Electron and electron-builder
downloads (~100MB re-fetched every run otherwise), and uploads the installer as
an artifact, so it needs neither a tag nor a version bump.
Kept as a separate file: release.yml comes from upstream and takes merges, so
editing it would conflict on every update.
Cost matters too on a private repository: macOS runners bill at 10x and Windows
at 2x, so the full matrix was paying for macOS builds on every tag.
The CLA Assistant appends each signature as a direct commit to the branch named in 'branch:'. That was main, which the 'Require CI on main' ruleset rejects ('Repository rule violations found'), so signatures were never recorded and the check stayed red however often a contributor signed — a deadlock for every outside contribution.
Point the store at the cla-signatures branch. The ruleset targets the default branch only, so the bot can append there without granting any actor a bypass on main. That branch is seeded with the existing signature and carries its own ruleset blocking deletion and non-fast-forward pushes.
Final whole-branch review (opus) fixes:
- docker-publish.yml: upload-sarif was if:always() but not continue-on-error, so a
Trivy SARIF-emit flake would fail the job and SKIP the multi-arch publish. Made it
non-blocking so a scanner hiccup never blocks a release.
- deployment.md: seed-admin-rotated.txt is root-owned (written via docker exec, which
bypasses the gosu drop) — reverted an over-correction. Corrected the canonical
runtime-stage build description (no toolchain; non-root gosu). First-boot chown note.
- restore.sh: comment ownership root -> uid 1000.
v0.28.0's composite action referenced a nested aquasecurity/setup-trivy@v0.2.1
tag that no longer exists, so the action failed to RESOLVE during job setup
(before any step ran) — continue-on-error can't catch a resolution failure, so
both Trivy jobs went red on every run. v0.36.0 pins setup-trivy to a real SHA
(v0.2.6) and still supports scan-type/scan-ref/scanners/format/output.
- OSV-Scanner ref was google/osv-scanner-action@<sha> (metadata-only root
action, no runs:) -> subpath google/osv-scanner-action/osv-scanner-action
which carries the docker action + scan-args input. Root ref would fail to
load and redden the job on every run (caught in final whole-branch review).
- security-scanning.md: note gitleaks findings land in job log (not SARIF);
add scorecard branch_protection_rule trigger; mark SBOM/provenance as not-
yet-live. CLAUDE.md row: image scan is a later plan, not current.
Makes local dev work on Windows: cross-env for the server dev port, pnpm --parallel to run server+web together (replacing the POSIX-only '&'), PowerShell setup docs, engines widened to Node >=20, and a Node 20 + 24 CI matrix.
CI keeps a stable required 'Build & test' status via an aggregate gate job so the matrix rename doesn't drop the context the main ruleset requires.
Co-authored-by: BadAtCaptchas <2359196+BadAtCaptchas@users.noreply.github.com>
Co-authored-by: Jannis Braun <151788261+TheZwiss@users.noreply.github.com>
GitHub is deprecating the Node 20 runtime for JS actions; every run printed a
warning that actions/checkout@v4, actions/setup-node@v4 and pnpm/action-setup@v4
were being force-run on Node 24. Bump each to its first Node 24 major (v5) across
all workflows — the smallest jump that clears the warning, avoiding the extra
behavior changes in checkout v6/v7 (credential persistence, fork-PR blocking)
that don't apply here. Our checkout jobs use push/pull_request, not
pull_request_target/workflow_run, so none are affected regardless.
Also bump the GitHub Pages actions in deploy-pages.yml (configure-pages v5->v6,
upload-pages-artifact v3->v5, deploy-pages v4->v5), which were likewise on Node
20. Inputs are unchanged; pnpm still pinned to 10.34.3 via the version input and
the packageManager field.
* ci: run typecheck, build & tests on PRs and main
Add a GitHub Actions workflow (.github/workflows/ci.yml) that installs on
Node 20 (the pinned runtime), builds shared/server/web, typechecks desktop,
and runs the full vitest suite across all packages on every pull request and
push to main. The repo already had 500+ tests but only ever ran them locally.
Also wires up the missing test/typecheck plumbing this exposed:
- web package had 55 test files but no test script — add test/test:watch/typecheck
- add root-level test and typecheck aggregate scripts
- fix a latent web type error the new typecheck surfaced: reference
vite-plugin-pwa/react types so virtual:pwa-register/react is typed
(useRegisterSW callback params were implicit any; the tsc half of the web
build script has been failing, masked by vite build ignoring type errors)
* test: fix two latent failures surfaced by CI on Node 20
- server: exclude dist/** from vitest. Vitest 4's default exclude dropped
dist/, so after `pnpm build` emits compiled .test.js files, vitest ran those
stale copies alongside src/*.test.ts and they failed (compiled vi.mock paths
resolve differently).
- web: polyfill Blob.prototype.stream in the jsdom test setup. jsdom's Blob has
no .stream() on Node 20 (Node 25 provided one, masking this locally); undici's
Response constructor calls blob.stream(), so new Response(blob) threw
'object.stream is not a function'.
Make Backspace self-hostable in any homelab environment, not just a clean host
that owns ports 80/443.
install.sh is now mode-aware and auto-detects which fits:
- allinone (default): bundled Caddy + auto-HTTPS — unchanged behavior
- proxy: behind your own reverse proxy (nginx / Traefik / Caddy / Nginx Proxy
Manager / SWAG) — app published on 127.0.0.1:APP_PORT, no bundled Caddy,
prints paste-ready proxy snippets
- tunnel: behind a tunnel (Cloudflare / Tailscale) — same, plus a 90MB upload
cap (under Cloudflare's 100MB body limit) and voice force-disabled (WebRTC
over UDP can't traverse a tunnel)
Port detection is Docker-aware (consults `docker ps` published ports, not just
`ss`), so a host whose proxy already owns 80/443 via iptables DNAT — with no
listening socket for `ss` to see — is correctly detected as "taken" instead of
dead-ending.
docker-compose.proxy.yml is a small overlay, layered via COMPOSE_FILE (written
into .env so no `-f` flags are ever needed), that publishes the loopback port and
parks Caddy in an inert profile. The base compose file is untouched, so All-in-One
behaves exactly as before.
Prebuilt image: .github/workflows/docker-publish.yml builds and pushes a
multi-arch (linux/amd64 + linux/arm64) image to ghcr.io/thezwiss/backspace on
release tags (and manual dispatch), so weak/ARM hosts skip the ~1.6GB local build
(the Vite build OOMs small ARM boxes). install.sh and docker-compose.yml default
to pulling it, fall back to an image already present on the host, and finally to a
from-source build — AGPL §13 commit stamping preserved on every path. Kept
deliberately separate from the desktop-installer workflow (release.yml).
Docs: README gains a "Deployment modes" section (all three modes, nginx / Caddy /
Traefik snippets, GUI-proxy field-by-field, cloudflared ingress, the update path,
and voice-per-mode caveats); docs/systems/deployment.md updated to match.
Verified live on a throwaway VM: proxy + all-in-one end-to-end through install.sh
(with a real Let's Encrypt cert), tunnel config generation, loopback-only binding,
and the local-image fallback path.
Unpinned `pnpm@latest` in the Dockerfile made fresh builds non-reproducible:
`latest` now resolves to pnpm 11, but the committed lockfile targets pnpm 10, so
`pnpm install --frozen-lockfile` fails with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH on
a clean host. Pin pnpm to 10.34.3 across the Dockerfile, the `packageManager`
field, and the release workflow; pin Node to 20 (LTS) via `.nvmrc` and `engines`
so Docker, CI, and from-source builds all use the same tested toolchain.
Also corrects the docs: the lockfile is v9.0 (requires pnpm 10, not "pnpm 8+"),
and "Node 20+" implied untested newer majors were supported.
CI run 2: mac/win/linux-x64 green, but linux-arm64 failed packaging .deb —
electron-builder's bundled fpm is x86_64-only and can't execute on the arm64
runner (Exec format error). The arm64 AppImage built fine; only fpm/.deb broke.
Install fpm natively on the Linux runners and set USE_SYSTEM_FPM=true so both
arches package .deb with a host-native fpm. Preserves arm64 .deb (the reason
the arm64 runner was added — Raspberry Pi users).
First real CI run failed on 3 of 4 platforms in postinstall (electron-rebuild
of uiohook-napi):
- Linux (x64+arm64): missing X11 dev headers — 'Xrandr.h: No such file'. Add the
full libuiohook header set (libxrandr-dev, libxinerama-dev, libx11-xcb-dev,
libxkbfile-dev, libxkbcommon-x11-dev) derived from its #include list.
- Windows: node-gyp on the windows-latest image can't detect VS 18
('unknown version undefined'). Pin to windows-2022 (VS 2022 / v17).
macOS built cleanly and is unaffected.