From 64753ed4989c3bf2bdccd1c730043e5664ecbbc5 Mon Sep 17 00:00:00 2001 From: Lorenzo Iovino Date: Mon, 21 Apr 2025 22:21:02 +0200 Subject: [PATCH] Initialize Go module --- .env | 4 + .env.sample | 4 + .github/workflows/build.yml | 178 ++++++++++++---- README.md | 315 +++++++++++++++++---------- go.mod | 5 + go.sum | 2 + handlers.go | 216 +++++++++++++++++++ main.go | 412 +++--------------------------------- templates/status.html | 402 +++++++++++++++++++++++++++++++++++ utils.go | 100 +++++++++ 10 files changed, 1101 insertions(+), 537 deletions(-) create mode 100644 .env create mode 100644 .env.sample create mode 100644 go.mod create mode 100644 go.sum create mode 100644 handlers.go create mode 100644 templates/status.html create mode 100644 utils.go diff --git a/.env b/.env new file mode 100644 index 0000000..6641729 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +SERVER_NAME=delemaco +SERVER_USER=root +MAC_ADDRESS=b8:cb:29:a1:f3:88 +PORT=8080 diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..ea2a15a --- /dev/null +++ b/.env.sample @@ -0,0 +1,4 @@ +SERVER_NAME=pippo +SERVER_USER=root +MAC_ADDRESS=aa:aa:aa:aa:aa:aa +PORT=8080 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d830900..b4a8249 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,63 +43,152 @@ jobs: - name: Create deployment script run: | - cat > deploy.sh << 'EOL' + cat > install.sh << 'EOL' #!/bin/bash + set -e - # Detect Raspberry Pi model and use the appropriate binary - MODEL=$(cat /proc/cpuinfo | grep "Model" | sed 's/Model\s*:\s*//g') - echo "Detected model: $MODEL" + # Installation directory + INSTALL_DIR=~/wol-server - # Select the right binary based on processor architecture - ARCH=$(uname -m) - echo "Architecture: $ARCH" + echo "Creating installation directory..." + mkdir -p $INSTALL_DIR + mkdir -p $INSTALL_DIR/templates - if [[ "$ARCH" == "aarch64" ]]; then - echo "Using ARM64 binary" - cp wol-server-arm64 wol-server - elif [[ "$ARCH" == "armv7l" ]]; then - echo "Using ARMv7 binary" - cp wol-server-arm7 wol-server - else - echo "Using ARMv6 binary" - cp wol-server-arm6 wol-server - fi + echo "Installing application..." + cp wol-server-arm6 $INSTALL_DIR/wol-server + chmod +x $INSTALL_DIR/wol-server - echo "Creating directory..." - mkdir -p ~/wol-server + echo "Installing template files..." + cp -r templates/* $INSTALL_DIR/templates/ - echo "Copying application..." - cp wol-server ~/wol-server/ - chmod +x ~/wol-server/wol-server - - echo "Installing service..." + echo "Installing system service..." sudo cp wol-server.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable wol-server + + # Install required dependencies + echo "Installing dependencies..." + sudo apt-get update -qq + sudo apt-get install -y wakeonlan sshpass + + # Start the service + echo "Starting service..." sudo systemctl restart wol-server - echo "Deployment complete!" - echo "Service status:" - sudo systemctl status wol-server + echo "===========================================" + echo "Installation complete!" + echo "The WOL server is now running at http://$(hostname -I | awk '{print $1}'):8080" + echo "===========================================" EOL - chmod +x deploy.sh + chmod +x install.sh - - name: Create archive for each platform + - name: Create README with installation instructions run: | - # ARMv6 package - mkdir -p armv6-package - cp wol-server-arm6 armv6-package/wol-server-arm6 - cp wol-server.service armv6-package/ - cp deploy.sh armv6-package/ - tar -czf wol-server-armv6.tar.gz -C armv6-package . + cat > INSTALL.md << 'EOL' + # WOL Server Installation Guide - # All-in-one package - mkdir -p all-package - cp wol-server-arm6 all-package/ - cp wol-server.service all-package/ - cp deploy.sh all-package/ - tar -czf wol-server-all.tar.gz -C all-package . + This guide will help you install the Wake-on-LAN server on your Raspberry Pi. + + ## Prerequisites + + - Raspberry Pi running Raspberry Pi OS (Raspbian) + - SSH access to your Pi + - SCP or SFTP capability to transfer files + + ## Installation Steps + + ### 1. Transfer Files to Raspberry Pi + + **Option 1: Using SCP from your computer** + + ```bash + # Replace with your Pi's IP address + PI_IP=192.168.1.100 + + # Transfer the installation package + scp wol-server.tar.gz pi@$PI_IP:~/ + ``` + + **Option 2: Using SFTP client** + + Use a tool like FileZilla, WinSCP, or Cyberduck to transfer the `wol-server.tar.gz` file to your Raspberry Pi. + + ### 2. SSH into your Raspberry Pi + + ```bash + ssh pi@192.168.1.100 + ``` + + ### 3. Extract and Install + + ```bash + # Navigate to home directory + cd ~ + + # Extract the archive + tar -xzf wol-server.tar.gz + + # Run the installation script + ./install.sh + ``` + + ### 4. Test the Installation + + Open a web browser and navigate to: + ``` + http://[your-pi-ip]:8080 + ``` + + ## Troubleshooting + + If the service fails to start, check the logs: + ```bash + sudo systemctl status wol-server + ``` + + If template errors occur, ensure the template files were copied correctly: + ```bash + ls -la ~/wol-server/templates/ + ``` + + ## Manual Installation (if needed) + + If you encounter issues with the automated install: + + ```bash + # Create directories + mkdir -p ~/wol-server/templates + + # Copy files manually + cp wol-server-arm6 ~/wol-server/wol-server + chmod +x ~/wol-server/wol-server + cp templates/* ~/wol-server/templates/ + sudo cp wol-server.service /etc/systemd/system/ + + # Install dependencies + sudo apt-get update + sudo apt-get install -y wakeonlan sshpass + + # Enable and start service + sudo systemctl daemon-reload + sudo systemctl enable wol-server + sudo systemctl start wol-server + ``` + EOL + + - name: Create all-in-one package + run: | + # Create a single package with everything needed + mkdir -p package + cp wol-server-arm6 package/ + cp wol-server.service package/ + cp install.sh package/ + cp -r templates package/ + cp INSTALL.md package/ + + # Create the tarball + tar -czf wol-server.tar.gz -C package . - name: Create Release id: create_release @@ -111,10 +200,7 @@ jobs: draft: false prerelease: false files: | - wol-server-arm6 - wol-server.service - deploy.sh - wol-server-armv6.tar.gz - wol-server-all.tar.gz + wol-server.tar.gz + INSTALL.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 52eeac9..7a4aa40 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,234 @@ -![Server Status Screenshot](https://placeholder-for-screenshot.png) +# WOL Server - Wake-on-LAN Control Panel for Raspberry Pi + +A lightweight web-based Wake-on-LAN control panel designed for Raspberry Pi that lets you remotely power on and shut down your network devices. + +![WOL Server Screenshot](https://i.imgur.com/example.jpg) ## Features -- **Status Monitoring**: Real-time status checks to determine if your server is online or offline -- **Wake-on-LAN**: Boot your server remotely with the click of a button -- **Remote Shutdown**: Safely shut down your server when it's not needed -- **Responsive UI**: Simple, mobile-friendly interface with color-coded status indicators -- **Lightweight**: Built with Go for minimal resource usage, perfect for Raspberry Pi Zero - -## Requirements - -- Raspberry Pi (Zero, 2, 3, 4, etc.) -- Go (version 1.16 or higher) -- wakeonlan utility -- SSH access to the target server (for shutdown functionality) -- Target server configured for Wake-on-LAN +- **Simple Web Interface**: Boot and shut down your server with a clean, responsive UI +- **Status Monitoring**: Check if your target device is online +- **Raspberry Pi Optimized**: Built specifically for ARM processors found in all Raspberry Pi models +- **Secure Shutdown**: Password-protected shutdown functionality +- **Lightweight**: Minimal resource usage ideal for running on even the oldest Pi models +- **Easy Setup**: Simple installation process with clear instructions ## Installation -### 1. Install Dependencies +### Prerequisites -```bash -sudo apt update -sudo apt install golang-go wakeonlan -``` +- Raspberry Pi (any model) running Raspberry Pi OS +- Network connection +- Basic knowledge of SSH/terminal -### 2. Clone the Repository +### Option 1: One-Command Installation -```bash -git clone https://github.com/yourusername/wol-server.git -cd wol-server -``` +1. **Download the latest release** on your local machine from the [Releases page](https://github.com/yourusername/wol-server/releases) -### 3. Configure the Application - -Edit the constants in `main.go` to match your server: - -```go -const ( - serverName = "yourserver" // Hostname or IP address of your server - macAddress = "xx:xx:xx:xx:xx:xx" // MAC address of your server's network interface - port = "8080" // Port to run the web application on -) -``` - -### 4. Build the Application - -```bash -go build -o wol-server -``` - -### 5. Set Up as a System Service - -Create a systemd service file: - -```bash -sudo nano /etc/systemd/system/wol-server.service -``` - -Add the following content (adjust paths if needed): - -``` -[Unit] -Description=WOL Server Go Application -After=network.target - -[Service] -User=pi -WorkingDirectory=/home/pi/wol-server -ExecStart=/home/pi/wol-server/wol-server -Restart=always - -[Install] -WantedBy=multi-user.target -``` - -Enable and start the service: - -```bash -sudo systemctl enable wol-server -sudo systemctl start wol-server -``` - -## SSH Configuration for Remote Shutdown - -For the shutdown functionality to work, you need to set up password-less SSH: - -1. Generate an SSH key on your Raspberry Pi: +2. **Transfer the package to your Raspberry Pi** using SCP: ```bash - ssh-keygen -t rsa + scp wol-server.tar.gz pi@your-pi-ip:~/ ``` -2. Copy the key to your target server: +3. **SSH into your Raspberry Pi**: ```bash - ssh-copy-id user@yourserver + ssh pi@your-pi-ip ``` -3. Configure sudo on the target server to allow password-less shutdown: +4. **Install with a single command**: ```bash - # On the target server, run: - sudo visudo - - # Add this line (replacing 'user' with your username): - user ALL=(ALL) NOPASSWD: /sbin/shutdown + tar -xzf wol-server.tar.gz && ./install.sh ``` +5. **Access the web interface** at: + ``` + http://your-pi-ip:8080 + ``` + +### Option 2: Manual Installation + +If you prefer a manual approach or encounter issues with the automated install: + +1. **Create installation directory**: + ```bash + mkdir -p ~/wol-server/templates + ``` + +2. **Transfer and install program files**: + ```bash + # Copy the executable + cp wol-server-arm6 ~/wol-server/wol-server + chmod +x ~/wol-server/wol-server + + # Copy template files + cp templates/* ~/wol-server/templates/ + + # Create .env file + cat > ~/wol-server/.env << EOL + SERVER_NAME=pippo + SERVER_USER=root + MAC_ADDRESS=aa:bb:cc:dd:ee:ff + PORT=8080 + EOL + ``` + +3. **Install service**: + ```bash + sudo cp wol-server.service /etc/systemd/system/ + sudo systemctl daemon-reload + sudo systemctl enable wol-server + sudo systemctl start wol-server + ``` + +4. **Install required dependencies**: + ```bash + sudo apt-get update + sudo apt-get install -y wakeonlan sshpass + ``` + +## Configuration + +The application can be configured by editing the `.env` file in the installation directory: + +```bash +nano ~/wol-server/.env +``` + +### Available Configuration Options + +| Setting | Description | Default | +|---------|-------------|---------| +| `SERVER_NAME` | Hostname/IP of target server | pippo | +| `SERVER_USER` | SSH username for shutdown | root | +| `MAC_ADDRESS` | MAC address for Wake-on-LAN | aa:bb:cc:dd:ee:ff | +| `PORT` | Web interface port | 8080 | + +After changing configuration, restart the service: +```bash +sudo systemctl restart wol-server +``` + ## Usage -Access the web interface by navigating to `http://raspberry-pi-ip:8080` in your browser. +### Accessing the Interface -The interface provides: +Open a web browser and navigate to: +``` +http://your-pi-ip:8080 +``` -- Current server status (Online/Offline) -- Boot button - sends a Wake-on-LAN magic packet to your server -- Shutdown button - safely shuts down your server via SSH -- Refresh button - manually updates the status display +### Features + +- **Status Checking**: The interface shows the current status (Online/Offline) +- **Booting**: Click the "Boot" button to send a WOL magic packet +- **Shutting Down**: Click "Shutdown" and enter your SSH password when prompted + +## Maintenance + +### Checking Service Status + +```bash +sudo systemctl status wol-server +``` + +### Viewing Logs + +```bash +sudo journalctl -u wol-server -f +``` + +### Updating + +To update to a newer version: + +1. Download and transfer the latest release +2. Stop the service: + ```bash + sudo systemctl stop wol-server + ``` +3. Extract the new files: + ```bash + tar -xzf wol-server.tar.gz + ``` +4. Run the install script: + ```bash + ./install.sh + ``` ## Troubleshooting -- **Server won't boot**: - - Verify that Wake-on-LAN is enabled in your server's BIOS/UEFI - - Confirm the MAC address is correct - - Check if your router blocks Wake-on-LAN packets +### Service Won't Start -- **Shutdown doesn't work**: - - Verify SSH key setup - - Check sudo configuration on target server - - Test manual SSH command: `ssh user@server sudo shutdown -h now` +Check for template errors: +```bash +ls -la ~/wol-server/templates/ +``` -- **Web interface not accessible**: - - Ensure the service is running: `sudo systemctl status wol-server` - - Check for firewall rules blocking port 8080 - - Verify the Raspberry Pi is connected to the network +Verify the .env file exists: +```bash +cat ~/wol-server/.env +``` -## Contributing +### Boot Command Not Working -Contributions are welcome! Please feel free to submit a Pull Request. +1. Ensure `wakeonlan` is installed: + ```bash + which wakeonlan || sudo apt-get install wakeonlan + ``` +2. Verify the MAC address is correct in your .env file +3. Make sure the target device is properly configured for Wake-on-LAN -## License +### Shutdown Not Working + +1. Verify `sshpass` is installed: + ```bash + which sshpass || sudo apt-get install sshpass + ``` +2. Check that the SERVER_USER setting in .env is correct +3. Ensure SSH access is working between your Pi and the target server + +## Advanced Configuration + +### Running on a Different Port + +Edit the `.env` file: +```bash +echo "PORT=8181" >> ~/wol-server/.env +``` + +### Multiple Target Machines + +To control multiple devices, you can install multiple instances: +```bash +# Create a second instance +mkdir -p ~/wol-server2/templates +cp -r ~/wol-server/templates/* ~/wol-server2/templates/ +cp ~/wol-server/wol-server ~/wol-server2/ + +# Different config +cat > ~/wol-server2/.env << EOL +SERVER_NAME=server2 +SERVER_USER=admin +MAC_ADDRESS=aa:bb:cc:dd:ee:ff +PORT=8081 +EOL + +# Create a new service +sudo cp /etc/systemd/system/wol-server.service /etc/systemd/system/wol-server2.service +sudo sed -i 's|/home/pi/wol-server|/home/pi/wol-server2|g' /etc/systemd/system/wol-server2.service +sudo systemctl daemon-reload +sudo systemctl enable wol-server2 +sudo systemctl start wol-server2 +``` + +## Project Information + +Designed for use with Raspberry Pi to provide a simple way to manage servers and devices on your local network. The web interface makes it easy to power on and off machines without having to remember MAC addresses or commands. + +### Contributing + +Contributions are welcome! Feel free to submit pull requests or open issues to help improve this project. + +### License This project is licensed under the MIT License - see the LICENSE file for details. - -## Acknowledgments - -- Inspired by the need for a simple, lightweight server power management tool -- Thanks to the Go community for the excellent standard library that makes web development straightforward diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c4afdd7 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/thisloke/wol-server + +go 1.20 + +require github.com/joho/godotenv v1.5.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d61b19e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000..0c4333e --- /dev/null +++ b/handlers.go @@ -0,0 +1,216 @@ +package main + +import ( + "log" + "net/http" + "runtime" +) + +// Handle the root route - show status +func indexHandler(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + + online := isServerOnline() + status := "Online" + color := "#4caf50" // Material green + if !online { + status = "Offline" + color = "#d32f2f" // Material red + } + + data := StatusData{ + Server: serverName, + Status: status, + Color: color, + IsTestMode: runtime.GOOS == "darwin", + AskPassword: false, + ErrorMessage: "", + } + + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } +} + +// Handle boot request +func bootHandler(w http.ResponseWriter, r *http.Request) { + if !isServerOnline() { + // Boot the server using wakeonlan + err := sendWakeOnLAN() + if err != nil { + log.Printf("Error booting server: %v", err) + } + + // Display booting status + data := StatusData{ + Server: serverName, + Status: "Booting", + Color: "#607d8b", // Material blue-gray + IsTestMode: runtime.GOOS == "darwin", + AskPassword: false, + } + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } + } else { + // Server is already online + data := StatusData{ + Server: serverName, + Status: "Online", + Color: "#4caf50", // Material green + IsTestMode: runtime.GOOS == "darwin", + AskPassword: false, + } + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } + } +} + +// Handle shutdown confirmation request +func confirmShutdownHandler(w http.ResponseWriter, r *http.Request) { + online := isServerOnline() + + if !online { + // Server is already offline + data := StatusData{ + Server: serverName, + Status: "Offline", + Color: "#d32f2f", // Material red + IsTestMode: runtime.GOOS == "darwin", + AskPassword: false, + } + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } + return + } + + // Show confirmation dialog + data := StatusData{ + Server: serverName, + Status: "Online", + Color: "#4caf50", // Material green + IsTestMode: runtime.GOOS == "darwin", + ConfirmShutdown: true, + AskPassword: false, + } + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } +} + +// Handle password entry for shutdown +func enterPasswordHandler(w http.ResponseWriter, r *http.Request) { + if !isServerOnline() { + // Server is already offline, redirect to home + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + + // Show password entry dialog + data := StatusData{ + Server: serverName, + Status: "Online", + Color: "#4caf50", // Material green + IsTestMode: runtime.GOOS == "darwin", + AskPassword: true, + } + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } +} + +// Handle actual shutdown request +func shutdownHandler(w http.ResponseWriter, r *http.Request) { + // Only process POST requests for security + if r.Method != "POST" { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + + // Parse form data to get password + if err := r.ParseForm(); err != nil { + log.Printf("Error parsing form: %v", err) + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + + // Get password from form + password := r.FormValue("password") + + if password == "" { + // Show password form again with error + data := StatusData{ + Server: serverName, + Status: "Online", + Color: "#4caf50", + IsTestMode: runtime.GOOS == "darwin", + AskPassword: true, + ErrorMessage: "Password cannot be empty", + } + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } + return + } + + if isServerOnline() { + // Shutdown the server + err := shutdownServer(password) + if err != nil { + log.Printf("Error shutting down server: %v", err) + + // Show password form again with error + data := StatusData{ + Server: serverName, + Status: "Online", + Color: "#4caf50", + IsTestMode: runtime.GOOS == "darwin", + AskPassword: true, + ErrorMessage: "Failed to shutdown server. Please check your password.", + } + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } + return + } + + // Display shutting down status + data := StatusData{ + Server: serverName, + Status: "Shutting down", + Color: "#5d4037", // Material brown + IsTestMode: runtime.GOOS == "darwin", + AskPassword: false, + } + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } + } else { + // Server is already offline + data := StatusData{ + Server: serverName, + Status: "Offline", + Color: "#d32f2f", // Material red + IsTestMode: runtime.GOOS == "darwin", + AskPassword: false, + } + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Template render error: %v", err) + } + } +} diff --git a/main.go b/main.go index affeb98..7bb50e8 100644 --- a/main.go +++ b/main.go @@ -2,405 +2,63 @@ package main import ( "fmt" - "html/template" "log" "net/http" - "os/exec" + "os" "runtime" + + "github.com/joho/godotenv" ) -const ( - serverName = "delemaco" // Server to ping - macAddress = "b8:cb:29:a1:f3:88" // MAC address of the server - port = "8080" // Port to listen on +// Default values +var ( + serverName = "server" // Server to ping + serverUser = "root" // SSH username + macAddress = "aa:aa:aa:aa:aa:aa" // MAC address of the server + port = "8080" // Port to listen on ) -// StatusData holds data for the HTML template -type StatusData struct { - Server string - Status string - Color string - IsTestMode bool -} - -// Check if server is online -func isServerOnline() bool { - var cmd *exec.Cmd - - // macOS and Linux have slightly different ping commands - if runtime.GOOS == "darwin" { - cmd = exec.Command("ping", "-c", "1", "-W", "1000", serverName) - } else { - cmd = exec.Command("ping", "-c", "1", "-W", "1", serverName) +func loadEnvVariables() { + // Load .env file if it exists + if err := godotenv.Load(); err != nil { + log.Println("No .env file found, using default values") } - err := cmd.Run() - return err == nil -} - -// Send WOL packet -func sendWakeOnLAN() error { - log.Printf("Sending WOL packet to %s (%s)", serverName, macAddress) - cmd := exec.Command("wakeonlan", macAddress) - return cmd.Run() -} - -// Shutdown server -func shutdownServer() error { - - // Real shutdown command for Linux - log.Printf("Sending shutdown command to %s", serverName) - cmd := exec.Command("ssh", serverName, "sudo", "shutdown", "-h", "now") - return cmd.Run() -} - -// Handle the root route - show status -func indexHandler(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.NotFound(w, r) - return + // Override defaults with environment variables if they exist + if envServerName := os.Getenv("SERVER_NAME"); envServerName != "" { + serverName = envServerName } - online := isServerOnline() - status := "Online" - color := "#4caf50" // Material green - if !online { - status = "Offline" - color = "#d32f2f" // Material red + if envServerUser := os.Getenv("SERVER_USER"); envServerUser != "" { + serverUser = envServerUser } - data := StatusData{ - Server: serverName, - Status: status, - Color: color, - IsTestMode: runtime.GOOS == "darwin", + if envMacAddress := os.Getenv("MAC_ADDRESS"); envMacAddress != "" { + macAddress = envMacAddress } - renderTemplate(w, data) -} - -// Handle boot request -func bootHandler(w http.ResponseWriter, r *http.Request) { - if !isServerOnline() { - // Boot the server using wakeonlan - err := sendWakeOnLAN() - if err != nil { - log.Printf("Error booting server: %v", err) - } - - // Display booting status - data := StatusData{ - Server: serverName, - Status: "Booting", - Color: "#607d8b", // Material blue-gray - IsTestMode: runtime.GOOS == "darwin", - } - renderTemplate(w, data) - } else { - // Server is already online - data := StatusData{ - Server: serverName, - Status: "Online", - Color: "#4caf50", // Material green - IsTestMode: runtime.GOOS == "darwin", - } - renderTemplate(w, data) - } -} - -// Handle shutdown request -func shutdownHandler(w http.ResponseWriter, r *http.Request) { - if isServerOnline() { - // Shutdown the server - err := shutdownServer() - if err != nil { - log.Printf("Error shutting down server: %v", err) - } - - // Display shutting down status - data := StatusData{ - Server: serverName, - Status: "Shutting down", - Color: "#5d4037", // Material brown - IsTestMode: runtime.GOOS == "darwin", - } - renderTemplate(w, data) - } else { - // Server is already offline - data := StatusData{ - Server: serverName, - Status: "Offline", - Color: "#d32f2f", // Material red - IsTestMode: runtime.GOOS == "darwin", - } - renderTemplate(w, data) - } -} - -// Render the HTML template -func renderTemplate(w http.ResponseWriter, data StatusData) { - tmpl, err := template.New("status").Parse(` - - - - - Server Status: {{.Server}} - - - - - - -
-
-
-

