diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..04f30f0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/thisloke/wol-delemaco + +go 1.20 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..dac22ec 100644 --- a/main.go +++ b/main.go @@ -2,405 +2,29 @@ package main import ( "fmt" - "html/template" "log" "net/http" - "os/exec" "runtime" ) const ( serverName = "delemaco" // Server to ping + serverUser = "root" // SSH username macAddress = "b8:cb:29:a1:f3:88" // 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) - } - - 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 - } - - 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", - } - - 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 - } - - err = tmpl.Execute(w, data) - if err != nil { - http.Error(w, "Failed to render template", http.StatusInternalServerError) - log.Printf("Template render error: %v", err) - } -} - func main() { + // 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 +32,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 +}