gapmind/services/replica/main.go
2026-09-11 23:59:05 -04:00

79 lines
2.4 KiB
Go

package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"time"
)
type report struct {
NodeID string `json:"nodeId"`
Kind string `json:"kind"`
Address string `json:"address,omitempty"`
Version string `json:"version"`
Status string `json:"status"`
Metadata map[string]any `json:"metadata"`
}
func main() {
listen := flag.String("listen", ":8091", "HTTP listen address")
source := flag.String("source", ".", "directory permitted for replication")
flag.Parse()
root, err := filepath.Abs(*source)
if err != nil {
panic(err)
}
go heartbeat(root)
http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) })
http.HandleFunc("/replicate", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "POST required", http.StatusMethodNotAllowed)
return
}
var request struct {
Host string `json:"host"`
Destination string `json:"destination"`
}
if json.NewDecoder(r.Body).Decode(&request) != nil || request.Host == "" || request.Destination == "" || filepath.IsAbs(request.Destination) || filepath.Clean(request.Destination) == ".." {
http.Error(w, "host and relative destination required", http.StatusBadRequest)
return
}
output, err := exec.Command("scp", "-r", root+"/.", request.Host+":"+request.Destination).CombinedOutput()
if err != nil {
http.Error(w, string(output), http.StatusBadGateway)
return
}
w.WriteHeader(http.StatusAccepted)
})
fmt.Println(http.ListenAndServe(*listen, nil))
}
func heartbeat(root string) {
central := os.Getenv("CENTRAL_URL")
if central == "" {
return
}
host, _ := os.Hostname()
for {
body, _ := json.Marshal(report{NodeID: host, Kind: "replica", Address: os.Getenv("PEER_PUBLIC_URL"), Version: "0.1.0", Status: "online", Metadata: map[string]any{"source": root}})
request, _ := http.NewRequest(http.MethodPost, central+"/api/peers/heartbeat", bytes.NewReader(body))
request.Header.Set("Content-Type", "application/json")
if token := os.Getenv("PEER_API_TOKEN"); token != "" {
request.Header.Set("Authorization", "Bearer "+token)
}
response, err := http.DefaultClient.Do(request)
if err == nil {
_, _ = io.Copy(io.Discard, response.Body)
_ = response.Body.Close()
}
time.Sleep(time.Minute)
}
}