79 lines
2.4 KiB
Go
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)
|
|
}
|
|
}
|