* 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>
* 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
* 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