Skip to main content

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)