feat: terminal as a chosen OS user, create users from the panel (0.0.2)

- agent reports the OS user it runs as (enroll + poll header)
- per-machine terminal user on the edit page; defaults to the agent's own user (root on Linux)
- create a new user from the panel: name, password, optional sudo group
- show the reason in the terminal when a shell fails to start
- bump version to 0.0.2
This commit is contained in:
redtea committed 2026-10-13 01:28:57 +03:00
1 parent be7fbabd9e
commit 3dde07f403
18 files changed
+530 -45

No files matched your search

+12 -5
View File
@@ -28,7 +28,7 @@ func (s *Server) handleEnroll(w http.ResponseWriter, r *http.Request) {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
id, token, err := s.enroll(req.Name, req.Hostname, req.OS, req.Arch, req.Version)
id, token, err := s.enroll(req.Name, req.Hostname, req.OS, req.Arch, req.Version, req.User)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
@@ -43,9 +43,12 @@ func (s *Server) handlePoll(w http.ResponseWriter, r *http.Request) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
// The agent sends its version occasionally; record it when present.
if v := r.Header.Get("X-Agent-Version"); v != "" && v != a.Version {
_ = s.db.TouchAgentVersion(a.ID, v)
// The agent sends its version and OS user occasionally; record them when
// present. Agents older than 0.0.2 send no user, so keep what we have.
v := r.Header.Get("X-Agent-Version")
u := firstNonEmpty(r.Header.Get("X-Agent-User"), a.RunUser)
if v != "" && (v != a.Version || u != a.RunUser) {
_ = s.db.TouchAgentInfo(a.ID, v, u)
} else {
_ = s.db.TouchAgent(a.ID)
}
@@ -121,7 +124,11 @@ func (s *Server) handleTermOut(w http.ResponseWriter, r *http.Request) {
return
}
if msg.Exit {
s.hub.CloseSession(sess.ID)
// End the stream now, but keep the session reachable for a moment so
// the panel can still collect the last output (e.g. why the shell
// failed to start).
sess.close()
time.AfterFunc(pollHold, func() { s.hub.CloseSession(sess.ID) })
}
w.WriteHeader(http.StatusNoContent)
}