feat: route Fleet SSH over existing Tailscale - #1

Merged
extoci merged 7 commits into
mainfrom
feat/tailscale
Aug 4, 2026
Merged

feat: route Fleet SSH over existing Tailscale#1
extoci merged 7 commits into
mainfrom
feat/tailscale

Conversation

@extoci

@extociextoci commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep ssh <name>.local as the only normal Fleet SSH interface.
  • Add a local OpenSSH ProxyCommand that uses LAN first and an existing Tailscale route when a confirmed mapping is available.
  • Learn exact member Tailscale FQDNs over the existing pinned SSH connection; persist no peer IPs or Tailscale credentials.
  • Preserve the existing .local host-key pin and OpenSSH authentication path.
  • Add forced-route recovery with fleet connect <member> --via lan|tailscale.
  • Reject join requests arriving from Tailscale address space and keep mapping refresh best-effort.
  • Document the design and rollout in docs/tailscale-integration-spec.md.

Verification

  • cargo test --all-targets (76 unit tests, 20 CLI tests)
  • cargo clippy --all-targets -- -D warnings

The implementation deliberately does not install, authenticate, administer, or relay through Tailscale.

Greptile Summary

This PR adds optional Tailscale routing for Fleet SSH without installing or administering Tailscale. Fleet learns each member's Tailscale FQDN over the existing pinned SSH channel, stores only the FQDN (never IPs), and resolves it live through the captain's local Tailscale client at connect time.

  • Adds fleet transport connect as an OpenSSH ProxyCommand that races LAN (150 ms head start) against the mapped Tailscale peer, passing the winning TCP socket to the outer SSH process unchanged.
  • Adds validate_join_source to enforce LAN-only joins and hardens the generated SSH config with Port 22, UpdateHostKeys no, and a shell-safe quoted ProxyCommand.

Confidence Score: 5/5

Safe to merge; all findings are diagnostic edge cases with no impact on correctness, data integrity, or security.

The transport selector, FQDN mapping, ProxyCommand generation, and join-source validation all implement their stated contracts correctly. The two flagged items — an uninformative error message when both routes time out simultaneously, and an opaque server error when OS interface enumeration fails during a join — degrade diagnostics only and do not cause incorrect behavior or security regression.

Files Needing Attention: src/ssh_client.rs (connect_auto error path) and src/service.rs (validate_join_source error propagation) are worth a second glance but neither requires a blocking fix.

Important Files Changed

FilenameOverview
src/tailscale.rsNew module: read-only Tailscale adapter with FQDN mapping persistence, range-validated IP resolution, bounded subprocess execution, and concurrent-safe file locking.
src/ssh_client.rsAdds transport selection (LAN race vs Tailscale), ProxyCommand generation with shell-safe quoting, proxy_stdio, and refresh_mapping. connect_auto error message is empty when both routes time out without replying.
src/service.rsAdds validate_join_source to enforce LAN-only joins; propagates get_if_addrs failures via ?, blocking joins when interface enumeration is transiently unavailable.
src/network.rsNew module: on-link peer validation with subnet matching, IPv4-mapped normalization, and interface name/IP-based exclusion lists. Well-tested.
src/commands.rsWires new commands and refactors status --check to use the transport selector; calls refresh_mapping after update-all.
src/remote.rsAdds ssh_output_direct (ProxyCommand=none) for bootstrap mapping pulls over raw LAN.

Reviews (2): Last reviewed commit: "fix: address Tailscale transport review ..." | Re-trigger Greptile

@extoci

Copy link
Copy Markdown
OwnerAuthor

@greptile review

Comment threadsrc/ssh_client.rs
Comment threadsrc/ssh_client.rs
@extoci
extoci merged commit 372441f into mainAug 4, 2026
5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@extoci
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: route Fleet SSH over existing Tailscale - #1

Merged
extoci merged 7 commits into
mainfrom
feat/tailscale
Aug 4, 2026
Merged

feat: route Fleet SSH over existing Tailscale#1
extoci merged 7 commits into
mainfrom
feat/tailscale

Conversation

@extoci

