Both installers still invoked `ostp --setup` / `ostp.exe --setup` to launch
the first-run wizard on a fresh install. The CLI's subcommand refactor
(2026-07-08, "Refactor CLI to subcommands") turned `setup` into
`Commands::Setup { .. }` with no top-level `--setup` flag left in Args at
all, so every fresh install has hit "error: unexpected argument '--setup'
found" and dropped the user out of the installer instead of the wizard.
Verified `ostp setup --help` parses correctly with the fix.
scripts/gha.ps1's -Branch ValidateSet accepted 'pre-release' and would
`git checkout pre-release` to promote alpha, but no such branch has ever
existed in this repo — only `beta` does (confirmed: `git branch -a`, and
the existing 0.4.6-beta/0.4.7-beta release history was cut from `beta`).
The very first beta release under the new gha.ps1 versioning scheme would
have failed outright on the checkout step.
This naming mismatch had spread through the whole release surface:
- scripts/gha.ps1: -Branch ValidateSet + all internal checks
- .github/workflows/release.yml: a dead branch-name check (harmless only
because the workflow currently triggers on tag-push, not branch-push)
plus two comments
- CONTRIBUTING.md / .ru.md: branch-strategy table documented a
`pre-release` branch that doesn't exist
- README.md / .ru.md and ostp/src/main.rs: the `ostp update -b <name>`
CLI help text/docs
- scripts/install.sh: the channel match the CLI flag feeds into
Renamed all of it to `beta` to match the branch that actually exists.
Left scripts/gha.ps1:21's "semver pre-release identifier" alone — that's
the generic semver spec term, unrelated to the branch name, and got
reverted after a blanket replace briefly clobbered it.
Note: install.sh's alpha/beta self-update paths still assume a rolling
GitHub release tagged literally "alpha"/"beta" exists, which no gha.ps1
release ever publishes (only versioned tags like v0.4.7-beta.3) - that's
a separate, real bug, tracked apart from this rename since fixing it needs
either a floating tag from gha.ps1 or an API-query rewrite of install.sh.
A script parameter named exactly $Switch collides with PowerShell's `switch`
statement keyword - confirmed by bisection - and made every `$X = switch (...)
{...}` in the script silently evaluate to empty instead of erroring. This is
what produced the malformed "v0.4.1-.0" tag on the last release attempt
(Channel resolved to "" instead of "stable", Iteration to 0). Renaming the
parameter is the only fix; nothing else about the switch statement itself
was wrong.
Previous scheme conflated "which release is this" with "how many times has
it been rebuilt": every run bumped the patch version, so by the time a build
was ready to promote to master the version number had already crept forward
by however many alpha/beta iterations it took to get there.
Now a release cycle has one fixed target version (e.g. 0.4.1) that stays in
every manifest unchanged through all alpha/beta iterations; only a
per-channel counter increments, and that counter lives ONLY in the git tag,
never in Cargo.toml:
v0.4.1-alpha.1 -> v0.4.1-alpha.2 -> ... -> v0.4.1-alpha.N
v0.4.1-beta.1 -> v0.4.1-beta.2 -> ... -> v0.4.1-beta.N
v0.4.1 <- master, iteration dropped
Deliberately "0.4.1-alpha.N" (dot AFTER the hyphen — a semver pre-release
identifier), not "0.4.1.N-alpha" (a 4th dot component before the hyphen):
the latter isn't valid semver and Cargo's version parser rejects it outright,
so it can never appear in Cargo.toml. That's also why the target version
itself never needs to change on a plain iteration — bumping every manifest +
refreshing both Cargo.locks is now skipped entirely unless -Switch actually
changes the target, making a routine alpha/beta push fast (just the state
file's counter + a tag).
Also fixes a real bug found while touching this: release.yml's push trigger
is tags-only ("v*") with no branch trigger, so the old `git push origin
$branch`-only path for alpha/pre-release never actually started a CI run —
only the master path (which already pushed a tag) worked. Every channel now
always pushes a real tag, which is what actually triggers the build.
release.yml's resolve-channel needed no changes: its tag-channel detection
already does substring matching (*-alpha*/*-beta*), so it classifies
"v0.4.1-alpha.37" correctly without modification.
-Prefix is gone — channel was always 1:1 with -Branch (alpha/pre-release/
master), so it was a redundant, independently-settable axis that could
silently drift from the branch (e.g. -Branch alpha -Prefix beta).
- fix(cli): stop printing the startup banner ("ostp-cli vX.Y.Z | OS: ...")
to stderr on every single command invocation. init_tracing() ran
unconditionally before command dispatch, so `ostp -V`, `ostp gk`, etc. all
showed it. It's still written to the log file (useful there), just no
longer echoed via the stderr tracing layer for one-shot commands.
- feat(client): add ostp-client::migrate, the ONE place config migration
runs. Previously there were three uncoordinated migration paths: a Python
snippet embedded in scripts/install.sh (only touched server api.* fields,
ran on every update), the old 0.3.x line's auto-migration on every hot
reload (silent besides a log warning), and nothing at all for the current
rebuild. Consolidated into one module covering every config shape that's
actually existed:
- v0.3.1-v0.3.21 modular (inbounds/outbounds/routing) -> current flat
schema, including correctly resolving routing.default_outbound through
a urltest/selector group to the real server, and reporting (not
silently dropping) every additional server a multi-server config had.
- pre-0.3.1 flat configs carrying now-dead fields (tun.wintun_path,
tun.ipv4_address, transport.wss) -> dropped with an explicit reason,
everything else passes through untouched.
- server configs -> backfills api.* defaults and drops legacy api.token
(ported straight from the install.sh Python, same behavior, correct
place).
6 unit tests cover all of the above against realistic fixtures. Wired up
as `ostp migrate` (was missing from Commands entirely) — no other code
path calls into this module, so a config's shape only ever changes when
explicitly asked.
- feat(cli): `ostp import <url>` now asks the same TUN/mux/debug questions
`ostp connect <url>` always did. Previously import just wrote flat
defaults to disk with no way to turn any of that on short of hand-editing
the resulting config.json afterward. Extracted the shared prompt into
prompt_client_options() so both paths stay in sync.
- chore(install): remove the embedded Python config-migration snippet from
install.sh; schema migration must never happen implicitly during an
install/update. Points users at `ostp migrate` instead.
Replaces ad-hoc manual tag pushes (which is how the confusing v0.4.1-beta /
v0.4.2-beta / bare "nightly" / "pre-release" release mess happened) with one
script that always goes through the same path: bump every version manifest,
commit, and push in the way release.yml's resolve-channel job actually
expects (branch push for nightly/pre-release, a real "vX.Y.Z" tag for
master — never a hand-pushed "vX.Y.Z-beta"-style tag).
Remembers the last {version, branch, prefix} used in .release-state.json, so
a bare run repeats last time's channel with the patch version bumped, and
-Switch starts a new version line (e.g. 0.3.x -> 0.4.0) without disturbing
which channel is currently being released to.
Brings the useful work from the old master/pre-release lineage (5c2b5a0)
onto the clean rebuild, since that lineage never had the multi-server/WSS/
Reality removal or any of the 0.4.0 stability work. This is a manual port,
not a cherry-pick — this file's Args/ClientConfig shape had already
diverged too much for the patch to apply mechanically.
- ostp/src/main.rs: flat Args -> clap subcommands (setup, init, generate-key,
links, check, connect, uninstall, update, import, proxy-env,
proxy-env-clear), bridged onto the existing ~500 lines of flag-driven
dispatch via a LegacyArgs struct so none of that logic had to change.
- Fixed a real bug found while porting: GenerateKey's `count` used
short='c', colliding with the global `--config` short (also 'c'), which
clap validates across the whole command tree on first parse() -- a
duplicate short flag there could break every subcommand's parsing, not
just generate-key's.
- `update` now takes `-b/--branch` and `-v/--version` explicitly (was a
bare flag with no way to target a channel or exact version).
- Added the UAC elevation step for TUN mode that this lineage's CLI was
completely missing (`run_client_directly` went straight to creating the
TUN adapter unelevated). Also fixed the elevation check itself: it only
tested `ret <= 32`, but ShellExecuteW returns ERROR_CANCELLED (1223) when
the user clicks "No" on the UAC prompt -- > 32, so a denied prompt was
read as success and the process exited silently without starting the
tunnel. Now ret==1223 is reported explicitly, and a genuine failure logs
GetLastError() so the real Win32 cause is visible next time.
- scripts/install.sh: added -b/--branch alongside the existing -v/--version,
and channel-aware release resolution (nightly/pre-release use their own
rolling tag; stable resolves via the GitHub API's "latest").
- release.yml: added nightly/pre-release branch-push triggers for rolling
prereleases. Fixed the tag_name logic from 5c2b5a0, which mapped
`master` pushes to a release tagged "nightly" -- backwards from master
being the most stable channel. github.ref_name already equals the
branch or tag name that triggered the run, so no per-branch remapping is
needed at all; master is intentionally left off the branch-push list --
it only ever gets real version tags.
Verified: `cargo build -p ostp` succeeds, and `ostp update --help` /
`ostp generate-key --help` / `ostp --help` show the expected flags with no
clap panic.
Co-Authored-By: Claude <noreply@anthropic.com>
security / protocol:
- Derive a PER-KEY junk marker (obfuscation.rs, info byte 0x04) instead of the
global constant [0x88,0x1A,0x93,0x5D]. A fixed marker was a universal DPI
signature identifying ALL OSTP users at once — exactly what the HKDF version
gate avoids for the handshake. Server drops junk via a new DispatchOutcome::Junk
inside the existing key-trial loop (secrets already derived → zero extra cost);
client stamps its own key's marker.
- §E: configurable junk/fragmentation params (junk_pc / junk_ps / frag_chunk / frag_sleep).
GUI (desktop):
- Light theme + toggle, GUI version footer in Settings.
- Fix mouse-wheel scroll on Settings (flex child needed min-height: 0).
- Drop the false "process exclusions unsupported in TUN mode" warning — they DO
work (native_handler maps port->process via GetExtendedTcpTable).
release / infra:
- build.ps1: add -PreRelease (tag CURRENT version as v<ver>-beta.N, no bump, no
master commit); guard the panel build when ostp-control ships no source; bump
the real ostp-gui/package.json instead of the nonexistent ostp-control one.
- release.yml: mark hyphenated tags as GitHub pre-releases; don't hard-fail the
web-panel step when there is no source (use committed dist/).
- Versions aligned to 0.4.1; README license badge BSL 1.1 -> AGPL v3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "system" TUN stack that shelled out to a bundled tun2socks binary is
long-dead — Flutter already hardcodes the native OSTP stack — and only
bloats the build.
- ostp-jni: drop the tun2socks spawn branch and the tun_child handle;
the native TUN (run_native_tunnel_from_fd) is now unconditional. The
JNI signature is kept ABI-stable (t2sBinPath/localProxy retained but
ignored) to avoid breaking the Kotlin linkage without an Android build.
- Delete the committed 10 MB tun2socks-arm64 asset; drop the tun2socks
download steps from the Android build scripts.
- Remove the dead tun2socks.exe entry from the desktop build_dist.js
(it required a file nothing downloads, breaking the GUI dist build).
- Reword stale tun2socks references in proxy.rs, the GUI config comment,
install.ps1, the release workflow matrix, and CONTRIBUTING.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Binary at /opt/ostp/ostp, symlink at /usr/local/bin/ostp
- Config moved to /etc/ostp/config.json (standard Linux layout)
- Auto-migration from legacy paths: ~/ostp, /root/ostp, old /opt/ostp/config.json
- Systemd service updated with RUST_LOG=info
- Test script updated to discover binary via PATH first
build.ps1:
- Added mandatory cargo check pre-flight that blocks releases on errors
- Added --Check flag for check-only mode (no build, no release)
- Reverts version bump if check fails
- Professionalized all output (removed informal language)
- Cleaner output structure with consistent [ok], [warn], [error] tags
install.ps1 / install.sh:
- Professionalized all prompts and messages
- Removed informal phrasing
- Consistent formatting
test_linux.sh:
- Updated all log string matchers to match professionalized output:
'Connection established' (was 'Bridge connection established')
'Starting server' (was 'Starting in SERVER mode')
'Starting client' (was 'Starting in CLIENT mode')
RTT regex updated for new format
release.yml:
- Added cargo check pre-flight step before native compilation