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 0440983d41b1024edc5548c56e0f093cbd7d9a52
parent 4946b648a1ace9b7d2f9a11ada7cb2860410faa3
Author: sqzer <110329244+sqzer-x@users.noreply.github.com>
Date: Thu, 6 Aug 2026 16:19:19 +0900

Two bugs found by running the paths that had never been run

transtation warp register destroyed a working identity. It deleted the live
profile and then attempted registration, so a rate-limited retry -- the single
most likely outcome of running the command by hand, since Cloudflare limits
registration per source IP for about fifteen minutes -- left the server with no
WARP configuration at all, and it degraded to direct egress on its next restart
without saying so. Registration now writes to temporary files and promotes them
only on success. Proved by planting a profile, forcing a rate-limited retry,
and confirming both the profile and the account file survive byte for byte.

The client installer could never report a failure. Its self-test piped curl
into grep into tr with the fallback message attached to the end of the
pipeline, so it hung off tr, which always succeeds. A client passing no traffic
whatsoever still finished with a success banner, which is exactly what happened
when the path was finally run. The result is captured and tested now, retried a
few times for slow links, and a client that is not working exits non-zero with
somewhere to look. Verified both directions: exit 1 against a dead server,
exit 0 against a live one.

Also points the documented install at the release that contains these fixes.

v1.0.4 and v1.0.5 were tagged from the wrong commit and are identical to
v1.0.3. They are left in place rather than moved: re-pointing a published tag
is the hazard this project's own digest pinning exists to avoid, and their
images are already on the registry.

Diffstat:
Minstall.sh | 34+++++++++++++++++++++++++++-------
Mserver/transtation | 25+++++++++++++++++++------
2 files changed, 46 insertion(+), 13 deletion(-)

diff --git a/install.sh b/install.sh
@@ -341,16 +341,36 @@ install_client() {
 			"$(client_image)" >/dev/null
 	fi
 
-	sleep 4
 	printf '  %-14s' self-test
-	if [ "$_tun" = 1 ]; then
-		curl -fsS --max-time 10 https://cloudflare.com/cdn-cgi/trace 2>/dev/null |
-			grep -E '^(ip|warp|colo)=' | tr '\n' ' ' || printf 'no answer yet'
+	# The result has to be captured and tested. An earlier version piped curl
+	# into grep into tr with `|| printf "no answer yet"` on the end -- which hangs
+	# off `tr`, the last command in the pipeline, and `tr` always succeeds. The
+	# fallback could never fire, so a client that was not passing any traffic at
+	# all still finished with a success banner.
+	_probe=""
+	_n=0
+	while [ "$_n" -lt 6 ]; do
+		if [ "$_tun" = 1 ]; then
+			_probe=$(curl -fsS --max-time 10 https://cloudflare.com/cdn-cgi/trace 2>/dev/null |
+				grep -E '^(ip|warp|colo)=' | tr '\n' ' ')
+		else
+			_probe=$(curl -fsS --max-time 10 --socks5-hostname 127.0.0.1:1080 https://cloudflare.com/cdn-cgi/trace 2>/dev/null |
+				grep -E '^(ip|warp|colo)=' | tr '\n' ' ')
+		fi
+		[ -n "$_probe" ] && break
+		_n=$((_n + 1))
+		sleep 2
+	done
+	if [ -n "$_probe" ]; then
+		say "$_probe"
 	else
-		curl -fsS --max-time 10 --socks5-hostname 127.0.0.1:1080 https://cloudflare.com/cdn-cgi/trace 2>/dev/null |
-			grep -E '^(ip|warp|colo)=' | tr '\n' ' ' || printf 'no answer yet -- check: docker logs transtation-client'
+		say "NO TRAFFIC"
+		say ""
+		say "  The client started but nothing is getting through. Look at:"
+		say "      docker logs transtation-client"
+		[ "$_tun" = 1 ] && say "      sudo transtation-panic     # to undo the tunnel and killswitch"
+		exit 1
 	fi
-	say ""
 
 	if [ "$_tun" = 0 ]; then
 		cat <<-'EOF'
diff --git a/server/transtation b/server/transtation
@@ -131,17 +131,29 @@ warp_ready() { warp_wanted && [ -f "$PROFILE" ]; }
 warp_register() {
 	# ONE attempt. Never a retry loop: hammering Cloudflare's registration
 	# endpoint is how a source IP -- historically a whole region -- gets blocked.
-	if wgcf register --accept-tos --config "$ACCOUNT" >/dev/null 2>&1 &&
-		wgcf generate --config "$ACCOUNT" --profile "$PROFILE" >/dev/null 2>&1; then
-		chmod 600 "$ACCOUNT" "$PROFILE"
+	#
+	# Registers into temporary files and promotes them only on success. An
+	# earlier version deleted the live profile first, so a rate-limited retry --
+	# the single most likely outcome of running this by hand -- destroyed a
+	# working WARP identity and silently degraded the server to direct egress on
+	# its next restart.
+	_ta="$DATA/.acct.$$"
+	_tp="$DATA/.prof.$$"
+	rm -f "$_ta" "$_tp"
+	if wgcf register --accept-tos --config "$_ta" >/dev/null 2>&1 &&
+		wgcf generate --config "$_ta" --profile "$_tp" >/dev/null 2>&1; then
+		chmod 600 "$_ta" "$_tp"
+		mv -f "$_ta" "$ACCOUNT"
+		mv -f "$_tp" "$PROFILE"
 		return 0
 	fi
-	rm -f "$PROFILE"
+	rm -f "$_ta" "$_tp"
 	warn "Cloudflare WARP registration failed (rate limit, or the API changed).
     Cloudflare rate-limits registration per source IP for roughly 15 minutes.
     Continuing with DIRECT egress from this VPS's own IP -- the proxy works.
       retry:            transtation warp register
-      turn off for good: set WARP=0 in your .env and 'docker compose up -d'"
+      turn off for good: set WARP=0 in your .env and 'docker compose up -d'
+    Any WARP identity you already had is untouched."
 	return 1
 }
 
@@ -1080,7 +1092,8 @@ case "${1:-run}" in
 		shift
 		case "${1:-}" in
 			register)
-				rm -f "$PROFILE"
+				# No pre-emptive delete: warp_register promotes its result only on
+				# success, so a failed retry leaves a working identity in place.
 				warp_register && { echo "WARP registered."; apply; }
 				;;
 			*) die "unknown: warp ${1:-}" ;;