servctl 1.0.1__tar.gz → 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {servctl-1.0.1 → servctl-1.0.2}/PKG-INFO +1 -1
- {servctl-1.0.1 → servctl-1.0.2}/pyproject.toml +1 -1
- {servctl-1.0.1 → servctl-1.0.2}/src/servctl/main.py +130 -92
- {servctl-1.0.1 → servctl-1.0.2}/README.md +0 -0
- {servctl-1.0.1 → servctl-1.0.2}/man/servctl.1 +0 -0
- {servctl-1.0.1 → servctl-1.0.2}/src/servctl/__init__.py +0 -0
|
@@ -995,7 +995,7 @@ def run_worker(args):
|
|
|
995
995
|
server.serve_forever()
|
|
996
996
|
|
|
997
997
|
# ---------------------------------------------------------------------------
|
|
998
|
-
# CLI
|
|
998
|
+
# CLI Commands
|
|
999
999
|
# ---------------------------------------------------------------------------
|
|
1000
1000
|
|
|
1001
1001
|
def cmd_start(args):
|
|
@@ -1131,7 +1131,7 @@ def cmd_list(args):
|
|
|
1131
1131
|
print(sep)
|
|
1132
1132
|
print("Tip: Run 'servctl status show <ID>' to view full connection URLs and configuration.\n")
|
|
1133
1133
|
|
|
1134
|
-
def
|
|
1134
|
+
def cmd_status_show(args):
|
|
1135
1135
|
reg = clean_registry()
|
|
1136
1136
|
if not reg:
|
|
1137
1137
|
print("No servers running. Start one using: servctl start")
|
|
@@ -1139,97 +1139,120 @@ def cmd_status(args):
|
|
|
1139
1139
|
|
|
1140
1140
|
lan_ip = get_local_ip()
|
|
1141
1141
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
else:
|
|
1150
|
-
matched = False
|
|
1151
|
-
for sid, meta in reg.items():
|
|
1152
|
-
if sid == args.target or str(meta["port"]) == args.target:
|
|
1153
|
-
print(f"\nConfiguration & Runtime for '{sid}':")
|
|
1154
|
-
print_server_status_card(meta, lan_ip)
|
|
1155
|
-
print()
|
|
1156
|
-
matched = True
|
|
1157
|
-
break
|
|
1158
|
-
if not matched:
|
|
1159
|
-
print(f"Error: No running server matching '{args.target}'.")
|
|
1160
|
-
|
|
1161
|
-
# Subcommand: status change
|
|
1162
|
-
elif args.status_cmd == "change":
|
|
1163
|
-
target_id = None
|
|
1142
|
+
if getattr(args, "all", False) or not getattr(args, "target", None):
|
|
1143
|
+
print("\nDetailed Server Configurations & Status:")
|
|
1144
|
+
for sid, meta in reg.items():
|
|
1145
|
+
print_server_status_card(meta, lan_ip)
|
|
1146
|
+
print()
|
|
1147
|
+
else:
|
|
1148
|
+
matched = False
|
|
1164
1149
|
for sid, meta in reg.items():
|
|
1165
1150
|
if sid == args.target or str(meta["port"]) == args.target:
|
|
1166
|
-
|
|
1151
|
+
print(f"\nConfiguration & Runtime for '{sid}':")
|
|
1152
|
+
print_server_status_card(meta, lan_ip)
|
|
1153
|
+
print()
|
|
1154
|
+
matched = True
|
|
1167
1155
|
break
|
|
1168
|
-
|
|
1169
|
-
if not target_id:
|
|
1156
|
+
if not matched:
|
|
1170
1157
|
print(f"Error: No running server matching '{args.target}'.")
|
|
1171
|
-
return
|
|
1172
1158
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
# Check if anything changed
|
|
1180
|
-
if (new_port == old_meta["port"] and
|
|
1181
|
-
new_access == old_meta["access"] and
|
|
1182
|
-
new_root == old_meta["root"] and
|
|
1183
|
-
new_target == old_meta.get("target") and
|
|
1184
|
-
not args.name):
|
|
1185
|
-
print("Notice: No property changes specified. Use -p, -r, -a, or --target to modify properties.")
|
|
1186
|
-
return
|
|
1159
|
+
def cmd_status_change(args):
|
|
1160
|
+
reg = clean_registry()
|
|
1161
|
+
if not reg:
|
|
1162
|
+
print("No servers running. Start one using: servctl start")
|
|
1163
|
+
return
|
|
1187
1164
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
return
|
|
1165
|
+
target_id = None
|
|
1166
|
+
for sid, meta in reg.items():
|
|
1167
|
+
if sid == args.target or str(meta["port"]) == args.target:
|
|
1168
|
+
target_id = sid
|
|
1169
|
+
break
|
|
1194
1170
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
return
|
|
1171
|
+
if not target_id:
|
|
1172
|
+
print(f"Error: No running server matching '{args.target}'.")
|
|
1173
|
+
return
|
|
1199
1174
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1175
|
+
old_meta = reg[target_id]
|
|
1176
|
+
|
|
1177
|
+
# Resolve optional positional extra (could be port or root directory)
|
|
1178
|
+
parsed_port = args.port
|
|
1179
|
+
parsed_root = args.root
|
|
1180
|
+
if getattr(args, "extra", None):
|
|
1181
|
+
if args.extra.isdigit() and not parsed_port:
|
|
1182
|
+
parsed_port = int(args.extra)
|
|
1183
|
+
elif not parsed_root:
|
|
1184
|
+
parsed_root = args.extra
|
|
1185
|
+
|
|
1186
|
+
# Target new properties
|
|
1187
|
+
new_type = args.type if getattr(args, "type", None) else old_meta["type"]
|
|
1188
|
+
if new_type in ("websecure", "https"):
|
|
1189
|
+
new_type = "websecure"
|
|
1190
|
+
|
|
1191
|
+
new_port = parsed_port if parsed_port is not None else old_meta["port"]
|
|
1192
|
+
new_access = args.access if args.access is not None else old_meta["access"]
|
|
1193
|
+
new_root = str(Path(parsed_root).expanduser().resolve()) if parsed_root is not None else old_meta["root"]
|
|
1194
|
+
new_target = args.target_url if getattr(args, "target_url", None) is not None else old_meta.get("target")
|
|
1195
|
+
|
|
1196
|
+
# Check if anything changed
|
|
1197
|
+
if (new_port == old_meta["port"] and
|
|
1198
|
+
new_access == old_meta["access"] and
|
|
1199
|
+
new_root == old_meta["root"] and
|
|
1200
|
+
new_type == old_meta["type"] and
|
|
1201
|
+
new_target == old_meta.get("target") and
|
|
1202
|
+
not getattr(args, "name", None)):
|
|
1203
|
+
print("Notice: No property changes specified. Use -p, -r, -t, -a, or --target to modify properties.")
|
|
1204
|
+
return
|
|
1207
1205
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1206
|
+
# Check directory
|
|
1207
|
+
if new_type in ("web", "websecure", "ftp", "ftps", "webdav") and not Path(new_root).is_dir():
|
|
1208
|
+
print(f"Error: Directory '{new_root}' does not exist.")
|
|
1209
|
+
return
|
|
1210
1210
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1211
|
+
# Check port conflicts
|
|
1212
|
+
new_host = "0.0.0.0" if new_access == "network" else "127.0.0.1"
|
|
1213
|
+
is_udp = (new_type == "dns")
|
|
1214
|
+
if new_port != old_meta["port"] and is_port_in_use(new_port, new_host, udp=is_udp):
|
|
1215
|
+
print(f"Error: Port {new_port} ({'UDP' if is_udp else 'TCP'}) is already in use on {new_host}.")
|
|
1216
|
+
return
|
|
1215
1217
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1218
|
+
# Check privilege
|
|
1219
|
+
if new_port < 1024 and os.name != "nt" and os.geteuid() != 0:
|
|
1220
|
+
print(f"Error: Port {new_port} is a privileged port (< 1024) requiring root/sudo.")
|
|
1221
|
+
return
|
|
1222
|
+
|
|
1223
|
+
# Determine new ID
|
|
1224
|
+
if getattr(args, "name", None):
|
|
1225
|
+
new_id = args.name
|
|
1226
|
+
elif old_meta["id"] == f"{old_meta['type']}-{old_meta['port']}":
|
|
1227
|
+
new_id = f"{new_type}-{new_port}"
|
|
1228
|
+
else:
|
|
1229
|
+
new_id = old_meta["id"]
|
|
1230
|
+
|
|
1231
|
+
print(f"Applying property changes to '{old_meta['id']}'...")
|
|
1232
|
+
stop_server_by_id(target_id, reg)
|
|
1233
|
+
|
|
1234
|
+
pid, log_path = spawn_server(new_id, new_type, new_port, new_host, new_root, new_target)
|
|
1235
|
+
if not pid:
|
|
1236
|
+
print(f"Error: Failed to re-launch server with new settings. Check log ({log_path}).")
|
|
1237
|
+
return
|
|
1238
|
+
|
|
1239
|
+
reg[new_id] = {
|
|
1240
|
+
"id": new_id,
|
|
1241
|
+
"type": new_type,
|
|
1242
|
+
"port": new_port,
|
|
1243
|
+
"host": new_host,
|
|
1244
|
+
"access": new_access,
|
|
1245
|
+
"root": new_root,
|
|
1246
|
+
"target": new_target,
|
|
1247
|
+
"pid": pid,
|
|
1248
|
+
"started_at": time.time(),
|
|
1249
|
+
"log_file": str(log_path)
|
|
1250
|
+
}
|
|
1251
|
+
write_registry(reg)
|
|
1252
|
+
|
|
1253
|
+
print(f"\n✔ Successfully updated '{old_meta['id']}' -> '{new_id}'!")
|
|
1254
|
+
print_server_status_card(reg[new_id])
|
|
1255
|
+
print()
|
|
1233
1256
|
|
|
1234
1257
|
def cmd_logs(args):
|
|
1235
1258
|
reg = read_registry()
|
|
@@ -1272,7 +1295,7 @@ SYNOPSIS
|
|
|
1272
1295
|
servctl stop [PORT | ID ...] [--all]
|
|
1273
1296
|
servctl list
|
|
1274
1297
|
servctl status show [PORT | ID | --all]
|
|
1275
|
-
servctl status change <PORT | ID> [-p PORT] [-r ROOT] [-a ACCESS] [--target TARGET] [-n NAME]
|
|
1298
|
+
servctl status change <PORT | ID> [-p PORT] [-r ROOT] [-t TYPE] [-a ACCESS] [--target TARGET] [-n NAME]
|
|
1276
1299
|
servctl logs [PORT | ID] [-f] [-n LINES]
|
|
1277
1300
|
servctl man
|
|
1278
1301
|
|
|
@@ -1325,8 +1348,8 @@ STATUS & CONFIGURATION MANAGEMENT
|
|
|
1325
1348
|
Displays detailed status cards for all active servers.
|
|
1326
1349
|
|
|
1327
1350
|
servctl status change <ID/PORT> [OPTIONS...]
|
|
1328
|
-
Dynamically changes running server properties (port, root,
|
|
1329
|
-
target) and restarts it seamlessly in the background.
|
|
1351
|
+
Dynamically changes running server properties (port, root, type,
|
|
1352
|
+
access, target) and restarts it seamlessly in the background.
|
|
1330
1353
|
|
|
1331
1354
|
EXAMPLES
|
|
1332
1355
|
1. Start a default HTTP web server:
|
|
@@ -1341,6 +1364,7 @@ EXAMPLES
|
|
|
1341
1364
|
|
|
1342
1365
|
4. Change a running server's properties on the fly:
|
|
1343
1366
|
$ servctl status change web-8080 -p 8085 -a network
|
|
1367
|
+
$ servctl status change web-8080 -t websecure
|
|
1344
1368
|
|
|
1345
1369
|
5. Stop multiple servers at once:
|
|
1346
1370
|
$ servctl stop 8080 8443
|
|
@@ -1368,6 +1392,16 @@ def normalize_status_argv(argv):
|
|
|
1368
1392
|
else:
|
|
1369
1393
|
return argv[:idx + 1] + ["show"] + args_after
|
|
1370
1394
|
|
|
1395
|
+
def setup_change_parser(p):
|
|
1396
|
+
p.add_argument("target", help="Server ID or Port to modify")
|
|
1397
|
+
p.add_argument("extra", nargs="?", default=None, help="Optional new port or root directory")
|
|
1398
|
+
p.add_argument("-p", "--port", type=int, default=None, help="New port number")
|
|
1399
|
+
p.add_argument("-r", "--root", default=None, help="New root directory")
|
|
1400
|
+
p.add_argument("-t", "--type", choices=["web", "websecure", "https", "webdav", "ftp", "ftps", "dns", "proxy"], default=None, help="Change server type")
|
|
1401
|
+
p.add_argument("-a", "--access", choices=["local", "network"], default=None, help="New access scope")
|
|
1402
|
+
p.add_argument("--target", dest="target_url", default=None, help="New upstream target URL")
|
|
1403
|
+
p.add_argument("-n", "--name", default=None, help="Rename server ID")
|
|
1404
|
+
|
|
1371
1405
|
def main():
|
|
1372
1406
|
parser = argparse.ArgumentParser(prog="servctl", description="Unprivileged background server and proxy manager")
|
|
1373
1407
|
subparsers = parser.add_subparsers(dest="command")
|
|
@@ -1406,12 +1440,11 @@ def main():
|
|
|
1406
1440
|
|
|
1407
1441
|
# status change
|
|
1408
1442
|
p_change = sub_status.add_parser("change", help="Change properties of a running server")
|
|
1409
|
-
p_change
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
p_change.add_argument("-n", "--name", default=None, help="Rename server ID")
|
|
1443
|
+
setup_change_parser(p_change)
|
|
1444
|
+
|
|
1445
|
+
# Top-level direct 'servctl change'
|
|
1446
|
+
p_top_change = subparsers.add_parser("change", help="Change properties of a running server")
|
|
1447
|
+
setup_change_parser(p_top_change)
|
|
1415
1448
|
|
|
1416
1449
|
# LOGS
|
|
1417
1450
|
p_logs = subparsers.add_parser("logs", help="View server logs")
|
|
@@ -1444,7 +1477,12 @@ def main():
|
|
|
1444
1477
|
elif args.command == "list":
|
|
1445
1478
|
cmd_list(args)
|
|
1446
1479
|
elif args.command == "status":
|
|
1447
|
-
|
|
1480
|
+
if getattr(args, "status_cmd", None) == "change":
|
|
1481
|
+
cmd_status_change(args)
|
|
1482
|
+
else:
|
|
1483
|
+
cmd_status_show(args)
|
|
1484
|
+
elif args.command == "change":
|
|
1485
|
+
cmd_status_change(args)
|
|
1448
1486
|
elif args.command == "logs":
|
|
1449
1487
|
cmd_logs(args)
|
|
1450
1488
|
elif args.command == "man":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|