├── .github
└── workflows
│ └── release.yaml
├── .gitignore
├── .goreleaser.yaml
├── LICENSE.md
├── README.md
├── assets
└── images
│ └── web.png
├── cmd
├── list.go
├── root.go
├── send.go
├── serve.go
├── templates
│ └── index.html
└── version.go
├── config.example.yaml
├── config
└── config.go
├── docker
├── Dockerfile
└── Dockerfile.template
├── examples
└── reverse-proxy.yml
├── go.mod
├── go.sum
├── magicpacket
└── magicpacket.go
└── main.go
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | on:
4 | push:
5 | # run only against tags
6 | tags:
7 | - "*"
8 |
9 | permissions:
10 | contents: write
11 | packages: write # Add permission for publishing packages
12 |
13 | jobs:
14 | goreleaser:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v4
19 | with:
20 | fetch-depth: 0
21 | - name: Set up Go
22 | uses: actions/setup-go@v5
23 | with:
24 | go-version: stable
25 |
26 | - name: Set up QEMU
27 | uses: docker/setup-qemu-action@v3
28 |
29 | - name: Set up Docker Buildx
30 | uses: docker/setup-buildx-action@v3
31 |
32 | - name: Login to GitHub Container Registry
33 | uses: docker/login-action@v3
34 | with:
35 | registry: ghcr.io
36 | username: ${{ github.actor }}
37 | password: ${{ secrets.GITHUB_TOKEN }}
38 |
39 | - name: Run GoReleaser
40 | uses: goreleaser/goreleaser-action@v6
41 | with:
42 | # either 'goreleaser' (default) or 'goreleaser-pro'
43 | distribution: goreleaser
44 | version: "~> v2"
45 | args: release --clean
46 | env:
47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build artifacts
2 | wol
3 | *.exe
4 | dist/
5 |
6 | # Local config file
7 | config.yaml
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | before:
4 | hooks:
5 | - go mod tidy
6 | - go generate ./...
7 |
8 | builds:
9 | - env:
10 | - CGO_ENABLED=0
11 | goos:
12 | - linux
13 | - windows
14 | - darwin
15 | goarch:
16 | - amd64
17 | - arm64
18 | - arm
19 | - "386"
20 | goarm:
21 | - "7"
22 | ldflags:
23 | - -s -w
24 | - -X github.com/trugamr/wol/cmd.version={{.Version}}
25 | - -X github.com/trugamr/wol/cmd.commit={{.Commit}}
26 | - -X github.com/trugamr/wol/cmd.date={{.Date}}
27 |
28 | archives:
29 | - format: tar.gz
30 | name_template: >-
31 | {{ .ProjectName }}_
32 | {{- title .Os }}_
33 | {{- if eq .Arch "amd64" }}x86_64
34 | {{- else if eq .Arch "386" }}i386
35 | {{- else }}{{ .Arch }}{{ end }}
36 | {{- if .Arm }}v{{ .Arm }}{{ end }}
37 | format_overrides:
38 | - goos: windows
39 | format: zip
40 |
41 | dockers:
42 | - image_templates:
43 | - "ghcr.io/trugamr/wol:{{ .Version }}-amd64"
44 | - "ghcr.io/trugamr/wol:latest-amd64"
45 | dockerfile: docker/Dockerfile.template
46 | use: buildx
47 | build_flag_templates:
48 | - "--platform=linux/amd64"
49 | - "--label=org.opencontainers.image.title={{ .ProjectName }}"
50 | - "--label=org.opencontainers.image.description=A Wake-On-LAN tool that works via CLI and web interface"
51 | - "--label=org.opencontainers.image.url=https://github.com/trugamr/wol"
52 | - "--label=org.opencontainers.image.source=https://github.com/trugamr/wol"
53 | - "--label=org.opencontainers.image.version={{ .Version }}"
54 | - "--label=org.opencontainers.image.created={{ .Date }}"
55 | - "--label=org.opencontainers.image.revision={{ .FullCommit }}"
56 | - "--label=org.opencontainers.image.licenses=MIT"
57 | - image_templates:
58 | - "ghcr.io/trugamr/wol:{{ .Version }}-arm64"
59 | - "ghcr.io/trugamr/wol:latest-arm64"
60 | dockerfile: docker/Dockerfile.template
61 | use: buildx
62 | goarch: arm64
63 | build_flag_templates:
64 | - "--platform=linux/arm64"
65 | - "--label=org.opencontainers.image.title={{ .ProjectName }}"
66 | - "--label=org.opencontainers.image.description=A Wake-On-LAN tool that works via CLI and web interface"
67 | - "--label=org.opencontainers.image.url=https://github.com/trugamr/wol"
68 | - "--label=org.opencontainers.image.source=https://github.com/trugamr/wol"
69 | - "--label=org.opencontainers.image.version={{ .Version }}"
70 | - "--label=org.opencontainers.image.created={{ .Date }}"
71 | - "--label=org.opencontainers.image.revision={{ .FullCommit }}"
72 | - "--label=org.opencontainers.image.licenses=MIT"
73 | - image_templates:
74 | - "ghcr.io/trugamr/wol:{{ .Version }}-armv7"
75 | - "ghcr.io/trugamr/wol:latest-armv7"
76 | dockerfile: docker/Dockerfile.template
77 | use: buildx
78 | goarch: arm
79 | goarm: 7
80 | build_flag_templates:
81 | - "--platform=linux/arm/v7"
82 | - "--label=org.opencontainers.image.title={{ .ProjectName }}"
83 | - "--label=org.opencontainers.image.description=A Wake-On-LAN tool that works via CLI and web interface"
84 | - "--label=org.opencontainers.image.url=https://github.com/trugamr/wol"
85 | - "--label=org.opencontainers.image.source=https://github.com/trugamr/wol"
86 | - "--label=org.opencontainers.image.version={{ .Version }}"
87 | - "--label=org.opencontainers.image.created={{ .Date }}"
88 | - "--label=org.opencontainers.image.revision={{ .FullCommit }}"
89 | - "--label=org.opencontainers.image.licenses=MIT"
90 |
91 | docker_manifests:
92 | - name_template: ghcr.io/trugamr/wol:{{ .Version }}
93 | image_templates:
94 | - ghcr.io/trugamr/wol:{{ .Version }}-amd64
95 | - ghcr.io/trugamr/wol:{{ .Version }}-arm64
96 | - ghcr.io/trugamr/wol:{{ .Version }}-armv7
97 | - name_template: ghcr.io/trugamr/wol:latest
98 | image_templates:
99 | - ghcr.io/trugamr/wol:latest-amd64
100 | - ghcr.io/trugamr/wol:latest-arm64
101 | - ghcr.io/trugamr/wol:latest-armv7
102 |
103 | changelog:
104 | sort: asc
105 | filters:
106 | exclude:
107 | - "^docs:"
108 | - "^test:"
109 | - "^Merge pull request"
110 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Shivam Dua
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wol 🦭
2 |
3 | A CLI tool to send Wake-On-LAN (WOL) magic packets to wake up devices on your
4 | network. Features both CLI commands and a web interface.
5 |
6 |
7 |
8 | ## Features
9 |
10 | - Send WOL magic packets via CLI or web interface
11 | - Configure multiple machines with names for easy access
12 | - List configured machines
13 | - Web interface for easy wake-up
14 | - Docker support
15 |
16 | ## Installation
17 |
18 | ### Pre-built binaries
19 |
20 | Download the latest release for your platform from the
21 | [releases page](https://github.com/trugamr/wol/releases).
22 |
23 | Available for:
24 |
25 | - Linux (x86_64, arm64, armv7)
26 | - macOS (x86_64, arm64)
27 | - Windows (x86_64)
28 |
29 | ### Using Go
30 |
31 | ```sh
32 | go install github.com/trugamr/wol@latest
33 | ```
34 |
35 | ### Using Docker
36 |
37 | ```sh
38 | docker run --network host -v $(pwd)/config.yaml:/etc/wol/config.yaml ghcr.io/trugamr/wol:latest
39 | ```
40 |
41 | Or using docker-compose:
42 |
43 | ```yaml
44 | # Method 1: Using bind mount
45 | services:
46 | wol:
47 | image: ghcr.io/trugamr/wol:latest
48 | command: serve # To start the web interface
49 | network_mode: "host"
50 | volumes:
51 | - ./config.yaml:/etc/wol/config.yaml
52 |
53 | # Method 2: Using environment variables
54 | services:
55 | wol:
56 | image: ghcr.io/trugamr/wol:latest
57 | command: serve # To start the web interface
58 | network_mode: "host"
59 | environment:
60 | WOL_CONFIG: |
61 | machines:
62 | - name: desktop
63 | mac: "00:11:22:33:44:55"
64 | ip: "192.168.1.100" # Optional, for status checking
65 | - name: server
66 | mac: "AA:BB:CC:DD:EE:FF"
67 | ip: "server.local"
68 | server:
69 | listen: ":7777" # Optional, defaults to :7777
70 | ping:
71 | privileged: false # Optional, set to true to use privileged ping
72 | ```
73 |
74 | Check out `examples/reverse-proxy.yml` for an example of running wol behind
75 | reverse proxy with basic auth, https etc.
76 |
77 | > [!NOTE]
78 | > The config file should be mounted to `/etc/wol/config.yaml` inside the
79 | > container. Host networking is recommended for Wake-on-LAN packets to work
80 | > properly on your local network.
81 |
82 | ## Configuration
83 |
84 | Create a `config.yaml` file in one of these locations (in order of precedence):
85 |
86 | - `./config.yaml` (current directory)
87 | - `~/.wol/config.yaml` (home directory)
88 | - `/etc/wol/config.yaml` (system-wide)
89 |
90 | Alternatively, you can provide the configuration via the `WOL_CONFIG` environment variable:
91 |
92 | ```sh
93 | export WOL_CONFIG='
94 | machines:
95 | - name: desktop
96 | mac: "00:11:22:33:44:55"
97 | ip: "192.168.1.100" # Optional, for status checking
98 | - name: server
99 | mac: "AA:BB:CC:DD:EE:FF"
100 | ip: "server.local"
101 |
102 | server:
103 | listen: ":7777" # Optional, defaults to :7777
104 | '
105 | ```
106 |
107 | Example configuration:
108 |
109 | ```yaml
110 | machines:
111 | - name: desktop
112 | mac: "00:11:22:33:44:55"
113 | ip: "192.168.1.100" # Optional, for status checking
114 | - name: server
115 | mac: "AA:BB:CC:DD:EE:FF"
116 | ip: "server.local"
117 |
118 | server:
119 | listen: ":7777" # Optional, defaults to :7777
120 |
121 | ping:
122 | privileged: false # Optional, set to true if you need privileged ping
123 | ```
124 |
125 | ## Usage
126 |
127 | ### CLI Commands
128 |
129 | ```sh
130 | # List all configured machines
131 | wol list
132 |
133 | # Wake up a machine by name
134 | wol send --name desktop
135 |
136 | # Wake up a machine by MAC address
137 | wol send --mac "00:11:22:33:44:55"
138 |
139 | # Start the web interface
140 | wol serve
141 |
142 | # Show version information
143 | wol version
144 | ```
145 |
146 | ### Web Interface
147 |
148 | The web interface is available at `http://localhost:7777` when running the serve
149 | command. It provides:
150 |
151 | - List of all configured machines
152 | - One-click wake up buttons
153 | - Real-time machine status monitoring (when IP is configured)
154 | - Version information
155 | - Links to documentation and support
156 |
157 | ## Building from Source
158 |
159 | ```sh
160 | # Clone the repository
161 | git clone https://github.com/trugamr/wol.git
162 | cd wol
163 |
164 | # Build
165 | go build
166 |
167 | # Run
168 | ./wol
169 | ```
170 |
171 | ## Known Issues
172 |
173 | ### Docker Container Ping Permissions
174 |
175 | When running in a Docker container, the machine status feature that uses ping may not work due to permission issues. This is because the application uses [pro-bing](https://github.com/prometheus-community/pro-bing) for sending pings, which requires specific Linux kernel settings.
176 |
177 | To fix this issue, you need to set the following sysctl parameter on your host system:
178 |
179 | ```sh
180 | sysctl -w net.ipv4.ping_group_range="0 2147483647"
181 | ```
182 |
183 | To make this change persistent, add it to your `/etc/sysctl.conf` file.
184 |
185 | You can also try experimenting with setting `ping.privileged: true` in your configuration as an alternative solution.
186 |
187 | For more details, see [issue #12](https://github.com/Trugamr/wol/issues/12).
188 |
189 | ## License
190 |
191 | This project is licensed under the MIT License. See the [LICENSE](LICENSE.md)
192 | file for details.
193 |
194 | ## Contributing
195 |
196 | Contributions are welcome! Feel free to open issues or submit pull requests.
197 |
--------------------------------------------------------------------------------
/assets/images/web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Trugamr/wol/3e06254c649eb2375d590828b3250732ee6632f3/assets/images/web.png
--------------------------------------------------------------------------------
/cmd/list.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 | )
8 |
9 | func init() {
10 | rootCmd.AddCommand(listCmd)
11 | }
12 |
13 | var listCmd = &cobra.Command{
14 | Use: "list",
15 | Short: "List machines from config file",
16 | Long: "Show a list of all the configured machines",
17 | Args: cobra.NoArgs,
18 | Run: func(cmd *cobra.Command, args []string) {
19 | if len(cfg.Machines) == 0 {
20 | fmt.Println("No machines configured")
21 | return
22 | }
23 |
24 | // Render the list of machines
25 | fmt.Println("Name\tMAC")
26 | for _, machine := range cfg.Machines {
27 | fmt.Printf("%s\t%s\n", machine.Name, machine.Mac)
28 | }
29 | },
30 | }
31 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/trugamr/wol/config"
8 | )
9 |
10 | var cfg = config.NewConfig()
11 |
12 | var rootCmd = &cobra.Command{
13 | Use: "wol",
14 | Short: "Discover and wake up devices on the network",
15 | Long: "Discover devices on the network and wake them by sending magic Wake-On-LAN packets",
16 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
17 | return cfg.Load()
18 | },
19 | }
20 |
21 | func Execute() {
22 | err := rootCmd.Execute()
23 | if err != nil {
24 | os.Exit(1)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/cmd/send.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net"
7 | "strings"
8 |
9 | "github.com/spf13/cobra"
10 | "github.com/trugamr/wol/magicpacket"
11 | )
12 |
13 | func init() {
14 | rootCmd.AddCommand(sendCmd)
15 |
16 | sendCmd.Flags().StringP("mac", "m", "", "MAC address of the device to wake up")
17 | sendCmd.Flags().StringP("name", "n", "", "Name of the device to wake up")
18 | }
19 |
20 | var sendCmd = &cobra.Command{
21 | Use: "send",
22 | Short: "Send a magic packet to specified mac address",
23 | Long: "Send a magic packet to wake up a device on the network using the specified mac address",
24 | Args: cobra.NoArgs,
25 | PreRunE: func(cmd *cobra.Command, args []string) error {
26 | // Only one of the flags should be specified
27 | if cmd.Flags().Changed("mac") == cmd.Flags().Changed("name") {
28 | return fmt.Errorf("either --mac or --name must be specified")
29 | }
30 | return nil
31 | },
32 | Run: func(cmd *cobra.Command, args []string) {
33 | var mac net.HardwareAddr
34 |
35 | // Retrieve mac address using one of the flags
36 | switch true {
37 | case cmd.Flags().Changed("mac"):
38 | value, err := cmd.Flags().GetString("mac")
39 | if err != nil {
40 | cobra.CheckErr(err)
41 | }
42 |
43 | mac, err = net.ParseMAC(value)
44 | if err != nil {
45 | cobra.CheckErr(err)
46 | }
47 | case cmd.Flags().Changed("name"):
48 | // Get the name of the machine
49 | name, err := cmd.Flags().GetString("name")
50 | if err != nil {
51 | cobra.CheckErr(err)
52 | }
53 |
54 | // Find machine with the specified name
55 | mac, err = getMacByName(name)
56 | if err != nil {
57 | cobra.CheckErr(err)
58 | }
59 | default:
60 | log.Fatalf("mac address should come from either --mac or --name")
61 | }
62 |
63 | log.Printf("Sending magic packet to %s", mac)
64 | mp := magicpacket.NewMagicPacket(mac)
65 | if err := mp.Broadcast(); err != nil {
66 | cobra.CheckErr(err)
67 | }
68 |
69 | log.Printf("Magic packet sent")
70 | },
71 | }
72 |
73 | // getMacByName returns the MAC address of the machine with the specified name
74 | func getMacByName(name string) (net.HardwareAddr, error) {
75 | for _, machine := range cfg.Machines {
76 | if strings.EqualFold(machine.Name, name) {
77 | mac, err := net.ParseMAC(machine.Mac)
78 | if err != nil {
79 | return nil, fmt.Errorf("failed to parse MAC address: %w", err)
80 | }
81 | return mac, nil
82 | }
83 | }
84 |
85 | return nil, fmt.Errorf("machine with name %q not found", name)
86 | }
87 |
--------------------------------------------------------------------------------
/cmd/serve.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "embed"
5 | "encoding/json"
6 | "fmt"
7 | "html/template"
8 | "log"
9 | "net/http"
10 | "sync"
11 | "time"
12 |
13 | probing "github.com/prometheus-community/pro-bing"
14 | "github.com/spf13/cobra"
15 | "github.com/trugamr/wol/config"
16 | "github.com/trugamr/wol/magicpacket"
17 | )
18 |
19 | //go:embed templates/*
20 | var templates embed.FS
21 |
22 | func init() {
23 | rootCmd.AddCommand(serveCmd)
24 | }
25 |
26 | var serveCmd = &cobra.Command{
27 | Use: "serve",
28 | Short: "Serve a web interface to wake up machines",
29 | Long: "Serve a web interface that lists all the configured machines and allows you to wake them up",
30 | Args: cobra.NoArgs,
31 | Run: func(cmd *cobra.Command, args []string) {
32 | mux := http.NewServeMux()
33 |
34 | mux.HandleFunc("GET /{$}", handleIndex)
35 | mux.HandleFunc("POST /wake", handleWake)
36 | mux.HandleFunc("GET /status", handleStatus)
37 |
38 | log.Printf("Listening on %s", cfg.Server.Listen)
39 | err := http.ListenAndServe(cfg.Server.Listen, mux)
40 | if err != nil {
41 | cobra.CheckErr(err)
42 | }
43 | },
44 | }
45 |
46 | func handleIndex(w http.ResponseWriter, r *http.Request) {
47 | // Parse the template
48 | index, err := template.ParseFS(templates, "templates/index.html")
49 | if err != nil {
50 | log.Printf("Error parsing template: %v", err)
51 | http.Error(w, "Internal Server Error", http.StatusInternalServerError)
52 | return
53 | }
54 |
55 | // Execute the template
56 | data := map[string]interface{}{
57 | "Machines": cfg.Machines,
58 | "Version": version,
59 | "Commit": commit,
60 | "Date": date,
61 | "FlashMessage": consumeFlashMessage(w, r), // Get flash message from cookie
62 | }
63 | err = index.Execute(w, data)
64 | if err != nil {
65 | log.Printf("Error executing template: %v", err)
66 | http.Error(w, "Internal Server Error", http.StatusInternalServerError)
67 | return
68 | }
69 | }
70 |
71 | // setFlashMessage sets a flash message in a cookie
72 | func setFlashMessage(w http.ResponseWriter, message string) {
73 | http.SetCookie(w, &http.Cookie{
74 | Name: "flash",
75 | Value: message,
76 | Path: "/",
77 | })
78 | }
79 |
80 | // consumeFlashMessage retrieves and clears the flash message from the request
81 | func consumeFlashMessage(w http.ResponseWriter, r *http.Request) string {
82 | cookie, err := r.Cookie("flash")
83 | if err == nil {
84 | // Clear the cookie
85 | http.SetCookie(w, &http.Cookie{
86 | Name: "flash",
87 | Value: "",
88 | Path: "/",
89 | Expires: time.Now().Add(-1 * time.Hour),
90 | })
91 |
92 | return cookie.Value
93 | }
94 | return ""
95 | }
96 |
97 | func handleWake(w http.ResponseWriter, r *http.Request) {
98 | machineName := r.FormValue("name")
99 | mac, err := getMacByName(machineName)
100 | if err != nil {
101 | http.Error(w, err.Error(), http.StatusBadRequest)
102 | return
103 | }
104 |
105 | log.Printf("Sending magic packet to %s", mac)
106 | mp := magicpacket.NewMagicPacket(mac)
107 | if err := mp.Broadcast(); err != nil {
108 | log.Printf("Error sending magic packet: %v", err)
109 | http.Error(w, err.Error(), http.StatusInternalServerError)
110 | return
111 | }
112 |
113 | // Set flash message cookie
114 | setFlashMessage(w, fmt.Sprintf("Wake-up signal sent to %s. The machine should wake up shortly.", machineName))
115 |
116 | http.Redirect(w, r, "/", http.StatusSeeOther)
117 | }
118 |
119 | // getMachineStatus returns the status of a machine
120 | func getMachineStatus(machine config.Machine) (string, error) {
121 | if machine.IP == nil {
122 | return "unknown", nil
123 | }
124 |
125 | reachable, err := isAddressReachable(*machine.IP)
126 | if err != nil {
127 | return "unknown", err
128 | }
129 | if reachable {
130 | return "online", nil
131 | }
132 |
133 | return "offline", nil
134 | }
135 |
136 | // getMachinesStatus returns a map of machine names to their statuses concurrently
137 | func getMachinesStatus() map[string]string {
138 | var mu sync.Mutex
139 | statuses := make(map[string]string)
140 | var wg sync.WaitGroup
141 |
142 | for _, machine := range cfg.Machines {
143 | wg.Add(1)
144 | go func(machine config.Machine) {
145 | defer wg.Done()
146 | status, err := getMachineStatus(machine)
147 | if err != nil {
148 | log.Printf("Error getting status for machine %s: %v", machine.Name, err)
149 | return
150 | }
151 |
152 | mu.Lock()
153 | statuses[machine.Name] = status
154 | mu.Unlock()
155 | }(machine)
156 | }
157 |
158 | wg.Wait()
159 |
160 | return statuses
161 | }
162 |
163 | func handleStatus(w http.ResponseWriter, r *http.Request) {
164 | w.Header().Set("Content-Type", "text/event-stream")
165 | w.Header().Set("Cache-Control", "no-cache")
166 | w.Header().Set("Connection", "keep-alive")
167 |
168 | // Sends the current status of all machines
169 | sendMachinesStatus := func() {
170 | statuses := getMachinesStatus()
171 | data, err := json.Marshal(statuses)
172 | if err != nil {
173 | log.Printf("Error marshaling status: %v", err)
174 | return
175 | }
176 |
177 | _, err = fmt.Fprintf(w, "data: %s\n\n", data)
178 | if err != nil {
179 | log.Printf("Error writing status: %v", err)
180 | return
181 | }
182 |
183 | w.(http.Flusher).Flush()
184 | }
185 |
186 | // Sends initial status
187 | sendMachinesStatus()
188 |
189 | // Send status updates every few seconds
190 | ticker := time.NewTicker(5 * time.Second)
191 | defer ticker.Stop()
192 |
193 | for {
194 | select {
195 | case <-r.Context().Done():
196 | return
197 | case <-ticker.C:
198 | sendMachinesStatus()
199 | }
200 | }
201 | }
202 |
203 | func isAddressReachable(addr string) (bool, error) {
204 | pinger, err := probing.NewPinger(addr)
205 | if err != nil {
206 | return false, fmt.Errorf("error creating pinger: %v", err)
207 | }
208 | // Set privileged mode based on config
209 | pinger.SetPrivileged(cfg.Ping.Privileged)
210 |
211 | // We only want to ping once and wait 2 seconds for a response
212 | pinger.Timeout = 2 * time.Second
213 | pinger.Count = 1
214 |
215 | err = pinger.Run()
216 | if err != nil {
217 | return false, fmt.Errorf("error pinging: %v", err)
218 | }
219 |
220 | // If we receive even a single packet, the address is reachable
221 | stats := pinger.Statistics()
222 | if stats.PacketsRecv == 0 {
223 | return false, nil
224 | }
225 |
226 | return true, nil
227 | }
228 |
--------------------------------------------------------------------------------
/cmd/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Wake-on-LAN web interface
266 | {{if .Machines}} 267 |List of configured machines and their current status
269 |No machines configured
290 |291 | Add machines to your configuration file to start using Wake-on-LAN. 292 | Check the documentation for setup instructions. 293 |
294 |