WireGuard bring-up: host setup script, in-VM test tunnel, portable repo_root
wg-setup-host.sh sets up the tunnel on the backend host: keys generated there and kept there, wg0.conf, wg-quick enabled, and the phone's config printed as a scannable QR. Split tunnel (AllowedIPs is only the backend subnet), single-address addressing per PLAN.md, and the three things it can't do for you -- router UDP forward, DDNS, hairpin check -- spelled out at the end. test-wg-tunnel.sh stands up a real WireGuard tunnel between two network namespaces inside one machine, so the production posture (bind wg0 and nothing else) is testable with no router, phone, or internet exposure. Verified: real handshake, server listening on 10.66.0.1:8443 only, and an authorized request from inside the tunnel answering 200 over pinned TLS -- the leaf's 10.66.0.1 SAN is what a phone will validate too. repo_root() now resolves from the running executable before falling back to the compiled-in path: the repo is shared host<->VM over virtiofs at different absolute paths with a shared target/, so a binary built on one side and run on the other looked for its config where nothing exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
1 parent
8841effc6a
commit
29f8b31f0b
5 files changed
+324
-4
No files matched your search
+20
-4
@@ -35,13 +35,29 @@ const WG_INTERFACE: &str = "wg0";
|
||||
/// The repo root, one level above this crate. Everything the server reads
|
||||
/// by default -- the TLS cert, the config, the session data -- resolves
|
||||
/// from here, so there's one definition of it rather than one per caller.
|
||||
///
|
||||
/// Found from the running executable first, and only then from the path
|
||||
/// compiled in. This repo is shared between a VM and its host over
|
||||
/// virtiofs at *different* absolute paths (`~/host/repos/ai-app` vs
|
||||
/// `~/stuff/vm/ai/repos/ai-app`), and `target/` is shared along with it --
|
||||
/// so a binary built on one side and run on the other would otherwise look
|
||||
/// for its config under a path that doesn't exist there, which is exactly
|
||||
/// what happened once. Where both agree (the ordinary case) the answer is
|
||||
/// identical either way; the flags below override it regardless.
|
||||
fn repo_root() -> &'static Path {
|
||||
static ROOT: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
|
||||
ROOT.get_or_init(|| {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("CARGO_MANIFEST_DIR has a repo-root parent")
|
||||
.to_path_buf()
|
||||
// target/{debug,release}/ai-server -> four levels up is the root.
|
||||
let from_exe = std::env::current_exe().ok().and_then(|exe| {
|
||||
let root = exe.ancestors().nth(4)?.to_path_buf();
|
||||
root.join("server/Cargo.toml").is_file().then_some(root)
|
||||
});
|
||||
from_exe.unwrap_or_else(|| {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("CARGO_MANIFEST_DIR has a repo-root parent")
|
||||
.to_path_buf()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[2m2026-08-25T06:18:57.533304Z[0m [32m INFO[0m [2mai_server[0m[2m:[0m config: /home/bob/host/repos/ai-app/config.json
|
||||
[2m2026-08-25T06:18:57.537682Z[0m [32m INFO[0m [2mai_server[0m[2m:[0m serving https://10.66.0.1:8443
|
||||
[2m2026-08-25T06:20:43.470080Z[0m [32m INFO[0m [2mai_server[0m[2m:[0m config: /home/bob/host/repos/ai-app/config.json
|
||||
[2m2026-08-25T06:20:43.474488Z[0m [32m INFO[0m [2mai_server[0m[2m:[0m serving https://10.66.0.1:8443
|
||||
Reference in new issue
Block a user