English · 한국어 · 简体中文 · 日本語 · Project overview
These instructions target a user-level installation in an environment that preserves /workspace across environment replacements. The recovery path reuses saved installation files, authentication state, and SSH host keys, without a new login while saved credentials remain valid. Installation does not register a startup hook; run the start command in each new environment.
The initial public repository clone and raw downloads were verified against the prepared source. Release v0.4.0 includes ZIP and tar.gz archives plus tailscale-setup.sh, shim.py, dots-ts, dots_proxy.py, and source-manifest.json as individual assets. The installation examples pin the source to v0.4.0. SHA256SUMS covers the release artifacts. The bundled Tailscale version remains 1.102.4.
In the Dots SSH environment tested on 2026-10-15, direct HTTPS requests to the GitHub repository, raw file, and release asset URLs failed, while requests explicitly using http://browser-proxy:8889 succeeded. A proxy-backed clone resolved v0.2.0 to commit 643484, and the downloaded raw files matched that revision's checksums. These observations apply to that environment; they do not establish a global GitHub block. Browser downloads must be checked separately; these terminal checks do not diagnose browser errors.
If clone or download fails, run these bounded checks in the affected terminal to inspect the proxy path. They discard response bodies and print the final HTTP status without exposing redirected, signed download URLs:
for url in \
'https://github.com/abruption/dots-tailscale' \
'https://raw.githubusercontent.com/abruption/dots-tailscale/v0.2.0/tailscale-setup.sh' \
'https://github.com/abruption/dots-tailscale/releases/download/v0.2.0/tailscale-setup.sh'
do
printf '%s\n' "$url"
curl --fail --silent --show-error --location \
--proxy http://browser-proxy:8889 --connect-timeout 5 --max-time 20 \
--output /dev/null --write-out 'HTTP %{http_code}\n' "$url"
doneFor a direct-path comparison, replace --proxy http://browser-proxy:8889 with --noproxy '*' in the same commands. curl exit code 7 means a connection failed and 28 means the time limit was reached; neither result alone proves a hostname is blocked. Keep certificate verification enabled and use the proxy address actually provided by your environment if it differs.
The settings serve different scopes:
| Setting | Scope |
|---|---|
HTTPS_PROXY / HTTP_PROXY before git clone or git fetch |
Only that Git command; no global Git setting is changed |
curl --proxy |
Only that download or diagnostic request |
Installer UPSTREAM_PROXY |
Tailscale package/checksum downloads during installation, and the shim's upstream proxy during startup |
Local shim at 127.0.0.1:18089 |
TLS/WebSocket bridge used by the started Tailscale daemon through its process environment |
The local shim does not configure a global proxy or route unrelated applications through Tailscale. Set UPSTREAM_PROXY explicitly for installation and subsequent starts if your environment uses a different upstream address.
Clone the repository from https://github.com/abruption/dots-tailscale into /workspace so the setup script and shim.py are available together. Cloning requires Git and GitHub HTTPS access through the cloud computer's proxy. The setup requires Linux x86_64, Python 3, timeout, flock, OpenSSL, ssh-keygen, and /usr/sbin/sshd. Installation additionally requires curl, tar, and sha256sum. Binding port 22 must be allowed for the current user; root or systemd is not required.
For v0.4.0, keep all four source files together with source-manifest.json: tailscale-setup.sh, shim.py, dots-ts, and dots_proxy.py. The older v0.2.0 installation uses only the original two files and does not include these helpers.
cd /workspace
HTTPS_PROXY=http://browser-proxy:8889 HTTP_PROXY=http://browser-proxy:8889 \
git clone --branch v0.4.0 --depth 1 'https://github.com/abruption/dots-tailscale' dots-tailscale
cd dots-tailscale
bash tailscale-setup.sh check
bash tailscale-setup.sh install
bash /workspace/.dots-tailscale/start.sh loginThese proxy settings apply only to the clone command; they do not change global Git configuration. Use the proxy supplied by your environment if its address differs. Keep HTTPS certificate verification enabled.
check lists the presence of selected installation files and prints Tailscale status if the daemon socket and CLI are available. It does not start components or run dependency checks. Missing files alone do not cause a nonzero exit: an uninstalled environment can print missing: lines and exit 0. Use successful startup and an actual SSH connection to assess readiness.
install places files without starting processes. Existing binaries are retained; a conflicting configuration or script stops installation for manual review. The downloaded Tailscale archive is pinned to the version in the script and checked against its official SHA-256 file.
login starts missing components and runs interactive Tailscale authentication, with a 180-second limit. Open the printed URL and approve the intended account and tailnet. No auth key is embedded in these scripts; optional file-based authentication is described below.
After authentication, use this command to start missing components:
bash /workspace/.dots-tailscale/start.shFor inspection or explicit authentication, pass check or login to that installed wrapper. Installation generates start.sh in the installation directory and copies tailscale-setup.sh to setup.sh. The generated wrapper derives that directory from its own location and invokes the neighboring setup.sh. For an existing installation, use the update procedure below.
| Setting | Default |
|---|---|
DOTS_TS_DIR |
/workspace/.dots-tailscale |
UPSTREAM_PROXY |
http://browser-proxy:8889 |
NODE_HOSTNAME |
dots-sandbox |
An alternate DOTS_TS_DIR must be an absolute path without whitespace. UPSTREAM_PROXY must be an unauthenticated HTTP proxy URL. Use consistent settings for installation and subsequent starts. The shim currently listens on port 18089 and SSH on port 22; this is a single-installation setup.
SSH listens on 127.0.0.1:22 and uses public-key authentication. Tailscale SSH is disabled; the local OpenSSH server handles shell sessions. If HOME already contains .ssh/authorized_keys, installation copies it into the persistent installation. Otherwise, add your client public key to:
/workspace/.dots-tailscale/sshd/authorized_keys
Append a client public key as one complete line and set the file to mode 600. On your client, retrieve the public key from its .pub file; never transfer the private key. Verify the server host-key fingerprint in the cloud computer before accepting it on your client:
ssh-keygen -lf /workspace/.dots-tailscale/sshd/ssh_host_ed25519_key.pubIn the Dots terminal, obtain the node address through the installed wrapper:
bash /workspace/.dots-tailscale/start.sh checkThis uses the installed CLI and its per-user /tmp/dots-tailscale-$(id -u)/tailscaled.sock; no global tailscale command or default socket is required. The daemon must already be running to display its address. For a custom installation, use that installation's start.sh.
For a first connection, run the following on your tailnet client. At the host-key prompt, compare the displayed fingerprint with the one you obtained independently in the Dots terminal. If they differ, answer no and stop; do not enroll the key or remove an existing entry. Do not trust ssh-keyscan output alone. Accept it only if they match; this records the key in the client's known_hosts file.
ssh -o StrictHostKeyChecking=ask -i /path/to/client_private_key 'agent@<tailscale-ip>' 'id; pwd'Once the verified key is recorded, use strict checking for subsequent connections:
ssh -o StrictHostKeyChecking=yes -i /path/to/client_private_key 'agent@<tailscale-ip>' 'id; pwd'Replace the example username if your cloud-computer user differs. Use the address reported by start.sh check while the daemon is running. StrictHostKeyChecking=yes requires an already trusted host key; it cannot accept a new key interactively. If the key changes, verify the new fingerprint independently before changing the saved entry; do not bypass host-key verification.
running: Tailscale and local SSH ready means the backend is Running and the local SSH banner responds. It does not prove client key authentication; test an actual SSH command separately.
| Start result | Exit code |
|---|---|
| Running and local SSH ready | 0 |
Authentication required (needs_auth) |
10 |
| Backend readiness deadline or another unavailable state | 11 |
| No valid backend state received by the deadline | 12 |
Administrator machine approval required (needs_machine_auth) |
13 |
| Explicit authentication command fails while backend is Running | Original command failure code |
| Setup or component failure | Nonzero, normally 1 |
Backend readiness uses one 30-second monotonic deadline per phase, including retries and sleeps; each status query has at most three seconds and no more than the remaining budget. NoState and Starting are retried. If a valid transient state was observed, expiry remains exit 11 even if a later query fails; exit 12 means no valid state was obtained during the phase. Successful CLI communication alone is not readiness. The deadline covers the backend phase, not all component initialization or explicit authentication: login and auth first wait for a valid local API response (up to 30 seconds). This precheck accepts NoState/Starting without requiring Running, so a fresh unauthenticated daemon can reach explicit up. Login retains its separate 180-second command limit; auth retains its separate 60-second authentication limit; both then perform the final backend-readiness phase. An already pending machine approval skips up. If up fails or times out, backend inspection still runs: NeedsMachineAuth maps to 13, NeedsLogin to 10; a Running backend does not hide the command failure. A new-launch status probe is capped at three seconds.
Ordinary start does not invoke login. Previously authenticated state disappearing causes a failure for manual review rather than silent identity replacement. Existing conflicting files, partial CA installations, and missing established host keys also require review. Do not delete the installation directory to update a script: it holds node state and private keys.
The custom TLS/WebSocket shim implements the configured local HTTP CONNECT bridge. Local tests cover initial payload preservation and transport boundaries using isolated sockets. The revised shim was deployed on 2026-10-14 and passed live DERP ping and strict public-key SSH checks through the proxy path. With the previous shim, the maintainer observed retained installation files and successful manual restart after an environment replacement. The revised version has not yet been verified across a later replacement. Changes to the platform proxy can break it.
There is no VM cron, systemd service, or automatic startup registration in this repository. Startup through Dots automation has not been tested. A process loop inside the VM cannot survive destruction of that environment. Start the installation explicitly in a new environment or through a separately verified platform execution mechanism.
This feature is included in v0.4.0. Publishing a release does not upgrade an existing installation.
/workspace/.dots-tailscale/dots-ts version
/workspace/.dots-tailscale/dots-ts version --jsonThe command checks local package files without contacting GitHub or the tailnet. It reports the manifest’s declared package version, source-file integrity, and the installed Tailscale binary version separately. An unknown source revision stays unknown. A matching manifest proves file consistency, not the identity of the publisher or readiness of the running service.
Keep source-manifest.json with the four source files from the same reviewed revision. The new installer verifies that bundle and preserves the manifest in the installation. Never generate a new expected manifest from modified installed files to make a mismatch disappear. Existing v0.2/v0.3 installations without metadata report an unknown package version; the Tailscale version alone cannot identify the package.
When staging an update or a rollback, keep the original manifest with its matching source files and restore them together. Do not copy a newer manifest over older code. Preserve node state, private keys, SSH identity and proxy settings. The manual update precautions below still apply; this command does not update or restart anything.
JSON separates package.declared_version, package.source_revision, package.integrity and per-file results from tailscale.version and tailscale.status. Integrity is match, mismatch (modified or mixed files), incomplete (missing or unreadable files), or unknown (missing, invalid or unreadable metadata). source_revision is currently null; no commit is guessed. Exit code is 1 for mismatch/incomplete and 0 otherwise, so exit 0 alone does not prove a known version or available Tailscale binary. doctor reports unknown metadata as degraded and mismatch/incomplete as unhealthy.
For maintainers: after intentional source changes, run python3 scripts/source_manifest.py, review the resulting diff, then run python3 scripts/source_manifest.py --check. CI runs the check without rewriting files. Regenerate only in the reviewed source checkout, not in an installed directory. A release must distribute the matching manifest alongside the four source assets; the existing v0.3.1 release does not contain it.
These commands are included in v0.4.0 and require its helpers; publishing a release does not update an existing installation. Release checks accept only stable releases from abruption/dots-tailscale with the four source files and a matching source-manifest.json. Older releases without this manifest, including v0.3.1, are reported as unsupported rather than treated as verified update bundles.
/workspace/.dots-tailscale/dots-ts update check --json
/workspace/.dots-tailscale/dots-ts update check --tag v0.4.0 --proxy http://browser-proxy:8889 --jsonThe second example selects a specific stable version; it works only after that release exists. Without --proxy, the check uses direct HTTPS and does not inherit proxy credentials from the shell. Pass the platform download proxy explicitly when needed. This is separate from the shim and application SOCKS/HTTP proxies. HTTPS certificate verification remains enabled. No GitHub token is needed.
update check changes no installed code, node state, configuration or processes. The JSON status is update_available, current, local_newer, not_comparable, or check_failed; selection identifies latest versus pinned selection. The exit code is 0 for a valid comparison, 1 when the local installation cannot be compared, and 2 for a lookup failure. Check the status rather than assuming exit 0 means an update exists. Unknown, modified or mixed local sources do not become a trusted version merely because a remote release was found.
Notifications are opt-in and local. They do not install updates, register a timer or send external messages:
/workspace/.dots-tailscale/dots-ts update notifications configure --enable --interval-hours 24
/workspace/.dots-tailscale/dots-ts update notifications refresh --proxy http://browser-proxy:8889
/workspace/.dots-tailscale/dots-ts update notifications show
/workspace/.dots-tailscale/dots-ts update notifications notify
/workspace/.dots-tailscale/dots-ts update notifications configure --disablerefresh performs the bounded network check and saves a local result. It reuses a fresh result within the configured interval; --force explicitly refreshes early. notify reads the cache only, respects expiry and suppresses repeated notices for the same version. Successful interactive startup can display a cached notice on stderr within a short time limit; failed notification checks never change the startup result. Raw cli forwarding and JSON output do not gain notice text. Read-only storage or failed refreshes do not turn old data into a new successful check. Use show to inspect the recorded time and stale/error state. A separate, explicitly configured caller is needed for periodic refresh, and no VM-local command runs while the VM is absent.
Run from an environment where the installation, its runtime socket and Linux process information are visible. update apply requires Linux with Python 3.9 or newer and pidfd support. Before starting, open the cloud computer's independent terminal and make sure you can control it. --confirm-recovery-terminal records your acknowledgement; it cannot check that terminal for you. SSH initiation is supported because the transaction worker detaches from the calling session, but the SSH connection itself is not a recovery path.
Choose a fixed stable release after checking it:
/workspace/.dots-tailscale/dots-ts update apply --tag v0.4.1 --proxy http://browser-proxy:8889 --confirm-recovery-terminal --json
/workspace/.dots-tailscale/dots-ts update status --id TRANSACTION_ID --jsonThese are examples, not an announcement that v0.4.1 exists. Replace TRANSACTION_ID with the ID returned by apply. A reviewed local source bundle can be used when release downloads are blocked or when explicitly testing an unreleased candidate:
/workspace/.dots-tailscale/dots-ts update apply --bundle /workspace/reviewed-bundle --confirm-recovery-terminal --jsonThe bundle must contain the four source files and their matching original manifest. Local selection can contain a development version; it is not presented as a published GitHub release. The updater validates hashes and syntax before replacing files. A matching checksum proves consistency with the selected bundle, not independent trust in its author.
started means the detached worker has accepted the operation, not that the update succeeded. Poll update status after reconnecting. Terminal outcomes distinguish applied, failed_rolled_back, and rollback_failed_manual_required. Local readiness and version verification do not prove external SSH access: after applied or a successful rollback, open a new SSH connection with strict host-key checking and test the intended connection separately.
The worker backs up and replaces only the four source files and their manifest, while holding the same operation lock used by setup and proxy apply. It restarts only the identified shim process; tailscaled and sshd stay running. Node state, login credentials, private keys, SSH configuration and proxy settings are not copied or reset. Releases that change the bundled Tailscale binary version are outside this updater's scope and are refused. No unknown process is killed and no socket is removed.
A matching installed manifest is required. For older installations without metadata, first compare all four installed files against an independently obtained, reviewed release. Do not generate expected checksums from the installed files themselves. If provenance cannot be established or files are mixed, stop for manual review. A source checkout used for a deliberate migration must also match its own manifest before its updater can run against an existing installation.
A prior incomplete transaction blocks new updates. After a killed worker or VM replacement, inspect status and the code-only backup from the independent terminal; do not delete the journal to force a retry or assume the next step is safe. If rollback cannot establish readiness, keep the evidence and recover manually from the verified backup. The updater is not an automatic VM recovery service.
Transaction records live under .update-transactions/TRANSACTION_ID/: journal.json records the phase, stage/ contains the selected bundle, backup/ contains only the previous source bundle, and supervisor/ holds the updater code snapshot. Keep these records until you have checked external connectivity. update status returns exit 0 for a running or applied transaction and 1 for a rejected, rolled-back, interrupted or manual-recovery result. Exit 0 for a running transaction is not completion.
applied means the selected code matches its manifest and local readiness checks passed. It does not test the shim's end-to-end CONNECT/WebSocket path. The restarted shim keeps its upstream host/port and absolute SSL certificate-path settings; unrelated shell environment and debug settings are not inherited. Its stdout/stderr are discarded, so use the transaction's fixed error codes and separate connectivity checks rather than expecting new output in the old shim.log.
After an interrupted transaction, a manual recovery can be concluded without deleting evidence:
- In the independent terminal, positively establish that the worker has exited. An inaccessible PID or an uncertain
statusresult is not proof of exit. - Restore a reviewed code bundle if needed, start it and verify its manifest, local readiness and a new external strict-host-key SSH connection. A legacy helper may require a separately reviewed source runner to inspect its manifest.
- With the installation's
.operation.lockheld and no active worker, move the entire reviewed transaction directory to an owned, mode-700.update-recovery-archive/directory outside.update-transactions/. Preserve the journal and backups together. This manual archive removes it from the pending-update scan; never archive an active or unreviewed transaction just to unblock apply.
The reviewed development candidate was tested in Dots on 2026-10-21. See the dated update validation report for normal apply, intentional failure/rollback, external SSH evidence and limits.
These commands are included in v0.4.0 and are also available in v0.3.1; they are not available in release v0.2.0. dots-ts uses Python 3's standard library and finds the installation from its own location. No global tailscale command, socket symlink, or shell profile change is needed.
/workspace/.dots-tailscale/dots-ts cli status
/workspace/.dots-tailscale/dots-ts doctor
/workspace/.dots-tailscale/dots-ts doctor --json
/workspace/.dots-tailscale/dots-ts doctor --target api.example.internal:8443cli executes the installed Tailscale CLI with the installation's runtime socket and forwards its arguments. It preserves raw output and interactive behavior, has no helper timeout, and can expose addresses or other sensitive CLI output. Apply your own limit for a long-running command. It does not log in or start the daemon on its own.
doctor is read-only: it does not start processes, authenticate, repair files, or change proxy settings. It reports redacted local checks by layer rather than declaring every application ready. Read overall and the individual results: degraded can still have exit code 0. --target HOST:PORT additionally performs a bounded TCP reachability check through the local Tailscale daemon, not the application HTTP/SOCKS listener; it does not send credentials or prove TLS, API authentication, or authorization. A target name is still an explicit network request, so choose a service you intend to contact. See the private HTTP API recipe for application checks and the validation matrix.
doctor describes only the current execution context and its visible processes. Its additive JSON context object reports installation/runtime visibility, metadata-based read/write access, and filesystem read_only/read_write/unknown state. These are metadata and access checks; no write is attempted. advice contains fixed guidance IDs and skipped_checks identifies checks not performed; the human output expands the guidance.
A missing socket can mean that it is invisible in this context while the daemon is running elsewhere. It remains unhealthy with exit 1 for the current context; a permission-denied or unresponsive socket is also a failure. Unknown inspection results and a read-only installation with otherwise healthy services are degraded with exit 0; healthy is true only for overall=healthy. Inspect the individual fields rather than using exit 0 alone as proof of readiness. Socket metadata and connection results distinguish missing, access-denied and unavailable states.
If the installation/runtime is read-only or inaccessible, or the socket is missing/unresponsive, open the cloud computer's independent GUI terminal and run doctor there. Do not infer that reinstalling, logging in, changing permissions, or resetting state is required. Equal user IDs do not prove equal filesystem or socket visibility. This diagnostic does not enter another namespace or change mounts.
/workspace/.dots-tailscale/dots-ts proxy show
/workspace/.dots-tailscale/dots-ts proxy configure --socks-port 1080 --http-port 8080Configuration records the desired loopback listeners in /workspace/.dots-tailscale/outbound-proxy.json; it does not restart the daemon or make a listener immediately available. SOCKS5 and HTTP proxies are disabled by default. Use different ports for the two listeners: this implementation initially rejects a shared port even though native Tailscale can support one. Configuration persists in /workspace, while sockets and running processes must be recreated after an environment replacement.
Applying a changed configuration requires an explicit daemon restart and can interrupt Tailscale SSH. First secure the independent Dots GUI terminal, record the current settings and connectivity, and prepare rollback. Run /workspace/.dots-tailscale/dots-ts proxy apply from that terminal. A changed configuration is refused when SSH_CONNECTION or SSH_CLIENT is set; an already-applied configuration is a no-op. If the replacement fails and rollback succeeds, the previous daemon is restored while the desired configuration remains pending. Do not treat an existing SSH session as a recovery path or assume a successful local restart proves remote access. Recheck daemon status, local SSH, external strict-key SSH, and the intended application; roll back from the GUI terminal if checks fail. No production proxy deployment is performed by following proxy configure alone.
/workspace/.dots-tailscale/dots-ts proxy disabledisable saves a disabled configuration; an already running listener remains until the change is explicitly applied. Disabling or applying does not remove node state, keys, or the installation.
Application proxies listen on loopback for explicitly configured local clients. They are distinct from the daemon's TLS/WebSocket shim on 127.0.0.1:18089. Do not use that shim as an application HTTP proxy. Set a proxy on an individual curl request or child process; there is no global environment, browser, MCP, or ChatGPT connector propagation. Local processes that can reach the loopback proxy can attempt to use it, so network policy and API authentication are still required.
Download the four source files and source-manifest.json into a separate source directory. Stop if any request fails, and review the files and hashes before installation:
(
set -euo pipefail
mkdir -p /workspace/dots-tailscale-v0.4.0
cd /workspace/dots-tailscale-v0.4.0
DOTS_TS_REF=v0.4.0
for file in tailscale-setup.sh shim.py dots-ts dots_proxy.py source-manifest.json; do
curl -fsSL --proxy http://browser-proxy:8889 --connect-timeout 5 --max-time 60 \
"https://github.com/abruption/dots-tailscale/releases/download/${DOTS_TS_REF}/${file}" \
-o "$file"
done
sha256sum tailscale-setup.sh shim.py dots-ts dots_proxy.py source-manifest.json
)After all downloads succeed and you have reviewed the files, run these commands for a fresh installation:
cd /workspace/dots-tailscale-v0.4.0
bash tailscale-setup.sh check
bash tailscale-setup.sh install
bash /workspace/.dots-tailscale/start.sh loginKeep the four source files and source-manifest.json in the same directory before running setup. If the release-asset host is unavailable, use the same tag and filenames at https://raw.githubusercontent.com/abruption/dots-tailscale/v0.4.0/, or transfer the reviewed files from another trusted device. For an existing v0.4.0 installation, use update management; for older installations, review the legacy procedure. install does not overwrite differing installed source files.
If browser-free authentication is needed, supply a Tailscale auth key through a private file, then explicitly run auth:
chmod 600 /workspace/.dots-tailscale/auth.key
DOTS_TS_AUTH_KEY_FILE=/workspace/.dots-tailscale/auth.key \
bash /workspace/.dots-tailscale/start.sh authCreate the key file separately using a secure method. Do not paste the key into chat, source code, command-line arguments, or Git. DOTS_TS_AUTH_KEY_FILE must name an absolute path to a regular file owned by the current user, with no symlink and permissions exactly 600 or 400. The key is passed to Tailscale as --auth-key=file:PATH.
If the backend is already Running, auth skips authentication without reading or requiring the key file. This mode still refuses missing state after an established authentication marker; it does not silently replace an existing node identity. The script does not change tailnet tags or ACL policy, force reauthentication, or reset preferences. Auth commands are bounded by a 60-second process timeout and a 45-second connection timeout; they do not fall back to a browser. Actual auth-key enrollment has not been tested. Ordinary start never falls back to an auth key automatically.
Run the probe from the repository checkout in Dots:
python3 /workspace/dots-tailscale/home-persistence-probe.pyThe first run creates a random marker in $HOME/.dots-home-persistence-marker.json and a baseline, workspace marker, latest result, and history under /workspace/.dots-persistence-probe. Later runs compare against that original baseline. The probe reads its own markers and process context, not authentication state or user documents.
assessment |
Meaning |
|---|---|
baseline_created |
First observation recorded |
markers_intact |
Both marker identities match the baseline |
home_marker_changed_workspace_intact |
HOME marker changed or disappeared while workspace marker remained intact |
marker_continuity_changed |
Other marker continuity change |
Marker loss is evidence of marker loss, not proof that the entire HOME directory was reset. Hostname, boot ID, and PID 1 start ticks provide context; boot ID alone cannot distinguish a container replacement from a host reboot. If the workspace baseline disappears too, the next run creates a new baseline, so retain a result outside the VM for comparison. You can run the probe over SSH and save its result on your client:
ssh agent@dots-sandbox 'python3 /workspace/dots-tailscale/home-persistence-probe.py' > observation.jsonAdjust the SSH key and host alias for your setup. The result includes the hostname and marker IDs; anonymize them before sharing it publicly.
Custom paths are available through --home and --probe-root. No periodic execution is registered for this probe.
This section preserves the older manual procedure for installations without package metadata. For v0.4.0, use the verified manual updater, which handles the four source files and their manifest together. Stage and review tailscale-setup.sh and shim.py together outside /workspace/.dots-tailscale, using a selected commit or release rather than updating installed files in place from a moving branch. Record that version and both SHA-256 hashes. Validate the staged shell syntax and parse the staged Python source before changing the installation.
When updating to v0.3.1, also stage dots-ts and dots_proxy.py from that exact commit. The compatibility block below validates the original setup/shim pair; it does not validate the new helpers. Additionally compile both Python helpers without executing them and record all four source hashes. In the deployment and rollback steps, include dots-ts and dots_proxy.py at the installation root alongside setup.sh and shim/shim.py, with mode 700. Preserve outbound-proxy.json and node identity files. Do not apply a proxy configuration or restart the daemon as an incidental documentation/update step.
python3 -c 'from pathlib import Path; [compile(Path(p).read_text(), p, "exec") for p in ("dots-ts", "dots_proxy.py")]'
sha256sum tailscale-setup.sh shim.py dots-ts dots_proxy.pyUse a separate staging checkout at /workspace/dots-tailscale (adjust the path if needed). Replace <REVIEWED_COMMIT> with the full 40-character SHA you reviewed. This same block works from a branch or detached HEAD, including repeated updates. It refuses tracked, staged, and untracked changes without discarding them. Fetch, revision, checkout, or validation failures stop the subshell; continue with deployment only if the entire block succeeds. It changes only the staging checkout, not installed files or authentication state. Proxy settings apply only to fetch; keep certificate verification enabled.
(
set -euo pipefail
cd /workspace/dots-tailscale
REVIEWED_COMMIT='<REVIEWED_COMMIT>'
[[ "$REVIEWED_COMMIT" =~ ^[0-9a-fA-F]{40}$ ]] || {
printf 'Stop: specify the full reviewed commit SHA.\n' >&2
exit 1
}
status=$(git status --porcelain --untracked-files=all)
[[ -z "$status" ]] || {
printf 'Stop: preserve and review local changes before updating.\n%s\n' "$status" >&2
exit 1
}
HTTPS_PROXY=http://browser-proxy:8889 HTTP_PROXY=http://browser-proxy:8889 \
git fetch --tags origin
target=$(git rev-parse --verify "${REVIEWED_COMMIT}^{commit}")
git switch --detach "$target"
[[ "$(git rev-parse HEAD)" = "$target" ]]
bash -n tailscale-setup.sh
python3 -c 'from pathlib import Path; compile(Path("shim.py").read_text(), "shim.py", "exec")'
git rev-parse HEAD
sha256sum tailscale-setup.sh shim.py
)Use this deployment sequence when the shim changes:
- Open the cloud computer's platform GUI terminal, independently of Tailscale SSH. Keep that terminal available for recovery. Record the installed
setup.shandshim/shim.pyhashes, the node address, and the public SSH host-key fingerprint outside the VM. Confirm an authenticated baseline and record it separately from the candidate version. - Save private rollback copies of the old
setup.shandshim/shim.pysource files only; for v0.3.1 also include existingdots-tsanddots_proxy.pyand record whether they were absent before the update. Leave authentication state, CA keys, SSH host keys, and authorized keys in place; do not copy state into another installation. Have a bounded, detached restart plan and a rollback plan ready before stopping a process. The detached action must survive loss of the SSH session; the legacy v0.2/v0.3 tools do not provide that updater. - Identify the running shim by its exact command line and installed script path. From the independent terminal, stop only that identified shim PID. Do not use broad process-name termination. Confirm it exited and that no duplicate shim remains. Install the staged
shim.pyatshim/shim.pyand the stagedtailscale-setup.shatsetup.sh, with mode700. For v0.3.1, also place the stageddots-tsanddots_proxy.pyat the installation root with mode700. - Execute the prepared detached startup, then compare the installed source hashes with the staged hashes. Confirm a single shim and the expected daemon processes. Check Tailscale CLI state and the local SSH banner in the independent terminal, then test external DERP connectivity and a real SSH command with strict host-key verification. Record the results against the candidate version.
- If validation fails, use the independent terminal to stop only the identified candidate shim, restore the old source files, and run startup again. Recheck the old hashes and baseline connection. Preserve existing state and keys throughout rollback; investigate any unexpected host-key change instead of accepting it.
Do not run install as an update shortcut or delete the installation directory. Installation intentionally rejects conflicting files, and the installed directory contains identity material. A setup-script-only update still needs version and hash records and startup validation; it does not establish that a separately changed shim has been loaded.
A successful controlled restart verifies process recovery for that environment, not persistence after VM replacement. The continuity probe remains observational: wait for a natural environment change, retain its results externally, and evaluate authenticated continuity separately. No forced reset or restart is needed for the continuity probe.
| Path | Contents |
|---|---|
/workspace/.dots-tailscale/state |
Tailscale authentication state |
/workspace/.dots-tailscale/shim |
Shim, CA, private keys, and certificates |
/workspace/.dots-tailscale/sshd |
SSH configuration, host keys, and authorized_keys |
/tmp/dots-tailscale-$(id -u) |
Per-user sockets, PIDs, and runtime logs |
The SSH server uses StrictModes no because permissions on parent /workspace directories can otherwise cause OpenSSH to reject public-key authentication. The setup restricts storage directories to mode 700 and SSH keys, authorized_keys, and the CA private key to mode 600. Startup checks the Tailscale CLI and local SSH banner; it does not test remote public-key login on every start. On restart, logs larger than 1 MiB are rotated for one generation. Logs have no size limit while a process keeps running.
Process inspection continues past /proc entries that cannot be accessed. If the CLI responds, the setup reuses the existing tailscaled. It does not automatically remove a socket belonging to an unidentified daemon.
Keep Tailscale state, host and client private keys, shim CA private keys, auth-key files, generated backups, and runtime logs out of Git. Do not copy one Tailscale node's state into multiple simultaneously running VMs. /workspace is not automatically a private location; only use a directory whose access you control.
This maintainer-provided screenshot shows the existing installation files and status, plus Tailscale version 1.102.4, in the Dots desktop terminal. It does not show a new installation or a VM replacement.
bash /workspace/.dots-tailscale/start.sh check
/workspace/.dots-tailscale/tailscale version- The maintainer previously observed the installation running for more than 24 hours. After an environment replacement, the
/workspaceinstallation files remained andstart.shrestarted it manually. - On 2026-10-14, the revised shim passed a live DERP ping and strict-host-key SSH public-key login and command checks. Recovery after a later VM replacement has not been observed with that revision.
checkreports files and local status.running: Tailscale and local SSH readymeans the backend is Running and the local SSH banner responds; it does not verify remote public-key login.- Auth-key enrollment and startup through Dots automation have not been tested.
Run python3 -m unittest discover -s tests -v and bash -n tailscale-setup.sh from the repository checkout. The onboarding tests require Git, Bash, Python 3, and OpenSSH client/server tools. They use disposable repositories, keys, known_hosts, a mocked Tailscale CLI, and an isolated Unix socket. SSH runs over local stdio without a listener; these tests do not authenticate to a tailnet, exercise the live proxy, or validate VM replacement.
cannot inspect processorprocess inspection failed: The current script skips/procentries it cannot access. Update the installedsetup.shusing the reviewed procedure above.Permission denied (publickey): Check the public-key fingerprint insshd/authorized_keysand add your client public key if needed. Therunningmessage does not confirm remote public-key authentication.existing file differs: An installed file differs from the source. Review the configuration and keys, then update only the files that need it. Do not delete the installation directory; it also contains state and host keys.socket exists but daemon cannot be identified: Check the existing daemon and socket. The script does not automatically remove an unidentified socket.- Terminal text such as
^[[200~appears after pasting: Cancel the command and enter it one line at a time. Check the command and host-key fingerprint before continuing.
