Managing wake-on-LAN (WoL) for a fleet of devices typically means SSHing into a machine or writing scripts. UpSnap puts that behind a web UI with user management, scheduled wake/shutdown via cron, and network scanning, so a small team can administer devices from a browser without touching a shell.
What This Does
UpSnap is a self-hosted WoL dashboard. The backend/ is a Go service built on PocketBase (a SQLite-backed API and admin UI), handling device wake, shutdown, sleep, and network scanning. The frontend/ is a SvelteKit app with i18n (23 translation files), theming, and pages for devices, users, settings, and account management.
The stack is deliberately small: Go for the networking layer, PocketBase for persistence and auth, SvelteKit for the UI, Docker for distribution. The backend/main.go starts PocketBase and registers custom handlers, while backend/networking/ contains the platform-specific implementations (Linux vs. other OSes) for pinging and sending magic packets.
How It Is Wired
Execution starts at main in backend/main.go, which calls StartPocketBase from backend/pb/pb.go. That function sets up the database, runs migrations, and registers HTTP handlers from backend/pb/handlers.go — HandlerWake, HandlerShutdown, HandlerSleep, HandlerReboot, and HandlerWakeGroup.
The core path for a wake request is: HandlerWake → WakeDevice (in backend/networking/wake.go) → SendMagicPacket (in backend/networking/magicpacket.go) → getBroadcastIp + wakeUDP, which makes the outbound network call. WakeDevice also calls PingDevice (platform-specific, runs an external ping command) and KillProcess to manage background processes.
The most-connected functions — asyncCall (4 callers), PingDevice (3), KillProcess (2) — are the ones a change to would break the most. The cron system in backend/cronjobs/cronjobs.go (SetPingJobs, SetWakeShutdownJobs, etc.) schedules these operations, and StopAll stops them cleanly.
The module graph shows no circular dependencies, but the DeviceForm.svelte component at 721 lines is the largest single unit and routes a lot of UI logic through it.
How To Use It
git clone https://github.com/moses-y/UpSnap
cd UpSnap
# Run the backend directly
cd backend
go run main.go serve --http=0.0.0.0:8090
# Or run the frontend in dev mode
cd frontend
pnpm install
pnpm dev
For Docker, the docker-compose.yml uses network_mode: host and exposes the service on port 8090. Configuration is via environment variables (e.g., UPSNAP_HTTP_LISTEN for the port) and the PocketBase admin UI.
Real-World Use
A homelab operator with 10-20 machines (NAS, media servers, workstations) deploys UpSnap in Docker, adds each device's MAC and IP via the UI, and schedules a cron job to wake the backup server at 2 AM and shut it down at 6 AM. The network scan feature (requires nmap) discovers devices on the LAN to populate the dashboard. User management lets a team share the dashboard without handing out SSH access.
Code Health & Issues
Static analysis (deterministic, not opinion) found 19 findings: 9 high, 10 medium. The main issues:
High – Cognitive load: Deep nesting (max depth 9) in DeviceCard.svelte, DeviceCardNic.svelte, and DeviceForm.svelte. Fix with early returns/guard clauses.
High – Duplicated code: 88 repeated 6-line blocks across 11 files in backend/networking/. Extract shared helpers.
Medium – Oversized file: DeviceForm.svelte at 721 lines. Split by responsibility.
The code health audit flags a committed frontend/.env (tracked despite .gitignore), unpinned GitHub Actions (tags can be moved — a known supply-chain risk), and network_mode: host in Docker Compose (removes network isolation). It also recommends least-privilege GITHUB_TOKEN permissions, Dependabot, and pinning the alpine:3 base image by digest.
The Bottom Line
UpSnap is a functional, well-structured WoL dashboard that works out of the box for a homelab or small team. The Go backend is cleanly separated from the SvelteKit frontend. The main concerns are operational: a committed .env, unpinned CI actions, and host networking in Docker — all fixable in an afternoon. Worth deploying if you need WoL management without building it yourself.
What the analyser found
Deployment readiness
6/7
✓Container image
✓CI pipeline
✓Lockfile committed
✓Test suite
✓README
✓License
✗No committed secrets
Composition
155 files
Go32
JSON26
Svelte17
TypeScript15
YAML12
Markdown2
ReactSvelteDocker
Findings
LowSecret-shaped paths present; the code-health audit confirms or clears them frontend/.env
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
high4
medium8
low1
Untrack the file your own .gitignore says to ignorehigh
frontend/.env
tracked although .gitignore excludes it
The author already decided this file must never be committed, so its presence is an accident nobody noticed, which means the credentials in it are the ones actually in use.
Fix: git rm --cached the path and rotate; the ignore rule is already correct.
Pin third-party GitHub Actions to a commit SHAhigh8 occurrences
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Drop privileged mode and host networkinghigh2 occurrences
docker-compose.yml
network_mode: host
privileged hands the container every capability including direct device access, which makes escaping to the host a documented one-liner, and host networking removes the network boundary so every port the container opens is open on the host.
Fix: Grant the specific capability the workload needs with cap_add, and publish ports explicitly.
Remove the committed .env and rotate what it holdshigh
frontend/.env
frontend/.env
A tracked .env is the most common route for a working key to reach a public clone, and it is the file the app actually loads, so the value is usually live.
Fix: git rm --cached the file, add it to .gitignore, rotate every credential it names, commit a .env.example with empty values.
Declare least-privilege permissions for GITHUB_TOKENmedium3 occurrences
.github/workflows/codeberg-mirror.yml
3 workflow(s) declare no permissions, 3 of them reference secrets
With no declaration the token inherits the repository default, so any injected step can push commits or mint releases from inside your own CI.
Fix: Add permissions: contents: read at the top of the workflow and widen per job only where needed.
Enable Dependabot or Renovatemedium
2 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Pin the container base image by digestmedium2 occurrences
Dockerfile
alpine:3, alpine:3
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Gate pull requests on a dependency vulnerability scanmedium
.github/workflows
no dependency scan in CI
This is the one gate that would catch a known-vulnerable package before it reaches a build, and no repository in the sample had it.
Fix: Add dependency-review-action on pull_request, or osv-scanner on push and a schedule.
Add a pre-commit secret gatemedium
a secret-shaped file is tracked and no repo-level gate is visible
Without a gate the same class of leak recurs on the next commit, so a leak finding is the symptom and this is the cause.
Fix: Add a pre-commit hook scanning staged content, and enable push protection.
Set persist-credentials: false on checkoutmedium
.github/workflows/release.yml
checkout keeps the token, then dependencies are installed
The token stays in .git/config for every later step, so a malicious postinstall script reads a pushable credential without one ever being passed to it.
Fix: Add with: persist-credentials: false, and pass an explicit token only to the step that pushes.
Add a non-root USER to the imagemedium
Dockerfile
CMD or ENTRYPOINT with no USER directive
A process running as root in the container is root against every mounted volume, and it turns any container escape or writable-mount mistake from a contained problem into a host one.
Fix: Create an unprivileged user, chown what it needs, and end the Dockerfile with USER.
Expand the test suite; it cannot cover this codebasemedium2 occurrences
2 test files against 66 source files (ratio 0.030)
A reviewer reads "has tests" and assumes a safety net that covers two files out of eighty-five, which is how untested paths ship behind a green badge.
Fix: Add tests for the highest fan-in modules first.
1 further finding(s) are listed in the full report.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.