Skip to content

Commit 7cd54e2

Browse files
committed
Panel-side commands, bootstrap --panel/--join, psm-agent 0.4.0
The PSM panel needs a server to do everything without questions, including a server that has never had PSM or a core. All of it is psm commands (the panel never runs anything psm cannot), and the interactive menus and installs are unchanged. bootstrap.sh - Fixed "PSM_DIR�: unbound variable" when re-run on a server that has PSM: in "…($PSM_DIR)" bash read the first byte of the full-width bracket as part of the name under some locales, and set -u stopped it. Such variables are braced; scripts/ci.sh now fails on any $NAME written directly before non-ASCII text. - Takes --panel URL --join TOKEN (the panel's install command): a fresh server gets PSM without questions (install.sh PSM_UNATTENDED=1: no language prompt, no menu), an existing one is updated, then both run `psm agent join`. Without the arguments nothing changes. New commands - psm core list|install xray|sing-box|mihomo [--if-missing]: the unattended core install psm migrate already used. - psm standalone install|show|export|remove snell|ss2022: the official snell-server v4, v5 or v6 (the newest official build of that major version; v6 only has betas and release candidates upstream, so the newest of those) or ss-rust, with a given port and PSK / key / method, as a systemd or OpenRC service, in the config files the menus use. Refused on musl for Snell, with the reason. - psm traffic list|set|reset|unset: metering and limits per node; a limit of 0 now meters without limiting (the check skipped such nodes before). - psm node export --format singbox: a sing-box client outbound. - psm agent join|status|remove, psm version. Also: ss-rust's download needs xz, which Debian and Ubuntu call xz-utils (mapped now); the shared download helper is quiet for the standalone command. psm-agent 0.4.0: installs a node's core first when missing, runs the standalone and traffic tasks, exports sing-box outbounds, sends the traffic counters every ten minutes (or when the panel asks), and gathers a status report (version, cores, psm doctor, nodes, traffic, standalone servers). New arguments are allowlisted like the rest. .github/workflows/agent-release.yml publishes agent-vX.Y.Z releases (amd64, arm64, armv7, SHA256SUMS) that psm agent join downloads. Downloads: the retries back off exponentially now (five tries over about 30 s; --retry-delay had fixed the delay at 2 s, and a 504 from GitHub outlasted it in CI). Tests (all on the test VPS, in containers): - tests/integration/panelside.sh (CI: Debian, Alpine, Rocky 9): unattended install, a core on demand, standalone Snell v4/v5/v6 and ss-rust installed, replaced, exported (a sing-box client gets through ss-rust) and removed, traffic counted, paused over a limit, reset and unset. - psm-agent unit tests for every new task and rejection. - the panel end to end (psm-panel tests/e2e.sh): a bare Debian joins with the one-click command and gets PSM, psm-agent and three cores; a server with PSM installed from the command line joins through the update path with its own node untouched. The whole CI matrix, run on the test VPS before this commit: 32/32 jobs (full, e2e, nginx443, users, snell, slim, retry and panelside suites across Debian 13 / 10, Ubuntu 24.04 / 22.04, Alpine, Rocky 9 and AlmaLinux 8, and the three migrate jobs), plus scripts/ci.sh; the panel end to end: 99/99.
1 parent a010f33 commit 7cd54e2

19 files changed

Lines changed: 1557 additions & 80 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: psm-agent release
2+
3+
# Pushing a tag agent-vX.Y.Z builds psm-agent for Linux amd64, arm64 and armv7
4+
# and publishes the binaries with SHA256SUMS as a GitHub release, which
5+
# `psm agent join` (lib/agent.sh, which pins the version) downloads. It runs on
6+
# GitHub's runners: tags are pushed by the owner only, never from a fork.
7+
on:
8+
push:
9+
tags: ['agent-v*']
10+
11+
permissions:
12+
contents: write
13+
14+
jobs:
15+
release:
16+
if: github.repository == 'jinqians/proxy-stack'
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 15
19+
steps:
20+
- uses: actions/checkout@v4
21+
- name: the tag, agent/main.go and lib/agent.sh name the same version
22+
run: |
23+
v="${GITHUB_REF_NAME#agent-v}"
24+
grep -q "^const agentVersion = \"$v\"\$" agent/main.go
25+
grep -q "^PSM_AGENT_VERSION=\"$v\"\$" lib/agent.sh
26+
- name: gofmt, vet, test, static builds (golang container)
27+
run: |
28+
mkdir -p dist
29+
docker run --rm -v "$PWD/agent:/src:ro" -v "$PWD/dist:/dist" golang:1.27-alpine sh -c '
30+
set -e
31+
cp -a /src /w && cd /w
32+
test -z "$(gofmt -l .)"
33+
go vet ./... && go test -count=1 ./...
34+
for t in amd64:amd64 arm64:arm64 arm:armv7; do
35+
CGO_ENABLED=0 GOARCH=${t%%:*} GOARM=7 go build -trimpath -ldflags "-s -w" -o /dist/psm-agent-linux-${t##*:} .
36+
done
37+
cd /dist && sha256sum psm-agent-linux-* > SHA256SUMS && cat SHA256SUMS'
38+
- name: GitHub release
39+
env:
40+
GH_TOKEN: ${{ github.token }}
41+
run: |
42+
gh release create "$GITHUB_REF_NAME" dist/* \
43+
--title "psm-agent ${GITHUB_REF_NAME#agent-v}" \
44+
--notes "psm-agent for the PSM panel (jinqians/psm-panel). Installed by \`psm agent join\`, which checks SHA256SUMS."

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ jobs:
117117
- { os: debian, suite: retry }
118118
- { os: alpine, suite: retry }
119119
- { os: alma8, suite: retry }
120+
- { os: debian, suite: panelside }
121+
- { os: alpine, suite: panelside }
122+
- { os: rocky9, suite: panelside }
120123
steps:
121124
- uses: actions/checkout@v4
122125
- name: tests/integration/container.sh ${{ matrix.os }} ${{ matrix.suite }}

‎CHANGELOG.md‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@ All notable user-facing changes should be recorded here.
44

55
## Unreleased
66

7-
- Downloads retry. A single 500 or timeout from GitHub while fetching a core used to fail the whole install (it happened in CI: sing-box's release download answered 500 once). Every download of a core (Xray, sing-box, mihomo, Hysteria2, realm, Snell, ss-rust, cloudflared), of jq, the acme.sh and Docker installers, rule sets, the geo data, the VPN Gate server list and the latest-version lookups now retries three times on timeouts and HTTP 408/429/5xx, 2, 4 and 8 seconds apart; bootstrap.sh does the same for its own download. Covered by `tests/integration/retry.sh` on Debian, Alpine and AlmaLinux 8: against a local stand-in for GitHub that refuses every file twice before redirecting to the real one, the sing-box and Xray installs still succeed, a lasting error still fails after four tries, and no download line in the scripts is left without the retries.
7+
- Fixed `bash <(curl …) --panel … --join …` stopping with `PSM_DIR�: unbound variable` on a server that already had PSM, and ignoring `--panel` / `--join`. In `"…($PSM_DIR)"` bash read the first byte of the full-width bracket as part of the variable name under some locales; such variables are braced now, and `scripts/ci.sh` fails on any `$NAME` written directly before non-ASCII text. bootstrap.sh now takes `--panel URL --join TOKEN` (the PSM panel's install command): on a fresh server it installs PSM without questions (install.sh with `PSM_UNATTENDED=1`: no language prompt, no menu at the end) and then connects the server to the panel; on a server with PSM it updates PSM and connects it. The interactive install and update are unchanged.
8+
- New scriptable commands, used by the PSM panel's psm-agent and usable on their own. The menus are unchanged:
9+
- `psm core list|install xray|sing-box|mihomo [--if-missing] [--json]` installs a core without questions (the latest stable release and its service), so a server that has only PSM gets the core its first node needs.
10+
- `psm standalone install|show|export|remove snell|ss2022` installs the standalone Snell server (v4, v5 or v6: the newest official build of that major version; v6 is still a beta upstream, so its newest beta or release candidate) or ss-rust without questions, with a given port and PSK / key / method, on systemd and OpenRC, into the config files the menus use. `export` gives the Surge line, the `ss://` link or a sing-box outbound. The official snell-server does not run on musl, so on Alpine it is refused with that reason (Snell runs there on sing-box or mihomo).
11+
- `psm traffic list|set|reset|unset [--json]` meters a node (a core node by its tag, the standalone servers as `snell` / `ss2022`) and limits it: `set TAG --limit-bytes N | --limit-gb N [--reset-day D]`, where a limit of 0 meters without limiting (the traffic menu only metered limited nodes). A node over its limit is paused by the periodic check until the monthly reset or `psm traffic reset`.
12+
- `psm node export … --format singbox` prints the node as a sing-box client outbound.
13+
- `psm agent join --panel URL --token TOKEN` downloads psm-agent from this repository's release (checked against its SHA256SUMS), joins the panel and runs it as a service; `psm agent status`, `psm agent remove`.
14+
- `psm version` prints the date and commit of the checkout.
15+
Covered by `tests/integration/panelside.sh` on Debian, Alpine and Rocky Linux 9: PSM's unattended install, a core installed on demand, standalone Snell v4/v5/v6 and ss-rust installed, replaced on a new port, exported and removed (a sing-box client reaches the internet through ss-rust), and traffic counted, limited (the node refuses connections), reset and unset.
16+
- psm-agent 0.4.0: installs a node's core first when the server has never run it, installs and removes the standalone Snell / ss-rust servers, sets and resets traffic limits, sends the traffic counters to the panel every ten minutes (or when the panel asks), exports each node's sing-box outbound for subscriptions, and gathers a status report (PSM's version, the cores, `psm doctor`, the nodes, the traffic, the standalone servers) for the panel's diagnostics. New task arguments are checked against allowlists like the rest (a PSK or key that could be read as an option never reaches psm). Released as GitHub release `agent-v0.4.0` by `.github/workflows/agent-release.yml` (amd64, arm64, armv7 and SHA256SUMS; the tag must match the version in `agent/main.go` and `lib/agent.sh`).
17+
- ss-rust's download needs `xz`, which Debian and Ubuntu package as `xz-utils`: the name is mapped now, so the native ss-rust install works there too (it was only used on OpenRC before).
18+
- Downloads retry. A single 500 or timeout from GitHub while fetching a core used to fail the whole install (it happened in CI: sing-box's release download answered 500 once). Every download of a core (Xray, sing-box, mihomo, Hysteria2, realm, Snell, ss-rust, cloudflared), of jq, the acme.sh and Docker installers, rule sets, the geo data, the VPN Gate server list and the latest-version lookups now retries five times on timeouts and HTTP 408/429/5xx, backing off 1, 2, 4, 8 and 16 seconds (a first version waited a fixed 2 s three times, which a 504 from GitHub lasting longer outran in CI); bootstrap.sh does the same for its own download. Covered by `tests/integration/retry.sh` on Debian, Alpine and AlmaLinux 8: against a local stand-in for GitHub that refuses every file twice before redirecting to the real one, the sing-box and Xray installs still succeed, a lasting error still fails after six tries, and no download line in the scripts is left without the retries.
819
- Added `agent/`: psm-agent, which connects a server to the upcoming PSM panel (jinqians/psm-panel). A small static Go program with no dependencies that opens no port at all: like Xboard's node backends, it connects out to the panel over HTTPS (every 30 seconds when idle, every 3 while there is work; the panel sets the pace), reports the results of its last tasks and takes new ones. `psm-agent join -panel … -token …` trades the one-time token from the panel's install command for the server's own agent token (kept in a root-only config); `psm-agent run` is the sync loop. Every task is checked against allowlists (core, protocol, a node name that cannot start with "-", settings size) and run as a psm `--json` command with its arguments passed as an argv array, never through a shell; a task that fails the checks never reaches psm. Results survive a failed sync and are delivered on the next one. PSM does not install it yet: the install command (`bootstrap.sh --panel … --join …`) comes next. CI vets and tests it and builds it for amd64 and arm64. It replaces the loopback HTTP psm-api of the previous commit: the panel no longer needs a subdomain or a Tunnel per server.
920
- Fixed the main menu's right-hand column drifting left on servers without a UTF-8 locale (a fresh VPS often has none, and an SSH client's LANG may not exist on the server). The padding counted `${#s}`, which is bytes under the C locale, so every Chinese or Korean label looked three cells wide instead of two; Russian labels were off in every locale (counted 1.5 cells a letter), and the longest one overflowed the fixed 20-cell column. Label width is now read from the UTF-8 bytes whatever the locale (one cell for ASCII and two-byte sequences, two for three- and four-byte ones), and the left column is as wide as its longest label. Checked in all four languages under both the C and the C.UTF-8 locale; the full suites check that the menu is byte-identical in both.
1021
- Installing and updating no longer download the READMEs, the screenshots, the CI files or the tests to the server; `/opt/psm` holds only what PSM runs. bootstrap.sh clones with a partial-clone filter and a sparse checkout, so those files' contents never leave GitHub, and all three update paths (running the install command again, the menu's auto-update, `update.sh`) turn an existing full clone into the same slim one before they pull, removing the files it had. Their pulls skip the diffstat (`--no-stat`), which would otherwise read, and so download, every changed README and screenshot. Covered by `tests/integration/slim.sh` against a local stand-in for GitHub on Debian 13, Debian 10 (git 2.20), Ubuntu, Alpine and the Red Hat family: after each path the files are absent, their contents are not in git's object store, and the checkout is clean.

0 commit comments

Comments
 (0)