Multi-node, payment registry, flexible registration — release 1.0.0 - #30
Merged
Conversation
…atomic report claim
… timer stop, decoy on internal errors Addresses adversarial review findings: deleted node now reliably revoked instead of serving with live credentials; wake-registry no longer leaks channels; long-poll timer stopped on early wake; pre-auth internal errors serve decoy to preserve stealth.
…yRaw internal/nodeagent: join, long-poll sync loop with backoff, config apply (ACME/hop/ connguard/decoy), stats poll with inflight/pending idempotent batching. cmd/rospanel/ node.go: install/run/set-panel/status/uninstall + systemd unit. Cert paths use sentinels the agent substitutes with its own absolute paths.
…ss nodes sub.ShareLinksAll / ClashYAMLMulti / SingBoxJSONMulti iterate [local + each enabled, connected node]; each node's links carry its name via Settings.NodeLabel. Manager .NodeLinkSettings provides per-node settings clones; subscription handler spans them. Single-server output is byte-identical (golden test).
…th section
Sync response gains Update (agent runs updater.Apply + restarts) and panel_url
broadcast when a node reaches a stale host (auto-heals a panel move; node set-panel is
the manual fallback). Panel/API endpoints POST /nodes/{id}/update + /nodes/update-all.
Health report gains a nodes section (offline / version-skew warnings).
internal/provision: SSH into a target (password or key), run the install command,
stream the log; host key fingerprint via trust-on-first-use, creds never stored.
POST /api/nodes/{id}/provision streams the install over SSE. NodesPanel add-node
dialog gains an 'Install over SSH' tab with a live log. Adds golang.org/x/crypto/ssh.
- provision: serialize SSE writes (stdout+stderr goroutines) — data race - agent: back off on repeated config-apply failure (version skew no longer storms) - nodes: enforce unique node names + dedup subscription labels (dup names broke Clash/sing-box) - panel-URL broadcast: only a bare-domain :443 host (no IP/IPv6/port/localhost bricking) + agent validates the URL - --insecure now persists and applies to the sync client, not just join - ensureDecoy: swap handler on a live listener (no masquerade gap / relisten race) - honest SSH TOFU comment; install.sh --join arg guard
…real cert Add certLoop to the node agent (mirrors the panel's tlsLoop): retries ACME fast while on a self-signed fallback, slow once a CA cert is in place, and reloads Xray when the cert changes so the real Let's Encrypt cert is served the moment it's issued. The next sync auto-reports the new fingerprint so the panel drops the pin from the node's links. Cert-file writes serialized (certMu) so applyState and certLoop don't clash.
The SSH-provision endpoint is a POST (needs the creds in the body) and is audited, so its ResponseWriter is wrapped by auditStatus, which forwards Flush only via Unwrap. The direct type assertion in sseStart missed it → 'стриминг не поддерживается'. sseStart now walks the Unwrap chain (regression test included).
…f-signed A panel on a bare IP / self-signed cert can't be TLS-verified by a joining node, so the join failed with x509 and the node never connected. nodeInstallCommand now appends --insecure when the panel isn't on a CA-valid cert (copy-paste + SSH-provision + API); a real-domain panel omits it. The node persists the flag and uses it for sync too.
… over SSH Shown on any offline/never-connected remote node; re-runs the SSH install with a fresh token and streams the log, which also surfaces why the node didn't connect.
…url|bash The install command downloaded install.sh from main + the release binary — neither has node support until the feature is merged/released, so the SSH install put a full PANEL on the target instead of a node. Now the panel uploads its own (gzipped) binary over the existing SSH connection and runs 'rospanel node install' with it: exact same build, no GitHub dependency, works for unreleased builds. The node installer also stops a conflicting rospanel PANEL service on the target (a box can't be both).
…polish
- xray.VersionMatchesPinned tolerates the 'v' prefix (xray reports 26.6.27, pinned is
v26.6.27) — no more spurious ⚠ on every node
- NodeRoutingDialog: per-node routing (block/direct rules only — lanes/WARP/Opera don't
apply on nodes) + DNS, each inherit-or-override; POST /api/nodes/{id}/routing
- NodeView carries the routing/dns overrides so the editor prefills
- remove 'Сбросить протоколы' button; fix install-modal wording (node already listed)
On a node everything egresses direct already (no proxy lanes to bypass), so the 'Напрямую' domain/IP rules were no-ops — leave only the block rules. Renamed the control to 'Блокировки и DNS'.
…the fallback A browser hitting a node's :443 negotiates HTTP/2 (Xray offers ALPN h2), and Xray forwards it prior-knowledge over the plaintext fallback to the agent's decoy. The decoy http.Server lacked UnencryptedHTTP2, so it couldn't parse the h2 frames → ERR_HTTP2_PROTOCOL_ERROR and no masquerade. Enable HTTP/1.1 + h2c like the panel's own server.
New geo_refresh_hours setting (0 = never): the panel re-downloads stale geo on a
timer and reloads Xray, and pushes the cadence to nodes (NodeMeta.GeoRefreshHours)
so each node's agent auto-refreshes its own geo on the same schedule and restarts
Xray to pick it up. Cadence is edited in the master card's Geo tab (Никогда / раз в
день / 3 дня / неделю); enabling it refreshes immediately if geo is already stale.
Migration 0029; GET /api/geo now returns {files, refresh_hours}; POST /api/geo/cadence.
…nodes)
REALITY keys were already per-node; this makes the DONOR (masquerade SNI) per-server
too and adds a regenerate-keys action on each server card.
- nodes.reality_dest (migration 0030): the node's own donor, empty ⇒ inherit the
panel's. nodeSettings uses it when set. NodeView exposes each server's donor +
public key / short id / gRPC service (private key stays hidden).
- SetNodeReality / SetMasterReality: validate the donor (master also live-probes a
changed donor while REALITY is on), persist it, and optionally regenerate the
server's REALITY keypair; nodes wake, the master reloads Xray.
- Endpoints POST /api/nodes/master-reality and /api/nodes/{id}/reality.
- UI: a REALITY section on the master and node cards — donor SNIs, the keys, and a
'перегенерировать ключи' button (confirmed; it invalidates that server's links).
… Подключения
- Node Geo tab: the agent auto-refreshes geo on the fleet cadence; a 'Обновить
сейчас' button flags an immediate refresh (RequestNodeGeoRefresh → sync response
RefreshGeo → agent geo.Refresh + Xray restart). New POST /api/nodes/{id}/geo-refresh.
- ConnectionsPanel: removed the REALITY donor (SNI), key display and regenerate
button — REALITY donor + keys are per-server on the «Сервера» cards now. The port
and anti-replay (transport) stay.
Groundwork for full per-node connections. nodes.connections_config (migration 0031)
holds a node's own transport (WS path, ports, hop range/interval, REALITY port +
anti-replay, uTLS fingerprints, connection names, anti-DPI) as a JSON blob; empty ⇒
inherit the master's. nodeSettings applies it, and NodeMeta now derives hop/hysteria
ports from the node's effective settings (ns) rather than the master's.
- buildConnectionsStatus(set) factored out of ConnectionsInfo so a node reuses it.
- NodeConnectionsInfo / ApplyNodeConnections (protocols + REALITY donor/keys +
transport, syntactic validation; the node's own xray -test is the backstop).
- store: SetNodeConnections, SetNodeProtocols; scan the blob.
- Endpoints GET/POST /api/nodes/{id}/connections. Inert: no frontend calls them yet
and an unconfigured node behaves exactly as before.
…чения tab Every server (master + each node) now edits ALL its connection settings — protocols on/off, names, uTLS fingerprints, ports, port-hopping, WS path, REALITY (donor/keys/regen/port/anti-replay) and anti-DPI — in a «Подключения» tab on its card. - ConnectionsEditor: the connections UI refactored into a controlled, scope-aware component (injected load/save; inline save instead of a SaveBar so it fits the card modal). Master → global connections (restarts the panel Xray); node → its own blob (applied by the node, no panel restart). - Card «Основное» slimmed to name/label + decoy; the separate Протоколы and REALITY sections (and their endpoints' UI) folded into Подключения. - Global Подключения settings tab removed; ConnectionsPanel.tsx deleted.
…ence, node domain like master - ConnectionsEditor + TLSPanel: flat bordered blocks (bg-gray-50/60) instead of the heavy shadowed/dimmed Cards, so they match the modal's other sections. - Server dialogs: the footer Save shows only on tabs it persists (Основное/Роутинг); the Подключения/Geo/Домен tabs have their own save, so no more double 'Сохранить'. - Nodes report their cert issuer + expiry (agent reads its cert); the node Домен tab now shows «выдан <issuer> · ещё N дн.» like the master's. - Per-node geo cadence: each node has its OWN geo auto-refresh schedule (migration 0033), edited in its Geo tab; NodeMeta pushes the node's own cadence. Master keeps its own. Migration 0032 adds the node cert columns. - Fixed stale text (geo desc, restore.tsx 'Настройки → Домен' → the master card).
…el/GeoSection)
- Per-node ACME: each node can pick its own CA provider (Let's Encrypt / ZeroSSL) +
e-mail; the panel fetches ZeroSSL EAB and pushes provider/email/EAB via NodeMeta
(migration 0034). Empty ⇒ inherit the panel's. GET/POST /api/nodes/{id}/tls +
NodeTLSStatus/SetNodeACME (builds cert info from the node's report).
- TLSPanel is now scope-aware (load/save injected, redirectOnSuccess flag): the node
Домен tab reuses the exact master UI (address + cert issuer/days + change form with
provider choice), re-issuing the node's cert without a panel redirect.
- Nodes report their geo file status every sync; GET /api/nodes/{id}/geo. The node
Geo tab now reuses GeoSection (file sizes/dates + cadence + refresh), like the master.
- Connections editor: save/cancel bar always visible (disabled until dirty).
Every tab (Основное/Роутинг/DNS) now saves on its own and stays open, matching Подключения/Geo/Домен, with a note that unsaved edits won't apply. Removes the shared footer Save and the save-then-close behaviour.
- New POST /api/nodes/{id}/dns (SetNodeDNS) so a node's DNS saves on its
own; the routing endpoint no longer carries DNS and preserves it.
- TabSaveBar gains 'Отменить' (revert to last-saved) + dirty-gated Save,
matching ConnectionsEditor; note clarifies each section saves separately.
- useServerRouting tracks a baseline for dirty/revert/commit; DnsEditor
re-syncs when its value is reset externally.
Backend: - agent: persist report-id watermark in state.json and resume it on restart — a restarted node no longer has its traffic silently dropped by the panel's forward-only watermark (was: accounting/quota broke after the first self-update/reboot). Panel: don't ack a report the DB failed to record, so the node resends instead of losing it. - nodeSettings: null the master-only proxy-mode inbound + credentials so they never leak into a node's config. - IngestNodeSync: reject a disabled node's traffic/device/status ingest up front (its token still authenticates only to be told to stop). - NodeLinkSettings: skip a self-signed node with no reported cert hash (unpinnable link fails silently); disambiguate a node whose name collides with the master's config label. - ConsumeJoinToken: atomic conditional UPDATE (re-asserts the token hash) so a concurrent consume / racing regen can't double-mint or clobber a revoke. - provision: issue the install token WITHOUT revoking the node's permanent token, so a failed SSH re-provision can't down a live node. - node API: read deadline on join/sync bodies (slow-loris hardening). - /v1 node mutations now land in the admin audit trail. - regen-join: 404 instead of a raw sql 500. - updateNode preserves protocols when omitted (no revert of a Подключения edit). - agent: prune stale per-user counters; self-update honours the agent ctx. Frontend: - node Основное save no longer echoes (stale) protocol flags — omits them. - SSH install reuses the created node id on retry (no orphan nodes). - master routing tab falls back to the node's own routing if the live fetch fails (a save can't wipe real rules). - DNS dirty-tracking seeded canonically; geo cadence rolls back on error; SSH dialogs non-dismissible mid-install; SSE flushes a trailing frame. Tests: report-id persistence across restart, proxy-mode non-leak, IssueJoinToken keeps the permanent token.
…nique node name - TestRoutingPropagatesMasterAndNode: master routing lands in the master config, a node's own routing lands in the exact JSON the panel pushes (NodeDesiredState), neither inherits the other's rule. - migration 0035: partial unique index on lower(name) WHERE deleted_at=0 as a DB backstop to the app-level NodeNameTaken check (a raced create/ rename could otherwise duplicate a name and collide Clash/sing-box tags). Store returns ErrNodeNameTaken → manager maps to a validation error. Name is reusable after a node is deleted (partial index over live rows).
- Servers page is now a single divided list of compact 2-line rows (status dot + name + state chip + host, then inline meta), replacing the tall per-server cards. Denser, scales to a larger fleet. - 'Обновить все ноды' shows whenever there are remote nodes (was hidden unless one was behind); a ⚠ marks that an update is available.
…on dark - Master name+decoy don't touch the Xray config, so saving them must not go through useXrayApply (which blocks polling for an Xray restart that never happens → ~2min hang). Use a plain save with its own busy state, matching the node dialog. Routing/DNS keep the apply-wait (they reload Xray). - Dropdown menu: border-gray-300 + shadow-xl so it reads as a distinct panel when overlaying a same-coloured card surface on the dark theme.
…lts weekly - Fold 0026–0036 (nodes table, master_label, per-node protocols/reality/ connections/cert-info/geo/acme, unique-name index, geo weekly default) into one 0026_multinode.sql for a clean feature migration. Verified the resulting schema is column-identical to the incremental one. - Geo auto-refresh defaults to weekly (168h): baked into the schema and set explicitly in CreateNode (defaultGeoRefreshHours) so new nodes are weekly even on installs whose nodes table predates the default. - Node/install log surfaces use theme-aware bg-gray-50 + text-danger/warning/ success (was bg-gray-900, which the ramp flips light on the dark theme).
Replace the hardcoded YooKassa/CryptoBot columns with a provider registry: each provider is one self-contained file (Descriptor + Client) and the settings API, webhook route and polling fallback are generic — adding a provider needs no UI change. Credentials move to a payment_providers table, encrypted at rest. New: PayPalych, RioPay, RollyPay, SeverPay, Platega, PayPear, AuraPay (cards/SBP, RUB) and Heleket (crypto, RUB invoice). Providers whose webhook signature is absent or fragile are confirmed by re-fetching status over the authenticated API instead of trusting the callback body. Per-provider custom pay-button name and payment-method selection (Platega/PayPear/AuraPay default to the provider's own method picker). Includes shared model/settings/api groundwork.
Replace the on/off registration toggle with modes: closed, open, moderation and invite-code (Settings -> Telegram). Moderation records a pending request — no user is created and the bot grants no access — that an admin approves or rejects via admin-bot buttons or the new "Заявки" tab (shown only in moderation mode); the account is created, plan applied and chat linked only on approval. Invite mode gates signup behind a code. The link-by-code flow from the user card is unchanged and works in every mode. The user card's Telegram section now shows the linked Telegram ID.
Review follow-ups on the moderation flow: - Atomic ClaimRegistrationRequest gates approval/rejection so a double-click or two admins (bot + panel) can't create the account twice or send contradictory pings. - Approval restores the request (and removes any orphaned account) if user creation or chat-linking fails after the claim, instead of losing the signup. - Guard against a duplicate when the chat got linked (via a panel code) while the request was pending. - A re-tap in moderation short-circuits before the global rate limit, so it can't be burned to block other signups. - Drop the dead admin-alert fallback (it could never fire); the panel queue is the authoritative surface. Reject an empty stored invite code. Rename the notifier's reqID param (was misleadingly userID).
Flip the tg_user_reg_enabled default (0005) from 1 to 0 so a brand-new database seeds registration mode 'off' (via the 0028 backfill) — enabling the user bot no longer opens signups by accident. The migration runner keys on file name, so this only affects databases that never ran 0005; existing installs keep their mode. Covered by TestFreshInstallRegistrationClosed.
Release-As: 1.0.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Мультинода
Панель управляет удалёнными нодами (агент
rospanel node, установка по SSH из UI, self-update, авто-broadcast panel-URL). Пер-серверная конфигурация: протоколы, routing/DNS, egress-бэкенды (proxy-lanes, WARP, Opera), REALITY-донор и ключи, Geo с авто-обновлением, просмотр логов ноды, health. Мульти-нод share-links / clash / singbox / subscription. Миграция0026_multinode.Платёжные провайдеры (реестр)
Захардкоженные колонки YooKassa/CryptoBot → реестр: один провайдер = один файл (Descriptor + Client), настройки/вебхук/поллинг — generic. Креды в таблице
payment_providers(шифрование at-rest, миграция0027). Новые: PayPalych, RioPay, RollyPay, SeverPay, Platega, PayPear, AuraPay (карты/СБП, ₽) и Heleket (крипта, счёт в ₽). Провайдеры без надёжной подписи вебхука подтверждаются ре-фетчем статуса. Кастомное название кнопки и выбор метода на провайдера.Гибкая саморегистрация
Режимы вместо тумблера: закрыта / открыта / модерация / по коду (миграции
0028,0029). Модерация = заявка (аккаунт не создаётся, доступа к боту нет до одобрения); одобрение/отклонение кнопками в админ-боте или на вкладке «Заявки». Атомарный claim от гонок, восстановление заявки при сбое. Telegram ID в карточке юзера. Дефолт для свежих установок — закрыта.