From 8354f41f09f59a1b4208f1aea070d63b33f7d39c Mon Sep 17 00:00:00 2001 From: jaydee Date: Sun, 30 Nov 2025 14:14:01 +0100 Subject: [PATCH] build --- __swarm/bitwarden/bitwarden-stack.yml | 39 +++++++++++ __swarm/bitwarden/bitwarden-swarm.yml | 14 ++-- yaml_convert.py | 8 ++- yaml_convert2.py | 99 +++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 __swarm/bitwarden/bitwarden-stack.yml create mode 100644 yaml_convert2.py diff --git a/__swarm/bitwarden/bitwarden-stack.yml b/__swarm/bitwarden/bitwarden-stack.yml new file mode 100644 index 0000000..b46f35d --- /dev/null +++ b/__swarm/bitwarden/bitwarden-stack.yml @@ -0,0 +1,39 @@ +services: + bitwarden: + environment: + WEBSOCKET_ENABLED: 'true' + SIGNUPS_ALLOWED: 'true' + DOMAIN: https://pw.sectorq.eu + SMTP_HOST: mail.sectorq.eu + SMTP_FROM: jaydee@sectorq.eu + SMTP_PORT: '465' + SMTP_SSL: 'true' + SMTP_USERNAME: jaydee@sectorq.eu + SMTP_PASSWORD: $SMTP_PASSWORD + ADMIN_TOKEN: $ADMIN_PASSWORD + image: ${DOCKER_REGISTRY:-}vaultwarden/server:latest + ports: + - target: 80 + published: 8181 + protocol: tcp + mode: ingress + restart: ${RESTART:-unless-stopped} + volumes: + - /share/docker_data/bitwarden/bw-data:/data + deploy: + labels: + com.centurylinklabs.watchtower.enable: 'true' + homepage.container: vaultwarden + homepage.description: Password manager + homepage.group: Utilities + homepage.href: https://pw.sectorq.eu + homepage.icon: bitwarden.png + homepage.name: Bitwarden + homepage.server: my-docker + homepage.weight: '1' + wud.watch: 'true' + wud.watch.digest: 'true' + replicas: 1 + placement: + constraints: + - node.role == manager diff --git a/__swarm/bitwarden/bitwarden-swarm.yml b/__swarm/bitwarden/bitwarden-swarm.yml index 3b72804..54e39ea 100644 --- a/__swarm/bitwarden/bitwarden-swarm.yml +++ b/__swarm/bitwarden/bitwarden-swarm.yml @@ -23,17 +23,17 @@ services: restart_policy: condition: any labels: - com.centurylinklabs.watchtower.enable: true + com.centurylinklabs.watchtower.enable: 'true' homepage.container: vaultwarden - homepage.description: Password manager - homepage.group: Utilities + homepage.description: password manager + homepage.group: utilities homepage.href: https://pw.sectorq.eu homepage.icon: bitwarden.png - homepage.name: Bitwarden + homepage.name: bitwarden homepage.server: my-docker - homepage.weight: 1 - wud.watch: true - wud.watch.digest: true + homepage.weight: '1' + wud.watch: 'true' + wud.watch.digest: 'true' placement: constraints: - node.role == manager diff --git a/yaml_convert.py b/yaml_convert.py index 7c8aa6e..fdaa773 100644 --- a/yaml_convert.py +++ b/yaml_convert.py @@ -46,9 +46,11 @@ def convert_service(service): continue swarm_service[key] = value - for en in swarm_service['environment']: - #print(f"Environment Variable: {en} : {swarm_service['environment'][en]}") - swarm_service['environment'][en] = str(swarm_service['environment'][en]).lower() + # for en in swarm_service['environment']: + # #print(f"Environment Variable: {en} : {swarm_service['environment'][en]}") + # print(en) + # print(swarm_service['environment'][en]) + # swarm_service['environment'][en] = str(swarm_service['environment'][en]).lower() #print("Deploy Section:") #print(swarm_service) # Merge user deploy section if present diff --git a/yaml_convert2.py b/yaml_convert2.py new file mode 100644 index 0000000..0854508 --- /dev/null +++ b/yaml_convert2.py @@ -0,0 +1,99 @@ +import yaml +import sys + +stack_name = sys.argv[1] +INPUT_FILE = f"{stack_name}/docker-compose.yml" +OUTPUT_FILE = f"__swarm/{stack_name}/{stack_name}-stack.yml" +def convert_ports(ports): + """Convert short port syntax to Swarm long syntax.""" + result = [] + for p in ports: + if isinstance(p, str): + # format: "8080:80" + pub, tgt = p.split(":") + result.append({ + "target": int(tgt), + "published": int(pub), + "protocol": "tcp", + "mode": "ingress" + }) + else: + result.append(p) + return result + +def to_str_lower(value): + """Convert value to string. Booleans become lowercase 'true'/'false'.""" + if isinstance(value, bool): + return "true" if value else "false" + return str(value) + +def env_list_to_dict(env_list): + """Convert environment from list ['KEY=VAL'] to dict {KEY: VAL} as strings.""" + env_dict = {} + for item in env_list: + key, value = item.split("=", 1) + # convert 'true'/'false' strings to lowercase + if value.lower() in ["true", "false"]: + env_dict[key] = value.lower() + else: + env_dict[key] = str(value) + return env_dict + +def ensure_labels_as_string(labels): + """Ensure all label values are strings, lowercase for booleans.""" + return {k: to_str_lower(v) for k, v in labels.items()} + +def convert_compose_to_swarm(data): + services = data.get("services", {}) + + for name, svc in services.items(): + + # 1) Convert environment list → dict (strings) + if "environment" in svc and isinstance(svc["environment"], list): + svc["environment"] = env_list_to_dict(svc["environment"]) + + # 2) Ensure deploy exists + deploy = svc.setdefault("deploy", {}) + + # 3) Move labels into deploy.labels, all as strings (lowercase booleans) + if "labels" in svc: + deploy.setdefault("labels", {}) + if isinstance(svc["labels"], dict): + deploy["labels"].update(ensure_labels_as_string(svc["labels"])) + elif isinstance(svc["labels"], list): + for label in svc["labels"]: + key, value = label.split("=", 1) + deploy["labels"][key] = value.lower() if value.lower() in ["true", "false"] else str(value) + del svc["labels"] + + # 4) Default replicas + deploy.setdefault("replicas", 1) + + # 5) Add placement constraint + deploy.setdefault("placement", {}) + deploy["placement"].setdefault("constraints", []) + if "node.role == manager" not in deploy["placement"]["constraints"]: + deploy["placement"]["constraints"].append("node.role == manager") + + # 6) Convert ports to long format + if "ports" in svc: + svc["ports"] = convert_ports(svc["ports"]) + + # 7) Remove container_name (not allowed in Swarm) + svc.pop("container_name", None) + + return data + +def main(): + with open(INPUT_FILE, "r") as f: + compose = yaml.safe_load(f) + + swarm = convert_compose_to_swarm(compose) + + with open(OUTPUT_FILE, "w") as f: + yaml.dump(swarm, f, sort_keys=False) + + print(f"Swarm stack file written to {OUTPUT_FILE}") + +if __name__ == "__main__": + main() \ No newline at end of file