up method
- String hostname = '',
- String? authKey,
- bool ephemeral = false,
- Uri? controlUrl,
- Duration timeout = const Duration(seconds: 30),
Brings the embedded Tailscale node up and connects to the control
plane — Tailscale's coordination service at
controlplane.tailscale.com, or a self-hosted
Headscale if you set
controlUrl. Registers the node on first launch, reconnects from
persisted credentials on subsequent launches.
authKey can enroll a fresh persistent node without user interaction;
get one from the tailnet admin panel at
login.tailscale.com/admin/settings/keys (see
tailscale.com/kb/1085/auth-keys). Reusable keys let you
call up from multiple processes. It is optional in persistent mode:
without a usable profile or key, upstream enters NodeState.needsLogin
and returns an authorization URL for interactive enrollment. Subsequent
launches can omit it because persisted session state reconnects.
Set ephemeral to register this process as a short-lived node. Ephemeral
nodes are removed from the tailnet automatically after they go inactive
by control-plane cleanup. A successful logout removes an ephemeral node
from the tailnet immediately while preserving the lower-level StateStore
container. Use this for CI jobs, preview
environments, disposable tests, and other nodes whose identity should not
outlive the process. Ephemeral mode retains no local identity: every new
up after down (or process restart) needs a valid auth key, and a
single-use key must be replaced. The configured stateDir is used only
for admission/coordination and does not need to be cleared.
hostname sets the tailnet-visible hostname and the
MagicDNS label, so the
node becomes reachable at <hostname>.<tailnet>.ts.net. Leave it unset
to use upstream tsnet's default: the lowercased host program name (or
tsnet when the embedded runtime cannot resolve one).
Resolves on the first stable state: running, needsLogin,
or needsMachineAuth. This intentionally differs from Go's
tsnet.Server.Up, which blocks only on running — a Dart app
that needs to drive an in-app auth flow should not have to
re-enter up just to see the TailscaleStatus.authUrl. Inspect
the returned TailscaleStatus.state to decide what to do next:
running— ready; http, tcp, etc. are usable.needsLogin— open TailscaleStatus.authUrl in a browser / web view; the node finishes connecting after the user completes the flow.needsMachineAuth— authenticated but awaiting admin approval on the control plane ( device approval).
Transitions delivered via onStateChange:
- First launch:
noState → starting → running - Reconnect with persisted creds:
stopped → starting → running - If creds are expired:
stopped → starting → needsLogin(with TailscaleStatus.authUrl populated)
No-op when a runtime is already active with the same hostname, effective control URL, and ephemeral mode. An auth key never replaces an active identity; call down before changing runtime configuration.
timeout bounds native startup and the stable-state wait. Once that
deadline expires, the Future waits as long as required to establish
fail-safe quarantine before returning, so total wall time can exceed
timeout when native teardown is slow. A non-cancellable late native
success is closed instead of becoming an unowned active node. Increase
the timeout for slow mobile networks or self-hosted control planes.
Throws TailscaleUpException if the node fails to start or reach a stable
state before timeout (e.g. control plane unreachable). When no auth key
or usable profile exists, upstream normally returns needsLogin.
Implementation
@override
Future<TailscaleStatus> up({
String hostname = '',
String? authKey,
bool ephemeral = false,
Uri? controlUrl,
Duration timeout = const Duration(seconds: 30),
}) async {
_requireInitialized();
if (timeout <= Duration.zero) {
throw const TailscaleUsageException('up timeout must be positive.');
}
if (ephemeral && (authKey == null || authKey.isEmpty)) {
throw const TailscaleUsageException(
'ephemeral up requires a non-empty authKey.',
);
}
final validatedHostname = validateRuntimeHostname(hostname);
final canonicalControlUrl = canonicalizeControlUrl(controlUrl);
if (_nativeStartInFlight) {
throw const TailscaleUpException(
'Another node start is already in progress.',
code: TailscaleErrorCode.lifecycleBusy,
);
}
// Reserve the complete public startup synchronously, including recovery,
// native construction, stable-state observation, and Dart capability
// setup. Teardown operations share this supervisor queue.
_nativeStartInFlight = true;
final startSettled = Completer<void>();
_nativeStartPending = startSettled.future;
final elapsed = Stopwatch()..start();
try {
return await _supervisorLifecycle.run(
() => _runUp(
hostname: validatedHostname,
authKey: authKey ?? '',
ephemeral: ephemeral,
controlUrl: canonicalControlUrl,
timeout: timeout,
elapsed: elapsed,
),
);
} finally {
_nativeStartInFlight = false;
_nativeStartPending = null;
startSettled.complete();
}
}