-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: multiple machines support #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
edf9fad
feat: add multi-device support with YAML config
daltonbr c9d63d7
add initial tests batch
daltonbr 82645d8
extract magic packet creation and update tests
daltonbr 7f9edc9
split documentation for simplicity
daltonbr 3b66fda
removing broadcast flag
daltonbr a078740
add test for port parameter
daltonbr f8bc671
add IP validation
daltonbr 46e99af
globalConfig is loaded once
daltonbr cf3df7c
validate MAC and IP in getDeviceConfig
daltonbr 8e35955
minor doc formating fix
daltonbr cc441ed
add port range validation
daltonbr 343e19c
Merge remote-tracking branch 'origin/main' into feat/multiple-machines
daltonbr 693490d
use OIDC (instead of token for CodeCov)
daltonbr 2cb8b81
updated go.sum
daltonbr 891541d
fix formatting issues
daltonbr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,8 +4,6 @@ | |
| "words": [ | ||
| "daltonbr", | ||
| "dylib", | ||
| "godotenv", | ||
| "joho", | ||
| "wolserver", | ||
| "wolserver" | ||
| ] | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
|
|
||
| # Build outputs | ||
| /wolserver | ||
| wol-server | ||
|
|
||
| # Go module / workspace | ||
| go.work | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,95 +1,162 @@ | ||
| # WOL-Server | ||
|
|
||
| 🔧 Project Overview | ||
| A lightweight HTTP server that sends Wake-on-LAN (WOL) magic packets to power on remote machines on your local network. Supports multiple devices through YAML configuration or direct API parameters. | ||
|
|
||
| This is a lightweight Go HTTP server that sends a Wake-on-LAN (WOL) magic packet to power on a remote machine on your local network. | ||
| ## Quick Start | ||
|
|
||
| 📦 How It Works | ||
| ### Docker (Recommended) | ||
|
|
||
| - The server exposes an HTTP endpoint: | ||
| ```yml | ||
| services: | ||
| wol-server: | ||
| image: ghcr.io/daltonbr/wol-server:latest | ||
| network_mode: "host" # Required for WOL broadcast on Linux | ||
| environment: | ||
| WOL_CONFIG: | | ||
| devices: | ||
| desktop: | ||
| mac: "00:11:22:33:44:55" | ||
| ip: "192.168.1.255" | ||
| port: 9 | ||
|
|
||
| laptop: | ||
| mac: "aa:bb:cc:dd:ee:ff" | ||
| ip: "192.168.1.100" | ||
| restart: unless-stopped | ||
|
|
||
| # macOS users: network_mode: "host" doesn't work on Docker Desktop | ||
| # Use port mapping instead and comment out network_mode above: | ||
| # ports: | ||
| # - "5000:5000" | ||
|
|
||
| # Alternative: Mount a config file instead of using WOL_CONFIG env var | ||
| # volumes: | ||
| # - type: bind | ||
| # source: ~/.config/wol-server/config.yaml | ||
| # target: /root/.config/wol-server/config.yaml | ||
| # read_only: true | ||
| ``` | ||
|
|
||
| ### Native Binary | ||
|
|
||
| ```bash | ||
| GET /wake | ||
| ``` | ||
| # Install config | ||
| mkdir -p ~/.config/wol-server | ||
| cp config.example.yaml ~/.config/wol-server/config.yaml | ||
| # Edit config.yaml with your device details | ||
|
|
||
| - When accessed, it reads environment variables to determine: | ||
| - The target MAC address | ||
| - The destination IP and port | ||
| - Whether to use broadcast | ||
| - It then sends a WOL magic packet to wake the device | ||
| ```bash | ||
| # Run | ||
| go run . | ||
| ``` | ||
|
|
||
| 🌱 Environment Variables | ||
| ## Usage | ||
|
|
||
| Set these before running the server: | ||
| **Wake a configured device:** | ||
|
|
||
| | Variable | Example | Description | | ||
| | -------- | ------- | ----------- | | ||
| | WOL_MAC | 00:11:22:33:44:55 | Target machine's MAC address (required) | | ||
| | WOL_IP | 192.168.1.255 | Target machine's IP or broadcast (optional, default: 192.168.1.255) | | ||
| | WOL_PORT | 9 | UDP port for WOL (optional, default: 9) | | ||
| | WOL_BROADCAST | `true` or `false` | Use broadcast or direct IP (optional) | | ||
| ```bash | ||
| curl http://localhost:5000/wake?device=desktop | ||
| ``` | ||
|
|
||
| ▶️ Running | ||
| **Wake using direct parameters (no config needed):** | ||
|
|
||
| ## Docker | ||
| ```bash | ||
| curl "http://localhost:5000/wake?mac=00:11:22:33:44:55&ip=192.168.1.255" | ||
| ``` | ||
|
|
||
| `docker-compose.yml` | ||
| **Check server status:** | ||
|
|
||
| ```yml | ||
| services: | ||
| wol-server: | ||
| image: ghcr.io/daltonbr/wol-server:latest | ||
| network_mode: "host" | ||
| environment: | ||
| ENV: production | ||
| WOL_MAC: "00:11:22:33:44:55" # Replace with your actual MAC address | ||
| WOL_IP: "192.168.1.255" # Your broadcast IP or direct device | ||
| WOL_PORT: "9" # Usually 7 or 9 | ||
| WOL_BROADCAST: "true" # Set false if using directed IP (not supported yet) | ||
| restart: unless-stopped | ||
| ```bash | ||
| curl http://localhost:5000/status | ||
| ``` | ||
|
|
||
| > [!TIP] | ||
| > Docker’s default bridge network does not allow broadcast e.g.`(192.168.1.255)` to go through - that is why we need `network-mode` set to `host`. | ||
| ## Configuration | ||
|
|
||
| ## How to use | ||
| ### Option 1: Environment Variable (Docker) | ||
|
|
||
| You need to enable wake-on-lan on your Network card and probably enable this on your BIOS setup (usually is disabled to save energy) | ||
| [Check this guide for more details](https://www.windowscentral.com/software-apps/windows-11/how-to-enable-wake-on-lan-on-windows-11). | ||
| Set `WOL_CONFIG` with YAML content (see docker-compose example above). | ||
|
|
||
| Turn on PC - send the magical packet | ||
| ### Option 2: Config File (Bare Metal) | ||
|
|
||
| ```http | ||
| http://localhost:5000/wake | ||
| Create `~/.config/wol-server/config.yaml`: | ||
|
|
||
| ```yaml | ||
| devices: | ||
| desktop: | ||
| mac: "00:11:22:33:44:55" | ||
| ip: "192.168.1.255" | ||
| port: 9 # optional, default: 9 | ||
|
|
||
| laptop: | ||
| mac: "11:22:33:44:55:66" | ||
| ip: "192.168.1.255" | ||
| ``` | ||
|
|
||
| Check status | ||
| See [`config.example.yaml`](config.example.yaml) for more examples. | ||
|
|
||
| ```http | ||
| http://localhost:5000/status | ||
| ``` | ||
| ### Configuration Fields | ||
|
|
||
| 📤 Publishing (GHCR) | ||
| | Field | Type | Description | Default | | ||
| | ----- | ---- | ----------- | ------- | | ||
| | mac | string | Target machine's MAC address (required) | - | | ||
| | ip | string | Target IP or broadcast address (required) | - | | ||
| | port | integer | UDP port for WOL | 9 | | ||
|
|
||
| To build and publish a multi-arch Docker image (specifically `linux/amd64`) to GitHub Container Registry (GHCR): | ||
| ## API Reference | ||
|
|
||
| ```bash | ||
| # Build for linux/amd64 | ||
| docker build --platform linux/amd64 -t wol-server . | ||
| ### Endpoints | ||
|
|
||
| #### `GET /wake` | ||
|
|
||
| Wake a device using either a configured device name or direct parameters. | ||
|
|
||
| **Query Parameters:** | ||
|
|
||
| Using configured device: | ||
|
|
||
| - `device` - Device name from config (required) | ||
|
|
||
| Using direct parameters: | ||
|
|
||
| - `mac` - MAC address in colon format (required) | ||
| - `ip` - Target IP or broadcast address (required) | ||
| - `port` - UDP port (optional, default: 9) | ||
|
|
||
| **Examples:** | ||
|
|
||
| # Tag with GHCR path and version | ||
| docker tag wol-server ghcr.io/daltonbr/wol-server:latest | ||
| docker tag wol-server ghcr.io/daltonbr/wol-server:<version> | ||
| ```bash | ||
| # Using device name | ||
| GET /wake?device=desktop | ||
|
|
||
| # Push to GHCR | ||
| docker push ghcr.io/daltonbr/wol-server:latest | ||
| docker push ghcr.io/daltonbr/wol-server:<version> | ||
| # Using direct parameters | ||
| GET /wake?mac=00:11:22:33:44:55&ip=192.168.1.255&port=9 | ||
| ``` | ||
|
|
||
| > 💡 You must be logged in to GHCR with a Personal Access Token (PAT) that has `write:packages` permission: | ||
| #### `GET /status` | ||
|
|
||
| Check if the server is running. | ||
|
|
||
| **Response:** `WOL service is running` | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| Your target machine must have: | ||
|
|
||
| - Wake-on-LAN enabled in BIOS/UEFI | ||
| - Wake-on-LAN enabled in network adapter settings | ||
| - Connected to power (or battery with WOL support) | ||
|
|
||
| [Guide: How to enable Wake-on-LAN on Windows 11](https://www.windowscentral.com/software-apps/windows-11/how-to-enable-wake-on-lan-on-windows-11) | ||
|
|
||
| ## Alternative: Volume Mount Config | ||
|
|
||
| Instead of using `WOL_CONFIG` env var, you can mount a config file (see commented example in docker-compose above). | ||
|
|
||
| ## Development | ||
|
|
||
| See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for: | ||
|
|
||
| > 💡 You must be logged in to GHCR using your GitHub username and a Personal Access Token (PAT) with `write:packages` permission: | ||
| > | ||
| > ```bash | ||
| > docker login ghcr.io | ||
| > ``` | ||
| - Building from source (requires Go 1.25.5+) | ||
| - Running tests | ||
| - Cross-compilation | ||
| - Publishing releases | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Example WOL Server Configuration | ||
| # Copy this to ~/.config/wol-server/config.yaml or use WOL_CONFIG env var | ||
|
|
||
| devices: | ||
| desktop: | ||
| mac: "00:11:22:33:44:55" | ||
| ip: "192.168.1.255" | ||
| port: 9 | ||
|
|
||
| gaming-pc: | ||
| mac: "aa:bb:cc:dd:ee:ff" | ||
| ip: "192.168.1.100" | ||
| port: 9 | ||
|
|
||
| laptop: | ||
| mac: "11:22:33:44:55:66" | ||
| ip: "192.168.1.255" | ||
| # port defaults to 9 if not specified |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,64 +1,106 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "net" | ||
| "os" | ||
| "strconv" | ||
| "path/filepath" | ||
|
|
||
| "gopkg.in/yaml.v3" | ||
| ) | ||
|
|
||
| // DeviceConfig defines the configuration for a single device. | ||
| type DeviceConfig struct { | ||
| MAC string `yaml:"mac"` | ||
| IP string `yaml:"ip"` | ||
| Port int `yaml:"port"` | ||
| } | ||
|
|
||
| // Config defines the YAML structure for all devices. | ||
| type Config struct { | ||
| Devices map[string]DeviceConfig `yaml:"devices"` | ||
| } | ||
|
|
||
| // WOLConfig defines the configuration for sending a Wake-on-LAN magic packet. | ||
| type WOLConfig struct { | ||
| // MACAddress is the target device's MAC address in colon format (e.g. "00:11:22:33:44:55"). | ||
| MACAddress string | ||
|
|
||
| // IPAddress is the destination IP to send the WOL packet to. | ||
| // Use "255.255.255.255" for global broadcast, | ||
| // or the specific device's local IP for directed WOL (e.g. 192.168.1.255:9) (subnet broadcast) | ||
| // Use a broadcast address (e.g., 192.168.1.255) for standard WOL, | ||
| // or a specific device IP for directed WOL. | ||
| IPAddress string | ||
|
|
||
| // Port is the destination UDP port for the WOL packet. | ||
| // Common values are 9 (default) or 7. | ||
| Port int | ||
| } | ||
|
|
||
| // globalConfig is loaded once at startup and is read-only during normal operation. | ||
| // This is safe for concurrent access since it's never modified after initialization. | ||
| // Tests may modify this variable for test isolation. | ||
| var globalConfig Config | ||
|
daltonbr marked this conversation as resolved.
|
||
|
|
||
| // Broadcast indicates whether to use UDP broadcast (true) or direct IP (false). | ||
| Broadcast bool | ||
| // loadConfig loads configuration from WOL_CONFIG env var or config file | ||
| func loadConfig() error { | ||
| // Try loading from WOL_CONFIG environment variable first | ||
| if configYaml := os.Getenv("WOL_CONFIG"); configYaml != "" { | ||
| if err := yaml.Unmarshal([]byte(configYaml), &globalConfig); err != nil { | ||
| return fmt.Errorf("failed to parse WOL_CONFIG env var: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // Fall back to config file | ||
| configDir := os.Getenv("XDG_CONFIG_HOME") | ||
| if configDir == "" { | ||
| homeDir, err := os.UserHomeDir() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get home directory: %w", err) | ||
| } | ||
| configDir = filepath.Join(homeDir, ".config") | ||
| } | ||
|
|
||
| // Alias is an optional name for the device (e.g. "Glados"). | ||
| // Useful if managing multiple configs later. | ||
| // Optional: not used in logic yet. | ||
| // Alias string | ||
| configPath := filepath.Join(configDir, "wol-server", "config.yaml") | ||
|
|
||
| // SendPing indicates whether a ping should be sent after the WOL packet | ||
| // to check if the device is responding. Optional feature for future use. | ||
| // SendPing bool | ||
| data, err := os.ReadFile(configPath) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to read config file %s: %w", configPath, err) | ||
| } | ||
|
|
||
| // PingTimeout defines the timeout in seconds when SendPing is enabled. | ||
| // PingTimeout int | ||
| if err := yaml.Unmarshal(data, &globalConfig); err != nil { | ||
| return fmt.Errorf("failed to parse config file: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func loadConfig() (WOLConfig, error) { | ||
| mac := os.Getenv("WOL_MAC") | ||
| ip := os.Getenv("WOL_IP") | ||
| portStr := os.Getenv("WOL_PORT") | ||
| broadcast := os.Getenv("WOL_BROADCAST") == "true" | ||
| // getDeviceConfig retrieves a device configuration by name | ||
| func getDeviceConfig(deviceName string) (WOLConfig, error) { | ||
| device, exists := globalConfig.Devices[deviceName] | ||
| if !exists { | ||
| return WOLConfig{}, fmt.Errorf("device '%s' not found in config", deviceName) | ||
| } | ||
|
|
||
| if mac == "" { | ||
| return WOLConfig{}, errors.New("WOL_MAC environment variable is required") | ||
| // Apply defaults | ||
| port := device.Port | ||
| if port == 0 { | ||
| port = 9 | ||
| } | ||
|
|
||
|
daltonbr marked this conversation as resolved.
|
||
| if ip == "" { | ||
| return WOLConfig{}, errors.New("WOL_IP environment variable is required") | ||
| // Validate MAC address format | ||
| if _, err := net.ParseMAC(device.MAC); err != nil { | ||
| return WOLConfig{}, fmt.Errorf("invalid MAC address '%s' for device '%s': %w", device.MAC, deviceName, err) | ||
| } | ||
|
|
||
| port := 9 // default | ||
| if p, err := strconv.Atoi(portStr); err == nil { | ||
| port = p | ||
| // Validate IP address format | ||
| if net.ParseIP(device.IP) == nil { | ||
| return WOLConfig{}, fmt.Errorf("invalid IP address '%s' for device '%s'", device.IP, deviceName) | ||
| } | ||
|
|
||
| return WOLConfig{ | ||
| MACAddress: mac, | ||
| IPAddress: ip, | ||
| MACAddress: device.MAC, | ||
| IPAddress: device.IP, | ||
| Port: port, | ||
| Broadcast: broadcast, | ||
| }, nil | ||
|
daltonbr marked this conversation as resolved.
|
||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.