@extociextoci commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep ssh <name>.local as the only normal Fleet SSH interface.
  • Add a local OpenSSH ProxyCommand that uses LAN first and an existing Tailscale route when a confirmed mapping is available.
  • Learn exact member Tailscale FQDNs over the existing pinned SSH connection; persist no peer IPs or Tailscale credentials.
  • Preserve the existing .local host-key pin and OpenSSH authentication path.
  • Add forced-route recovery with fleet connect <member> --via lan|tailscale.
  • Reject join requests arriving from Tailscale address space and keep mapping refresh best-effort.
  • Document the design and rollout in docs/tailscale-integration-spec.md.

Verification

  • cargo test --all-targets (76 unit tests, 20 CLI tests)
  • cargo clippy --all-targets -- -D warnings

The implementation deliberately does not install, authenticate, administer, or relay through Tailscale.

Greptile Summary

This PR adds optional Tailscale routing for Fleet SSH without installing or administering Tailscale. Fleet learns each member's Tailscale FQDN over the existing pinned SSH channel, stores only the FQDN (never IPs), and resolves it live through the captain's local Tailscale client at connect time.

  • Adds fleet transport connect as an OpenSSH ProxyCommand that races LAN (150 ms head start) against the mapped Tailscale peer, passing the winning TCP socket to the outer SSH process unchanged.
  • Adds validate_join_source to enforce LAN-only joins and hardens the generated SSH config with Port 22, UpdateHostKeys no, and a shell-safe quoted ProxyCommand.

Confidence Score: 5/5

Safe to merge; all findings are diagnostic edge cases with no impact on correctness, data integrity, or security.

The transport selector, FQDN mapping, ProxyCommand generation, and join-source validation all implement their stated contracts correctly. The two flagged items — an uninformative error message when both routes time out simultaneously, and an opaque server error when OS interface enumeration fails during a join — degrade diagnostics only and do not cause incorrect behavior or security regression.

Files Needing Attention: src/ssh_client.rs (connect_auto error path) and src/service.rs (validate_join_source error propagation) are worth a second glance but neither requires a blocking fix.

Important Files Changed

FilenameOverview
src/tailscale.rsNew module: read-only Tailscale adapter with FQDN mapping persistence, range-validated IP resolution, bounded subprocess execution, and concurrent-safe file locking.
src/ssh_client.rsAdds transport selection (LAN race vs Tailscale), ProxyCommand generation with shell-safe quoting, proxy_stdio, and refresh_mapping. connect_auto error message is empty when both routes time out without replying.
src/service.rsAdds validate_join_source to enforce LAN-only joins; propagates get_if_addrs failures via ?, blocking joins when interface enumeration is transiently unavailable.
src/network.rsNew module: on-link peer validation with subnet matching, IPv4-mapped normalization, and interface name/IP-based exclusion lists. Well-tested.
src/commands.rsWires new commands and refactors status --check to use the transport selector; calls refresh_mapping after update-all.
src/remote.rsAdds ssh_output_direct (ProxyCommand=none) for bootstrap mapping pulls over raw LAN.

Reviews (2): Last reviewed commit: "fix: address Tailscale transport review ..." | Re-trigger Greptile

@extoci

Copy link
Copy Markdown
OwnerAuthor

@greptile review

Comment threadsrc/ssh_client.rs
Comment threadsrc/ssh_client.rs
@extoci
extoci merged commit 372441f into mainAug 4, 2026
5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@extoci
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: route Fleet SSH over existing Tailscale - #1

Merged
extoci merged 7 commits into
mainfrom
feat/tailscale
Aug 4, 2026
Merged

feat: route Fleet SSH over existing Tailscale#1
extoci merged 7 commits into
mainfrom
feat/tailscale

Conversation

@extoci

@extociextoci commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep ssh <name>.local as the only normal Fleet SSH interface.
  • Add a local OpenSSH ProxyCommand that uses LAN first and an existing Tailscale route when a confirmed mapping is available.
  • Learn exact member Tailscale FQDNs over the existing pinned SSH connection; persist no peer IPs or Tailscale credentials.
  • Preserve the existing .local host-key pin and OpenSSH authentication path.
  • Add forced-route recovery with fleet connect <member> --via lan|tailscale.
  • Reject join requests arriving from Tailscale address space and keep mapping refresh best-effort.
  • Document the design and rollout in docs/tailscale-integration-spec.md.

Verification

  • cargo test --all-targets (76 unit tests, 20 CLI tests)
  • cargo clippy --all-targets -- -D warnings

