-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdocker_zerotier.py
252 lines (201 loc) · 10.3 KB
/
docker_zerotier.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import os
import subprocess
import argparse
import json
import sys
# ANSI escape sequences for cool output
GREEN = "\033[92m"
RED = "\033[91m"
BLUE = "\033[94m"
YELLOW = "\033[93m"
ENDC = "\033[0m"
DEFAULT_ZT_PORT = 9993 # ZeroTier default port for binding
def print_step(step_name, step_desc):
print(f"{BLUE}[{step_name}]{ENDC} {step_desc}")
def print_success(msg):
print(f"{GREEN}[SUCCESS]{ENDC} {msg}")
def print_error(msg):
print(f"{RED}[ERROR]{ENDC} {msg}")
def print_info(msg):
print(f"{YELLOW}[INFO]{ENDC} {msg}")
def run_command(command, dry_run=False, capture_output=False):
"""Executes the given command and returns output."""
if dry_run:
print_info(f"Dry-run command: {command}")
return ""
else:
try:
if capture_output:
result = subprocess.run(command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return result.stdout.strip()
else:
subprocess.run(command, shell=True, check=True)
print_success(f"Executed: {command}")
return ""
except subprocess.CalledProcessError as e:
print_error(f"Failed to execute: {command}. Error: {e}")
return None # Return None to allow further attempts
def check_zt_port_in_use():
"""Check if ZeroTier is listening on the default port (9993)."""
print_info(f"Checking if port {DEFAULT_ZT_PORT} is in use...")
output = run_command(f"sudo lsof -i :{DEFAULT_ZT_PORT}", capture_output=True)
if output:
print_success(f"Port {DEFAULT_ZT_PORT} is in use, indicating ZeroTier might be running.")
return True
else:
print_error(f"Port {DEFAULT_ZT_PORT} is not in use.")
return False
def check_zt_running_on_host():
"""Check if ZeroTier is running on the host system using systemctl."""
print_info("Checking if ZeroTier is running on the host...")
zt_host_status = run_command("systemctl is-active zerotier-one", capture_output=True)
if zt_host_status == "active":
print_success("ZeroTier is running on the host.")
return True
else:
print_error("ZeroTier is not running on the host.")
return False
def check_zt_cli_installed():
"""Check if zerotier-cli is installed and accessible on the host."""
print_info("Checking if zerotier-cli is installed on the host...")
cli_check = run_command("command -v zerotier-cli", capture_output=True)
if cli_check:
print_success("zerotier-cli is installed.")
return True
else:
print_error("zerotier-cli is not installed or not in the system PATH.")
return False
def find_zerotier_container():
"""Dynamically find the ZeroTier container by inspecting Docker containers."""
print_info("Looking for a ZeroTier container...")
# Step 1: List all running containers
docker_ps_output = run_command("docker ps --format '{{.ID}} {{.Image}} {{.Names}}'", capture_output=True)
if not docker_ps_output:
print_error("No Docker containers are currently running.")
return None
containers = docker_ps_output.splitlines()
# Step 2: Look for containers using the ZeroTier image or running on port 9993
for container in containers:
container_id, image_name, container_name = container.split()
# Step 3: Filter containers with "zerotier" in the image name for early detection
if "zerotier" in image_name.lower():
print_info(f"Container '{container_name}' appears to be a ZeroTier container (image: {image_name}).")
return container_name
# Step 4: If no match by image name, check if port 9993 is open in the container
print_info(f"Checking container {container_name} for ZeroTier service on port {DEFAULT_ZT_PORT}...")
port_check = run_command(f"docker exec {container_name} lsof -i :{DEFAULT_ZT_PORT}", capture_output=True)
if port_check:
print_success(f"Found ZeroTier container: {container_name} (ID: {container_id})")
return container_name
else:
# Advanced: Check running processes for 'zerotier-one'
print_info(f"Checking running processes in {container_name} for 'zerotier-one'...")
process_check = run_command(f"docker exec {container_name} ps aux | grep zerotier-one", capture_output=True)
# Ensure process_check is not None before iterating
if process_check and "zerotier-one" in process_check:
print_success(f"Found ZeroTier container: {container_name} (running 'zerotier-one' process)")
return container_name
else:
print_error(f"ZeroTier service not found in container '{container_name}'. Skipping this container.")
print_error("No ZeroTier container found. Ensure the ZeroTier container is running and exposing port 9993.")
return None
def detect_zt_environment(zt_network_id):
"""Determine if ZeroTier is running on the host or inside Docker, based on the port usage and CLI availability."""
# Step 1: Check if the ZeroTier port is in use
if check_zt_port_in_use():
# Step 2a: Check if ZeroTier is running on the host and if zerotier-cli is available
if check_zt_running_on_host() and check_zt_cli_installed():
return "host"
# Step 2b: Find the ZeroTier container dynamically
container_name = find_zerotier_container()
if container_name:
return container_name
# If neither host nor Docker has ZeroTier properly set up
print_error("ZeroTier is not fully configured on the host or Docker, even though the port is in use.")
sys.exit(1)
# If the ZeroTier port is not in use, nothing is running
print_error("ZeroTier does not seem to be running on this system (host or Docker).")
sys.exit(1)
def zt_exec(command, env, dry_run=False, capture_output=False):
"""Executes ZeroTier commands either on the host or in a Docker container based on the environment."""
if env != "host":
full_command = f"docker exec {env} zerotier-cli {command}"
else:
full_command = f"zerotier-cli {command}"
return run_command(full_command, dry_run=dry_run, capture_output=capture_output)
def verify_zt_network_membership(zt_network_id, env):
"""Check if the current node is already part of the ZeroTier network."""
print_info(f"Verifying if the node is already joined to the network {zt_network_id}...")
zt_status_json = zt_exec("listnetworks -j", env, capture_output=True)
if zt_status_json:
zt_status = json.loads(zt_status_json)
for network in zt_status:
if zt_network_id == network['nwid'] and network['status'] == "OK":
print_success(f"Node is already a member of the network {zt_network_id}.")
return True
print_info(f"Node is not a member of the network {zt_network_id}, joining now.")
return False
def check_zt_network_id():
"""Ensure ZeroTier network ID is provided by user input."""
zt_network_id = input(f"{YELLOW}[INPUT REQUIRED]{ENDC} Please enter the ZeroTier network ID to join: ")
if not zt_network_id:
print_error("ZeroTier network ID is required to proceed!")
sys.exit(1)
return zt_network_id
def enable_ip_forwarding(dry_run):
"""Enables IP forwarding on the host."""
print_step("1", "Enabling IP forwarding...")
run_command("sudo sysctl -w net.ipv4.ip_forward=1", dry_run)
run_command("sudo bash -c 'echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf'", dry_run)
run_command("sudo sysctl -p", dry_run)
def join_zt_network(zt_network_id, env, dry_run):
"""Join the ZeroTier network if the node is not already a member."""
if not verify_zt_network_membership(zt_network_id, env):
print_step("3", f"Joining ZeroTier network {zt_network_id}...")
join_cmd = f"join {zt_network_id}"
zt_exec(join_cmd, env, dry_run)
def detect_docker_networks():
"""Detect Docker networks dynamically."""
print_info("Detecting Docker networks...")
docker_networks = run_command("docker network inspect $(docker network ls -q)", capture_output=True)
# Parse Docker networks as JSON
networks = json.loads(docker_networks)
docker_network_ranges = []
for net in networks:
# Skip Docker default networks like 'host' and 'none'
if net['Name'] in ['host', 'none']:
print_info(f"Skipping Docker network '{net['Name']}' as it has no valid IPAM configuration.")
continue
# Safely check if 'IPAM' and 'Config' are present and not None
ipam_config = net.get("IPAM", {}).get("Config", [])
if ipam_config and isinstance(ipam_config, list) and len(ipam_config) > 0:
subnet = ipam_config[0].get("Subnet")
if subnet:
docker_network_ranges.append(subnet)
print_success(f"Detected Docker network: {subnet}")
else:
print_error(f"Network '{net.get('Name', 'Unnamed')}' has no valid IPAM configuration.")
if not docker_network_ranges:
print_error("No valid Docker networks found! Ensure Docker is running and containers are connected to networks.")
sys.exit(1)
return docker_network_ranges
def main():
parser = argparse.ArgumentParser(description="Automate Docker-ZeroTier configuration.")
parser.add_argument('--dry-run', action='store_true', help="Show commands without executing them")
args = parser.parse_args()
dry_run = args.dry_run
print(f"{GREEN}==== Docker-ZeroTier Configuration Automation ===={ENDC}")
# Step 1: Get ZeroTier network ID from user input
zt_network_id = check_zt_network_id()
# Step 2: Detect if ZeroTier is running on the host or in Docker
env = detect_zt_environment(zt_network_id)
# Step 3: Enable IP forwarding (only on the host)
if env == "host":
enable_ip_forwarding(dry_run)
# Step 4: Join the ZeroTier network (if not already joined)
join_zt_network(zt_network_id, env, dry_run)
# Step 5: Detect Docker Networks (if needed for routing)
detect_docker_networks()
print_success("All tasks completed successfully! Your Docker containers or host are now accessible over the ZeroTier network.")
if __name__ == '__main__':
main()