Skip to content

Releases: LEADBERG-studio/tunnellio-api-client

v0.6.6

Choose a tag to compare

@LEADBERG-studio LEADBERG-studio released this 15 Aug 18:19

tunnellio 0.6.6

Release summary

One line of behaviour, and it is diagnostic only: the client now tells the bridge
which client it is. Nothing else changed, and nothing on the server side is
required - the field was already read, recorded and displayed. It was simply
always empty.

Small as it is, this closes a hole that cost real time. During a two-session hunt
for a bridge fault, every server log line about an opened session ended with
version: not given. "Who is connected" was answerable; "with what version are
we talking" was not.

Highlights

  • The hello frame carries clientVersion as tunnellio-client/<version>.
  • It appears in the connection journal, in the live-channels view of the console,
    and in docker logs tcp-bridge on the server.
  • It also answers the operational question that comes before any strict rule:
    who is left to update.

Installation

Windows: download tunnellio.exe and run it. No install step, no dependencies.

Any platform: take tunnellio-source-v0.6.6.zip, then

pip install .

Requires Python 3.11 or newer.

Upgrading

Drop-in. Command line, config files and the bridge protocol are unchanged, so
0.6.5 and 0.6.6 can run side by side against the same server.

Changelog

The client says which client it is

The server has recorded the client version since the bridge existed, in the
journal and in its own log. Nobody ever filled the field, so every session read
clientVersion: not given - including in the log lines that were supposed to
settle arguments about which side was at fault.

  • The hello frame now carries clientVersion (tunnellio-client/<version>).
    It shows up in the connection journal, in the live-channels view, and in
    docker logs on the server.

That is the whole release. It changes no behaviour and requires nothing on the
server side: the field was already read and stored, it was simply always empty.
It matters because it answers the second question of every incident, right after
"who is connected": with what version are we talking, and who is left to update.

Artifacts

  • tunnellio.exe - standalone Windows x64 binary
  • tunnellio-windows-x64-v0.6.6.zip - the same binary, zipped
  • tunnellio-source-v0.6.6.zip - platform-neutral source archive, built from the
    release commit
  • SHA256SUMS.txt - checksums for everything above

v0.6.5 - stable tcp-bridge

Choose a tag to compare

@LEADBERG-studio LEADBERG-studio released this 14 Aug 21:37

Tunnellio v0.6.5

Коротко

  • Мост снова поднимается сам после обрыва связи. До этой версии не поднимался - ни на платном тарифе, ни на бесплатном, и это та самая причина, по которой на него жаловались. Переподключение в клиенте есть с 0.6.3 и работает, когда обрыв видно. Обрыв в дороге не видно: ни FIN, ни RST ни до кого не доходят, сокет остаётся открытым, - и обе половины были устроены так, что верили молчанию. Клиент стучал, сервер не отвечал, поэтому канал был молчащим по построению, а мёртвый туннель ничем не отличался от простаивающего.
  • Ровно поэтому SSH переподключался всегда, а мост никогда. У SSH есть ServerAliveInterval. У моста был стук, на который никто не отвечал.

Что изменилось

  • Молчание стало приговором, а не состоянием. Сервер отвечает на каждый стук, и у клиента появился признак жизни, который не стоит ни одного байта данных. Молчание дольше 45 секунд (три пропущенных ответа) закрывает сеанс и передаёт дело уже существующему переподключению. Адрес при этом не меняется.
  • Сервер больше не блокирует сам себя при переподключении. Прощание с мёртвым сокетом держало единственный общий замок, а прощание с мёртвым сокетом длится столько, сколько ядро повторяет попытки, то есть минуты. Всё это время мост стоял целиком: посетитель не мог найти сеанс, соединение не принималось, новое рукопожатие не регистрировалось. Туннель не возвращался вообще. Теперь под замком только обмен записями в таблицах.
  • Живость проверяет и ядро. Keepalive на управляющем соединении со сроками 30/10/3 вместо двух часов по умолчанию, с обеих сторон: мёртвый канал замечается даже когда через туннель ничего не идёт.
  • Санация того, что остаётся после обрыва. Соединение, у которого своя программа не приняла сокет, закрывается сразу, а не висит у сервера в очереди, пока посетитель ждёт свой таймаут. Список обслуженных соединений в клиенте чистится - он не чистился никогда, и у долгоживущего туннеля рос с каждым запросом. Посетитель, которого не удалось передать клиенту, снимает сеанс, а не оставляет его глотать всех следующих.