The implementation deliberately does not install, authenticate, administer, or relay through Tailscale.

Greptile Summary

This PR adds optional Tailscale routing for Fleet SSH without installing or administering Tailscale. Fleet learns each member's Tailscale FQDN over the existing pinned SSH channel, stores only the FQDN (never IPs), and resolves it live through the captain's local Tailscale client at connect time.

  • Adds fleet transport connect as an OpenSSH ProxyCommand that races LAN (150 ms head start) against the mapped Tailscale peer, passing the winning TCP socket to the outer SSH process unchanged.
  • Adds validate_join_source to enforce LAN-only joins and hardens the generated SSH config with Port 22, UpdateHostKeys no, and a shell-safe quoted ProxyCommand.

Confidence Score: 5/5

Safe to merge; all findings are diagnostic edge cases with no impact on correctness, data integrity, or security.

The transport selector, FQDN mapping, ProxyCommand generation, and join-source validation all implement their stated contracts correctly. The two flagged items — an uninformative error message when both routes time out simultaneously, and an opaque server error when OS interface enumeration fails during a join — degrade diagnostics only and do not cause incorrect behavior or security regression.

Files Needing Attention: src/ssh_client.rs (connect_auto error path) and src/service.rs (validate_join_source error propagation) are worth a second glance but neither requires a blocking fix.

Important Files Changed

FilenameOverview
src/tailscale.rsNew module: read-only Tailscale adapter with FQDN mapping persistence, range-validated IP resolution, bounded subprocess execution, and concurrent-safe file locking.
src/ssh_client.rsAdds transport selection (LAN race vs Tailscale), ProxyCommand generation with shell-safe quoting, proxy_stdio, and refresh_mapping. connect_auto error message is empty when both routes time out without replying.
src/service.rsAdds validate_join_source to enforce LAN-only joins; propagates get_if_addrs failures via ?, blocking joins when interface enumeration is transiently unavailable.
src/network.rsNew module: on-link peer validation with subnet matching, IPv4-mapped normalization, and interface name/IP-based exclusion lists. Well-tested.
src/commands.rsWires new commands and refactors status --check to use the transport selector; calls refresh_mapping after update-all.
src/remote.rsAdds ssh_output_direct (ProxyCommand=none) for bootstrap mapping pulls over raw LAN.

Reviews (2): Last reviewed commit: "fix: address Tailscale transport review ..." | Re-trigger Greptile

@extoci

Copy link
Copy Markdown
OwnerAuthor

@greptile review

Comment threadsrc/ssh_client.rs
Comment threadsrc/ssh_client.rs
@extoci
extoci merged commit 372441f into mainAug 4, 2026
5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@extoci
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: route Fleet SSH over existing Tailscale - #1

Merged
extoci merged 7 commits into
mainfrom
feat/tailscale
Aug 4, 2026
Merged

feat: route Fleet SSH over existing Tailscale#1
extoci merged 7 commits into
mainfrom
feat/tailscale

Conversation

@extoci

@extociextoci commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep ssh <name>.local as the only normal Fleet SSH interface.
  • Add a local OpenSSH ProxyCommand that uses LAN first and an existing Tailscale route when a confirmed mapping is available.
  • Learn exact member Tailscale FQDNs over the existing pinned SSH connection; persist no peer IPs or Tailscale credentials.
  • Preserve the existing .local host-key pin and OpenSSH authentication path.
  • Add forced-route recovery with fleet connect <member> --via lan|tailscale.
  • Reject join requests arriving from Tailscale address space and keep mapping refresh best-effort.
  • Document the design and rollout in docs/tailscale-integration-spec.md.

Verification

  • cargo test --all-targets (76 unit tests, 20 CLI tests)
  • cargo clippy --all-targets -- -D warnings

The implementation deliberately does not install, authenticate, administer, or relay through Tailscale.

Greptile Summary

This PR adds optional Tailscale routing for Fleet SSH without installing or administering Tailscale. Fleet learns each member's Tailscale FQDN over the existing pinned SSH channel, stores only the FQDN (never IPs), and resolves it live through the captain's local Tailscale client at connect time.

  • Adds fleet transport connect as an OpenSSH ProxyCommand that races LAN (150 ms head start) against the mapped Tailscale peer, passing the winning TCP socket to the outer SSH process unchanged.
  • Adds validate_join_source to enforce LAN-only joins and hardens the generated SSH config with Port 22, UpdateHostKeys no, and a shell-safe quoted ProxyCommand.