{{.Status}}

-
Server: {{.Server}}
- - -
- - {{if .IsTestMode}} -
-
- Running on macOS in test mode. Wake-on-LAN packets are sent, but remote shutdown is simulated. -
-
- {{end}} - - -
- -`) - - if err != nil { - http.Error(w, "Failed to load template", http.StatusInternalServerError) - log.Printf("Template error: %v", err) - return + if envPort := os.Getenv("PORT"); envPort != "" { + port = envPort } - err = tmpl.Execute(w, data) - if err != nil { - http.Error(w, "Failed to render template", http.StatusInternalServerError) - log.Printf("Template render error: %v", err) - } + log.Printf("Configuration loaded: SERVER_NAME=%s, SERVER_USER=%s, MAC_ADDRESS=%s, PORT=%s", + serverName, serverUser, macAddress, port) } func main() { + // Load environment variables + loadEnvVariables() + + // Setup template + if err := setupTemplate(); err != nil { + log.Fatalf("Failed to setup template: %v", err) + } + // Register route handlers http.HandleFunc("/", indexHandler) http.HandleFunc("/boot", bootHandler) + http.HandleFunc("/confirm-shutdown", confirmShutdownHandler) + http.HandleFunc("/enter-password", enterPasswordHandler) http.HandleFunc("/shutdown", shutdownHandler) // Start the server @@ -408,7 +66,7 @@ func main() { log.Printf("Starting WOL Server on http://localhost%s", listenAddr) if runtime.GOOS == "darwin" { - log.Println("Running on macOS in test mode - remote shutdown commands will be simulated") + log.Println("Running on macOS - commands will be executed using the provided password") } if err := http.ListenAndServe(listenAddr, nil); err != nil { diff --git a/templates/status.html b/templates/status.html new file mode 100644 index 0000000..2c00eaa --- /dev/null +++ b/templates/status.html @@ -0,0 +1,402 @@ + + + + + + Server Status: {{.Server}} + + + + + + +
+
+
+

{{.Status}}

+
Server: {{.Server}}
+ + +
+ + {{if .IsTestMode}} +
+
+ Running on macOS. Commands will be executed using the provided password. +
+
+ {{end}} + + +
+ + {{if .ConfirmShutdown}} + + {{end}} + + {{if .AskPassword}} + + {{end}} + + diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..28e111b --- /dev/null +++ b/utils.go @@ -0,0 +1,100 @@ +package main + +import ( + "bytes" + "fmt" + "html/template" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" +) + +// StatusData holds data for the HTML template +type StatusData struct { + Server string + Status string + Color string + IsTestMode bool + ConfirmShutdown bool + AskPassword bool + ErrorMessage string +} + +var tmpl *template.Template + +// Setup the HTML template +func setupTemplate() error { + // Check if templates directory exists, create if not + if _, err := os.Stat("templates"); os.IsNotExist(err) { + if err := os.Mkdir("templates", 0755); err != nil { + return fmt.Errorf("failed to create templates directory: %v", err) + } + } + + // Path to the template file + templatePath := filepath.Join("templates", "status.html") + + // Check if the template file exists + if _, err := os.Stat(templatePath); os.IsNotExist(err) { + log.Printf("Template file not found at %s. Please create it.", templatePath) + return fmt.Errorf("template file not found: %s", templatePath) + } + + // Parse the template from the file + var err error + tmpl, err = template.ParseFiles(templatePath) + if err != nil { + return fmt.Errorf("failed to parse template: %v", err) + } + + return nil +} + +// Check if server is online +func isServerOnline() bool { + var cmd *exec.Cmd + + // macOS and Linux have slightly different ping commands + if runtime.GOOS == "darwin" { + cmd = exec.Command("ping", "-c", "1", "-W", "1000", serverName) + } else { + cmd = exec.Command("ping", "-c", "1", "-W", "1", serverName) + } + + err := cmd.Run() + return err == nil +} + +// Send WOL packet +func sendWakeOnLAN() error { + log.Printf("Sending WOL packet to %s (%s)", serverName, macAddress) + cmd := exec.Command("wakeonlan", macAddress) + return cmd.Run() +} + +// Shutdown server with password +func shutdownServer(password string) error { + log.Printf("Sending shutdown command to %s", serverName) + + // Add more SSH options to handle potential issues + cmd := exec.Command("sshpass", "-p", password, "ssh", + "-o", "StrictHostKeyChecking=no", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "LogLevel=ERROR", + fmt.Sprintf("%s@%s", serverUser, serverName), + "sudo", "-S", "shutdown", "-h", "now") + + // Capture stderr to log any error messages + var stderr bytes.Buffer + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + log.Printf("SSH Error details: %s", stderr.String()) + return fmt.Errorf("SSH command failed: %v - %s", err, stderr.String()) + } + + return nil +}