-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.py
More file actions
executable file
·158 lines (136 loc) · 7.09 KB
/
Copy pathinstall.py
File metadata and controls
executable file
·158 lines (136 loc) · 7.09 KB
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
#! /usr/bin/env python3
#
# Install a service for acting as a RUDICS connection to an SFMC server
#
# Jan-2023, Pat Welch, pat@mousebrains.com
from argparse import ArgumentParser, Namespace
import getpass
import subprocess
from tempfile import NamedTemporaryFile
import os
import time
def barebones(content: str) -> list[str]:
lines = []
for line in content.split("\n"):
line = line.strip()
if not line or line[0] == "#":
continue
lines.append(line)
return lines
def substitute_template(content: str, args: Namespace, root: str) -> str:
"""Replace @MARKER@ placeholders in a service template with args values."""
content = content.replace("@DATE@", "Generated on " + time.asctime())
content = content.replace("@GENERATED@", str(args))
content = content.replace("@USERNAME@", args.username)
content = content.replace("@GROUPNAME@", args.group)
content = content.replace("@DIRECTORY@", args.directory)
content = content.replace("@EXECUTABLE@", os.path.join(root, args.executable))
content = content.replace("@HOSTNAME@", args.hostname)
content = content.replace("@PORT@", str(args.port))
content = content.replace("@BAUDRATE@", str(args.baudrate))
content = content.replace("@TIMEOUT@", str(args.timeout))
content = content.replace("@RESTARTSECONDS@", str(args.restartSeconds))
content = content.replace("@MEMORYMAX@", args.memoryMax)
return content
def validate_args(args: Namespace) -> None:
"""Validate arguments, raising SystemExit on invalid values."""
if not 1 <= args.port <= 65535:
raise SystemExit(f"--port must be 1-65535, got {args.port}")
if args.timeout < 1:
raise SystemExit(f"--timeout must be positive, got {args.timeout}")
if args.restartSeconds < 1:
raise SystemExit(f"--restartSeconds must be positive, got {args.restartSeconds}")
if not args.memoryMax:
raise SystemExit("--memoryMax must be a non-empty systemd memory value (e.g. 128M)")
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("service", type=str, nargs="*", help="Service file(s) to copy")
parser.add_argument("--serviceDirectory", type=str, default="/etc/systemd/system",
help="Where to copy service file to")
parser.add_argument("--device", type=str, action="append", help="Explicit devices to enable, ttyUSB0...")
grp = parser.add_argument_group(description="Service file translation related options")
grp.add_argument("--hostname", type=str, default="gliderfmc1.ceoas.oregonstate.edu",
help="Remote hostname")
grp.add_argument("--port", type=int, default=6565, help="Port number on remote host")
grp.add_argument("--username", type=str, help="User to execute service as")
grp.add_argument("--group", type=str, default="dialout", help="Group to execute service as")
grp.add_argument("--baudrate", type=int, default=115200, help="Baud rate for serial connection")
grp.add_argument("--timeout", type=int, default=3600,
help="Seconds for connection to timeout with no activity")
grp.add_argument("--directory", type=str, help="Directory to change to for running the service")
grp.add_argument("--restartSeconds", type=int, default=60,
help="Time before restarting the service after the previous instance exits")
grp.add_argument("--memoryMax", type=str, default="128M",
help="systemd MemoryMax= value per service (default 128M; lower if running many ports on tight RAM)")
grp.add_argument("--executable", type=str, default="serial2RUDICS.py",
help="Executable name to be executed by service")
parser.add_argument("--force", action="store_true", help="Force writing a new file")
parser.add_argument("--systemctl", type=str, default="/bin/systemctl",
help="systemctl executable")
parser.add_argument("--mkdir", type=str, default="/bin/mkdir", help="mkdir executable")
parser.add_argument("--cp", type=str, default="/bin/cp", help="cp executable")
parser.add_argument("--chmod", type=str, default="/bin/chmod", help="chmod executable")
parser.add_argument("--sudo", type=str, default="/usr/bin/sudo", help="sudo executable")
args = parser.parse_args()
if not args.service:
args.service.append("USBToRUDICS@.service")
if not args.device:
args.device = [f"ttyUSB{x}" for x in range(10)]
if args.username is None:
args.username = getpass.getuser()
if args.directory is None:
args.directory = os.path.expanduser(f"~{args.username}/logs")
validate_args(args)
args.directory = os.path.abspath(os.path.expanduser(args.directory))
args.serviceDirectory = os.path.abspath(os.path.expanduser(args.serviceDirectory))
root = os.path.dirname(os.path.abspath(__file__)) # Where the script is at
if not os.path.isdir(args.directory):
print("Creating working directory", args.directory)
os.makedirs(args.directory, exist_ok=True)
qDidSomething = False
for service in args.service: # Walk through services to copy over
target = os.path.join(args.serviceDirectory, service)
if not os.path.isabs(service):
service = os.path.join(root, service)
service = os.path.abspath(os.path.expanduser(service))
if not os.path.isfile(service):
print(f"ERROR {service} does not exist")
continue
with open(service) as fp:
content = fp.read() # Load the new service
content = substitute_template(content, args, root)
if not args.force and os.path.exists(target):
try:
with open(target) as fp:
current = barebones(fp.read()) # Current contents
proposed = barebones(content) # What we want to write
if current == proposed:
print("No need to update, identical")
continue
except Exception:
pass
if not os.path.isdir(os.path.dirname(target)):
wd = os.path.dirname(target)
print("Making", wd)
subprocess.run((args.sudo, args.mkdir, "-p", wd), shell=False, check=True)
# Write to a temporary file, then copy as root via sudo
with NamedTemporaryFile(mode="w") as tfp:
tfp.write(content)
tfp.flush()
print("Writing to", target)
subprocess.run((args.sudo, args.cp, tfp.name, target), shell=False, check=True)
subprocess.run((args.sudo, args.chmod, "0644", target), shell=False, check=True)
qDidSomething = True
if qDidSomething:
print("Forcing reload of daemon")
subprocess.run((args.sudo, args.systemctl, "daemon-reload"), shell=False, check=True)
if args.device:
devices = [
service.replace("@", "@" + device, 1)
for service in args.service
for device in args.device
]
cmd = [args.sudo, args.systemctl, "enable"]
cmd.extend(devices)
print("Enabling", " ".join(devices))
subprocess.run(cmd, shell=False, check=True)