Как пользоваться

  • В настройках, командах и флагах не изменилось ничего. Существующий профиль продолжает работать.
  • Два срока при необходимости правятся в src/tunnellio/bridge.py: HEARTBEAT_INTERVAL (15 с) и SILENCE_LIMIT (45 с). Предел должен оставаться не меньше трёх интервалов, иначе один потерянный пакет будет прочитан как обрыв.
  • Честное ограничение: между обрывом и тем, как клиент его заметит, проходит до 45 секунд. Посетители, пришедшие в это окно, получают 504 Gateway Timeout примерно через 15 секунд - это срок недоставленного соединения на сервере. Внятный отказ лучше страницы, которая грузится минуту, но это не нулевой простой, а сократить его можно только более частым стуком.

Changelog

  • Сервер моста отвечает на heartbeat. Раньше не мог: клиент стучит раз в 15 секунд, а ожидание чтения у сервера истекает через 30, поэтому свой heartbeat у сервера не наступал никогда и канал со стороны клиента был вечно молчащим.
  • Молчание дольше SILENCE_LIMIT (45 с) считается оборванным каналом: сеанс закрывается, дальше работает уже существующее переподключение с нарастающей паузой, под тем же именем.
  • На управляющем соединении включён TCP keepalive со сроками 30/10/3 - и в клиенте, и на сервере - вместо двух часов по умолчанию.
  • Сервер больше не прощается с прежним сеансом под общим замком. Прощание и ответы застрявшим посетителям уходят в фон со сроком, а сокет, не ответивший за этот срок, обрывается силой.
  • Посетитель, которого не удалось передать клиенту моста, снимает сеанс, а не оставляет его глотать каждого следующего.
  • Соединение, у которого своя программа не приняла сокет, закрывает свою сторону сразу, а не остаётся в очереди сервера.
  • Клиент чистит список потоков обслуженных соединений. Он не чистился никогда, и у долгоживущего туннеля рос с каждым запросом.
  • В ответе 503 застрявшему посетителю правильно указана длина тела: она была на байт меньше, и получатель дочитывал ответ до обрыва связи вместо аккуратного конца.
  • Снимок машины в мониторе моста снимается до замка, а не под ним.

Артефакты

  • tunnellio.exe - Windows x64, самостоятельный, Python не нужен
  • tunnellio-windows-x64-v0.6.5.zip - бинарник вместе с README, CHANGELOG, config.example.json и docs/
  • tunnellio-source-v0.6.5.zip - исходники из тега v0.6.5, без привязки к системе
  • SHA256SUMS.txt

0.6.1 - fix tcp-bridge

Choose a tag to compare

@LEADBERG-studio LEADBERG-studio released this 13 Aug 17:11

Tunnellio v0.6.1

Коротко

TCP-мост наконец поднимает туннель. Настроенный домен у зарегистрированного
пользователя не публиковался вовсе, а случайный только выглядел работающим,
потому что его никто ни о чём не спрашивал. Мешало шесть разных поломок
одновременно, и каждой по отдельности хватало, чтобы туннеля не было.

Главное

  • Настроенный домен работает — и по токену, и просто с регистрацией.
    POST /v1/sessions/open больше не требует keyId. В мосту нет SSH, значит
    нет и ключа, который можно назвать. Шесть источников уже отвечали
    requiresSshKey: false; седьмая, проверка запроса, говорила обратное и
    побеждала.
  • Ключ адреса. Выдаётся вместе с поддоменом, приезжает владельцу в
    connectionProfile.tcpBridge.token и идёт в кадре приветствия. До этого любой,
    кто знал чужой поддомен, забирал живой туннель себе: прежний сеанс закрывался
    как replaced, и никто ничего не спрашивал.
  • Рукопожатие завершается. Сервер отвечает публичным портом, и клиент
    перестал считать успешный вход провалом.
  • Туннель стоит. Тишина в управляющем канале больше не читается как разрыв,
    а клиент сам подаёт голос раз в пятнадцать секунд, чтобы домашний роутер не
    забыл сопоставление за ночь.
  • OAuth через мост отвергается честно. Мост передаёт байты насквозь, токен
    проверить негде. Раньше такой домен выглядел защищённым в консоли и был
    открыт всему интернету.

Что работает и при каких условиях

