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 6130401a3912467a1e47fe3f631526a57aa83669
parent ff4f5dedd288d28fb7a3653ce2aa651be0db1937
Author: sqzer <110329244+sqzer-x@users.noreply.github.com>
Date: Thu, 6 Aug 2026 20:55:49 +0900

Stop telling people to restart a server that is merely still starting

The runtime file is written a moment into startup, after the dest probe, so
`transtation show` immediately after `systemctl start` hit the not-started
branch and was told to run `systemctl restart transtation`. Following that
advice restarts the clock and produces the same message, which is the worst
kind of wrong: actionable, confident, and a loop.

It now waits while the service is genuinely coming up, and only says that when
it is not. The distinction matters more than the wait: a stopped server still
fails immediately rather than hanging for fifteen seconds, and test.sh asserts
that, because getting the guard backwards would make every command on a down
server feel broken.

Found by racing a restore on real hardware -- the restore itself was fine and
the identity came back byte-identical, but reading the link one second later
gave advice that could not work.

Diffstat:
MREADME.md | 8++++----
Minstall.sh | 4++--
Mserver/transtation | 16++++++++++++++++
Mtest.sh | 16++++++++++++++++
4 files changed, 38 insertion(+), 6 deletion(-)

diff --git a/README.md b/README.md
@@ -6,8 +6,8 @@ the client is a native install, because putting a machine behind a proxy means
 owning its routing table and a container is the wrong shape for that.
 
 ```
-curl -fsSLO https://raw.githubusercontent.com/sqzer-x/transtation/v1.0.17/install.sh
-sha256sum install.sh      # compare against the hash in the v1.0.17 release notes
+curl -fsSLO https://raw.githubusercontent.com/sqzer-x/transtation/v1.0.18/install.sh
+sha256sum install.sh      # compare against the hash in the v1.0.18 release notes
 less install.sh           # please actually read it
 sudo sh install.sh
 ```
@@ -49,8 +49,8 @@ how you feel about a container runtime on your VPS.
 ### In a container (default)
 
 ```
-curl -fsSLO https://raw.githubusercontent.com/sqzer-x/transtation/v1.0.17/install.sh
-sha256sum install.sh      # compare against the hash in the v1.0.17 release notes
+curl -fsSLO https://raw.githubusercontent.com/sqzer-x/transtation/v1.0.18/install.sh
+sha256sum install.sh      # compare against the hash in the v1.0.18 release notes
 less install.sh           # please actually read it
 sudo sh install.sh
 ```
diff --git a/install.sh b/install.sh
@@ -15,11 +15,11 @@
 # last line. If the download is truncated, nothing runs.
 set -eu
 
-VERSION=${TT_VERSION:-v1.0.17}
+VERSION=${TT_VERSION:-v1.0.18}
 IMAGE_SERVER=${TT_IMAGE_SERVER:-ghcr.io/sqzer-x/transtation}
 # 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.17, and the hash-verified installer you read would vouch for nothing.
+# v1.0.18, and the hash-verified installer you read would vouch for nothing.
 SERVER_DIGEST=${TT_SERVER_DIGEST:-}
 
 # sing-box ships no checksum file, so these were computed once from the official
diff --git a/server/transtation b/server/transtation
@@ -24,6 +24,13 @@ TAB=$(printf '\t')
 TT_MANAGED=${TT_MANAGED:-systemd}
 restart_hint() { [ "$TT_MANAGED" = docker ] && printf 'docker compose up -d' || printf 'systemctl restart transtation'; }
 env_hint() { [ "$TT_MANAGED" = docker ] && printf '/opt/transtation/.env' || printf '/etc/transtation/server.env'; }
+# Is the server up, or on its way up? Under docker this command only runs at all
+# because `docker exec` found a live container, so the answer is yes by
+# construction; natively, ask the init system.
+server_starting() {
+	[ "$TT_MANAGED" = docker ] && return 0
+	systemctl is-active --quiet transtation 2>/dev/null
+}
 
 set_paths() {
 	# The container mounts its volume at /data; a native install keeps state
@@ -323,6 +330,15 @@ write_runtime() {
 }
 
 load_runtime() {
+	# The runtime file is written a moment into startup, after the dest probe.
+	# Running `transtation show` straight after `systemctl start` therefore used
+	# to be told to restart -- advice that loops you, because waiting was all it
+	# needed. Wait briefly when the thing is genuinely coming up, and only then
+	# say so.
+	if [ ! -f "$RUNTIME" ] && server_starting; then
+		_w=0
+		while [ ! -f "$RUNTIME" ] && [ "$_w" -lt 15 ]; do sleep 1; _w=$((_w + 1)); done
+	fi
 	[ -f "$RUNTIME" ] || die "server has not started yet -- run '$(restart_hint)' first"
 	HOST=""; PORT=""; LISTEN_PORT=""; SNI=""; DEST=""; WARP=""; WARP_READY=""; SNI_REASON=""
 	while IFS='=' read -r _k _v; do
diff --git a/test.sh b/test.sh
@@ -121,7 +121,23 @@ wrapper_backup_is_0600() (
 	transtation restore "$_t/junk.tgz" >/dev/null 2>&1 && return 1
 	return 0
 )
+# `transtation show` straight after `systemctl start` used to be told to restart,
+# which loops you: waiting was all it needed. It now waits -- but only while the
+# service is actually coming up. A stopped server must still fail immediately,
+# or every command on a down server hangs for fifteen seconds.
+down_server_fails_fast() (
+	_d=$(mktemp -d) _b=$(mktemp -d) || return 1
+	trap 'rm -rf "$_d" "$_b"' EXIT
+	printf 'SCHEMA=1\nREALITY_PRIV=x\nREALITY_PUB=y\nCREATED=2026-01-01\n' >"$_d/state.env"
+	printf 'main\t8f3e21c4-7a09-4b2e-9d51-6c0f1a2b3c4d\t1c69b566b0480c74\n' >"$_d/users.tsv"
+	printf '#!/bin/sh\nexit 3\n' >"$_b/systemctl"   # "inactive"
+	chmod +x "$_b/systemctl"
+	_t0=$(date +%s)
+	PATH="$_b:$PATH" TT_MANAGED=systemd TT_DATA=$_d sh server/transtation status >/dev/null 2>&1 && return 1
+	[ $(( $(date +%s) - _t0 )) -lt 3 ]
+)
 check "both install paths implement the same transtation verbs" wrapper_parity
+check "a stopped server fails immediately instead of waiting for it" down_server_fails_fast
 check "native backup is 0600 even under root's umask, and will not clobber" wrapper_backup_is_0600