Add guided funnel fallback: prompt to install Go/tailscaled when funnel disabled
This commit is contained in:
53
src/index.ts
53
src/index.ts
@@ -12,6 +12,8 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import JSON5 from 'json5';
|
import JSON5 from 'json5';
|
||||||
|
import readline from 'node:readline/promises';
|
||||||
|
import { stdin as input, stdout as output } from 'node:process';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@@ -82,6 +84,15 @@ async function ensureBinary(name: string): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function promptYesNo(question: string, defaultYes = false): Promise<boolean> {
|
||||||
|
const rl = readline.createInterface({ input, output });
|
||||||
|
const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
|
||||||
|
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
||||||
|
rl.close();
|
||||||
|
if (!answer) return defaultYes;
|
||||||
|
return answer.startsWith('y');
|
||||||
|
}
|
||||||
|
|
||||||
function withWhatsAppPrefix(number: string): string {
|
function withWhatsAppPrefix(number: string): string {
|
||||||
// Ensure number has whatsapp: prefix expected by Twilio.
|
// Ensure number has whatsapp: prefix expected by Twilio.
|
||||||
return number.startsWith('whatsapp:') ? number : `whatsapp:${number}`;
|
return number.startsWith('whatsapp:') ? number : `whatsapp:${number}`;
|
||||||
@@ -301,16 +312,50 @@ async function getTailnetHostname() {
|
|||||||
throw new Error('Could not determine Tailscale DNS or IP');
|
throw new Error('Could not determine Tailscale DNS or IP');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureGoInstalled() {
|
||||||
|
// Ensure Go toolchain is present; offer Homebrew install if missing.
|
||||||
|
const hasGo = await runExec('go', ['version']).then(
|
||||||
|
() => true,
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
if (hasGo) return;
|
||||||
|
const install = await promptYesNo('Go is not installed. Install via Homebrew (brew install go)?', true);
|
||||||
|
if (!install) {
|
||||||
|
console.error('Go is required to build tailscaled from source. Aborting.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
await runExec('brew', ['install', 'go']);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureTailscaledInstalled() {
|
||||||
|
// Ensure tailscaled binary exists; install via Homebrew tailscale if missing.
|
||||||
|
const hasTailscaled = await runExec('tailscaled', ['--version']).then(
|
||||||
|
() => true,
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
if (hasTailscaled) return;
|
||||||
|
|
||||||
|
const install = await promptYesNo('tailscaled not found. Install via Homebrew (tailscale package)?', true);
|
||||||
|
if (!install) {
|
||||||
|
console.error('tailscaled is required for user-space funnel. Aborting.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
await runExec('brew', ['install', 'tailscale']);
|
||||||
|
}
|
||||||
|
|
||||||
async function ensureFunnel(port: number) {
|
async function ensureFunnel(port: number) {
|
||||||
// Ensure Funnel is enabled and publish the webhook port.
|
// Ensure Funnel is enabled and publish the webhook port.
|
||||||
try {
|
try {
|
||||||
const statusOut = (await runExec('tailscale', ['funnel', 'status', '--json'])).stdout.trim();
|
const statusOut = (await runExec('tailscale', ['funnel', 'status', '--json'])).stdout.trim();
|
||||||
const parsed = statusOut ? (JSON.parse(statusOut) as Record<string, unknown>) : {};
|
const parsed = statusOut ? (JSON.parse(statusOut) as Record<string, unknown>) : {};
|
||||||
if (!parsed || Object.keys(parsed).length === 0) {
|
if (!parsed || Object.keys(parsed).length === 0) {
|
||||||
console.error(
|
console.error('Tailscale Funnel is not enabled on this tailnet/device.');
|
||||||
'Tailscale Funnel is not enabled on this tailnet/device. Enable it in the Tailscale admin console, then re-run warelay setup.'
|
console.error('Enable in admin console: https://login.tailscale.com/admin (see https://tailscale.com/kb/1223/funnel)');
|
||||||
);
|
console.error('macOS user-space tailscaled docs: https://github.com/tailscale/tailscale/wiki/Tailscaled-on-macOS');
|
||||||
process.exit(1);
|
const proceed = await promptYesNo('Attempt local setup with user-space tailscaled?', false);
|
||||||
|
if (!proceed) process.exit(1);
|
||||||
|
await ensureGoInstalled();
|
||||||
|
await ensureTailscaledInstalled();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { stdout } = await runExec('tailscale', ['funnel', '--yes', '--bg', `${port}`], 200_000);
|
const { stdout } = await runExec('tailscale', ['funnel', '--yes', '--bg', `${port}`], 200_000);
|
||||||
|
|||||||
Reference in New Issue
Block a user