mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-19 05:03:10 +07:00
efc603f59c4730dbbe58caab0819ad675f06aecc
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d2ac3b4d7a |
fix(cli): let -getApiToken name the token it regenerates (#6405)
* fix(cli): let -getApiToken name the token it regenerates The flag's help text said "Display current API token". It cannot display anything -- tokens are stored as SHA-256 hashes, and the command's own first two output lines say so. What it does is destroy and reissue a credential: GetApiToken calls RecreateByName on the hardcoded name "cli-fallback". The help therefore invited an operator to run a command they believed was read-only, and it revoked a token someone else was holding. Because that name is a single global slot, two callers silently invalidate each other, and the loser is left with a token that answers HTTP 404 with an empty body -- indistinguishable from a wrong base path, so the failure does not even say what happened. install.sh is one of those callers, at lines 1231 and 1325, so the collision already exists inside this repository. Add -tokenName, defaulting to cli-fallback so install.sh and every existing invocation behave exactly as before. -getApiToken stays a boolean on purpose: install.sh calls it as `x-ui setting -getApiToken true`, and a string flag would swallow that trailing argument and mint a token named "true". The name now reaches both branches of GetApiToken. On a database with no tokens the command used to create one called "install", which the CLI could then never rotate -- defeating the stated purpose of the cli-fallback constant, that -getApiToken cannot accumulate admin-equivalent credentials it never revokes. Both branches use the resolved name, so repeated calls rotate a single slot instead of leaving a permanent token behind. Also cap the name at 64 characters in RecreateByName. Create already enforces that limit on the same column; RecreateByName did not, and it now receives operator input. Assisted-by: Claude Code:claude-opus-5 (mostly) * fix(cli): keep the installer's token out of the rotated slot Folding both branches of GetApiToken onto one name made the bug worse in the exact case this change is about. install.sh records the token it gets on a fresh panel; with both branches on cli-fallback, the next bare -getApiToken rotated that very row and silently invalidated the credential written into the install-result file. Restore the split default -- "install" when the database has no tokens, cli-fallback when it does -- so nothing about an unnamed call changes. An explicit -tokenName still applies to both branches, which is what keeps the flag coherent: -tokenName ci-bot now yields ci-bot on a fresh panel too, rather than "install". Pin it with a test that reads the install row's id and hash before and after a rotation, since a name-only assertion would pass against a deleted-and- recreated row. * fix(cli): stop the `-getApiToken true` form from swallowing -tokenName Three corrections from review. Go's flag package stops parsing at the first non-flag argument, so the trailing `true` in install.sh's invocation does not merely get ignored -- it terminates parsing. An operator copying that documented shape and writing `x-ui setting -getApiToken true -tokenName ci-bot` left tokenName empty, so the command rotated cli-fallback: the shared-slot collision this change exists to remove, reachable through the one form the repository itself demonstrates. Verified against the built binary, which printed `The API token "cli-fallback" has been regenerated`. Drop the stray `true` from both install.sh call sites so the documented form no longer teaches the trap, and warn whenever `setting` is given positional arguments, naming what was ignored. A warning rather than an error, because an older install.sh in the wild still passes `true` and must keep working. Cover both branches in the help strings. They described only the rotation path, so on a fresh panel -h announced cli-fallback while the command actually mints `install`, and nothing is regenerated or invalidated there at all -- misleading help being the defect this change set out to remove. Assert the concrete error in the name-length test. It checked only that some error came back, which RecreateByName's empty-name guard and its transaction errors would satisfy just as well. |
||
|
|
fc05249e0c |
fix(geofile): verify downloaded geo databases against published digests (#6404)
* fix(geofile): verify downloaded geo databases against published digests UpdateGeofile wrote whatever the three upstreams returned straight into the Xray asset folder with no integrity check. Xray parses these databases when it builds its routing matchers, so a corrupted or substituted file takes the core down at its next start. The panel already does this for the other artifact it downloads: installXray checks the release archive against the SHA-256 published in its .dgst sidecar. The geo databases were the one download that skipped it, even though all three upstreams publish a <asset>.sha256sum beside every .dat. Fetch that sidecar, compare it against the bytes that actually arrived, and stage every file in a temporary folder first, so one bad database installs nothing rather than leaving the core running databases from two releases. Match the digest line by base name rather than by the path it records. Loyalsoldier and runetfreedom write "<hash> geoip.dat" while chocolate4u writes "<hash> release/geoip.dat" -- the path from its own build -- so `sha256sum --check` semantics fail on a perfectly good download. Also skip the Xray restart when every upstream answered 304. The conditional GET was already there, but the restart ran unconditionally and dropped every client connection on a refresh that changed nothing. Assisted-by: Claude Code:claude-opus-5 (mostly) * fix(geofile): pin the release and scope atomicity to one upstream Four corrections to the digest verification, all from review. Pin the release. The asset and its .sha256sum were fetched as two independent requests to releases/latest/download/, so GitHub re-resolved "latest" between them. These upstreams publish several times a day -- 202609022346, 202609030908 and 202609031849 are three tags from one day -- so a release landing mid-batch had release N+1's digest checked against release N's bytes, reporting a healthy upstream as "corrupted or tampered with". Resolve the tag once per upstream from the redirect GitHub already returns, then fetch body and digest from it. Modeling the entry as repo + asset rather than an opaque URL is what makes that possible. Scope atomicity to one upstream. A single failure discarded every verified download, so one transient 5xx from one of three independent repositories threw away four good files and re-downloaded tens of MB on the next attempt. The integrity argument holds for a geoip/geosite pair out of one release; across repositories it buys nothing. Each upstream now installs or aborts on its own and errors are collected, as the code did before this feature. Make the all-or-none test deterministic. It ranged a map, so when the corrupt entry came first the run returned before the good file was ever requested and the assertions held trivially -- a coin flip that would also pass against an implementation installing each file as it verified. Iteration is sorted now, and the test asserts the good file was actually downloaded first. Assert which error. The error table checked only that err != nil, so its two branches could swallow each other's cases; each row now pins the message. Also trims three comment blocks to the two-line limit. Assisted-by: Claude Code:claude-opus-5 (mostly) |
||
|
|
0ff3c23948 |
fix(api-docs): generate request bodies for all encodings (#6296)
* fix(api-docs): generate request bodies for all encodings The OpenAPI generator only recognized generic body parameters, so JSON, form, and multipart declarations disappeared into empty application/json objects. Generate the declared media type and schema, preserve optionality and conditional requirements, and encode repeated form arrays the way Gin expects. Correct the request metadata exposed by the complete schemas and keep the panel and docs specifications synchronized. * fix(api-docs): align alternative request schemas Keep non-empty constraints on the selected request-body alternative without rejecting empty values for the alternatives that panel requests also include. Allow null client IP lists because model serialization emits them while cleared rows await pruning. * fix(api-docs): send object urlencoded fields as JSON, document the inbound update body Four defects the request-body rework exposed or left behind: - An object-typed field in an x-www-form-urlencoded body got no encoding entry, so OpenAPI 3.0 serialized it form-style. Swagger "Try it out" and generated clients sent memberWeights=3&memberWeights=0.2 to /panel/api/sub-balancers, and parseSubBalancerForm json.Unmarshals the raw field, so every such call failed with "invalid memberWeights". Emit encoding.<name>.contentType = application/json instead. - bodyRequiredOneOf names were never checked against the declared body params: a typo emitted an anyOf branch requiring a property that does not exist — unsatisfiable — and make gen still passed. Throw now, and extend the requestSchema guard to reject bodyRequiredOneOf as well. - /panel/api/inbounds/update/:id advertised no request body although its own summary says the shape mirrors /add and updateInbound binds one. Both entries now share an inboundBody const so they cannot drift. - The mixed-locations error was the only buildOperation throw without the method and path, aborting make gen without naming the offender. Regenerated frontend/public/openapi.json and copied it to docs/public/openapi.json. No MDX regeneration: no summary changed. --------- Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com> |
||
|
|
f294e1806d |
feat(release): publish SHA-256 sums and verify them in install.sh/update.sh (#6393)
* feat(release): publish SHA-256 sums and verify them in install.sh/update.sh The installer and updater fetched the release archive and extracted it after checking only that the file is not empty, and the release workflow published no checksums. TLS protects the transport, not the bytes: a truncated or swapped asset, a bad mirror or a TLS-terminating proxy was installed as root. #5396 added this verification for the Xray archive; the panel's own archive was the remaining unverified download. Publish <asset>.sha256 next to every release archive (Linux and Windows) and verify it before extracting. A mismatch aborts the install; a missing sidecar, which every release before this change has, only warns, so installing older tags keeps working. Assisted-by: Claude Code:claude-fable-5-1 * fix(install): fail closed when the checksum sidecar cannot be fetched Review follow-up. Any curl failure on the sidecar (5xx, reset, DNS) was treated as "no checksum published", so whoever can swap the archive could also drop the 90-byte sidecar request and skip the check. Only a 404, which every release before the sidecar existed returns, is still tolerated with a warning; every other outcome aborts and removes the downloaded archive. Assisted-by: Claude Code:claude-fable-5-1 * fix(install): restore the closing brace lost in the main merge |
||
|
|
4019f47de2 |
fix(x-ui.sh): put the fail2ban backend override in jail.d, not jail.conf (#6392)
* fix(x-ui.sh): put the fail2ban backend override in jail.d, not jail.conf create_iplimit_jails switched the global fail2ban backend to systemd on Debian 12+ and Ubuntu 22.04+ with sed on /etc/fail2ban/jail.conf. That file is the package's conffile: the next fail2ban upgrade either drops the edit or keeps a stale jail.conf, depending on the conffile prompt. Write the same override to /etc/fail2ban/jail.d/3x-ipl-backend.conf, which fail2ban reads after jail.conf and which upgrades leave alone, and remove it together with the other 3x-ipl files on uninstall. The 3x-ipl jail itself keeps its explicit backend=auto. Assisted-by: Claude Code:claude-fable-5-1 * fix(x-ui.sh): only override the stock fail2ban backend, keep it on partial removal Review follow-ups. The old sed only rewrote a literal 'backend = auto' in jail.conf's [DEFAULT], so an operator's own backend survived it; the override file was written unconditionally. Write it only when jail.conf still carries the stock value. And keep the file when only the IP-limit jail is removed: the sed was never reverted either, and deleting a [DEFAULT] override there would flip every inheriting jail back to auto on the restart in the same branch. The full /etc/fail2ban removal path still deletes it. Assisted-by: Claude Code:claude-fable-5-1 |
||
|
|
195988bdc1 |
fix(install): fetch x-ui.sh and unit files from the installed release tag (#6391)
* fix(install): fetch x-ui.sh and unit files from the installed release tag install.sh and update.sh pin the panel archive to a release tag but always took x-ui.sh, x-ui.rc and the service units from main, so the management script and the binary of one installation came from different commits: the fail2ban templates and setting flags the script writes drift silently against an older binary, two installs of the same tag differ, and a reviewed or digest-pinned installer still runs unreviewed code from main. Use the same ref as the archive, keeping main only for the rolling dev-latest build. The menu's "update menu" and update_shell paths now fetch the script matching the installed version and fall back to main with a visible notice when no script is published for it. Assisted-by: Claude Code:claude-fable-5-1 * fix(install): fall back to main for files a pinned tag does not publish Review follow-ups. install.sh accepts tags down to v2.3.5, but x-ui.rc only exists from v2.8.4 and the split x-ui.service.* files are newer still, so pinning those to the tag made an Alpine install of an old tag 404 after the previous install was already removed. Probe the tag for each file and fall back to main with a notice when it is missing, as the menu already does for x-ui.sh. The fail2ban auto-setup probe also trusted the exit status of 'x-ui setup-fail2ban', but scripts before v3.4.0 have no such subcommand and exit 0 from the usage banner, so the installer reported a setup that never ran. Skip with a notice when the installed script does not know the subcommand. Assisted-by: Claude Code:claude-fable-5-1 * fix(install): refuse a tag that does not publish a needed script Falling back to main reintroduced the binary/script mismatch the tag pinning exists to remove, and it fired at points where install.sh and update.sh have already stopped and removed the previous installation -- so the quiet path was also the one that could not be undone. Probe the tag instead, before anything is touched, for every file that is always fetched from GitHub (x-ui.sh, plus x-ui.rc on Alpine), and abort with the HTTP status when one is missing. The unit files stay unprobed: they are only fetched when the release tarball omits them, so an old tag that ships x-ui.service inside its tarball still installs. Their existing failure message now names the ref it tried. Also tighten the setup-fail2ban probe to the dispatcher's case arm rather than any mention of the string, which also matches a comment. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
23511108bf |
fix(database): keep the SQLite store owner-only (#6390)
* fix(database): keep the SQLite store owner-only InitDB created the data directory 0755 and let SQLite create x-ui.db and its -wal/-shm side files under the default umask, so on a stock install they are world-readable. The store holds client UUIDs, Reality private keys and the admin password hash, so any local account could read them. Create the directory 0700 and chmod the database files to 0600 right after opening. SQLite gives -wal/-shm the mode of the main file, so files created later inherit it; existing installs are tightened on the next start. PostgreSQL deployments are untouched. Assisted-by: Claude Code:claude-fable-5-1 * fix(database): tolerate chmod failures, keep the dump and install dir owner-only Review follow-ups. A store the panel cannot chmod (root_squash NFS, a foreign uid in a container) refused to start, which is worse than the 0644 it had before; log and continue instead, as the backup-directory cleanup above already does. install.sh reset /etc/x-ui to 0755 right after the binary created it 0700, so the directory hunk was inert on real installs; create it 0700 there too. The migrate-db dump in the same directory is a plaintext copy of the same secrets and was written 0644. Assisted-by: Claude Code:claude-fable-5-1 |
||
|
|
f9898e0b24 |
fix(sub): randomize fresh panel subscription paths (#6375)
* fix(sub): randomize fresh panel subscription paths Seed distinct cryptographically random paths for base64, JSON, and Clash subscriptions when a panel database is first created. Persist them so restarts keep published URLs stable while upgrades preserve existing settings. Generated-by: OpenCode:gpt-5.6-sol * fix(sub): regenerate paths on settings reset Keep subscription paths unpredictable after a factory reset, close the test database on failure, and update the builder, OpenAPI, and localized docs to describe panel-specific paths instead of obsolete fixed defaults. Generated-by: OpenCode:gpt-5.6-sol |