Ситуация Адрес Мост Цена
Без учётной записи, без ключей и токена случайный tmp-xxxxxxxx, 24 часа работает бесплатно
Регистрация, домен настроен на мост свой постоянный поддомен работает бесплатно
На домене задан пароль моста свой поддомен, нужен пароль работает Pro
Работа через API по токену, адрес любой настроенный или случайный работает Pro
  • Постоянный поддомен по-прежнему только для зарегистрированного: это имя в
    нашем домене на годы, и за ним обязан стоять человек. Случайный адрес живёт
    сутки и умирает сам.
  • На тарифе Free до 10 доменов; лишние помечаются plan_limited.
  • authMode: oauth и dual несовместимы с connectionMode: tcp_bridge и
    auto. Для OAuth — Cloud proxy, для моста — Legacy.
# Без всего, случайный адрес
.\tunnellio.exe connect --transport tcp-bridge --domain random --local-port 8787 --run

# Регистрация и настроенный домен, SSH-ключа нет нигде
.\tunnellio.exe --token <ТОКЕН> connect --domain existing:tuntun ^
  --local-host 127.0.0.1 --local-port 8787 --transport tcp-bridge --run --watch --name myvpn-files

# Домен с паролем моста (Pro)
.\tunnellio.exe --token <ТОКЕН> connect --domain existing:demo ^
  --tcp-bridge-password <ПАРОЛЬ> --local-port 8787 --transport tcp-bridge --run

Что изменилось

  • Ключ адреса уходит в кадре приветствия и подтверждает право публиковать под
    этим именем.
  • Ключ отдаётся всегда, когда сервер его прислал, а не только когда потребовал:
    мягкий сервер тоже должен знать, с кем говорит.
  • Пароль моста больше не берётся из служебного токена. Это разные вещи, и
    подстановка давала гарантированный invalid_bridge_password на каждом домене
    с паролем.
  • Рукопожатие с загадкой пропускается для родного протокола. Наш мост не
    присылает Challenge, поэтому каждое соединение стояло до таймаута, а
    выглядело это как «туннель поднялся и не отвечает».
  • Тишина в управляющем канале не принимается за разрыв. Ждали полсекунды,
    сервер подаёт голос раз в тридцать: мост умирал через полсекунды после
    успешного рукопожатия.
  • Клиент сам подаёт голос раз в пятнадцать секунд. Никто этого не требует, но
    домашний роутер забывает простаивающее сопоставление молча, и туннель,
    простоявший ночь, наутро уже никуда не ведёт.
  • Отсутствие публичного порта больше не считается провалом рукопожатия:
    HTTP-мост публикует по имени, и номера порта в ответе может не быть.
  • Идентификатор соединения посылается под двумя именами, id и connectionId.
    Сервер читает первое, мы посылали второе, и каждое соединение заканчивалось
    connection_not_found.
  • Предел кадра поднят до 8192 байт, как на сервере. 256 хватало ровно до того
    дня, когда в приветствии появились адрес и порт.
  • .well-known/oauth-protected-resource больше не запрашивается у публичного
    адреса на legacy- и бридж-доменах: там стоит чужая программа, она отвечает
    404, и этот 404 попадал в лог как ошибка.
  • Runtime name: печатается один раз; второй, пустой, убран.
  • docs/TCP_BRIDGE.md и docs/ru/TCP_BRIDGE.md приведены к тому, как протокол
    работает на самом деле: ключ адреса, коды отказов, ограничение по OAuth.

Со стороны сервера, для справки (репозиторий tunnellio)

  • POST /v1/sessions/open принимает запрос без keyId, если режим домена
    tcp_bridge или auto. Явно неверный keyId вида -5 по-прежнему
    validation_error, а Direct SSH без ключа так и остаётся невозможным.
  • Сессия без ключа больше не стирает ключ, уже настроенный на домене.
  • heartbeat, resume и close проверены на сессии без ключа; ключа они не
    требовали и не требуют.
  • Мост отвергает незнакомое имя (unknown_hostname), неверный ключ владельца
    (invalid_bridge_key, всегда) и недоступную базу (bridge_unavailable)
    вместо того, чтобы пускать всех. Клиент вовсе без ключа — по
    TCP_BRIDGE_OWNER_KEY_REQUIRED; пока 0, такой клиент проходит и
    записывается как bridge.legacy.
  • Приветствие сервера несёт публичный порт, публичный адрес и период голоса.
  • oauth_requires_proxy отвергает сочетание моста и OAuth и при создании
    домена, и при открытии сессии.
  • Ответ о сессии называет свой keyId, включая его отсутствие.

Файлы

  • tunnellio.exe
  • tunnellio-windows-x64-v0.6.1.zip
  • tunnellio-source-v0.6.1.zip

