feat(server,client): remote install mode, install survives missing systemd
This commit is contained in:
1 parent
c2e9284a63
commit
e89cf7864a
6 files changed
+83
-18
No files matched your search
@@ -1,6 +1,10 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
url="{{URL}}"
|
||||
code="{{CODE}}"
|
||||
mode="{{MODE}}"
|
||||
|
||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
arch=$(uname -m)
|
||||
case "$arch" in
|
||||
@@ -11,12 +15,26 @@ esac
|
||||
|
||||
bin="$HOME/.local/bin"
|
||||
mkdir -p "$bin"
|
||||
curl -fsSL "{{URL}}/dl/lodge-$os-$arch" -o "$bin/lodge.new"
|
||||
curl -fsSL "$url/dl/lodge-$os-$arch" -o "$bin/lodge.new"
|
||||
chmod +x "$bin/lodge.new"
|
||||
mv "$bin/lodge.new" "$bin/lodge"
|
||||
|
||||
"$bin/lodge" login "{{URL}}" "{{CODE}}"
|
||||
"$bin/lodge" install
|
||||
if [ "$mode" = login ]; then
|
||||
"$bin/lodge" login "$url" "$code"
|
||||
"$bin/lodge" install
|
||||
else
|
||||
plist="$HOME/Library/LaunchAgents/com.kotikot.lodge.plist"
|
||||
unit="$HOME/.config/systemd/user/lodge.service"
|
||||
restarted=yes
|
||||
if [ -f "$plist" ]; then
|
||||
launchctl kickstart -k "gui/$(id -u)/com.kotikot.lodge" || restarted=no
|
||||
elif [ -f "$unit" ]; then
|
||||
systemctl --user restart lodge.service || restarted=no
|
||||
fi
|
||||
[ "$restarted" = yes ] || echo "lodge: restart the running daemon so it runs the new binary" >&2
|
||||
(cd / && "$bin/lodge" remote "$url" "$code")
|
||||
[ -f "$plist" ] || [ -f "$unit" ] || "$bin/lodge" install
|
||||
fi
|
||||
|
||||
case ":$PATH:" in
|
||||
*":$bin:"*) ;;
|
||||
|
||||
@@ -59,7 +59,8 @@ type machineKey struct{}
|
||||
func New(store *Store, config Config) http.Handler {
|
||||
s := &Server{config: config, store: store, subscribers: map[chan protocol.Event]struct{}{}, streams: map[int64]int{}, seen: map[int64]int64{}}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /i/{code}", s.install)
|
||||
mux.HandleFunc("GET /i/{code}", s.install("login"))
|
||||
mux.HandleFunc("GET /i/{code}/remote", s.install("remote"))
|
||||
mux.Handle("GET /dl/", http.StripPrefix("/dl/", http.FileServer(http.Dir(config.ClientsDir))))
|
||||
mux.HandleFunc("POST /api/claim", s.claim)
|
||||
mux.HandleFunc("POST /api/session", s.login)
|
||||
@@ -159,14 +160,16 @@ func (s *Server) publicURL(r *http.Request) string {
|
||||
return scheme + "://" + r.Host
|
||||
}
|
||||
|
||||
func (s *Server) install(w http.ResponseWriter, r *http.Request) {
|
||||
code := r.PathValue("code")
|
||||
if !codePattern.MatchString(code) || !s.store.CodeValid(r.Context(), code) {
|
||||
http.Error(w, "echo 'lodge: this install link has expired, create a new one' >&2; exit 1", http.StatusNotFound)
|
||||
return
|
||||
func (s *Server) install(mode string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
code := r.PathValue("code")
|
||||
if !codePattern.MatchString(code) || !s.store.CodeValid(r.Context(), code) {
|
||||
http.Error(w, "echo 'lodge: this install link has expired, create a new one' >&2; exit 1", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/x-shellscript")
|
||||
_, _ = strings.NewReplacer("{{URL}}", s.publicURL(r), "{{CODE}}", code, "{{MODE}}", mode).WriteString(w, installScript)
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/x-shellscript")
|
||||
_, _ = strings.NewReplacer("{{URL}}", s.publicURL(r), "{{CODE}}", code).WriteString(w, installScript)
|
||||
}
|
||||
|
||||
func (s *Server) setSession(w http.ResponseWriter, r *http.Request, token string, expires time.Time) {
|
||||
@@ -264,9 +267,10 @@ func (s *Server) createCode(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, protocol.CodeResponse{
|
||||
Code: code,
|
||||
Command: fmt.Sprintf("curl -fsSL %s/i/%s | sh", s.publicURL(r), code),
|
||||
ExpiresAt: expires.UnixMilli(),
|
||||
Code: code,
|
||||
Command: fmt.Sprintf("curl -fsSL %s/i/%s | sh", s.publicURL(r), code),
|
||||
RemoteCommand: fmt.Sprintf("curl -fsSL %s/i/%s/remote | sh", s.publicURL(r), code),
|
||||
ExpiresAt: expires.UnixMilli(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -204,3 +206,36 @@ func TestMachinesShowWhoIsOnline(t *testing.T) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallScriptModes(t *testing.T) {
|
||||
url := newServer(t)
|
||||
var code protocol.CodeResponse
|
||||
call(t, http.MethodPost, url+"/api/codes", adminToken, nil, &code)
|
||||
if code.RemoteCommand != "curl -fsSL "+url+"/i/"+code.Code+"/remote | sh" {
|
||||
t.Fatalf("remote command = %q", code.RemoteCommand)
|
||||
}
|
||||
for path, mode := range map[string]string{"/i/" + code.Code: "login", "/i/" + code.Code + "/remote": "remote"} {
|
||||
res, err := http.Get(url + path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
script, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK || !strings.Contains(string(script), `mode="`+mode+`"`) || !strings.Contains(string(script), `code="`+code.Code+`"`) {
|
||||
t.Fatalf("%s: %d\n%s", path, res.StatusCode, script)
|
||||
}
|
||||
check := exec.Command("sh", "-n")
|
||||
check.Stdin = bytes.NewReader(script)
|
||||
if out, err := check.CombinedOutput(); err != nil {
|
||||
t.Fatalf("%s is not valid sh: %v %s", path, err, out)
|
||||
}
|
||||
}
|
||||
res, err := http.Get(url + "/i/abcdef/remote")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
if res.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("unknown code: %d", res.StatusCode)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user