transtation.git

transtation

~ Your own VLESS + REALITY proxy with optional Cloudflare WARP egress. Server in a container or native; native client with a fail-closed killswitch.
git clone https://github.com/debuman/transtation.git
commit 5c32a23129a433d96d8255d1ec350eaa8635f301
parent 4e35adf9138371b524fc4c2b36c391b22f783228
Author: sqzer <110329244+sqzer-x@users.noreply.github.com>
Date: Thu, 6 Aug 2026 16:42:28 +0900

The README told people to install a version five releases old

The version appears in the installer default and in every documented command,
and bumping it was a hand-run sed. It drifted: the README pointed at v1.0.2
while the current release was v1.0.7, so anyone following the instructions got
a build without the warp register fix, the client self-test fix or the
container hardening.

Fixed, and gated so it cannot happen again. test.sh checks that every version
string in install.sh and README.md agrees with the installer's own default, and
CI additionally requires that they equal the tag being released -- so a release
whose documentation points somewhere else fails before any image is published.

Diffstat:
M.github/workflows/ci.yml | 10++++++++++
MREADME.md | 12++++++------
Minstall.sh | 4++--
Mtest.sh | 11+++++++++++
4 files changed, 29 insertion(+), 8 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
@@ -19,6 +19,16 @@ jobs:
       - name: shellcheck
         run: sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck
 
+      - name: version strings must match the tag being released
+        if: startsWith(github.ref, 'refs/tags/v')
+        run: |
+          want="${GITHUB_REF_NAME}"
+          got=$(sed -n 's/^VERSION=${TT_VERSION:-\(v[0-9.]*\)}.*/\1/p' install.sh)
+          echo "tag=$want  install.sh=$got"
+          [ "$want" = "$got" ] || { echo "::error::install.sh pins $got but the tag is $want"; exit 1; }
+          bad=$(grep -oE 'v1\.[0-9]+\.[0-9]+' install.sh README.md | grep -v ":$want$" || true)
+          [ -z "$bad" ] || { echo "::error::stale version strings:"; echo "$bad"; exit 1; }
+
       # Builds both images for amd64 only and runs their offline self-checks.
       # Everything runs with --network none, so CI never registers a WARP
       # identity: hammering Cloudflare's registration endpoint from shared
diff --git a/README.md b/README.md
@@ -4,8 +4,8 @@ Your own VLESS + REALITY proxy, with optional Cloudflare WARP egress, in one
 container on any Linux box that has Docker.
 
 ```
-curl -fsSLO https://raw.githubusercontent.com/sqzer-x/transtation/v1.0.2/install.sh
-sha256sum install.sh      # compare against the hash in the v1.0.2 release notes
+curl -fsSLO https://raw.githubusercontent.com/sqzer-x/transtation/v1.0.8/install.sh
+sha256sum install.sh      # compare against the hash in the v1.0.8 release notes
 less install.sh           # please actually read it
 sudo sh install.sh
 ```
@@ -45,7 +45,7 @@ transtation exists because of a narrower set of preferences:
 Two-step form above is the recommended one. If you are going to pipe it anyway:
 
 ```
-curl -fsSL https://raw.githubusercontent.com/sqzer-x/transtation/v1.0.2/install.sh | sudo sh
+curl -fsSL https://raw.githubusercontent.com/sqzer-x/transtation/v1.0.8/install.sh | sudo sh
 ```
 
 The installer needs root, refuses anything other than x86_64/aarch64, installs
@@ -59,7 +59,7 @@ If you already run Docker and prefer to do it yourself:
 # /opt/transtation/docker-compose.yml
 services:
   proxy:
-    image: ghcr.io/sqzer-x/transtation:v1.0.2
+    image: ghcr.io/sqzer-x/transtation:v1.0.8
     container_name: transtation
     restart: unless-stopped
     ports: ["443:8443"]
@@ -132,7 +132,7 @@ install -Dm600 /dev/stdin /etc/transtation/uri   # paste the vless:// link, then
 docker run -d --name transtation-client --restart unless-stopped \
   -p 127.0.0.1:1080:1080 \
   -v /etc/transtation:/etc/transtation:ro \
-  ghcr.io/sqzer-x/transtation-client:v1.0.2
+  ghcr.io/sqzer-x/transtation-client:v1.0.8
 ```
 
 ```
@@ -166,7 +166,7 @@ docker run -d --name transtation-client --restart unless-stopped \
   -e MODE=tun \
   -e DIRECT_SUFFIXES=example.com,example.net \
   -v /etc/transtation:/etc/transtation:ro \
-  ghcr.io/sqzer-x/transtation-client:v1.0.2
+  ghcr.io/sqzer-x/transtation-client:v1.0.8
 ```
 
 Exactly three extra flags. `--privileged` is **not** needed and must not be
diff --git a/install.sh b/install.sh
@@ -15,12 +15,12 @@
 # last line. If the download is truncated, nothing runs.
 set -eu
 
-VERSION=${TT_VERSION:-v1.0.2}
+VERSION=${TT_VERSION:-v1.0.8}
 IMAGE_SERVER=${TT_IMAGE_SERVER:-ghcr.io/sqzer-x/transtation}
 IMAGE_CLIENT=${TT_IMAGE_CLIENT:-ghcr.io/sqzer-x/transtation-client}
 # Filled in by the release workflow. A tag is mutable -- the same stolen-token
 # compromise we already concede for Xray's .dgst would let someone retag
-# v1.0.2, and the hash-verified installer you read would vouch for nothing.
+# v1.0.8, and the hash-verified installer you read would vouch for nothing.
 SERVER_DIGEST=${TT_SERVER_DIGEST:-}
 CLIENT_DIGEST=${TT_CLIENT_DIGEST:-}
 
diff --git a/test.sh b/test.sh
@@ -56,6 +56,17 @@ present_in_compose() { server_compose | grep -qE "$1"; }
 check "the compose block caps the log size (xray logs to stdout)" present_in_compose 'max-size'
 check "the compose block uses a named volume, not a bind mount" present_in_compose 'transtation-data:/data'
 check "no script ever runs 'nft flush ruleset'" uncommented_absent 'flush ruleset' host/transtation-killswitch
+
+# The version is repeated in the installer and in every documented command.
+# It drifted five releases once, because bumping it was a hand-run sed, and the
+# README ended up telling people to install a version without the security
+# fixes. One check is cheaper than remembering.
+versions_agree() {
+    _v=$(sed -n 's/^VERSION=${TT_VERSION:-\(v[0-9.]*\)}.*/\1/p' install.sh)
+    [ -n "$_v" ] || return 1
+    ! grep -oE 'v1\.[0-9]+\.[0-9]+' install.sh README.md | grep -v ":$_v$" | grep -q .
+}
+check "every documented version string matches install.sh" versions_agree
 # Hardening the compose file grants. The running process was verified to hold
 # CapEff 0000000000000000 with these in place.
 check "the compose block drops all capabilities" present_in_compose "cap_drop"