Confidence Score: 5/5

Safe to merge; all findings are diagnostic edge cases with no impact on correctness, data integrity, or security.

The transport selector, FQDN mapping, ProxyCommand generation, and join-source validation all implement their stated contracts correctly. The two flagged items — an uninformative error message when both routes time out simultaneously, and an opaque server error when OS interface enumeration fails during a join — degrade diagnostics only and do not cause incorrect behavior or security regression.

Files Needing Attention: src/ssh_client.rs (connect_auto error path) and src/service.rs (validate_join_source error propagation) are worth a second glance but neither requires a blocking fix.

Important Files Changed

FilenameOverview
src/tailscale.rsNew module: read-only Tailscale adapter with FQDN mapping persistence, range-validated IP resolution, bounded subprocess execution, and concurrent-safe file locking.
src/ssh_client.rsAdds transport selection (LAN race vs Tailscale), ProxyCommand generation with shell-safe quoting, proxy_stdio, and refresh_mapping. connect_auto error message is empty when both routes time out without replying.
src/service.rsAdds validate_join_source to enforce LAN-only joins; propagates get_if_addrs failures via ?, blocking joins when interface enumeration is transiently unavailable.
src/network.rsNew module: on-link peer validation with subnet matching, IPv4-mapped normalization, and interface name/IP-based exclusion lists. Well-tested.
src/commands.rsWires new commands and refactors status --check to use the transport selector; calls refresh_mapping after update-all.
src/remote.rsAdds ssh_output_direct (ProxyCommand=none) for bootstrap mapping pulls over raw LAN.

Reviews (2): Last reviewed commit: "fix: address Tailscale transport review ..." | Re-trigger Greptile

@extoci

Copy link
Copy Markdown
OwnerAuthor

@greptile review

Comment threadsrc/ssh_client.rs
Comment threadsrc/ssh_client.rs
@extoci
extoci merged commit 372441f into mainAug 4, 2026
5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@extoci
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: route Fleet SSH over existing Tailscale - #1

Merged
extoci merged 7 commits into
mainfrom
feat/tailscale
Aug 4, 2026
Merged

feat: route Fleet SSH over existing Tailscale#1
extoci merged 7 commits into
mainfrom
feat/tailscale

Conversation

@extoci

@extociextoci commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep ssh <name>.local as the only normal Fleet SSH interface.
  • Add a local OpenSSH ProxyCommand that uses LAN first and an existing Tailscale route when a confirmed mapping is available.
  • Learn exact member Tailscale FQDNs over the existing pinned SSH connection; persist no peer IPs or Tailscale credentials.
  • Preserve the existing .local host-key pin and OpenSSH authentication path.
  • Add forced-route recovery with fleet connect <member> --via lan|tailscale.
  • Reject join requests arriving from Tailscale address space and keep mapping refresh best-effort.
  • Document the design and rollout in docs/tailscale-integration-spec.md.

Verification

  • cargo test --all-targets (76 unit tests, 20 CLI tests)
  • cargo clippy --all-targets -- -D warnings

The implementation deliberately does not install, authenticate, administer, or relay through Tailscale.

Greptile Summary

This PR adds optional Tailscale routing for Fleet SSH without installing or administering Tailscale. Fleet learns each member's Tailscale FQDN over the existing pinned SSH channel, stores only the FQDN (never IPs), and resolves it live through the captain's local Tailscale client at connect time.

  • Adds fleet transport connect as an OpenSSH ProxyCommand that races LAN (150 ms head start) against the mapped Tailscale peer, passing the winning TCP socket to the outer SSH process unchanged.
  • Adds validate_join_source to enforce LAN-only joins and hardens the generated SSH config with Port 22, UpdateHostKeys no, and a shell-safe quoted ProxyCommand.

Confidence Score: 5/5

Safe to merge; all findings are diagnostic edge cases with no impact on correctness, data integrity, or security.

The transport selector, FQDN mapping, ProxyCommand generation, and join-source validation all implement their stated contracts correctly. The two flagged items — an uninformative error message when both routes time out simultaneously, and an opaque server error when OS interface enumeration fails during a join — degrade diagnostics only and do not cause incorrect behavior or security regression.

