Skip to content

Commit 3e628d1

Browse files
committed
feat: implement cleanup function for multiproxy instances directory
set default WATCHTOWER_NO_STARTUP_MESSAGE=false
1 parent f2545af commit 3e628d1

File tree

3 files changed

+135
-6
lines changed

3 files changed

+135
-6
lines changed

template/user-config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
"WATCHTOWER_SCOPE=${M4B_WATCHTOWER_SCOPE}",
163163
"WATCHTOWER_POLL_INTERVAL=14400",
164164
"WATCHTOWER_ROLLING_RESTART=true",
165-
"WATCHTOWER_NO_STARTUP_MESSAGE=true",
165+
"WATCHTOWER_NO_STARTUP_MESSAGE=false",
166166
"WATCHTOWER_CLEANUP=true",
167167
"WATCHTOWER_NOTIFICATION_URL=${WATCHTOWER_NOTIFICATION_URL}"
168168
],
@@ -185,7 +185,7 @@
185185
"WATCHTOWER_SCOPE=${M4B_WATCHTOWER_SCOPE}",
186186
"WATCHTOWER_POLL_INTERVAL=14400",
187187
"WATCHTOWER_ROLLING_RESTART=false",
188-
"WATCHTOWER_NO_STARTUP_MESSAGE=true",
188+
"WATCHTOWER_NO_STARTUP_MESSAGE=false",
189189
"WATCHTOWER_CLEANUP=true",
190190
"WATCHTOWER_NOTIFICATION_URL=${WATCHTOWER_NOTIFICATION_URL}"
191191
],

tests/test_fn_setupApps.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import os
2+
import tempfile
3+
import unittest
4+
5+
from utils.fn_setupApps import cleanup_multiproxy_instances_dir
6+
7+
8+
class TestCleanupMultiproxyInstancesDir(unittest.TestCase):
9+
def test_missing_instances_dir_is_noop(self):
10+
with tempfile.TemporaryDirectory() as tmp_dir:
11+
instances_dir = os.path.join(tmp_dir, "m4b_proxy_instances")
12+
backup_root = os.path.join(tmp_dir, "m4b_proxy_instances_backup")
13+
14+
cleanup_multiproxy_instances_dir(instances_dir, backup_root)
15+
16+
self.assertFalse(os.path.exists(instances_dir))
17+
self.assertFalse(os.path.exists(backup_root))
18+
19+
def test_empty_instances_dir_is_removed(self):
20+
with tempfile.TemporaryDirectory() as tmp_dir:
21+
instances_dir = os.path.join(tmp_dir, "m4b_proxy_instances")
22+
backup_root = os.path.join(tmp_dir, "m4b_proxy_instances_backup")
23+
os.makedirs(instances_dir, exist_ok=True)
24+
25+
cleanup_multiproxy_instances_dir(instances_dir, backup_root)
26+
27+
self.assertFalse(os.path.exists(instances_dir))
28+
self.assertFalse(os.path.exists(backup_root))
29+
30+
def test_non_empty_instances_dir_is_moved_to_backup(self):
31+
with tempfile.TemporaryDirectory() as tmp_dir:
32+
instances_dir = os.path.join(tmp_dir, "m4b_proxy_instances")
33+
backup_root = os.path.join(tmp_dir, "m4b_proxy_instances_backup")
34+
os.makedirs(instances_dir, exist_ok=True)
35+
36+
instance_subdir = os.path.join(instances_dir, "money4band_1234")
37+
os.makedirs(instance_subdir, exist_ok=True)
38+
marker_file = os.path.join(instance_subdir, "docker-compose.yaml")
39+
with open(marker_file, "w") as f:
40+
f.write("services: {}")
41+
42+
cleanup_multiproxy_instances_dir(instances_dir, backup_root)
43+
44+
self.assertFalse(os.path.exists(instances_dir))
45+
self.assertTrue(os.path.isdir(backup_root))
46+
47+
backup_instances_root = os.path.join(backup_root, "m4b_proxy_instances")
48+
self.assertTrue(os.path.isdir(backup_instances_root))
49+
50+
backups = [
51+
entry
52+
for entry in os.listdir(backup_instances_root)
53+
if os.path.isdir(os.path.join(backup_instances_root, entry))
54+
]
55+
self.assertEqual(len(backups), 1)
56+
57+
moved_file = os.path.join(
58+
backup_instances_root,
59+
backups[0],
60+
"money4band_1234",
61+
"docker-compose.yaml",
62+
)
63+
self.assertTrue(os.path.isfile(moved_file))
64+
65+
66+
if __name__ == "__main__":
67+
unittest.main()

