Loading Dockerfile +3 −1 Original line number Diff line number Diff line Loading @@ -2,7 +2,9 @@ FROM golang:1.20 AS builder COPY serial/ /src/serial/ WORKDIR /src/serial RUN go get -d -v golang.org/x/net/html RUN go get -d -v github.com/gorilla/mux RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /src/serial/main . FROM debian:bookworm-20230320-slim Loading power.sh +23 −14 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ set -eu # Configure QEMU for graceful shutdown QEMU_MONPORT=7100 QEMU_POWERDOWN_TIMEOUT=30 QEMU_POWERDOWN_TIMEOUT=50 _QEMU_PID=/run/qemu.pid _QEMU_SHUTDOWN_COUNTER=/run/qemu.counter Loading @@ -22,24 +22,33 @@ _graceful_shutdown(){ local QEMU_POWERDOWN_TIMEOUT="${QEMU_POWERDOWN_TIMEOUT:-120}" set +e echo "Received $1 signal.." echo "Received $1 signal, shutting down..." echo 0 > "${_QEMU_SHUTDOWN_COUNTER}" # Don't send the powerdown signal because vDSM ignores ACPI signals # echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null # Send shutdown command to guest agent tools instead via serial port RESPONSE=$(curl -s -m 2 -S http://127.0.0.1:2210/write?command=6 2>&1) if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then echo "Could not send shutdown command to guest, error: $RESPONSE" FILE="${IMG}/agent.ver" [ ! -f "$FILE" ] && echo "1" > "$FILE" AGENT_VERSION=$(cat "${FILE}") # Don't send the powerdown signal because Synology ignores it # echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null if ((AGENT_VERSION < 2)); then echo "Please update the agent to allow gracefull shutdowns..." pkill -f qemu-system-x86_64 else # Send a NMI interrupt which will be detected by the agent # Send a NMI interrupt which will be detected by the kernel echo 'nmi' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null fi fi while [ "$(cat ${_QEMU_SHUTDOWN_COUNTER})" -lt "${QEMU_POWERDOWN_TIMEOUT}" ]; do # Increase the counter Loading @@ -54,7 +63,7 @@ _graceful_shutdown(){ fi done echo "Killing VM.." echo "Quitting..." echo 'quit' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null || true return Loading serial/main.go +88 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,9 @@ import ( "log" "net" "strconv" "net/http" "math/rand" "github.com/gorilla/mux" ) type REQ struct { Loading Loading @@ -35,16 +38,27 @@ var VMMVersion = flag.String("vmmversion", "2.6.1-12139", "VMM version") var VMMTimestamp = flag.Int("vmmts", 1679863686, "VMM Timestamp") var Cluster_UUID = "3bdea92b-68f4-4fe9-aa4b-d645c3c63864" var ApiPort = flag.String("api", ":2210", "API port") var ListenAddr = flag.String("addr", "0.0.0.0:12345", "Listen address") var LastConnection net.Conn func main() { flag.Parse() r := mux.NewRouter() r.HandleFunc("/", home) r.HandleFunc("/write", write) go http.ListenAndServe(*ApiPort, r) listener, err := net.Listen("tcp", *ListenAddr) if err != nil { log.Println("Error listening", err.Error()) return } log.Println("Start listen on " + *ListenAddr) for { Loading @@ -54,11 +68,15 @@ func main() { return } log.Printf("New connection from %s\n", conn.RemoteAddr().String()) go incoming_conn(conn) } } func incoming_conn(conn net.Conn) { LastConnection = conn for { buf := make([]byte, 4096) len, err := conn.Read(buf) Loading Loading @@ -173,3 +191,73 @@ func process_req(buf []byte, conn net.Conn) { conn.Write(buf) } } func home(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(`{"status": "error", "data": null, "message": "No command specified"}`)) } func write(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var err error var commandID int query := r.URL.Query() commandID, err = strconv.Atoi(query.Get("command")) if (err != nil || commandID < 1) { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(`{"status": "error", "data": null, "message": "Invalid command ID"}`)) return } if (send_command((int32)(commandID), 1) == false) { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(`{"status": "error", "data": null, "message": "Failed to send command"}`)) return } w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status": "success", "data": null, "message": null}`)) return } func send_command(CommandID int32, SubCommand int32) bool { var req REQ req.CommandID = CommandID req.SubCommand = SubCommand req.IsReq = 1 req.IsResp = 0 req.ReqLength = 0 req.RespLength = 0 req.NeedResponse = 0 req.GuestID = 10000000 req.RandID = rand.Int63() var buf = make([]byte, 0, 4096) var writer = bytes.NewBuffer(buf) // write to buf binary.Write(writer, binary.LittleEndian, &req) res := writer.Bytes() // full fill 4096 buf = make([]byte, 4096, 4096) copy(buf, res) //log.Printf("Writing command %d\n", CommandID) if (LastConnection == nil) { return false } LastConnection.Write(buf) return true } Loading
Dockerfile +3 −1 Original line number Diff line number Diff line Loading @@ -2,7 +2,9 @@ FROM golang:1.20 AS builder COPY serial/ /src/serial/ WORKDIR /src/serial RUN go get -d -v golang.org/x/net/html RUN go get -d -v github.com/gorilla/mux RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /src/serial/main . FROM debian:bookworm-20230320-slim Loading
power.sh +23 −14 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ set -eu # Configure QEMU for graceful shutdown QEMU_MONPORT=7100 QEMU_POWERDOWN_TIMEOUT=30 QEMU_POWERDOWN_TIMEOUT=50 _QEMU_PID=/run/qemu.pid _QEMU_SHUTDOWN_COUNTER=/run/qemu.counter Loading @@ -22,24 +22,33 @@ _graceful_shutdown(){ local QEMU_POWERDOWN_TIMEOUT="${QEMU_POWERDOWN_TIMEOUT:-120}" set +e echo "Received $1 signal.." echo "Received $1 signal, shutting down..." echo 0 > "${_QEMU_SHUTDOWN_COUNTER}" # Don't send the powerdown signal because vDSM ignores ACPI signals # echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null # Send shutdown command to guest agent tools instead via serial port RESPONSE=$(curl -s -m 2 -S http://127.0.0.1:2210/write?command=6 2>&1) if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then echo "Could not send shutdown command to guest, error: $RESPONSE" FILE="${IMG}/agent.ver" [ ! -f "$FILE" ] && echo "1" > "$FILE" AGENT_VERSION=$(cat "${FILE}") # Don't send the powerdown signal because Synology ignores it # echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null if ((AGENT_VERSION < 2)); then echo "Please update the agent to allow gracefull shutdowns..." pkill -f qemu-system-x86_64 else # Send a NMI interrupt which will be detected by the agent # Send a NMI interrupt which will be detected by the kernel echo 'nmi' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null fi fi while [ "$(cat ${_QEMU_SHUTDOWN_COUNTER})" -lt "${QEMU_POWERDOWN_TIMEOUT}" ]; do # Increase the counter Loading @@ -54,7 +63,7 @@ _graceful_shutdown(){ fi done echo "Killing VM.." echo "Quitting..." echo 'quit' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null || true return Loading
serial/main.go +88 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,9 @@ import ( "log" "net" "strconv" "net/http" "math/rand" "github.com/gorilla/mux" ) type REQ struct { Loading Loading @@ -35,16 +38,27 @@ var VMMVersion = flag.String("vmmversion", "2.6.1-12139", "VMM version") var VMMTimestamp = flag.Int("vmmts", 1679863686, "VMM Timestamp") var Cluster_UUID = "3bdea92b-68f4-4fe9-aa4b-d645c3c63864" var ApiPort = flag.String("api", ":2210", "API port") var ListenAddr = flag.String("addr", "0.0.0.0:12345", "Listen address") var LastConnection net.Conn func main() { flag.Parse() r := mux.NewRouter() r.HandleFunc("/", home) r.HandleFunc("/write", write) go http.ListenAndServe(*ApiPort, r) listener, err := net.Listen("tcp", *ListenAddr) if err != nil { log.Println("Error listening", err.Error()) return } log.Println("Start listen on " + *ListenAddr) for { Loading @@ -54,11 +68,15 @@ func main() { return } log.Printf("New connection from %s\n", conn.RemoteAddr().String()) go incoming_conn(conn) } } func incoming_conn(conn net.Conn) { LastConnection = conn for { buf := make([]byte, 4096) len, err := conn.Read(buf) Loading Loading @@ -173,3 +191,73 @@ func process_req(buf []byte, conn net.Conn) { conn.Write(buf) } } func home(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(`{"status": "error", "data": null, "message": "No command specified"}`)) } func write(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var err error var commandID int query := r.URL.Query() commandID, err = strconv.Atoi(query.Get("command")) if (err != nil || commandID < 1) { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(`{"status": "error", "data": null, "message": "Invalid command ID"}`)) return } if (send_command((int32)(commandID), 1) == false) { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(`{"status": "error", "data": null, "message": "Failed to send command"}`)) return } w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status": "success", "data": null, "message": null}`)) return } func send_command(CommandID int32, SubCommand int32) bool { var req REQ req.CommandID = CommandID req.SubCommand = SubCommand req.IsReq = 1 req.IsResp = 0 req.ReqLength = 0 req.RespLength = 0 req.NeedResponse = 0 req.GuestID = 10000000 req.RandID = rand.Int63() var buf = make([]byte, 0, 4096) var writer = bytes.NewBuffer(buf) // write to buf binary.Write(writer, binary.LittleEndian, &req) res := writer.Bytes() // full fill 4096 buf = make([]byte, 4096, 4096) copy(buf, res) //log.Printf("Writing command %d\n", CommandID) if (LastConnection == nil) { return false } LastConnection.Write(buf) return true }