Files Needing Attention: src/ssh_client.rs (connect_auto error path) and src/service.rs (validate_join_source error propagation) are worth a second glance but neither requires a blocking fix.

Important Files Changed

FilenameOverview
src/tailscale.rsNew module: read-only Tailscale adapter with FQDN mapping persistence, range-validated IP resolution, bounded subprocess execution, and concurrent-safe file locking.
src/ssh_client.rsAdds transport selection (LAN race vs Tailscale), ProxyCommand generation with shell-safe quoting, proxy_stdio, and refresh_mapping. connect_auto error message is empty when both routes time out without replying.
src/service.rsAdds validate_join_source to enforce LAN-only joins; propagates get_if_addrs failures via ?, blocking joins when interface enumeration is transiently unavailable.
src/network.rsNew module: on-link peer validation with subnet matching, IPv4-mapped normalization, and interface name/IP-based exclusion lists. Well-tested.
src/commands.rsWires new commands and refactors status --check to use the transport selector; calls refresh_mapping after update-all.
src/remote.rsAdds ssh_output_direct (ProxyCommand=none) for bootstrap mapping pulls over raw LAN.

Reviews (2): Last reviewed commit: "fix: address Tailscale transport review ..." | Re-trigger Greptile

@extoci

Copy link
Copy Markdown
OwnerAuthor

@greptile review

Comment threadsrc/ssh_client.rs
Comment threadsrc/ssh_client.rs
@extoci
extoci merged commit 372441f into mainAug 4, 2026
5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@extoci
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: route Fleet SSH over existing Tailscale - #1

Merged
extoci merged 7 commits into
mainfrom
feat/tailscale
Aug 4, 2026
Merged

feat: route Fleet SSH over existing Tailscale#1
extoci merged 7 commits into
mainfrom
feat/tailscale

Conversation

@extoci

@extociextoci commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep ssh <name>.local as the only normal Fleet SSH interface.
  • Add a local OpenSSH ProxyCommand that uses LAN first and an existing Tailscale route when a confirmed mapping is available.
  • Learn exact member Tailscale FQDNs over the existing pinned SSH connection; persist no peer IPs or Tailscale credentials.
  • Preserve the existing .local host-key pin and OpenSSH authentication path.
  • Add forced-route recovery with fleet connect <member> --via lan|tailscale.
  • Reject join requests arriving from Tailscale address space and keep mapping refresh best-effort.
  • Document the design and rollout in docs/tailscale-integration-spec.md.

Verification

  • cargo test --all-targets (76 unit tests, 20 CLI tests)
  • cargo clippy --all-targets -- -D warnings

The implementation deliberately does not install, authenticate, administer, or relay through Tailscale.

Greptile Summary

This PR adds optional Tailscale routing for Fleet SSH without installing or administering Tailscale. Fleet learns each member's Tailscale FQDN over the existing pinned SSH channel, stores only the FQDN (never IPs), and resolves it live through the captain's local Tailscale client at connect time.

  • Adds fleet transport connect as an OpenSSH ProxyCommand that races LAN (150 ms head start) against the mapped Tailscale peer, passing the winning TCP socket to the outer SSH process unchanged.
  • Adds validate_join_source to enforce LAN-only joins and hardens the generated SSH config with Port 22, UpdateHostKeys no, and a shell-safe quoted ProxyCommand.

Confidence Score: 5/5

Safe to merge; all findings are diagnostic edge cases with no impact on correctness, data integrity, or security.

The transport selector, FQDN mapping, ProxyCommand generation, and join-source validation all implement their stated contracts correctly. The two flagged items — an uninformative error message when both routes time out simultaneously, and an opaque server error when OS interface enumeration fails during a join — degrade diagnostics only and do not cause incorrect behavior or security regression.

Files Needing Attention: src/ssh_client.rs (connect_auto error path) and src/service.rs (validate_join_source error propagation) are worth a second glance but neither requires a blocking fix.

Important Files Changed

