@@ -23,6 +23,22 @@ class MadmailDriver(Driver):
2323 REPO_NAME = MADMAIL
2424 REQUIRED_SOURCE_PATHS = ["go.mod" , "Makefile" ]
2525
26+ @classmethod
27+ def add_cli_options (cls , parser , completer = None ):
28+ """Register madmail-specific deploy options."""
29+ super ().add_cli_options (parser , completer = completer )
30+ parser .add_argument (
31+ "--with-webadmin" ,
32+ action = "store_true" ,
33+ help = (
34+ "Build and enable the embedded admin web UI at /admin. "
35+ "Disabled by default."
36+ ),
37+ )
38+
39+ def configure_from_args (self , args ):
40+ self .with_admin = bool (args .with_webadmin )
41+
2642 @classmethod
2743 def on_prep_builder (cls , out , bld_ct , tmp_dest ):
2844 """Hook called by ``prep_builder`` to ensure the Go toolchain is ready."""
@@ -31,19 +47,56 @@ def on_prep_builder(cls, out, bld_ct, tmp_dest):
3147
3248 def on_init_relay (self , repo_path ):
3349 """Hook called by ``init_builder`` to build the maddy binary."""
34- with self .out .section (f"Building maddy binary for { self .ct .shortname } " ):
35- self .bld_ct .bash (f"""
36- if [ -f '{ repo_path } /admin-web/package.json' ]; then
37- cd '{ repo_path } /admin-web' && bun install
38- fi
39- """ )
40- self .out .print (f"Compiling maddy in { repo_path } (make build) ..." )
41- ret = self .out .shell (
42- f"incus exec { self .bld_ct .name } -- bash -c 'cd { repo_path } && make build'"
43- )
50+ mode = "with admin web UI" if self .with_admin else "without admin web UI"
51+ with self .out .section (
52+ f"Building maddy binary for { self .ct .shortname } ({ mode } )"
53+ ):
54+ if self .with_admin :
55+ # Ensure admin-web submodule is populated and dependencies installed;
56+ # build.sh copy_admin_web() handles the actual SPA build.
57+ self .bld_ct .bash (f"""
58+ if [ ! -f '{ repo_path } /admin-web/package.json' ]; then
59+ cd '{ repo_path } ' && git submodule update --init admin-web
60+ fi
61+ cd '{ repo_path } /admin-web'
62+ if command -v bun >/dev/null 2>&1; then
63+ bun install
64+ elif command -v npm >/dev/null 2>&1; then
65+ npm install
66+ fi
67+ """ )
68+ else :
69+ # Hide package.json so build.sh creates a placeholder instead.
70+ self .bld_ct .bash (f"""
71+ PKG='{ repo_path } /admin-web/package.json'
72+ BAK='{ repo_path } /admin-web/package.json.cmlxc-disabled'
73+ if [ -f "$PKG" ]; then mv "$PKG" "$BAK"; fi
74+ """ )
75+
76+ try :
77+ ret = self .out .shell (
78+ f"incus exec { self .bld_ct .name } -- bash -c "
79+ f"'cd { repo_path } && make clean build'"
80+ )
81+ finally :
82+ # Restore package.json if we hid it.
83+ self .bld_ct .bash (f"""
84+ BAK='{ repo_path } /admin-web/package.json.cmlxc-disabled'
85+ PKG='{ repo_path } /admin-web/package.json'
86+ if [ -f "$BAK" ] && [ ! -f "$PKG" ]; then mv "$BAK" "$PKG"; fi
87+ """ )
88+
4489 if ret :
4590 raise SetupError (f"maddy build failed in { repo_path } (exit { ret } )" )
4691
92+ if self .with_admin :
93+ check = self .bld_ct .bash (
94+ f"test -f { repo_path } /internal/adminweb/build/index.html" ,
95+ check = False ,
96+ )
97+ if check is None :
98+ raise SetupError ("admin-web build produced no index.html" )
99+
47100 def get_test_domain_or_ip (self ):
48101 if not self .ct .ipv4 :
49102 self .ct .wait_ready ()
@@ -94,15 +147,57 @@ def deploy(self, source=None):
94147 self .ct .bash ("systemctl daemon-reload" )
95148 self .ct .bash ("systemctl enable madmail" )
96149 self .ct .bash ("systemctl start madmail" )
150+
151+ if self .with_admin :
152+ self .out .print ("Configuring admin web interface at /admin ..." )
153+ self .ct .bash ("madmail admin-web path /admin" )
154+ self .ct .bash ("madmail admin-web enable" )
155+ # Path changes are applied at startup.
156+ self .ct .bash ("systemctl restart madmail" )
157+ else :
158+ self .out .print ("Disabling admin web interface ..." )
159+ self .ct .bash ("madmail admin-web disable" )
160+
97161 self .ct .bash ("rm -f /tmp/madmail" )
98162
99163 self .ct .write_deploy_state (MADMAIL , source = source )
100164 self .out .green (f"madmail deployed to { self .ct .shortname } ({ ip } )" )
165+ print_admin_info (self .out , self .ct , ip )
101166
102167 elapsed = time .time () - t_total
103168 self .out .section_line (f"deploy madmail complete ({ elapsed :.1f} s)" )
104169
105170
171+ def print_admin_info (out , ct , ip ):
172+ """Print admin API token and admin-web endpoint state."""
173+ try :
174+ token = ct .bash ("madmail admin-token --raw" , check = False ).strip ()
175+ if token :
176+ out .print (f"admin-token: { token } " )
177+
178+ status = ct .bash ("madmail admin-web status" , check = False ) or ""
179+ enabled , path = _parse_admin_web_status (status )
180+ if enabled and path :
181+ out .print (f"admin-url: https://{ ip } { path } /" )
182+ else :
183+ out .print ("admin-url: disabled" )
184+ except Exception :
185+ pass
186+
187+
188+ def _parse_admin_web_status (status ):
189+ enabled = "Admin Web Dashboard: enabled" in status
190+ path = None
191+ for line in status .splitlines ():
192+ if "Admin Web Path:" in line :
193+ _ , _ , value = line .partition ("Admin Web Path:" )
194+ value = value .strip ()
195+ if value .startswith ("/" ):
196+ path = value .rstrip ("/" )
197+ break
198+ return enabled , path
199+
200+
106201def prepare_build_container (bld_ct , go_mod_path ):
107202 """Install or update Go inside the builder according to go.mod."""
108203 bld_ct .bash ("""
0 commit comments