2026-03-30 15:15:22 +01:00
|
|
|
import pexpect
|
|
|
|
|
import json
|
2026-07-01 13:31:24 -04:00
|
|
|
import socket
|
2026-03-31 12:46:38 +01:00
|
|
|
from app import app
|
2026-04-03 13:53:21 +01:00
|
|
|
from subprocess import run
|
2026-03-31 12:46:38 +01:00
|
|
|
|
2026-07-01 13:31:24 -04:00
|
|
|
def resolve_butterbox_ip(hostname):
|
|
|
|
|
"""Best-effort lookup of the box's current address, falling back to the
|
|
|
|
|
configured default. Mirrors app.routes.resolve_butterbox_ip but avoids a
|
|
|
|
|
circular import (routes imports this module) and reuses the hostname that
|
|
|
|
|
was already loaded from settings.txt."""
|
|
|
|
|
# 1. Resolve the box's own mDNS/.local name.
|
|
|
|
|
try:
|
|
|
|
|
ip = socket.gethostbyname(f"{hostname}.local")
|
|
|
|
|
if ip and not ip.startswith("127."):
|
|
|
|
|
return ip
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# 2. Fall back to the address of the primary outbound interface. The
|
|
|
|
|
# connect() picks a route without sending any packets.
|
|
|
|
|
try:
|
|
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
|
|
|
s.connect(("8.8.8.8", 80))
|
|
|
|
|
ip = s.getsockname()[0]
|
|
|
|
|
if ip and not ip.startswith("127."):
|
|
|
|
|
return ip
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# 3. Give up and use the configured access-point default.
|
|
|
|
|
return app.config['BUTTERBOX_DEFAULT_IP']
|
|
|
|
|
|
2026-03-30 15:15:22 +01:00
|
|
|
def run_madmail_installer():
|
|
|
|
|
with open("./settings.txt", "r") as f:
|
|
|
|
|
settings = json.load(f)
|
|
|
|
|
butterbox_hostname = settings["butterbox_hostname"]
|
|
|
|
|
|
|
|
|
|
cmd = f"sudo ../madmail/madmail install"
|
2026-05-07 21:17:51 -04:00
|
|
|
child = pexpect.spawnu(cmd)
|
2026-03-30 15:15:22 +01:00
|
|
|
child.logfile = open("install_log.txt", "w")
|
|
|
|
|
child.expect("Customize every setting")
|
|
|
|
|
child.sendline("2")
|
|
|
|
|
child.expect("Primary domain")
|
2026-03-31 15:20:48 +01:00
|
|
|
child.sendline(f"{butterbox_hostname}.local")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("MX record")
|
2026-03-31 15:20:48 +01:00
|
|
|
child.sendline(f"{butterbox_hostname}.local")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("Public IP address")
|
2026-07-01 13:31:24 -04:00
|
|
|
child.sendline(resolve_butterbox_ip(butterbox_hostname))
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("Additional domains")
|
|
|
|
|
child.sendline("")
|
|
|
|
|
child.expect("State directory")
|
|
|
|
|
child.sendline("")
|
|
|
|
|
child.expect("Configuration directory")
|
|
|
|
|
child.sendline("")
|
2026-03-31 10:36:43 +01:00
|
|
|
child.expect("TLS mode")
|
2026-03-31 14:23:02 +01:00
|
|
|
child.sendline("self_signed")
|
2026-05-07 21:17:51 -04:00
|
|
|
child.expect("Disable TLS")
|
|
|
|
|
child.sendline("y")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("SMTP port")
|
2026-03-31 14:23:02 +01:00
|
|
|
child.sendline("25")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("Submission port")
|
2026-03-31 14:23:02 +01:00
|
|
|
child.sendline("587")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("Submission TLS port")
|
2026-03-31 14:23:02 +01:00
|
|
|
child.sendline("465")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("IMAP port")
|
2026-03-31 14:23:02 +01:00
|
|
|
child.sendline("143")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("IMAP TLS port")
|
2026-03-31 14:23:02 +01:00
|
|
|
child.sendline("993")
|
2026-05-07 21:17:51 -04:00
|
|
|
child.expect("Enable chatmail")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.sendline("y")
|
|
|
|
|
child.expect("Chatmail HTTP port")
|
|
|
|
|
child.sendline("8081")
|
|
|
|
|
child.expect("Chatmail HTTPS port")
|
|
|
|
|
child.sendline("8443")
|
|
|
|
|
child.expect("Chatmail username length")
|
2026-03-31 14:23:02 +01:00
|
|
|
child.sendline("8")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("Chatmail password length")
|
2026-03-31 14:23:02 +01:00
|
|
|
child.sendline("16")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("Enable Shadowsocks proxy for faster messaging")
|
|
|
|
|
child.sendline("n")
|
|
|
|
|
child.expect("Enable TURN server")
|
|
|
|
|
child.sendline("n")
|
2026-05-07 13:40:39 -04:00
|
|
|
child.expect("Allow cleartext SMTP submission")
|
|
|
|
|
child.sendline("n")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("Allow secure join requests without encryption")
|
2026-03-31 12:46:38 +01:00
|
|
|
child.sendline("y")
|
2026-03-30 15:15:22 +01:00
|
|
|
child.expect("Passthrough senders")
|
|
|
|
|
child.sendline("")
|
|
|
|
|
child.expect("Passthrough recipients")
|
|
|
|
|
child.sendline("")
|
|
|
|
|
child.expect("Maximum message size")
|
|
|
|
|
child.sendline("")
|
|
|
|
|
child.expect("Add Cloudflare proxy disable tags to DNS records")
|
|
|
|
|
child.sendline("n")
|
|
|
|
|
child.expect("Enable logging for the server")
|
|
|
|
|
child.sendline("y")
|
2026-03-31 15:20:48 +01:00
|
|
|
child.expect(pexpect.EOF)
|
2026-04-03 13:53:21 +01:00
|
|
|
|
2026-05-08 06:17:27 -04:00
|
|
|
daemon_enable = run(["sudo", "systemctl", "enable", "madmail"], capture_output=True, text=True)
|
|
|
|
|
if daemon_enable.returncode != 0:
|
|
|
|
|
print(f"Issue running systemctl daemon_enable")
|
|
|
|
|
|
|
|
|
|
daemon_start = run(["sudo", "systemctl", "start", "madmail"], capture_output=True, text=True)
|
|
|
|
|
if daemon_start.returncode != 0:
|
|
|
|
|
print(f"Issue running systemctl daemon_start")
|
|
|
|
|
|
2026-05-07 13:34:50 -04:00
|
|
|
daemon_reload = run(["sudo", "systemctl", "daemon-reload"], capture_output=True, text=True)
|
|
|
|
|
if daemon_reload.returncode != 0:
|
2026-05-07 13:40:39 -04:00
|
|
|
print(f"Issue running systemctl daemon_reload")
|