remvisual/claude-wol-remote-access/wake-panel
Claude Code and agent skill by remvisual/claude-wol-remote-access
- community
Checking that the SKILL.md file is still in the repository.
Use when setting up remote wake-on-LAN, remote power control, or a machine-status dashboard reachable over Tailscale — including "wake my PC from my phone", "remote reboot my workstation", "WoL over VPN/WAN doesn't work", "GPU temp dashboard", or hosting a control panel on a NAS, Raspberry Pi, or other always-on device.
Install
npx vskill@latest install remvisual/claude-wol-remote-access/wake-panelSKILL.md
Wake Panel
Build a password-protected web panel, hosted on an always-on device, that wakes and monitors the user's computers from anywhere on their tailnet.
The one fact that drives the whole design
A Wake-on-LAN magic packet is a layer-2 broadcast. Tailscale is layer 3 and forwards no broadcast traffic. A phone off the home LAN therefore cannot wake anything directly — not with Moonlight's "Wake PC", not with any WoL app, not through a Tailscale subnet router. This is not a limitation to engineer around; it is how WoL works.
So the packet must originate on the LAN. An always-on device hosts a relay:
The relay must NOT live on one of the machines being controlled — a PC cannot wake itself, and two PCs that are both off cannot wake each other.
Running this
Work through the stages in order. Each stage ends with a gate — do not proceed past a failed gate. Later stages assume earlier ones actually worked, and a silent failure at stage 3 surfaces as an inexplicable one at stage 6.
Do as much as possible yourself over SSH. Hand the user a command ONLY when it needs local elevation, physical access, or a password you must not see.
Read reference/TROUBLESHOOTING.md when anything fails. Nearly every failure in this domain has a specific, non-obvious cause already catalogued there.
Stage 0 — Preflight
Establish and confirm with the user:
| Need | Why | |
|---|---|---|
| An always-on device on the same LAN as the targets | The relay; it sends the broadcast | |
| Tailscale on that device and on the user's phone | Remote reach | |
| Target machines wired via Ethernet | Wi-Fi WoL (WoWLAN) is unreliable; say so | |
| Admin on each target | Installing sshd, changing power settings | |
| Docker on the relay host, or Python 3.9+ | Two deploy paths, stage 4 | |
| The relay's Tailscale client is current | A NAS app store may be years stale; it is the single point of failure |
Ask what the user wants controlled and confirm the relay host choice.
GATE: user confirms the relay host is always on, wired or reliably connected, and on the same LAN segment as the targets.
Stage 1 — Inventory
For each target, collect: hostname, LAN IP, wired NIC MAC, OS, SSH username.
Run assets/tools/discover-hosts.ps1 (or ip neigh / arp -a) to enumerate. Confirm each MAC belongs to the Ethernet adapter, not Wi-Fi or a virtual one — this is a common silent mistake that produces a panel that never wakes anything.
GATE: every target has a MAC you have positively identified as its wired NIC.
Stage 2 — Relay host
Get SSH access to the relay host and record its Tailscale IP (tailscale ip -4).
Determine the deploy path: Docker available → containerised; otherwise → systemd service running Python directly. Both are in stage 4.
GATE: you can SSH to the relay host non-interactively and know its tailnet IP.
Stage 3 — Prepare each target
Per target, in this order:
- Generate one dedicated relay keypair (not the user's personal key) — the
relay holds a key that can power off machines; keep it separately revocable.
- Install/enable an SSH server. Windows:
assets/agent/setup-host.ps1
does this plus keys plus the collector. It needs elevation, so the user runs it — it is idempotent and prints exactly what changed.
The bootstrap problem, and how to keep it to ONE line. A fresh machine has no SSH, so you cannot reach it yet — exactly one command must be typed at its console. Do not make the user copy files around. Serve the assets from the machine you are on, then hand them a single self-contained line:
`` python -m http.server 8765 --bind <your-lan-ip> --directory <skill>/assets ``
``powershell irm http://<your-lan-ip>:8765/agent/setup-host.ps1 -OutFile $env:TEMP\setup-host.ps1; irm http://<your-lan-ip>:8765/agent/telemetry.ps1 -OutFile $env:TEMP\telemetry.ps1; powershell -NoProfile -ExecutionPolicy Bypass -File $env:TEMP\setup-host.ps1 -PublicKey '<relay public key>' ``
`-ExecutionPolicy Bypass` is mandatory, not defensive. A fresh Windows defaults Windows PowerShell to Restricted, so invoking the downloaded script directly (& $env:TEMP\setup-host.ps1) fails outright with "running scripts is disabled on this system". Verified on a clean Win11 25H2 install. Passing it as -File to a child process bypasses for that one invocation and changes no machine policy.
Both files must land in the same directory — setup-host.ps1 copies telemetry.ps1 from its own folder. Stop the server afterwards.
From this point on everything is remote; this is the only console step.
- Disable Fast Startup on Windows (
powercfg /h off). Fast Startup makes
"shutdown" a hybrid hibernate and usually breaks WoL from S5.
- Install the telemetry collector (
assets/agent/telemetry.ps1). - Verify
ssh -i <relay key> user@hostworks from the relay host, not
just from your machine.
GATE: the relay host can SSH to every target with the relay key and get valid JSON from the collector.
Stage 4 — Deploy the relay
Write hosts.json from stage 1 (see assets/relay/hosts.example.json).
`network_mode: host` (Docker) or running directly on the host OS is mandatory. In a bridge network the broadcast dies inside the container's private subnet and never reaches the LAN — the relay looks perfectly healthy and simply never wakes anything.
The container needs an SSH client; the compose file installs openssh-client at start with || true so a relay host with no internet still boots degraded rather than failing entirely.
GATE: curl http://<relay>:8099/healthz returns {"ok": true}.
Stage 5 — Password
Have the user run assets/tools/set-password.py. It prompts locally, derives a scrypt hash, and writes only the hash.
Never ask the user to type a password into the conversation.
The relay fails closed: with no config it serves nobody. Verify this — / should redirect and /api/status should 401 before the password is set.
GATE: unauthenticated requests are refused; the user can log in.
Stage 6 — Verify for real
Testing in this order matters, because S3 succeeding tells you nothing about S5:
- Sleep → wake. Proves the whole packet path. Fast, low risk.
- Shutdown → wake. The real test. Warn the user first: if it fails the
machine needs a physical power button press.
If S3 works and S5 does not, the cause is nearly always firmware. See TROUBLESHOOTING — and note that the usual internet answer (ErP) is often not the culprit.
GATE: each machine has actually been woken from full shutdown, or is explicitly marked unverified. The panel blocks shutdown for any machine whose wake is unverified — keep it that way rather than trusting a setting.
Stage 7 — Hand off
Tell the user to open http://<tailnet-ip>:8099 on their phone and Add to Home Screen — it launches full-screen and behaves like an installed app.
Summarise: what works, what is unverified, and any BIOS change still outstanding.
Optional — a break-glass terminal
The panel gives you fixed verbs: wake, sleep, reboot, shutdown. When something unforeseen breaks, you want a shell inside the network instead. Since the relay host is by definition the always-on box, it is the right place to put one.
This is optional and additive; the panel works without it. Add it when the user asks for "a terminal", "access when everything is down", or hand-built commands against the rest of the network.
Run it as a separate container, not another service in the panel's compose project. Re-running docker compose up against the project that owns a working relay risks recreating it for no reason, and on a locked-down NAS the account may not even be able to create a new project directory. A standalone container with --restart unless-stopped survives reboots identically and cannot disturb the panel.
Both pieces ship with this skill:
| Asset | Purpose | |
|---|---|---|
assets/relay/terminal-run.sh | run on the relay host; launches and verifies the terminal | |
assets/tools/set-terminal-password.ps1 | only for standalone mode (see below); prompts and pipes the credential over SSH |
Two modes. Prefer PROXY.
Proxy mode (default, recommended) — ttyd binds loopback with --base-path /terminal, and the relay proxies /terminal* behind its own auth:
No ACL change is ever needed (the panel's port is already permitted), there is one URL and one login, ttyd is unreachable from the network, and no ttyd credential exists at all — so no plaintext sits in ps or docker inspect. This requires adding a WebSocket-aware proxy route to the relay; see TROUBLESHOOTING for the shape.
Standalone mode — ttyd binds the tailnet interface on its own port with --credential. Simpler to deploy, no relay changes, but it needs a tailnet ACL grant for that port and it carries a second, weaker credential:
Either way the container is the toolbox — a NAS BusyBox userland typically has no nc, no python3, no dig. Run it with --pid host --privileged and /:/host, then one alias gets a genuine root shell on the host:
nsenter beats chroot /host because it enters the host's real namespaces, so /proc, /sys and the host's own tooling all behave normally.
Three flags are load-bearing in both modes, and each has its own TROUBLESHOOTING entry:
- `--writable` — without it the terminal is read-only and looks broken.
- plain `bash`, no flags — a
-ipassed to the child segfaults ttyd with an
empty log, because -i is ttyd's own --interface.
- `|| true` on `apk add` — a relay with no internet still gets a usable shell
instead of a crash loop.
This is a deliberately privileged container. The Docker socket alone alreadygrants host root, so--privilegedadds convenience rather than exposure — butbe honest that the panel's password is what stands in front of it.
GATE — all four, in this order:
docker inspectshowsstate=runningwithrestarts=0. A crash loop reads
as "started successfully" from the docker run exit code alone.
netstat -tlnshows the tailnet address and nothing on `0.0.0.0`. Check
the actual binding; do not trust the flag.
- An unauthenticated request returns 401, and so does a request with wrong
credentials. Prove the guard refuses, not just that the page loads.
- It is reachable from the user's phone. In proxy mode this is automatic. In
standalone mode, a tailnet ACL will not grant a new port automatically — a bound, healthy service is unreachable until you add it, and the failure looks like a hang rather than a refusal, which is the main reason to prefer proxy mode.
Add the ACL rule additively, never by editing an existing rule: you are almost certainly connected through the tailnet while you edit it, and a narrowing mistake locks you out of the box you are working on.
Pair it with a genuinely independent fallback — a phone SSH client with its own key, generated on the phone so the private key never touches another machine. A single break-glass mechanism that shares a failure mode with the thing it rescues is not a fallback. If the terminal container is what's broken, SSH still works; if the tailnet client is what's broken, neither does, which is why keeping that client current matters (see TROUBLESHOOTING).
Design rules to preserve when modifying
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/wake-panel/SKILL.md
Popularity
- 7-day trend score
- 5
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/remvisual/claude-wol-remote-access/wake-panel)