Как проверялось

  • Тесты: 91 в клиенте, 154 на сервере.
  • Живая проверка насквозь, на настоящих сокетах, а не на заглушках: настоящий
    управляющий порт моста, настоящий публичный порт, настоящий локальный сервис и
    запрос снаружи. Настроенный домен по токену открыл сессию без ключа, запрос
    дошёл до локального сервиса и вернулся, удар сердца сессию не убил, а чужой
    ключ адреса отвергнут с invalid_bridge_key. Проверка лежит в репозитории
    сервера как tests/live_bridge_check.py — именно потому, что модульные тесты
    обеих половин были зелёными, пока половины не работали вместе.

Порядок обновления

  • Сначала деплой сервера: клиент присылает ключ адреса, и что с ним делать,
    знает только исправленный сервер.
  • Когда новый клиент разойдётся, поставить на сервере
    TCP_BRIDGE_OWNER_KEY_REQUIRED=1. До этого старые клиенты продолжают
    подключаться и видны в журнале как bridge.legacy — по этой строке и понятно,
    кого осталось обновить.
  • Конфигурации, команды и флаги не менялись.

v0.6.0 stable

Choose a tag to compare

@LEADBERG-studio LEADBERG-studio released this 03 Aug 20:01

Tunnellio v0.6.0

Summary

  • The API token is now the most capable credential, not a hard prerequisite. Three connection modes work entirely without it, and a plan limit is no longer reported as an authentication failure.

Highlights

  • Credential-aware modes. With an API token everything works. With only an SSH key you still get ssh_stable, tcp_stable and tcp_random. With no credentials at all the two keyless bridge modes still work. In those three modes the API token is never read and never validated.
  • Plan limits are not auth failures. 403 plan_required on the advisory POST /v1/meta used to become an AuthError and abort the entire connect, even for transports that never needed that metadata. It is now a distinct PlanRequiredError and the launch continues.
  • Offline SSH plan. A reserved domain plus its bound key already contains everything needed to connect, so the client builds the SSH command locally with no API call.

Availability matrix

Credentials Available modes
API token everything: full API flow, ssh_stable, tcp_stable, tcp_random
SSH key only ssh_stable, tcp_stable, tcp_random
nothing tcp_stable, tcp_random
# No credentials at all
.\tunnellio.exe connect --transport tcp-bridge --domain random --local-port 3000 --run

# Reserved domain, still no API token
.\tunnellio.exe connect --transport tcp-bridge --domain existing:mcp --local-port 3000 --run

# Reserved domain plus your own SSH key, still no API token
.\tunnellio.exe connect --transport ssh --domain existing:mcp --local-port 3000 --run

Artifacts

  • tunnellio.exe
  • tunnellio-windows-x64-v0.6.0.zip
  • tunnellio-source-v0.6.0.zip

Usage notes

  • Operations that genuinely use the Integration API still require a token: creating a domain or key, cloud proxy, OAuth flows and --transport auto. Nothing there changed.
  • POST /v1/meta and POST /v1/capabilities are advisory. When a plan refuses them, whatever was unavailable is listed in the degraded array of the JSON result.
  • When capabilities are unknown, the client no longer enforces local guesses about what the account may do. The server stays the source of truth and rejects anything it does not allow. When capabilities are known, every gate behaves exactly as before.
  • Backward compatible: existing configs, commands and flags are unchanged.

Changelog

  • Added tunnellio/modes.py with the credential matrix: resolve_mode(), available_modes(), requires_api_token(), requires_ssh_key().
  • connect derives the token requirement from the mode the launch resolves to, instead of demanding one up front.
  • Added Planner.build_offline_ssh_plan() for direct SSH to a reserved domain with no API call.
  • connect routes to the keyless bridge endpoint automatically for tcp_stable and tcp_random when no token is configured.
  • Added PlanRequiredError, PLAN_REQUIRED_CODES and is_plan_limit(); a plain forbidden with no plan wording is still an AuthError.
  • Added Capabilities.known / Capabilities.unknown() and PlanResult.degraded.
  • Fixed bridge --save-profile, which always crashed because _save_profile() called meta.to_dict() while the keyless path passes meta=None by design.
  • Added tests/test_errors.py and tests/test_modes.py; 91 tests pass across errors, modes, planner, cli, client, bridge, config and oauth.

Verification

  • Live-checked with the shipped binary and no API token: ssh_stable builds the correct reverse-forward command locally, tcp_stable and tcp_random reach the public keyless endpoint and receive a real server-issued domain.
  • Confirmed that an API-only flow (--domain new:...) still refuses to run without a token.

