tailscale/devcontrol: add a flag for running with audit logs
- ID
4187396- date
2026-05-26 09:59:24+00:00- author
Alex Chan <alexc@tailscale.com>- parent
36e882d- message
tailscale/devcontrol: add a flag for running with audit logs- changed files
3 files, 110 additions, 2 deletions
Changed files
requirements.in (168) → requirements.in (172)
diff --git a/requirements.in b/requirements.in
index f0da590..e46545b 100644
--- a/requirements.in
+++ b/requirements.in
@@ -1,4 +1,4 @@
-alexwlchan-chives[fetch]
+alexwlchan-chives[fetch]>=41
beautifulsoup4
cogapp
humanize
requirements.txt (1434) → requirements.txt (1434)
diff --git a/requirements.txt b/requirements.txt
index 55dd63b..8aaf2c7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in --output-file=requirements.txt --exclude-newer=P7D --exclude-newer-package alexwlchan-chives=false
-alexwlchan-chives==34
+alexwlchan-chives==41
# via -r requirements.in
beautifulsoup4==4.14.3
# via -r requirements.in
tailscale/devcontrol (0) → tailscale/devcontrol (3049)
diff --git a/tailscale/devcontrol b/tailscale/devcontrol
new file mode 100755
index 0000000..1ee6366
--- /dev/null
+++ b/tailscale/devcontrol
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+"""
+Wrap the devcontrol binary used for running a local instance of the
+Tailscale control plane.
+
+I use a tiny subset of the available flags and scenarios; this script
+wraps them in a friendly interface.
+"""
+
+import argparse
+import atexit
+import os
+from pathlib import Path
+import subprocess
+import sys
+from typing import TypedDict
+
+
+
+Args = TypedDict(
+ "args",
+ {
+ "audit_logs": bool,
+ "persist": bool,
+ "tfacc": bool,
+ },
+)
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(
+ prog="devcontrol",
+ description="Run devcontrol with different options",
+ )
+
+ parser.add_argument("--audit-logs", action="store_true", help="save audit logs")
+ # TODO: Can I make this a flag to store state in a specific dir?
+ parser.add_argument(
+ "--persist",
+ action="store_true",
+ help="save the devcontrol state in /tmp/devcontrol",
+ )
+ parser.add_argument(
+ "--tfacc",
+ action="store_true",
+ help="run the Terraform acceptance test scenario",
+ )
+
+ args = parser.parse_args()
+
+ return {
+ "audit_logs": args.audit_logs,
+ "persist": args.persist,
+ "tfacc": args.tfacc,
+ }
+
+
+if __name__ == "__main__":
+ args = parse_args()
+
+ corp_dir = Path.home() / "repos/corp"
+
+ tailcontrol_cmd = ["./tool/go", "run", "--tags=tailscale_saas", "./cmd/devcontrol"]
+ tailcontrol_env = {
+ "HOME": str(Path.home()),
+ "PATH": os.environ["PATH"],
+ }
+
+ # See https://www.notion.so/tailscale/Running-a-local-CONTROL-clients-and-DERP-d69826480c704c1f9f39950478e7303f#f744c21f49f14c4fa748bff0a944e9bb
+ if args["audit_logs"]:
+ # Compile logzservice before running it, so the terminate() signal
+ # goes to the logzservice process rather than `go run`.
+ #
+ # Otherwise, the compiled logzservice binary can be left running
+ # after this script exits.
+ subprocess.check_call(["./tool/go", "build", "./cmd/logzservice"], cwd=corp_dir)
+ logz_proc = subprocess.Popen(["./logzservice", "-dev"], cwd=corp_dir)
+ atexit.register(lambda: logz_proc.terminate())
+ tailcontrol_env.update(
+ {
+ "TS_LOG_TARGET": "http://localhost:9951",
+ "TS_DEBUG_CONTROL_LOGTAIL_UPLOAD": "true",
+ }
+ )
+
+ def stop_logz_proc() -> None:
+ logz_proc.terminate()
+
+ atexit.register(stop_logz_proc)
+
+ if args["persist"]:
+ tailcontrol_cmd.append("--dir=/tmp/devcontrol")
+
+ if args["tfacc"]:
+ tailcontrol_cmd.append("--generate-test-devices=terraform-acceptance-testing")
+
+ debug_string = ["->"]
+ for name, value in tailcontrol_env.items():
+ if name in {"HOME", "PATH"}:
+ continue
+ debug_string.append(f"{name}={value}")
+ debug_string.extend(tailcontrol_cmd)
+
+ print(" ".join(debug_string))
+ try:
+ subprocess.check_call(tailcontrol_cmd, env=tailcontrol_env, cwd=corp_dir)
+ except KeyboardInterrupt:
+ sys.exit(0)