FilenameOverview
src/tailscale.rsNew module: read-only Tailscale adapter with FQDN mapping persistence, range-validated IP resolution, bounded subprocess execution, and concurrent-safe file locking.
src/ssh_client.rsAdds transport selection (LAN race vs Tailscale), ProxyCommand generation with shell-safe quoting, proxy_stdio, and refresh_mapping. connect_auto error message is empty when both routes time out without replying.
src/service.rsAdds validate_join_source to enforce LAN-only joins; propagates get_if_addrs failures via ?, blocking joins when interface enumeration is transiently unavailable.
src/network.rsNew module: on-link peer validation with subnet matching, IPv4-mapped normalization, and interface name/IP-based exclusion lists. Well-tested.
src/commands.rsWires new commands and refactors status --check to use the transport selector; calls refresh_mapping after update-all.
src/remote.rsAdds ssh_output_direct (ProxyCommand=none) for bootstrap mapping pulls over raw LAN.

Reviews (2): Last reviewed commit: "fix: address Tailscale transport review ..." | Re-trigger Greptile

@extoci

Copy link
Copy Markdown
OwnerAuthor

@greptile review

Comment threadsrc/ssh_client.rs
Comment threadsrc/ssh_client.rs
@extoci
extoci merged commit 372441f into mainAug 4, 2026
5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@extoci
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: route Fleet SSH over existing Tailscale - #1

Merged
extoci merged 7 commits into
mainfrom
feat/tailscale
Aug 4, 2026
Merged

feat: route Fleet SSH over existing Tailscale#1
extoci merged 7 commits into
mainfrom
feat/tailscale

Conversation

@extoci

@extociextoci commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep ssh <name>.local as the only normal Fleet SSH interface.
  • Add a local OpenSSH ProxyCommand that uses LAN first and an existing Tailscale route when a confirmed mapping is available.
  • Learn exact member Tailscale FQDNs over the existing pinned SSH connection; persist no peer IPs or Tailscale credentials.
  • Preserve the existing .local host-key pin and OpenSSH authentication path.
  • Add forced-route recovery with fleet connect <member> --via lan|tailscale.
  • Reject join requests arriving from Tailscale address space and keep mapping refresh best-effort.
  • Document the design and rollout in docs/tailscale-integration-spec.md.

Verification

  • cargo test --all-targets (76 unit tests, 20 CLI tests)
  • cargo clippy --all-targets -- -D warnings

The implementation deliberately does not install, authenticate, administer, or relay through Tailscale.

Greptile Summary

This PR adds optional Tailscale routing for Fleet SSH without installing or administering Tailscale. Fleet learns each member's Tailscale FQDN over the existing pinned SSH channel, stores only the FQDN (never IPs), and resolves it live through the captain's local Tailscale client at connect time.

  • Adds fleet transport connect as an OpenSSH ProxyCommand that races LAN (150 ms head start) against the mapped Tailscale peer, passing the winning TCP socket to the outer SSH process unchanged.
  • Adds validate_join_source to enforce LAN-only joins and hardens the generated SSH config with Port 22, UpdateHostKeys no, and a shell-safe quoted ProxyCommand.

Confidence Score: 5/5

Safe to merge; all findings are diagnostic edge cases with no impact on correctness, data integrity, or security.

The transport selector, FQDN mapping, ProxyCommand generation, and join-source validation all implement their stated contracts correctly. The two flagged items — an uninformative error message when both routes time out simultaneously, and an opaque server error when OS interface enumeration fails during a join — degrade diagnostics only and do not cause incorrect behavior or security regression.

Files Needing Attention: src/ssh_client.rs (connect_auto error path) and src/service.rs (validate_join_source error propagation) are worth a second glance but neither requires a blocking fix.

Important Files Changed

FilenameOverview
src/tailscale.rsNew module: read-only Tailscale adapter with FQDN mapping persistence, range-validated IP resolution, bounded subprocess execution, and concurrent-safe file locking.
src/ssh_client.rsAdds transport selection (LAN race vs Tailscale), ProxyCommand generation with shell-safe quoting, proxy_stdio, and refresh_mapping. connect_auto error message is empty when both routes time out without replying.
src/service.rsAdds validate_join_source to enforce LAN-only joins; propagates get_if_addrs failures via ?, blocking joins when interface enumeration is transiently unavailable.
src/network.rsNew module: on-link peer validation with subnet matching, IPv4-mapped normalization, and interface name/IP-based exclusion lists. Well-tested.
src/commands.rsWires new commands and refactors status --check to use the transport selector; calls refresh_mapping after update-all.
src/remote.rsAdds ssh_output_direct (ProxyCommand=none) for bootstrap mapping pulls over raw LAN.

