diff --git a/README.md b/README.md index d7b72df..4787a64 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# LPRA — LongPolling Remote Access +# LPRA - LongPolling Remote Access Лёгкий self-hosted сервис для доступа к Linux-машинам **без белого IP** (за NAT). Агент на клиенте сам исходяще подключается к серверу по HTTP long-polling; админ @@ -11,9 +11,9 @@ ``` ## Компоненты -- **Сервер** (`cmd/server`) — админ-панель + точка встречи для агентов. За nginx (TLS). -- **Агент** (`cmd/agent`) — Linux-бинарь, systemd-сервис от root, PTY по запросу. -- **install.sh** — интерактивный установщик, отдаётся сервером как статика. +- **Сервер** (`cmd/server`) - админ-панель + точка встречи для агентов. За nginx (TLS). +- **Агент** (`cmd/agent`) - Linux-бинарь, systemd-сервис от root, PTY по запросу. +- **install.sh** - интерактивный установщик, отдаётся сервером как статика. ## Сборка ```sh @@ -28,7 +28,7 @@ Поставьте перед ним nginx по `deploy/nginx.conf.example` (TLS, long-poll/WS). Локально без TLS можно добавить `-insecure-cookies`. -Первый запуск: откройте `/setup` — мастер создаст администратора. Дальше `/settings`: +Первый запуск: откройте `/setup` - мастер создаст администратора. Дальше `/settings`: адрес хоста (по умолчанию `https://localhost:8445`), enroll-секрет и готовая команда установки. @@ -37,36 +37,36 @@ ```sh curl -fsSL https://localhost:8445/install.sh | sudo HOST=https://localhost:8445 ENROLL_SECRET=<секрет> sh ``` -Или интерактивно (спросит секрет, а если агент уже стоит — предложит удалить): +Или интерактивно (спросит секрет, а если агент уже стоит - предложит удалить): ```sh curl -fsSL https://localhost:8445/install.sh | sudo sh ``` ## Проброс портов и прямой туннель Правила проброса создаются на странице машины. Режим `http` идёт через адрес панели (long-poll), -режимы `tcp`/`udp` — через прямой туннель: агент держит исходящее соединение к порту туннеля хоста -(по умолчанию `8446`, флаг `-tunnel` / `LPRA_TUNNEL_ADDR`). +режимы `tcp`/`udp` - через прямой туннель: агент держит исходящее соединение к порту туннеля хоста +(флаг `-tunnel` / `LPRA_TUNNEL_ADDR`). Порт туннеля и хост, к которому для него подключаются агенты (например, IP вместо домена панели), -меняются в `/settings` → «Прямой туннель». Агенты забирают адрес с панели сами — при подключении и +меняются в `/settings` → «Прямой туннель». Агенты забирают адрес с панели сами - при подключении и при каждом опросе, переустановка не нужна. Локальные `TUNNEL_ADDR` / `TUNNEL_PORT` в окружении агента имеют приоритет над настройкой панели (`TUNNEL_ADDR=off` отключает туннель). Если на хосте активен ufw и сервер запущен от root, панель сама открывает порт хоста правила -(tcp для `http`/`tcp`, udp для `udp`) и порт туннеля для прямых режимов, а при удалении правила — закрывает. +(tcp для `http`/`tcp`, udp для `udp`) и порт туннеля для прямых режимов, а при удалении правила - закрывает. При `ProtectSystem=strict` в systemd-юните ufw нужны права на запись: `ReadWritePaths=/etc/ufw /run`. ## Версия -Версия задаётся в одном месте — `VERSION.txt`; файл вшивается в сервер и агент при сборке. +Версия задаётся в одном месте - `VERSION.txt`; файл вшивается в сервер и агент при сборке. ## Пользователь терминала -По умолчанию терминал открывается от пользователя, под которым запущен агент (на Linux — root). +По умолчанию терминал открывается от пользователя, под которым запущен агент (на Linux - root). В окне «Изменить машину» можно выбрать другого пользователя машины или создать нового -(имя, пароль, опционально — добавить в sudo). Смена и создание работают, только если агент запущен от root. +(имя, пароль, опционально - добавить в sudo). Смена и создание работают, только если агент запущен от root. Файловый менеджер по-прежнему работает от пользователя агента. ## Безопасность -- Пароли админов — bcrypt; сессии в HttpOnly/Secure/SameSite куках; CSRF на формах; rate-limit логина. +- Пароли админов - bcrypt; сессии в HttpOnly/Secure/SameSite куках; CSRF на формах; rate-limit логина. - Агенты: общий enroll-секрет → индивидуальный токен (хранится хешем, сверка constant-time). - Открытие терминала пишется в таблицу `audit` (терминал даёт root-доступ), как и смена/создание пользователя. diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 0bcce03..2334ec7 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -10,9 +10,11 @@ import ( "net/url" "os" "os/signal" + "strconv" "syscall" "github.com/x-grk/longpolling-remote-access/internal/agent" + "github.com/x-grk/longpolling-remote-access/internal/tunnel" ) func main() { @@ -48,7 +50,7 @@ func envOr(key, def string) string { return def } -// tunnelAddr derives the direct-tunnel address (host:8446) from the server URL. +// tunnelAddr derives the direct-tunnel address (host:port) from the server URL. // It is only the starting point: the agent follows the host and port set in // the panel settings, unless TUNNEL_ADDR (the whole address) or TUNNEL_PORT // (just the port) pins it here. Set TUNNEL_ADDR=off to disable the tunnel. @@ -63,5 +65,5 @@ func tunnelAddr(hostURL string) string { if err != nil || u.Hostname() == "" { return "" } - return net.JoinHostPort(u.Hostname(), envOr("TUNNEL_PORT", "8446")) + return net.JoinHostPort(u.Hostname(), envOr("TUNNEL_PORT", strconv.Itoa(tunnel.DefaultPort))) } diff --git a/cmd/server/main.go b/cmd/server/main.go index 85a9958..33b1959 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -10,16 +10,18 @@ import ( "log" "net/http" "os" + "strconv" "time" "github.com/x-grk/longpolling-remote-access/internal/server" + "github.com/x-grk/longpolling-remote-access/internal/tunnel" ) func main() { addr := flag.String("addr", envOr("LPRA_ADDR", "127.0.0.1:8091"), "listen address (behind nginx)") dbPath := flag.String("db", envOr("LPRA_DB", "lpra.db"), "path to SQLite database") distDir := flag.String("dist", envOr("LPRA_DIST", "dist"), "directory with agent binaries served at /download/") - tunnelAddr := flag.String("tunnel", envOr("LPRA_TUNNEL_ADDR", ":8446"), "listen address for the direct TCP+UDP forward tunnel (empty disables)") + tunnelAddr := flag.String("tunnel", envOr("LPRA_TUNNEL_ADDR", ":"+strconv.Itoa(tunnel.DefaultPort)), "listen address for the direct TCP+UDP forward tunnel (empty disables)") insecureCookies := flag.Bool("insecure-cookies", envOr("LPRA_INSECURE_COOKIES", "") == "1", "do not mark cookies Secure (plain-HTTP local testing only)") flag.Parse() diff --git a/deploy/lpra-server.service b/deploy/lpra-server.service index 1c2c83e..685fe42 100644 --- a/deploy/lpra-server.service +++ b/deploy/lpra-server.service @@ -10,15 +10,15 @@ # sudo cp deploy/lpra-server.service /etc/systemd/system/ # sudo systemctl daemon-reload && sudo systemctl enable --now lpra-server # -# Direct port forwarding (tcp/udp modes) uses port 8446 on the host — open it in -# the firewall: ufw allow 8446/tcp ; ufw allow 8446/udp -# (The "http" forward mode and terminal/files go through nginx :8445 and need -# nothing extra.) Override with LPRA_TUNNEL_ADDR, or set it empty to disable. +# Direct port forwarding (tcp/udp modes) uses the tunnel port on the host, which +# has to be open in the firewall for tcp and udp. (The "http" forward mode and +# terminal/files go through nginx :8445 and need nothing extra.) Set the tunnel +# address with LPRA_TUNNEL_ADDR, or set it empty to disable. # The port, and the host agents dial for it, can also be changed in the panel # settings; agents pick the new address up from the panel by themselves. # # ufw integration (the panel opening forwarded ports itself) needs root and a -# writable /etc/ufw and /run — see ReadWritePaths below. +# writable /etc/ufw and /run - see ReadWritePaths below. [Unit] Description=LPRA remote access host (admin panel + agent rendezvous) diff --git a/internal/agent/client.go b/internal/agent/client.go index 6b6805a..5577a32 100644 --- a/internal/agent/client.go +++ b/internal/agent/client.go @@ -33,7 +33,7 @@ type Config struct { EnrollSecret string Name string // optional friendly name StatePath string // where the enrolled id+token are persisted - TunnelAddr string // host:port for the direct forward tunnel (e.g. REDACTED:8446); empty disables + TunnelAddr string // host:port for the direct forward tunnel (e.g. panel.example:port); empty disables // TunnelPinned keeps TunnelAddr as configured locally (TUNNEL_ADDR / // TUNNEL_PORT) instead of following the address set in the panel. TunnelPinned bool diff --git a/internal/agent/terminal.go b/internal/agent/terminal.go index a43cbca..488589f 100644 --- a/internal/agent/terminal.go +++ b/internal/agent/terminal.go @@ -15,7 +15,7 @@ type PTY struct { } // StartPTY launches a login shell attached to a new PTY. With user empty (or -// equal to the agent's own user) it is the agent's shell — root by default, +// equal to the agent's own user) it is the agent's shell - root by default, // since the agent runs as a root systemd service. Any other user is entered via // `su -l`, which only a root agent can do without a password. func StartPTY(user string) (*PTY, error) { diff --git a/internal/agent/update.go b/internal/agent/update.go index 2e29c6e..c1c3a91 100644 --- a/internal/agent/update.go +++ b/internal/agent/update.go @@ -17,7 +17,7 @@ import ( // doUpdate downloads the latest agent binary for this platform, atomically // replaces the running executable, and schedules a service restart. It never -// uninstalls — only updates. +// uninstalls - only updates. func (a *Agent) doUpdate() proto.TaskResult { exe, err := os.Executable() if err != nil { diff --git a/internal/server/db.go b/internal/server/db.go index cc076dd..b1f4607 100644 --- a/internal/server/db.go +++ b/internal/server/db.go @@ -400,7 +400,7 @@ type UfwRule struct { ID int64 Port int Proto string - RuleID int64 // bound forward rule id, or 0 for shared/manual (e.g. tunnel 8446) + RuleID int64 // bound forward rule id, or 0 for shared/manual (e.g. the tunnel port) } func (db *DB) AddUfwRule(port int, proto string, ruleID int64) error { diff --git a/internal/server/forward.go b/internal/server/forward.go index cc0670e..c16e291 100644 --- a/internal/server/forward.go +++ b/internal/server/forward.go @@ -14,8 +14,8 @@ import ( // supports three transports: // - "http": tunneled over the HTTP long-poll channel (works anywhere nginx // reaches, highest latency); -// - "tcp": direct TCP multiplexed over the 8446 tunnel (low latency); -// - "udp": direct UDP over the 8446 channel. +// - "tcp": direct TCP multiplexed over the tunnel port (low latency); +// - "udp": direct UDP over the tunnel port. type forwardManager struct { hub *Hub tun *tunnelHub @@ -92,11 +92,11 @@ func (m *forwardManager) acceptTCP(rule ForwardRule, ln net.Listener) { } } -// bridgeDirect wires a connection to the agent's low-latency 8446 tunnel. +// bridgeDirect wires a connection to the agent's low-latency direct tunnel. func (m *forwardManager) bridgeDirect(rule ForwardRule, conn net.Conn) { at := m.tun.get(rule.AgentID) if at == nil { - // No direct tunnel (agent offline or 8446 unreachable). + // No direct tunnel (agent offline or the tunnel port unreachable). conn.Close() return } diff --git a/internal/server/handlers_agent.go b/internal/server/handlers_agent.go index 03bfd35..cee1d74 100644 --- a/internal/server/handlers_agent.go +++ b/internal/server/handlers_agent.go @@ -16,7 +16,7 @@ func writeJSON(w http.ResponseWriter, v any) { _ = json.NewEncoder(w).Encode(v) } -// POST /api/agent/enroll — exchange the shared enroll secret for a per-agent id+token. +// POST /api/agent/enroll - exchange the shared enroll secret for a per-agent id+token. func (s *Server) handleEnroll(w http.ResponseWriter, r *http.Request) { secret := r.Header.Get("X-Enroll-Secret") if !constantTimeEqual(secret, s.db.EnrollSecret()) { @@ -36,7 +36,7 @@ func (s *Server) handleEnroll(w http.ResponseWriter, r *http.Request) { writeJSON(w, proto.EnrollResponse{AgentID: id, Token: token}) } -// GET /api/agent/poll — control channel. Held until a job is queued or timeout. +// GET /api/agent/poll - control channel. Held until a job is queued or timeout. func (s *Server) handlePoll(w http.ResponseWriter, r *http.Request) { a := s.authenticateAgent(r) if a == nil { @@ -68,7 +68,7 @@ func (s *Server) setTunnelHeader(w http.ResponseWriter) { } } -// GET /api/agent/config — settings an agent picks up when it connects, without +// GET /api/agent/config - settings an agent picks up when it connects, without // waiting out a poll hold. func (s *Server) handleAgentConfig(w http.ResponseWriter, r *http.Request) { if s.authenticateAgent(r) == nil { @@ -79,7 +79,7 @@ func (s *Server) handleAgentConfig(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -// GET /api/agent/task/{id} — the agent fetches the task to run. +// GET /api/agent/task/{id} - the agent fetches the task to run. func (s *Server) handleTaskFetch(w http.ResponseWriter, r *http.Request) { if s.authenticateAgent(r) == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) @@ -93,7 +93,7 @@ func (s *Server) handleTaskFetch(w http.ResponseWriter, r *http.Request) { writeJSON(w, env) } -// POST /api/agent/task/{id}/result — the agent returns the task result. +// POST /api/agent/task/{id}/result - the agent returns the task result. func (s *Server) handleTaskResult(w http.ResponseWriter, r *http.Request) { if s.authenticateAgent(r) == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) @@ -108,7 +108,7 @@ func (s *Server) handleTaskResult(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -// GET /api/agent/term/{sid}/in — downstream (panel -> agent) long-poll. +// GET /api/agent/term/{sid}/in - downstream (panel -> agent) long-poll. func (s *Server) handleTermIn(w http.ResponseWriter, r *http.Request) { if s.authenticateAgent(r) == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) @@ -127,7 +127,7 @@ func (s *Server) handleTermIn(w http.ResponseWriter, r *http.Request) { writeJSON(w, proto.InBatch{Frames: frames}) } -// POST /api/agent/term/{sid}/out — upstream (agent PTY -> panel). +// POST /api/agent/term/{sid}/out - upstream (agent PTY -> panel). func (s *Server) handleTermOut(w http.ResponseWriter, r *http.Request) { if s.authenticateAgent(r) == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) diff --git a/internal/server/handlers_panel.go b/internal/server/handlers_panel.go index e95e43c..0d84280 100644 --- a/internal/server/handlers_panel.go +++ b/internal/server/handlers_panel.go @@ -137,7 +137,7 @@ func seenText(online bool, lastSeen time.Time) string { return humanSince(lastSeen) } -// GET /api/panel/agents/presence — who is online, for the dashboard to keep +// GET /api/panel/agents/presence - who is online, for the dashboard to keep // its status column live without reloading. func (s *Server) handlePresence(w http.ResponseWriter, r *http.Request) { agents, err := s.db.ListAgents() @@ -227,7 +227,7 @@ func (s *Server) agentUsers(agentID string) (*proto.SysUsersResult, error) { case res := <-ch: if !res.OK { // Agents older than 0.0.2 don't know this task. - return nil, errors.New("клиент не поддерживает выбор пользователя — обновите его") + return nil, errors.New("клиент не поддерживает выбор пользователя - обновите его") } var out proto.SysUsersResult if err := json.Unmarshal(res.Data, &out); err != nil { @@ -258,7 +258,7 @@ func (s *Server) handleAgentSetUser(w http.ResponseWriter, r *http.Request) { } name := strings.TrimSpace(r.FormValue("user")) if !s.online(a) { - editUserRedirect(w, r, id, "uerr", "Машина офлайн — сменить пользователя нельзя.") + editUserRedirect(w, r, id, "uerr", "Машина офлайн - сменить пользователя нельзя.") return } // Only accept a user the machine itself reports, so a typo (or an agent too @@ -299,7 +299,7 @@ func (s *Server) handleAgentAddUser(w http.ResponseWriter, r *http.Request) { return } if !s.online(a) { - editUserRedirect(w, r, id, "uerr", "Машина офлайн — создать пользователя нельзя.") + editUserRedirect(w, r, id, "uerr", "Машина офлайн - создать пользователя нельзя.") return } name := strings.TrimSpace(r.FormValue("name")) @@ -402,7 +402,7 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) { }) } -// POST /settings/tunnel — host and port agents use for the direct tunnel. +// POST /settings/tunnel - host and port agents use for the direct tunnel. func (s *Server) handleSetTunnel(w http.ResponseWriter, r *http.Request) { if !checkCSRF(r) { http.Error(w, "bad csrf", http.StatusForbidden) diff --git a/internal/server/handlers_panel_fs.go b/internal/server/handlers_panel_fs.go index 736a0ec..868cd98 100644 --- a/internal/server/handlers_panel_fs.go +++ b/internal/server/handlers_panel_fs.go @@ -37,7 +37,7 @@ func (s *Server) handleFilesPage(w http.ResponseWriter, r *http.Request) { s.render(w, "files.html", map[string]any{"Agent": a, "EditLimit": proto.FSReadCap}) } -// POST /api/panel/agents/{id}/fs/{op} — run a file-manager operation on the +// POST /api/panel/agents/{id}/fs/{op} - run a file-manager operation on the // agent and return its result. The request body is the op's JSON args and is // forwarded verbatim to the agent. func (s *Server) handleFSOp(w http.ResponseWriter, r *http.Request) { @@ -74,7 +74,7 @@ func (s *Server) handleFSOp(w http.ResponseWriter, r *http.Request) { } } -// GET /api/panel/agents/{id}/fs/download?path=... — stream a file as a download. +// GET /api/panel/agents/{id}/fs/download?path=... - stream a file as a download. func (s *Server) handleFSDownload(w http.ResponseWriter, r *http.Request) { agentID := r.PathValue("id") p := r.URL.Query().Get("path") diff --git a/internal/server/handlers_panel_stat.go b/internal/server/handlers_panel_stat.go index 73c90e9..fac0f84 100644 --- a/internal/server/handlers_panel_stat.go +++ b/internal/server/handlers_panel_stat.go @@ -103,12 +103,12 @@ func (s *Server) onlineAgent(w http.ResponseWriter, r *http.Request) (string, bo // the task. func oldAgentError(res proto.TaskResult) proto.TaskResult { if !res.OK && strings.Contains(res.Error, "неизвестная операция") { - res.Error = "клиент не поддерживает эту функцию — обновите его (нужен 0.0.5+)" + res.Error = "клиент не поддерживает эту функцию - обновите его (нужен 0.0.5+)" } return res } -// GET /api/panel/agents/{id}/stat — CPU, RAM and disk of the machine right now. +// GET /api/panel/agents/{id}/stat - CPU, RAM and disk of the machine right now. func (s *Server) handleAgentStat(w http.ResponseWriter, r *http.Request) { id, ok := s.onlineAgent(w, r) if !ok { @@ -128,7 +128,7 @@ func (s *Server) handleAgentStat(w http.ResponseWriter, r *http.Request) { writeJSON(w, e.res) } -// POST /api/panel/agents/{id}/speed — have the agent measure its link to us. +// POST /api/panel/agents/{id}/speed - have the agent measure its link to us. func (s *Server) handleAgentSpeed(w http.ResponseWriter, r *http.Request) { id, ok := s.onlineAgent(w, r) if !ok { @@ -150,7 +150,7 @@ var speedBlock = sync.OnceValue(func() []byte { return b }) -// GET /api/agent/speed/down?bytes=N — stream N bytes (default: as much as fits +// GET /api/agent/speed/down?bytes=N - stream N bytes (default: as much as fits // in the time window) for the agent to time. bytes=0 is the ping probe. func (s *Server) handleSpeedDown(w http.ResponseWriter, r *http.Request) { if s.authenticateAgent(r) == nil { @@ -182,7 +182,7 @@ func (s *Server) handleSpeedDown(w http.ResponseWriter, r *http.Request) { } } -// POST /api/agent/speed/up — swallow the agent's upload. +// POST /api/agent/speed/up - swallow the agent's upload. func (s *Server) handleSpeedUp(w http.ResponseWriter, r *http.Request) { if s.authenticateAgent(r) == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) diff --git a/internal/server/handlers_panel_term.go b/internal/server/handlers_panel_term.go index 7a8128b..ee5e2e4 100644 --- a/internal/server/handlers_panel_term.go +++ b/internal/server/handlers_panel_term.go @@ -12,7 +12,7 @@ import ( // same transport the agent uses), so it works through any reverse proxy without // WebSocket upgrade support. -// POST /api/panel/term/{agentID}/open — create a terminal session. +// POST /api/panel/term/{agentID}/open - create a terminal session. func (s *Server) handlePanelOpen(w http.ResponseWriter, r *http.Request) { agentID := r.PathValue("agentID") agent, err := s.db.GetAgent(agentID) @@ -28,7 +28,7 @@ func (s *Server) handlePanelOpen(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]string{"session_id": sess.ID}) } -// GET /api/panel/term/{sid}/out — long-poll for PTY output. +// GET /api/panel/term/{sid}/out - long-poll for PTY output. func (s *Server) handlePanelOut(w http.ResponseWriter, r *http.Request) { sess := s.hub.Session(r.PathValue("sid")) if sess == nil { @@ -51,7 +51,7 @@ type panelInMsg struct { Rows uint16 `json:"rows,omitempty"` } -// POST /api/panel/term/{sid}/in — send keystrokes/resize to the agent PTY. +// POST /api/panel/term/{sid}/in - send keystrokes/resize to the agent PTY. func (s *Server) handlePanelIn(w http.ResponseWriter, r *http.Request) { sess := s.hub.Session(r.PathValue("sid")) if sess == nil { @@ -72,7 +72,7 @@ func (s *Server) handlePanelIn(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -// POST /api/panel/term/{sid}/close — tear the session down. +// POST /api/panel/term/{sid}/close - tear the session down. func (s *Server) handlePanelClose(w http.ResponseWriter, r *http.Request) { s.hub.CloseSession(r.PathValue("sid")) w.WriteHeader(http.StatusNoContent) diff --git a/internal/server/hub.go b/internal/server/hub.go index c0f1be5..49c2db6 100644 --- a/internal/server/hub.go +++ b/internal/server/hub.go @@ -37,7 +37,7 @@ type taskEntry struct { } // Session is one live byte stream between the panel and an agent (a PTY terminal -// or a raw TCP forward — the hub treats both identically). +// or a raw TCP forward - the hub treats both identically). type Session struct { ID string AgentID string diff --git a/internal/server/server.go b/internal/server/server.go index ddfe75a..24a9475 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -26,8 +26,8 @@ type Options struct { // nginx TLS); set false only for plain-HTTP local testing. SecureCookies bool // TunnelAddr is the listen address for the direct TCP+UDP forward tunnel - // (default ":8446"). Empty disables direct forwarding. The port can be - // overridden from the settings page. + // Empty disables direct forwarding. The port can be overridden from the + // settings page. TunnelAddr string } diff --git a/internal/server/udp.go b/internal/server/udp.go index b49277c..3771313 100644 --- a/internal/server/udp.go +++ b/internal/server/udp.go @@ -11,11 +11,11 @@ import ( "github.com/x-grk/longpolling-remote-access/internal/tunnel" ) -// udpRelay carries UDP forwards over the direct 8446 UDP channel. The agent +// udpRelay carries UDP forwards over the direct UDP channel. The agent // registers its UDP return address; end-user datagrams arriving on a rule's // host port are relayed to the agent and replies are sent back. // -// Datagram framing on the 8446 control socket: +// Datagram framing on the tunnel control socket: // // registration (agent->server): "R " // data (both): 'D' | sid(4) | port(2) | payload diff --git a/internal/server/ufw.go b/internal/server/ufw.go index 9619fab..dcc02a0 100644 --- a/internal/server/ufw.go +++ b/internal/server/ufw.go @@ -172,7 +172,7 @@ func (u *ufwManager) pruneShared() { // allowForward opens the host ports a forward rule needs: its host port (bound // to the rule so it is revoked with it) plus the shared tunnel port for direct -// tcp/udp modes (bound to 0 — shared across rules). A rule made "without ufw" +// tcp/udp modes (bound to 0 - shared across rules). A rule made "without ufw" // opens nothing, so nothing is revoked with it either. func (u *ufwManager) allowForward(rule ForwardRule) { if !u.available() { diff --git a/internal/server/web/templates/dashboard.html b/internal/server/web/templates/dashboard.html index e8f7322..fcecdb5 100644 --- a/internal/server/web/templates/dashboard.html +++ b/internal/server/web/templates/dashboard.html @@ -3,7 +3,7 @@ -Машины — LPRA +Машины - LPRA @@ -49,7 +49,7 @@
+ onsubmit="return confirm('Удалить машину {{.Name}}? Сервис на ней продолжит работать, но перестанет быть виден. Чтобы снова подключиться - переустановите агента.')">
{{else}} @@ -64,7 +64,7 @@ {{else if .UsersErr}}

Список пользователей недоступен: {{.UsersErr}}.

{{else}} -

Машина офлайн — сменить пользователя можно, когда она на связи.

+

Машина офлайн - сменить пользователя можно, когда она на связи.

{{end}} @@ -90,7 +90,7 @@

Удаление

Удаляет машину из панели. Сервис на самой машине продолжит работать, - но перестанет быть виден; чтобы подключиться снова — переустановите агента.

+ но перестанет быть виден; чтобы подключиться снова - переустановите агента.

diff --git a/internal/server/web/templates/files.html b/internal/server/web/templates/files.html index 45c508b..e904a81 100644 --- a/internal/server/web/templates/files.html +++ b/internal/server/web/templates/files.html @@ -3,7 +3,7 @@ -Файлы · {{.Agent.Name}} — LPRA +Файлы · {{.Agent.Name}} - LPRA