Notes for GitHub publication

  • Upload the binary and zip from this release subfolder.
  • Recheck that the changelog matches CHANGELOG.md for the target version.
  • Recheck that the version number in the title matches the shipped artifacts.

0.5.0 - TCP tunnel adding (+ private mode)

Choose a tag to compare

@LEADBERG-studio LEADBERG-studio released this 02 Aug 14:50

Tunnellio v0.5.0

Summary

  • This release adds native TCP bridge password support, a fully self-sufficient pure-Python TCP bridge client, and a keyless public launch endpoint.
  • The client now works on Windows, Linux, and macOS without any external binaries — the bridge is implemented natively inside the package.

Highlights

  • Native TCP bridge client (src/tunnellio/bridge.py) — pure Python, no external binaries, bore-compatible wire protocol with HMAC-SHA256 auth
  • Keyless bridge commandtunnellio bridge --local-port 3000 --run works without API token, without SSH key, without any external binary
  • TCP bridge password--tcp-bridge-password flag, passwordRequired and clientProtocol in connection profile, automatic password in hello/accept frames
  • Native Tunnellio wire format{"type":"hello","hostname":"...","password":"..."} when clientProtocol.hello is present, bore-format fallback otherwise
  • Public keyless endpointPOST /v1/tcp-bridge/launch without Bearer auth
  • Auto-fallback--transport auto tries SSH first, falls back to TCP bridge on quick failure
  • Cleaned repository — removed stray nested clone and obsolete bootstrap script

Artifacts

  • tunnellio.exe — standalone Windows binary (Python not required on target machine)
  • tunnellio-source-v0.5.0.zip — universal source archive (Python 3.11+ required)

Usage notes

Simplest tunnel (no token, no key, no external binary)

.\tunnellio.exe bridge --local-port 3000 --run --name my-bridge

With a chosen subdomain

.\tunnellio.exe bridge --domain new:my-app --local-port 3000 --run --name my-bridge

With TCP bridge password

.\tunnellio.exe --token YOUR_TOKEN connect --domain new:my-app --local-port 3000 --transport tcp-bridge --tcp-bridge-password demo-secret --run --name my-bridge

In a Unix sandbox

unzip tunnellio-source-v0.5.0.zip -d tunnellio && cd tunnellio
pip install .
tunnellio bridge --local-port 3000 --run --name my-bridge

Transport comparison

Command Token SSH key External binary Platform
bridge no no no Windows, Linux, macOS
connect --transport tcp-bridge yes no no Windows, Linux, macOS
connect --transport ssh yes yes OpenSSH any
connect --transport auto yes yes OpenSSH any

Changelog

  • Added TCP bridge password support (tcpBridgePassword, passwordRequired, clientProtocol)
  • Added --tcp-bridge-password CLI flag for plan, connect, and bridge commands
  • Added TcpBridgeClientProtocol model with hello template from server
  • Added passwordRequired and clientProtocol fields to TcpBridgeProfile
  • Added tcpBridgePassword field to DomainSummary
  • Updated bridge.py to send native Tunnellio wire format when clientProtocol.hello is provided
  • Updated bridge.py to send {"type":"accept","connectionId":"...","password":"..."} for connection accept
  • Updated cli.py to pass hello_template, password, password_required to launch_bridge()
  • Updated planner.py to pass tcpBridgePassword in launch-spec domain block and keyless bridge payload
  • Updated config template and config example with tcpBridgePassword field
  • Added tests for password flow, hello template, and CLI flag parsing
  • Backward compatible: bore-protocol mode still works when clientProtocol is absent
  • Implemented native TCP bridge client in pure Python (tunnellio/bridge.py) — no external binaries needed
  • Wire protocol: null-delimited JSON frames, bore-compatible control handshake, HMAC-SHA256 auth
  • cli.py runs the bridge natively via launch_bridge() instead of subprocess.Popen for tcp_bridge transport
  • TcpBridgeProcess provides the same pid/poll/terminate/kill/wait interface as subprocess.Popen
  • Bidirectional TCP copy via select for connection forwarding
  • Client is now fully self-sufficient on Windows, Linux, and macOS — only Python 3.11+ required
  • Added bridge CLI command for one-shot keyless TCP bridge tunnels — no API token, no SSH key required
  • Added public keyless endpoint POST /v1/tcp-bridge/launch (no Bearer auth) in ApiClient
  • Added Planner.build_keyless_bridge_plan() that skips meta/capabilities/launch-spec and calls the public endpoint directly
  • Added requiresApiToken field to ConnectionProfile and TcpBridgeProfile
  • Added is_tokenless property to ConnectionProfile
  • Made PlanResult.meta and PlanResult.domain optional (None for keyless bridge flow)
  • Made bridge command exempt from the API token requirement
  • Added TCP bridge transport (connectionMode = tcp_bridge) as a keyless alternative to reverse SSH
  • Added --transport CLI flag (ssh, tcp-bridge, auto) for explicit transport selection
  • Added automatic fallback from SSH to TCP bridge when --transport auto is used and SSH fails quickly
  • Removed stray nested clone and obsolete bootstrap script from repository

