Compare commits

..

16 Commits

Author SHA1 Message Date
jaydee 5adb2c7c2e build 2026-03-29 20:18:13 +02:00
jaydee 8067ac8561 build 2026-03-28 12:33:16 +01:00
jaydee 79e9e708b4 build 2026-03-28 12:33:00 +01:00
ladislav.dusa 64c3615705 build 2026-03-25 14:56:28 +01:00
jaydee f5c883964a build 2026-03-24 23:19:58 +01:00
jaydee bc984f05d2 build 2026-03-24 12:26:15 +01:00
jaydee a9a4de2038 build 2026-03-24 12:16:29 +01:00
jaydee 9205e0c8f7 build 2026-03-23 16:56:46 +01:00
jaydee 062176a875 build 2026-03-22 15:28:56 +01:00
jaydee db7005b304 build 2026-03-21 22:46:02 +01:00
jaydee 0e8fcaa530 build 2026-03-21 21:53:36 +01:00
jaydee df151c4c7f build 2026-03-21 21:24:03 +01:00
jaydee eba757bf25 build 2026-03-21 21:19:48 +01:00
jaydee 1e1f82e658 build 2026-03-21 21:07:18 +01:00
jaydee 5e36820f88 build 2026-03-21 19:09:41 +01:00
jaydee 546f695a0d build 2026-03-21 11:33:26 +01:00
9 changed files with 109 additions and 40 deletions
Binary file not shown.
+81 -31
View File
@@ -16,6 +16,8 @@ import time
import base64
import shutil
import requests
import tempfile
import subprocess
from portainer.api import PortainerApi
from git import Repo
from concurrent.futures import ThreadPoolExecutor
@@ -46,7 +48,7 @@ def setup_vault():
# Specify the mount point of your KV engine
return vclient
VERSION = "0.1.75"
VERSION = "0.1.77"
defaults = {
@@ -140,7 +142,7 @@ parser.add_argument(
default=None,
help="Service ID to limit service operations",
)
parser.add_argument("--stack", "-s", type=str, default=None, nargs="+", help="Stack ID for operations")
parser.add_argument("--stack", "-s", type=str, default=None, help="Stack ID for operations")
parser.add_argument("--action", "-a", type=str, default=None, help="Action to perform")
parser.add_argument(
"--autostart", "-Z", action="store_true", help="Auto-start created stacks"
@@ -152,6 +154,8 @@ parser.add_argument("--gpu", "-g", action="store_true")
parser.add_argument("--timeout", type=int, default=10, help="Request timeout seconds")
parser.add_argument("--deploy-mode", "-m", type=str, default="git", help="Deploy mode")
parser.add_argument("--stack-mode", "-w", default=None, help="Stack mode")
parser.add_argument("--print-command", "-P", action="store_true", help="Print quick command from action")
parser.add_argument(
"-E", "--excluded",
nargs="+",
@@ -222,6 +226,41 @@ def wl(msg):
if args.debug:
print(msg)
def run(cmd, cwd=None):
result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(result.stderr)
return result.stdout.strip()
def get_compose_files():
#git clone --depth=1 --filter=blob:none --no-checkout https://github.com/user/repo.git
with tempfile.TemporaryDirectory() as tmpdir:
repo_path = os.path.join(tmpdir, "repo")
# Clone with minimal data (no checkout, no blobs)
run([
"git", "clone",
"--depth=1",
"--filter=blob:none",
"--no-checkout",
"git@gitlab.sectorq.eu:/home/docker-compose.git",
repo_path
])
# List files in HEAD
output = run([
"git", "ls-tree",
"-r",
"HEAD",
"--name-only"
], cwd=repo_path)
folders = []
for line in output.splitlines():
if "/" in line and line.split("/")[0] != "__swarm":
folders.append(line.split("/")[0])
return list(dict.fromkeys(folders))
def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
"""
@@ -257,13 +296,14 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
elif field == "stack":
if args.action == "create_stack":
# input(json.dumps(stacks, indent=2))
commands = [
'api_server', 'authentik', 'bitwarden', 'bookstack', 'databasus', 'dockermon', 'duplicati', 'fail2ban', 'filebrowser', 'gitea', 'gitlab', 'grafana', 'grocy',
'hashicorp', 'home-assistant', 'homebox','homepage', 'immich', 'influxdb', 'jupyter', 'kestra', 'kopia', 'linkding', 'linkwarden', 'mailu3',
'mealie', 'mediacenter', 'mosquitto', 'motioneye', 'n8n', 'nebula', 'nextcloud', 'nginx',
'node-red', 'octoprint', 'ollama', 'onlyoffice', 'paperless-ngx', 'pihole', 'portainer-ce','portainerce', 'rancher', 'registry',
'regsync', 'repo_mirror', 'searxng','semaphore', 'unifibrowser', 'uptime-kuma', 'watchtower', 'wazuh', 'webhub', 'wordpress',
'wud', 'zabbix-server']
commands = get_compose_files()
# commands = [
# 'api_server', 'authentik', 'bitwarden', 'bookstack', 'databasus', 'dockermon', 'duplicati', 'fail2ban', 'filebrowser', 'gitea', 'gitlab', 'grafana', 'grocy',
# 'hashicorp', 'home-assistant', 'homebox','homepage', 'immich', 'influxdb', 'jupyter', 'kestra', 'kopia', 'linkding', 'linkwarden', 'mailu3',
# 'mealie', 'mediacenter', 'mosquitto', 'motioneye', 'n8n', 'nebula', 'nextcloud', 'nginx',
# 'node-red', 'octoprint', 'ollama', 'onlyoffice', 'paperless-ngx', 'pihole', 'portainer-ce','portainerce', 'puppet', 'puppet-agent', 'rancher', 'registry',
# 'regsync', 'repo_mirror', 'searxng','semaphore', 'unifibrowser', 'uptime-kuma', 'watchtower', 'wazuh', 'webhub', 'wordpress',
# 'wud', 'zabbix-server']
try:
print(por.all_data['stacks'][defaults_in[f"PORTAINER_ENDPOINT_ID".upper()]]['by_name'].keys())
for s in por.all_data['stacks'][defaults_in[f"PORTAINER_ENDPOINT_ID".upper()]]['by_name'].keys():
@@ -322,7 +362,7 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
value_in.sort()
if "pihole" in value_in:
if args.action == "delete_stack":
if args.action in ["delete_stack","stop_stack"]:
value_in.remove("pihole")
value_in.append("pihole")
else:
@@ -469,7 +509,6 @@ if __name__ == "__main__":
for key, value in secrets.items():
res = por.create_secret(key, value, args.endpoint_id, args.timeout)
print(res)
sys.exit()
if args.action == "delete_stack":
args = prompt_missing_args(
@@ -490,7 +529,6 @@ if __name__ == "__main__":
args.endpoint_id,
args.stack,
)
sys.exit()
if args.action == "create_stack":
por.action = "create_stack"
@@ -515,7 +553,7 @@ if __name__ == "__main__":
args.autostart,
args.stack_mode,
)
sys.exit()
if args.action == "stop_stack":
args = prompt_missing_args(
@@ -529,7 +567,6 @@ if __name__ == "__main__":
)
por.stop_stack(args.stack, args.endpoint_id)
sys.exit()
if args.action == "start_stack":
args = prompt_missing_args(
@@ -542,7 +579,7 @@ if __name__ == "__main__":
],
)
por.start_stack(args.stack, args.endpoint_id)
sys.exit()
if args.action == "restart_service":
args = prompt_missing_args(
@@ -554,7 +591,7 @@ if __name__ == "__main__":
],
)
por.restart_service(args.endpoint_id, "lala")
sys.exit()
if args.action == "update_service":
args = prompt_missing_args(
@@ -568,7 +605,7 @@ if __name__ == "__main__":
por.update_service()
if args.launcher:
input("\nPress ENTER to continue...")
sys.exit()
if args.action == "update_containers":
@@ -583,7 +620,6 @@ if __name__ == "__main__":
],
)
por.update_containers()
sys.exit()
if args.action == "list_stacks":
args = prompt_missing_args(
@@ -598,21 +634,19 @@ if __name__ == "__main__":
if args.launcher:
input("Press ENTER to continue...")
# print(json.dumps(por.all_data, indent=2))
sys.exit()
if args.action == "list_all_stacks":
por.get_stacks_failed()
if args.launcher:
input("Press ENTER to continue...")
# print(json.dumps(por.all_data, indent=2))
sys.exit()
if args.action == "delete_ophaned_stacks":
por.delete_failed_stack()
if args.launcher:
input("Press ENTER to continue...")
# print(json.dumps(por.all_data, indent=2))
sys.exit()
if args.action == "list_containers":
@@ -628,7 +662,6 @@ if __name__ == "__main__":
print("\n".join(por.get_containers()))
if args.launcher:
input("\nPress ENTER to continue...")
sys.exit()
if args.action == "update_stack":
args = prompt_missing_args(
@@ -643,17 +676,16 @@ if __name__ == "__main__":
por.update_stack(args)
if args.launcher:
input("\nPress ENTER to continue...")
sys.exit()
if args.action == "print_all_data":
print(json.dumps(por.all_data, indent=2))
if args.launcher:
input("\nPress ENTER to continue...")
sys.exit()
if args.action == "update_status":
por.update_status(args.endpoint_id, args.stack)
sys.exit()
if args.action == "list_endpoints":
eps = por.get_endpoints(args)
@@ -664,7 +696,7 @@ if __name__ == "__main__":
print(tabulate(export_data, headers=headers, tablefmt="github"))
if args.launcher:
input("\nPress ENTER to continue...")
sys.exit()
if args.action == "stop_containers":
# TODO: does not work
@@ -678,14 +710,14 @@ if __name__ == "__main__":
)
if por.all_data["endpoints_status"][args.endpoint_id] != 1:
print(f"Endpoint {por.get_endpoint_name(args.endpoint_id)} is offline")
sys.exit()
print(f"Stopping containers on {por.get_endpoint_name(args.endpoint_id)}")
cont = []
for c in por.all_data["containers"][args.endpoint_id]:
if args.stack in (c, "all"):
cont += por.all_data["containers"][args.endpoint_id][c]
por.stop_containers(args.endpoint_id, cont)
sys.exit()
if args.action == "start_containers":
print("Starting containers")
@@ -695,7 +727,7 @@ if __name__ == "__main__":
if args.stack in (c, "all"):
cont += por.all_data["containers"][args.endpoint_id][c]
por.start_containers(args.endpoint_id, cont)
sys.exit()
if args.action == "start_containers":
print("Starting containers")
cont = []
@@ -704,10 +736,28 @@ if __name__ == "__main__":
if args.stack in (c, "all"):
cont += por.all_data["containers"][args.endpoint_id][c]
por.start_containers(args.endpoint_id, cont)
sys.exit()
if args.action == "refresh_environment":
cont = por.refresh()
sys.exit()
if args.action == "refresh_status":
por.refresh_status(args)
if args.print_command:
one_time_command = "portainer"
if args.action:
one_time_command += f" --action={args.action}"
if por.endpoint_name:
one_time_command += f" --endpoint-id={por.endpoint_name}"
if por.site:
one_time_command += f" --site={por.site}"
if args.stack:
if type(args.stack) == list:
args.stack = ",".join(args.stack)
one_time_command += f" --stack={args.stack}"
width = shutil.get_terminal_size().columns
input(width)
print("#"*width)
print(f"COMMAND : {one_time_command}")
print("#"*width)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+25 -6
View File
@@ -13,6 +13,8 @@ import tabulate
from git import Repo
import requests
import hvac
import subprocess
import tempfile
from prompt_toolkit import prompt
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.shortcuts import checkboxlist_dialog
@@ -37,6 +39,7 @@ class PortainerApi:
self.timeout = timeout
self.git_url = "git@gitlab.sectorq.eu:home/docker-compose.git"
self.stack_name = None
self.stack_names = []
self.stacks_all = {}
self.stack_id = None
self.stack_ids = []
@@ -122,6 +125,7 @@ class PortainerApi:
self.cur_config = config
def get_site(self, site):
self.site = site
if site == "portainer":
self.base_url = os.getenv(
"PORTAINER_URL", "https://portainer.sectorq.eu/api"
@@ -176,7 +180,7 @@ class PortainerApi:
def _api_get(self, path, timeout=120):
url = f"{self.base_url.rstrip('/')}{path}"
headers = {"X-API-Key": f"{self.token}"}
resp = requests.get(url, headers=headers, timeout=timeout)
resp = requests.get(url, headers=headers, timeout=120)
if resp.status_code != 200:
return resp.status_code
print(f"Error: {resp.status_code} - {resp.text}")
@@ -273,7 +277,7 @@ class PortainerApi:
stcks = []
stacks = self._api_get(path, timeout=timeout)
self.stacks_all = {}
fail_endponts = [20, 39, 41, 32, 43]
fail_endponts = [20, 39, 41, 32, 43, 44]
# print(json.dumps(stacks,indent=2))
webhooks = {}
for s in stacks:
@@ -656,11 +660,17 @@ class PortainerApi:
autostart=False,
stack_mode="swarm",
):
diff_stacks = ['mediacenter']
for stack in stacks:
if self.endpoint_name == "nas":
server = "_nas"
else:
server = ""
server = ""
print("Stack:", stack)
print("Endpoint:", endpoint)
if stack in diff_stacks:
if endpoint == "nas":
server = "_nas"
elif endpoint == "m-s":
server = "_m-server"
if stack_mode == "swarm":
swarm_id = self.get_swarm_id(endpoint)
@@ -1221,6 +1231,8 @@ class PortainerApi:
self.get_endpoint(endpoint_id)
size = 0
if stack is not None:
if type(stack) == str:
stack = stack.split(",")
for s in stack:
if len(s) > size:
size = len(s)
@@ -1254,6 +1266,7 @@ class PortainerApi:
"""Stop one stack or all stacks on an endpoint."""
# print(f"Stopping stack {stack}")
protected_stack = ['hashicorp','nginx','pihole',]
ok = "\033[92m✔\033[0m"
ok2 = "\033[93m✔\033[0m"
err = "\033[91m✖\033[0m"
@@ -1261,6 +1274,8 @@ class PortainerApi:
self.get_endpoint(endpoint_id)
size = 0
if stack is not None:
if type(stack) == str:
stack = stack.split(",")
for s in stack:
if size < len(s):
size = len(s)
@@ -1268,6 +1283,10 @@ class PortainerApi:
size = size + 5
self.stack_ids = list(dict.fromkeys(self.stack_ids))
for stck in self.stack_ids:
if self.stacks_all[self.endpoint_id]['by_id'] in protected_stack:
ans = input(f"Really stop {self.stacks_all[self.endpoint_id]['by_id'][stck]} ? ") or "n"
if ans != "y":
continue
print(
f"Stopping stack {self.stacks_all[self.endpoint_id]['by_id'][stck][:size].ljust(size)}",
end="",