utils/fn_setupApps.py

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,73 @@ def safe_rmtree(directory: str) -> bool:
100100
f"{Fore.RED}Permission denied when removing {directory}. Please ensure no files are in use and you have proper permissions.{Style.RESET_ALL}"
101101
)
102102
return False
103+
104+
105+
def cleanup_multiproxy_instances_dir(
106+
instances_dir: str = "m4b_proxy_instances",
107+
backup_root: str = ".backup",
108+
) -> None:
109+
"""
110+
Clean up multiproxy instances directory when multiproxy is disabled.
111+
112+
Behavior:
113+
- If directory does not exist: no-op
114+
- If directory is empty: remove it
115+
- If directory has content: move it to a timestamped backup folder
116+
117+
Args:
118+
instances_dir (str): Path to multiproxy instances directory.
119+
backup_root (str): Path to backup root directory.
120+
"""
121+
if not os.path.isdir(instances_dir):
122+
logging.info(
123+
"No multiproxy instances directory found. Nothing to clean up."
124+
)
125+
return
126+
127+
try:
128+
entries = os.listdir(instances_dir)
103129
except Exception as e:
104-
logging.error(f"Failed to remove directory {directory}: {str(e)}")
130+
logging.error(
131+
f"Unable to inspect multiproxy instances directory '{instances_dir}': {str(e)}"
132+
)
133+
return
134+
135+
if not entries:
136+
if safe_rmtree(instances_dir):
137+
print(
138+
f"{Fore.GREEN}Removed empty multiproxy instances directory '{instances_dir}'.{Style.RESET_ALL}"
139+
)
140+
logging.info(
141+
f"Removed empty multiproxy instances directory '{instances_dir}'."
142+
)
143+
return
144+
145+
timestamp = time.strftime("%Y%m%d_%H%M%S")
146+
backup_base_dir = os.path.join(backup_root, "m4b_proxy_instances")
147+
os.makedirs(backup_base_dir, exist_ok=True)
148+
backup_dir = os.path.join(backup_base_dir, f"instances_{timestamp}")
149+
150+
suffix = 1
151+
while os.path.exists(backup_dir):
152+
backup_dir = os.path.join(backup_base_dir, f"instances_{timestamp}_{suffix}")
153+
suffix += 1
154+
155+
try:
156+
shutil.move(instances_dir, backup_dir)
105157
print(
106-
f"{Fore.RED}Failed to remove directory {directory}: {str(e)}{Style.RESET_ALL}"
158+
f"{Fore.YELLOW}Multiproxy disabled: moved existing instances to '{backup_dir}'.{Style.RESET_ALL}"
159+
)
160+
logging.info(
161+
f"Multiproxy disabled. Moved '{instances_dir}' to backup '{backup_dir}'."
162+
)
163+
except Exception as e:
164+
logging.error(
165+
f"Failed to move multiproxy instances directory '{instances_dir}' to backup: {str(e)}"
166+
)
167+
print(
168+
f"{Fore.RED}Failed to back up multiproxy instances directory '{instances_dir}'.{Style.RESET_ALL}"
107169
)
108-
return False
109170

110171

111172
def ipv4_to_int(ip_address: str) -> int:
@@ -745,7 +806,7 @@ def setup_watchtower(user_config: dict[str, Any]) -> None:
745806
if ask_question_yn(
746807
"Do you want M4B to manage container auto-updates via its built-in Watchtower?\n"
747808
"(Disable only if another Watchtower instance is already running on this host)",
748-
default=current_enabled,
809+
default=True,
749810
):
750811
watchtower_config["enabled"] = True
751812
print("M4B built-in Watchtower enabled. Container images will be auto-updated.")
@@ -1074,6 +1135,7 @@ def main(app_config_path: str, m4b_config_path: str, user_config_path: str) -> N
10741135
user_config["proxies"]["url"] = ""
10751136
user_config["proxies"]["enabled"] = False
10761137
write_json(user_config, user_config_path)
1138+
cleanup_multiproxy_instances_dir()
10771139
assemble_docker_compose(
10781140
m4b_config_path,
10791141
app_config_path,

0 commit comments

Comments
 (0)