Notes for GitHub publication

  • Upload tunnellio.exe and tunnellio-source-v0.5.0.zip from this release subfolder.
  • Version: 0.5.0 (verified in pyproject.toml and src/tunnellio/__init__.py).
  • Commit: 813d289
  • Changelog matches CHANGELOG.md.

v0.1.7 OAuth clients contract

Choose a tag to compare

@LEADBERG-studio LEADBERG-studio released this 25 Jul 13:33

Tunnellio v0.1.7

Summary

  • This release aligns the shipped version number, documentation, and release artifacts to 0.1.7.
  • It includes the live OAuth Authorization Code + PKCE client flow and validated release packaging.
  • The client works with the real server-side OAuth contract and includes corrected token refresh persistence behavior.

Highlights

  • Live OAuth Authorization Code + PKCE flow through oauth-login, oauth-refresh, and oauth-introspect.
  • Discovery and protected-resource metadata support aligned with the real server contract.
  • Refresh flow preserves the previous refreshToken when the server omits a new one.

Artifacts

  • tunnellio.exe
  • tunnellio-source-v0.1.7.zip
  • tunnellio-windows-x64-v0.1.7.zip

Usage notes

  • The server remains the source of truth for final authMode and connectionMode.
  • For saved OAuth sessions, use --token-name or --token-file with the OAuth commands.

Changelog

  • Aligned the client with the new discovery, auth, and session contract.
  • Added OAuth discovery helpers, protected-resource metadata loading, PKCE utilities, and extended session-aware planning/runtime metadata.
  • Added live-validated session lifecycle handling with resume-token aware heartbeat, resume, and close calls.
  • Expanded config, runtime snapshots, tests, and docs for the new server contract.

Notes for GitHub publication

  • Upload the binary and zip from this release subfolder.
  • Version corrected to 0.1.7.
  • Commit: eb0612eebc23d1f1088cc8e2ccd603691211e6b5

v0.1.6 new clients contract

Choose a tag to compare

@LEADBERG-studio LEADBERG-studio released this 25 Jul 07:13

Tunnellio v0.1.6

Summary

  • This release aligns the client with the new Tunnellio discovery, auth, and session contract.
  • The client now reflects the server's final auth/connection contract, supports OAuth discovery helpers, and keeps richer runtime/session metadata.
  • Release validation included local source checks, the expanded manual suite, and live plan/session verification against test.tunnellio.site.

Highlights

  • Added OAuth discovery helpers and PKCE utilities.
  • Added session-aware runtime handling for open, heartbeat, resume, close, and complete flows.
  • Added resume-token aware heartbeat/close handling and richer runtime snapshot metadata.
  • Expanded config, planner, tests, and documentation for the new server contract.

Artifacts

  • tunnellio.exe — standalone Windows binary
  • tunnellio-source-v0.1.6.zip — universal Python/source archive

Usage notes

  • The server remains the source of truth for the final authMode and connectionMode; callers should read the returned runtime snapshot instead of assuming requested modes were granted unchanged.
  • For random or ephemeral domains, launch with a runtime name and then call show-config --name <runtime> to read back the final server-side hostname and public URL.
  • The ready binary is Windows-specific; the source archive is the portable project package for Python/OpenSSH environments.

Changelog

  • Aligned the client with the new discovery, auth, and session contract.
  • Added OAuth discovery helpers, PKCE utilities, and extended session-aware planning/runtime metadata.
  • Added live-validated session lifecycle handling with resume-token aware heartbeat, resume, and close calls.
  • Expanded config, runtime snapshots, tests, and docs for the new server contract.

Notes for GitHub publication

  • Upload both tunnellio.exe and tunnellio-source-v0.1.6.zip from this folder.
  • Reuse this Markdown as the GitHub release body.
  • Publish the release from the main commit for v0.1.6 after creating the GitHub release.