Reviews (2): Last reviewed commit: "fix: address Tailscale transport review ..." | Re-trigger Greptile

@extoci

Copy link
Copy Markdown
OwnerAuthor

@greptile review

Comment threadsrc/ssh_client.rs
Comment threadsrc/ssh_client.rs
@extoci
extoci merged commit 372441f into mainAug 4, 2026
5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@extoci
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: route Fleet SSH over existing Tailscale - #1

Merged
extoci merged 7 commits into
mainfrom
feat/tailscale
Aug 4, 2026
Merged

feat: route Fleet SSH over existing Tailscale#1
extoci merged 7 commits into
mainfrom
feat/tailscale

Conversation

@extoci

@extociextoci commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep ssh <name>.local as the only normal Fleet SSH interface.
  • Add a local OpenSSH ProxyCommand that uses LAN first and an existing Tailscale route when a confirmed mapping is available.
  • Learn exact member Tailscale FQDNs over the existing pinned SSH connection; persist no peer IPs or Tailscale credentials.
  • Preserve the existing .local host-key pin and OpenSSH authentication path.
  • Add forced-route recovery with fleet connect <member> --via lan|tailscale.
  • Reject join requests arriving from Tailscale address space and keep mapping refresh best-effort.
  • Document the design and rollout in docs/tailscale-integration-spec.md.

Verification

  • cargo test --all-targets (76 unit tests, 20 CLI tests)
  • cargo clippy --all-targets -- -D warnings

The implementation deliberately does not install, authenticate, administer, or relay through Tailscale.

Greptile Summary

This PR adds optional Tailscale routing for Fleet SSH without installing or administering Tailscale. Fleet learns each member's Tailscale FQDN over the existing pinned SSH channel, stores only the FQDN (never IPs), and resolves it live through the captain's local Tailscale client at connect time.

  • Adds fleet transport connect as an OpenSSH ProxyCommand that races LAN (150 ms head start) against the mapped Tailscale peer, passing the winning TCP socket to the outer SSH process unchanged.
  • Adds validate_join_source to enforce LAN-only joins and hardens the generated SSH config with Port 22, UpdateHostKeys no, and a shell-safe quoted ProxyCommand.

Confidence Score: 5/5

Safe to merge; all findings are diagnostic edge cases with no impact on correctness, data integrity, or security.

The transport selector, FQDN mapping, ProxyCommand generation, and join-source validation all implement their stated contracts correctly. The two flagged items — an uninformative error message when both routes time out simultaneously, and an opaque server error when OS interface enumeration fails during a join — degrade diagnostics only and do not cause incorrect behavior or security regression.

Files Needing Attention: src/ssh_client.rs (connect_auto error path) and src/service.rs (validate_join_source error propagation) are worth a second glance but neither requires a blocking fix.

Important Files Changed

FilenameOverview
src/tailscale.rsNew module: read-only Tailscale adapter with FQDN mapping persistence, range-validated IP resolution, bounded subprocess execution, and concurrent-safe file locking.
src/ssh_client.rsAdds transport selection (LAN race vs Tailscale), ProxyCommand generation with shell-safe quoting, proxy_stdio, and refresh_mapping. connect_auto error message is empty when both routes time out without replying.
src/service.rsAdds validate_join_source to enforce LAN-only joins; propagates get_if_addrs failures via ?, blocking joins when interface enumeration is transiently unavailable.
src/network.rsNew module: on-link peer validation with subnet matching, IPv4-mapped normalization, and interface name/IP-based exclusion lists. Well-tested.
src/commands.rsWires new commands and refactors status --check to use the transport selector; calls refresh_mapping after update-all.
src/remote.rsAdds ssh_output_direct (ProxyCommand=none) for bootstrap mapping pulls over raw LAN.

Reviews (2): Last reviewed commit: "fix: address Tailscale transport review ..." | Re-trigger Greptile

@extoci

Copy link
Copy Markdown
OwnerAuthor

@greptile review

Comment threadsrc/ssh_client.rs
Comment threadsrc/ssh_client.rs
@extoci
extoci merged commit 372441f into mainAug 4, 2026
5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@extoci