An upgrade is four moves: git pull,
cargo build --release on the VM,
systemctl restart qnt-server, and the smoke. Migrations
ride along on restart, and the restart drains in-flight connections
cleanly. If a release goes bad, the rollback is the same shape in
reverse — check out the last-known-good SHA and rebuild.
The repo is checked out at /opt/ngrok-qt-quic on the VM,
the binary lives at
/opt/ngrok-qt-quic/target/release/qnt-server, and it
runs as the systemd unit qnt-server. Build on the VM —
it's the target box.
From the repo root, pull (or check out a tag), put cargo on
PATH, build the release binary, then restart the unit:
cd /opt/ngrok-qt-quic
git pull
source ~/.cargo/env
cargo build --release -p qnt-server
systemctl restart qnt-server The build takes ~4 min on 2 vCPU. Pin to a tag or SHA for reproducible deploys rather than tracking a moving branch.
Give it a few seconds, then check the unit is active:
sleep 5 && systemctl is-active qnt-server active is-active returning active means
qnt-server bound its ports and stayed up. If it doesn't, jump to
section 5.
The binary catches SIGINT to drain in-flight
connections, and the unit sets KillSignal=SIGINT with
TimeoutStopSec=30s — so systemctl restart
lets active tunnels finish rather than cutting them.
There is no manual migrate step. Any new migrations shipped in a release run automatically when qnt-server starts.
Because migrations run at boot, a bad migration can wedge the boot — qnt-server won't come up clean. That's exactly what the rollback in section 4 is for. Take a backup before an upgrade so the restore path is short.
is-active proves the process is up; the smoke proves the
product still works. Treat it as the post-condition of every upgrade.
The repo ships an end-to-end smoke script. Run it after the restart:
bash scripts/agent-day1-smoke.sh
A clean run is the signal the upgrade landed. A failing smoke on a
unit that's otherwise active is your cue to roll back
before customers notice.
Rollback is the upgrade in reverse: check out the last-known-good SHA, rebuild, restart, and re-run the smoke. Same box, same shape.
List the recent history and pick the commit you were on before the bad release:
cd /opt/ngrok-qt-quic
git log -5 --oneline Or grab the previous commit straight into a variable:
PREVIOUS=$(git log --oneline -5 | sed -n 2p | awk '{print $1}')
Swap <sha> for the last-known-good commit, then
run the same build-and-restart you'd use for an upgrade:
git checkout <sha>
source ~/.cargo/env && cargo build --release -p qnt-server
systemctl restart qnt-server sleep 5 && bash scripts/agent-day1-smoke.sh Migrations are idempotent, so rebuilding the previous binary and restarting brings the database and service back to a known-good pairing.
If qnt-server doesn't come back after a restart, the logs tell you why — and systemd has a backstop so a crash loop surfaces instead of spinning forever.
Pull the last 100 lines of the unit's journal to see what it choked on:
journalctl -u qnt-server -n 100 --no-pager
The unit runs Restart=on-failure with
StartLimitBurst=10 in 120s. After 10 rapid failures
systemd stops retrying and surfaces the failure in
systemctl status rather than looping indefinitely.
systemctl status qnt-server
If you've hit the limit and fixed the cause, clear the failed state
with systemctl reset-failed qnt-server before
restarting.
Upgrades and rollback in hand. Round out the operational guides.