remvisual/claude-wol-remote-access/wake-panel

Claude Code and agent skill by remvisual/claude-wol-remote-access

Trust tier 3
  • community
Source check pending

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-panel

SKILL.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:

phone (anywhere) --Tailscale--> relay host --LAN broadcast--> target PC

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:

NeedWhy
An always-on device on the same LAN as the targetsThe relay; it sends the broadcast
Tailscale on that device and on the user's phoneRemote reach
Target machines wired via EthernetWi-Fi WoL (WoWLAN) is unreliable; say so
Admin on each targetInstalling sshd, changing power settings
Docker on the relay host, or Python 3.9+Two deploy paths, stage 4
The relay's Tailscale client is currentA 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:

  1. 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.

  1. 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.

  1. Disable Fast Startup on Windows (powercfg /h off). Fast Startup makes

"shutdown" a hybrid hibernate and usually breaks WoL from S5.

  1. Install the telemetry collector (assets/agent/telemetry.ps1).
  2. Verify ssh -i <relay key> user@host works 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:

  1. Sleep → wake. Proves the whole packet path. Fast, low risk.
  2. 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:

AssetPurpose
assets/relay/terminal-run.shrun on the relay host; launches and verifies the terminal
assets/tools/set-terminal-password.ps1only 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:

sh
ttyd --interface lo --port 7681 --base-path /terminal --writable bash

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:

sh
ttyd --interface tailscale0 --port 7681 --writable --credential "$CRED" bash

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:

sh
alias nas='nsenter -t 1 -m -u -n -i -p -- sh'

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 -i passed 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 already
grants host root, so --privileged adds convenience rather than exposure — but
be honest that the panel's password is what stands in front of it.

GATE — all four, in this order:

  1. docker inspect shows state=running with restarts=0. A crash loop reads

as "started successfully" from the docker run exit code alone.

  1. netstat -tln shows the tailnet address and nothing on `0.0.0.0`. Check

the actual binding; do not trust the flag.

  1. An unauthenticated request returns 401, and so does a request with wrong

credentials. Prove the guard refuses, not just that the page loads.

  1. 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

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
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/remvisual/claude-wol-remote-access/wake-panel/badge)](https://verified-skill.com/skills/remvisual/claude-wol-remote-access/wake-panel)

Something wrong with this skill or its page? Tell us.