以前、Tailscale SSHについて紹介しました。最初にSSH化するならコマンドにtailscale upする際にオプションを付け、tailscale up --sshで実行するだけと簡単なのですが、Tailscaleのサービスを色々設定したあとに追加する場合は、適切にオプションを指定しないと既存の環境が壊れてしまう可能性があるのでスクリプトを組んでみました。
読み取る設定一覧
debug prefs のキー | tailscale up オプション |
|---|---|
RouteAll | --accept-routes |
CorpDNS | --accept-dns |
ShieldsUp | --shields-up |
ExitNodeID/IP | --exit-node=... |
AdvertiseRoutes | --advertise-routes=... / --advertise-exit-node |
Hostname | --hostname=... |
OperatorUser | --operator=... |
NetfilterMode | --netfilter-mode=... |
NoSNAT | --snat-subnet-routes=false |
スクリプトを作成
mkdir -p /opt/lxd-data/script
cd /opt/lxd-data/script
sudo nano tailscale-enable-ssh.sh
sudo chmod +x tailscale-enable-ssh.sh
#!/usr/bin/env bash
# tailscale-enable-ssh.sh
# 現在のTailscale設定を保持しつつ --ssh を追加して再起動する
set -euo pipefail
info() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
ok() { echo -e "\033[1;32m[OK]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
die() { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; exit 1; }
# sudoでPATHが変わる対策: tailscaleのフルパスを探す
TAILSCALE=$(command -v tailscale 2>/dev/null \
|| { for p in /usr/bin/tailscale /usr/local/bin/tailscale /usr/sbin/tailscale; do
[[ -x "$p" ]] && echo "$p" && break; done; })
[[ -n "$TAILSCALE" ]] || die "tailscale が見つかりません"
command -v jq >/dev/null 2>&1 || die "jq が必要です: sudo apt install jq"
info "現在の Tailscale 設定を取得中..."
PREFS=$(tailscale debug prefs 2>/dev/null || "$TAILSCALE" debug prefs 2>/dev/null) \
|| die "tailscale debug prefs の取得に失敗しました"
jq_bool() { echo "$PREFS" | jq -r "$1 // false"; }
jq_str() { echo "$PREFS" | jq -r "$1 // empty"; }
OPTS=("--ssh")
[[ "$(jq_bool '.RouteAll')" == "true" ]] && OPTS+=("--accept-routes")
[[ "$(jq_bool '.CorpDNS')" == "true" ]] && OPTS+=("--accept-dns")
[[ "$(jq_bool '.ShieldsUp')" == "true" ]] && OPTS+=("--shields-up")
[[ "$(jq_bool '.ExitNodeAllowLANAccess')" == "true" ]] && OPTS+=("--exit-node-allow-lan-access")
EXIT_NODE=$(jq_str '.ExitNodeID // empty')
[[ -z "$EXIT_NODE" ]] && EXIT_NODE=$(jq_str '.ExitNodeIP // empty')
[[ -n "$EXIT_NODE" ]] && OPTS+=("--exit-node=$EXIT_NODE")
if echo "$PREFS" | jq -e '.AdvertiseRoutes[]? | select(. == "0.0.0.0/0")' >/dev/null 2>&1; then
OPTS+=("--advertise-exit-node")
fi
ROUTES=$(echo "$PREFS" | jq -r '.AdvertiseRoutes[]? // empty' 2>/dev/null \
| grep -v '^0\.0\.0\.0/0$' | grep -v '^::/0$' | paste -sd, - || true)
[[ -n "$ROUTES" ]] && OPTS+=("--advertise-routes=$ROUTES")
HOSTNAME=$(jq_str '.Hostname')
[[ -n "$HOSTNAME" ]] && OPTS+=("--hostname=$HOSTNAME")
OPERATOR=$(jq_str '.OperatorUser')
[[ -n "$OPERATOR" ]] && OPTS+=("--operator=$OPERATOR")
case "$(jq_str '.NetfilterMode')" in
1) OPTS+=("--netfilter-mode=on") ;;
2) OPTS+=("--netfilter-mode=nodivert") ;;
3) OPTS+=("--netfilter-mode=off") ;;
esac
[[ "$(jq_bool '.NoStatefulFiltering')" == "true" ]] && OPTS+=("--stateful-filtering=false")
[[ "$(jq_bool '.NoSNAT')" == "true" ]] && OPTS+=("--snat-subnet-routes=false")
CMD="sudo $TAILSCALE up ${OPTS[*]}"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
info "実行するコマンド:"
echo ""
echo " $CMD"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# ── ドライランオプション ────────────────────────────────
if [[ "${1:-}" == "--dry-run" ]]; then
warn "ドライラン: 実際には実行しません"
exit 0
fi
# ── 実行確認 ───────────────────────────────────────────
read -rp "実行しますか? [y/N] " CONFIRM
[[ "$CONFIRM" =~ ^[Yy]$ ]] || { warn "キャンセルしました"; exit 0; }
# ── Auth Key を使うか確認 ──────────────────────────────
echo ""
echo "Tailscale Auth Key を使用しますか?"
echo " 使う場合 : 認証済みノードとして自動ログイン (tskey-auth-... または tskey-...)"
echo " 使わない場合: 現在のログイン状態のまま継続"
echo ""
read -rp "Auth Key を使用しますか? [y/N] " USE_KEY
if [[ "$USE_KEY" =~ ^[Yy]$ ]]; then
# 入力中はエコーしない (キーを画面に表示させない)
echo -n "Auth Key を貼り付けてください: "
read -rs AUTH_KEY
echo ""
# 簡易バリデーション
if [[ -z "$AUTH_KEY" ]]; then
die "Auth Key が空です"
fi
if [[ ! "$AUTH_KEY" =~ ^tskey- ]]; then
warn "Auth Key の形式が想定と異なります (tskey- で始まらない)"
read -rp "このまま続けますか? [y/N] " FORCE
[[ "$FORCE" =~ ^[Yy]$ ]] || { warn "キャンセルしました"; exit 0; }
fi
CMD="$CMD --authkey=$AUTH_KEY"
info "Auth Key を使用して実行します"
else
info "Auth Key なしで実行します"
fi
# ── 実行 ──────────────────────────────────────────────
echo ""
info "tailscale up を実行中..."
eval "$CMD"
ok "完了! SSH が有効になりました"
echo ""
"$TAILSCALE" status
まずはコマンド確認(実際には実行しない)
まず確認します。
./tailscale-enable-ssh.sh --dry-run
本番実行
問題なければ本番。なお、TailscaleのAuth Keyを使用していれば、途中で下記の確認があるのでそこで入力出来ます。tskey- で始まらない場合は警告表示し、 誤貼り付けを防ぐようにしています。
実行しますか? [y/N] y
Tailscale Auth Key を使用しますか?
使う場合 : 認証済みノードとして自動ログイン (tskey-auth-... または tskey-...)
使わない場合: 現在のログイン状態のまま継続
Auth Key を使用しますか? [y/N] y
Auth Key を貼り付けてください: ※入力は画面に表示されません
また実行中に次のような最終確認が表示されます。
実行しますか? [y/N] y
[INFO] tailscale up を実行中...
Warning: netfilter=nodivert; add iptables calls to ts-* chains manually.
You are connected over Tailscale; this action will reroute SSH traffic to Tailscale SSH and will result in your session disconnecting.
To skip this warning, use --accept-risk=lose-ssh
Continue? [y/N]
この警告は「現在Tailscale経由でSSH接続中の場合、セッションが切れるよ」という確認です。ローカルで作業している場合は切断は起きません。Tailscale SSH経由で接続中の場合は一度切断されますが、すぐ再接続できます。
./tailscale-enable-ssh.sh


