Skip to content

Commit b9d243c

Browse files
authored
Auto-find available port and version-check running instances (#13)
- Bind TCP listener before SMB setup; auto-increment port on AddrInUse (up to 100 tries) so the proxy never fails on a busy port. - Add version to Server header (spiceio/X.Y.Z) so the setup action can detect stale instances. - Setup action now compares running version against installed binary and replaces outdated instances. Bare "Server: spiceio" (pre-versioned builds) is treated as outdated. - Readiness loop discovers actual endpoint from SPICEIO_LOG_FILE, handling port auto-increment correctly.
1 parent a5e2c9d commit b9d243c

3 files changed

Lines changed: 68 additions & 21 deletions

File tree

.github/actions/setup/action.yml

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,29 @@ runs:
8080
run: |
8181
set -euo pipefail
8282
83-
ENDPOINT="http://${SPICEIO_BIND}"
84-
85-
# Skip if spiceio is already listening on the requested address
86-
if curl -sf -I "$ENDPOINT/" 2>/dev/null | grep -qi "server: spiceio"; then
87-
echo "spiceio already running at $ENDPOINT — skipping start"
88-
echo "endpoint=$ENDPOINT" >> "$GITHUB_OUTPUT"
89-
exit 0
83+
# Check if spiceio is already listening on the requested address
84+
WANT_VERSION=$(spiceio --version | awk '{print $2}')
85+
RUNNING_HEADER=$(curl -sf -I "http://${SPICEIO_BIND}/" 2>/dev/null | grep -i '^server:.*spiceio' | tr -d '\r' || true)
86+
if [[ -n "$RUNNING_HEADER" ]]; then
87+
# Extract version from "Server: spiceio/X.Y.Z"; bare "Server: spiceio"
88+
# (no slash) means a pre-versioned build — treat as outdated.
89+
RUNNING_VERSION="${RUNNING_HEADER##*/}"
90+
RUNNING_VERSION="${RUNNING_VERSION// /}"
91+
if [[ "$RUNNING_HEADER" == */* && "$RUNNING_VERSION" == "$WANT_VERSION" ]]; then
92+
echo "spiceio $WANT_VERSION already running at http://${SPICEIO_BIND} — skipping start"
93+
echo "endpoint=http://${SPICEIO_BIND}" >> "$GITHUB_OUTPUT"
94+
exit 0
95+
fi
96+
# Old or mismatched version — kill it so we can replace it
97+
mapfile -t OLD_PIDS < <(lsof -ti "tcp:${SPICEIO_BIND##*:}" -sTCP:LISTEN 2>/dev/null || true)
98+
if (( ${#OLD_PIDS[@]} > 0 )); then
99+
echo "replacing spiceio ${RUNNING_VERSION:-unknown} (PID(s) ${OLD_PIDS[*]}) with $WANT_VERSION"
100+
kill "${OLD_PIDS[@]}" 2>/dev/null || true
101+
for i in $(seq 1 10); do
102+
lsof -ti "tcp:${SPICEIO_BIND##*:}" -sTCP:LISTEN >/dev/null 2>&1 || break
103+
sleep 1
104+
done
105+
fi
90106
fi
91107
92108
# Parse smb://user:pass@server:port/share
@@ -127,21 +143,28 @@ runs:
127143
export SPICEIO_SMB_SERVER SPICEIO_SMB_PORT SPICEIO_SMB_USER SPICEIO_SMB_PASS SPICEIO_SMB_SHARE
128144
export SPICEIO_BUCKET SPICEIO_REGION SPICEIO_BIND
129145
146+
SPICEIO_LOG="${RUNNER_TEMP}/spiceio.log"
147+
: > "$SPICEIO_LOG"
148+
export SPICEIO_LOG_FILE="$SPICEIO_LOG"
149+
130150
spiceio &
131151
PID=$!
132-
echo "endpoint=$ENDPOINT" >> "$GITHUB_OUTPUT"
133152
134-
# Wait for readiness
153+
# Wait for spiceio to report its actual listening address (port may
154+
# auto-increment if the requested port was busy).
135155
echo "Waiting for spiceio on ${SPICEIO_BIND}..."
156+
ENDPOINT=""
136157
for i in $(seq 1 30); do
137-
if curl -sf -o /dev/null "$ENDPOINT/" 2>/dev/null; then
138-
echo "spiceio ready at $ENDPOINT (PID $PID)"
139-
exit 0
140-
fi
141158
if ! kill -0 "$PID" 2>/dev/null; then
142159
echo "::error::spiceio exited unexpectedly"
143160
exit 1
144161
fi
162+
ENDPOINT=$(grep 'listening on' "$SPICEIO_LOG" 2>/dev/null | grep -o 'http://[^ ]*' | tail -1 || true)
163+
if [[ -n "$ENDPOINT" ]] && curl -sf -I "$ENDPOINT/" 2>/dev/null | grep -qi "server: spiceio"; then
164+
echo "spiceio ready at $ENDPOINT (PID $PID)"
165+
echo "endpoint=$ENDPOINT" >> "$GITHUB_OUTPUT"
166+
exit 0
167+
fi
145168
sleep 1
146169
done
147170
echo "::error::spiceio failed to start within 30s"

src/main.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,32 @@ async fn main() {
9292

9393
let config = Config::from_env();
9494

95+
// Bind TCP listener early (before SMB setup). If the port is taken,
96+
// auto-increment until an available port is found.
97+
let (listener, bind_addr) = {
98+
let mut addr = config.bind_addr;
99+
let start_port = addr.port();
100+
loop {
101+
match TcpListener::bind(addr).await {
102+
Ok(l) => break (l, addr),
103+
Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
104+
let next = match addr.port().checked_add(1) {
105+
Some(n) if n - start_port <= 100 => n,
106+
_ => {
107+
serr!("no available port in range {start_port}–{}", addr.port());
108+
std::process::exit(1);
109+
}
110+
};
111+
addr.set_port(next);
112+
}
113+
Err(e) => {
114+
serr!("failed to bind TCP listener: {e}");
115+
std::process::exit(1);
116+
}
117+
}
118+
}
119+
};
120+
95121
slog!(
96122
"[spiceio] connecting to smb://****@{}:{}/{} ({}x)",
97123
config.smb_server,
@@ -128,13 +154,6 @@ async fn main() {
128154
multipart: MultipartStore::new(),
129155
});
130156

131-
let bind_addr = config.bind_addr;
132-
133-
// Bind TCP listener
134-
let listener = TcpListener::bind(bind_addr)
135-
.await
136-
.expect("failed to bind TCP listener");
137-
138157
slog!("[spiceio] listening on http://{bind_addr}");
139158
slog!(
140159
"[spiceio] bucket: {} region: {}",

src/s3/router.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1402,7 +1402,12 @@ fn with_common_headers(
14021402
}
14031403
headers.insert(X_AMZ_ID_2, request_id.parse().unwrap());
14041404
headers.insert(X_AMZ_BUCKET_REGION, region.parse().unwrap());
1405-
headers.insert("Server", "spiceio".parse().unwrap());
1405+
headers.insert(
1406+
"Server",
1407+
concat!("spiceio/", env!("CARGO_PKG_VERSION"))
1408+
.parse()
1409+
.unwrap(),
1410+
);
14061411
// CORS allow
14071412
if !headers.contains_key("access-control-allow-origin") {
14081413
headers.insert("Access-Control-Allow-Origin", "*".parse().unwrap());

0 commit comments

Comments
 (0)