From e1638acd8d64f206e979c567b7bb0c58c771d3c4 Mon Sep 17 00:00:00 2001 From: jaydee Date: Sun, 30 Nov 2025 23:34:46 +0100 Subject: [PATCH] build --- .../.env.influxdb2-admin-password | 0 .../home-assistant/.env.influxdb2-admin-token | 1 - .../.env.influxdb2-admin-username | 0 .../home-assistant/home-assistant-swarm.yml | 230 ------------------ backup_volumes copy.py | 185 -------------- backup_volumes.py | 92 ------- converted.yml | 148 ----------- copu_to_volume.py | 63 ----- requirements.txt | 2 - yaml_convert.py | 124 ---------- yaml_convert2.py | 172 ------------- 11 files changed, 1017 deletions(-) delete mode 100644 __swarm/home-assistant/.env.influxdb2-admin-password delete mode 100644 __swarm/home-assistant/.env.influxdb2-admin-token delete mode 100644 __swarm/home-assistant/.env.influxdb2-admin-username delete mode 100644 __swarm/home-assistant/home-assistant-swarm.yml delete mode 100644 backup_volumes copy.py delete mode 100644 backup_volumes.py delete mode 100644 converted.yml delete mode 100644 copu_to_volume.py delete mode 100644 requirements.txt delete mode 100644 yaml_convert.py delete mode 100644 yaml_convert2.py diff --git a/__swarm/home-assistant/.env.influxdb2-admin-password b/__swarm/home-assistant/.env.influxdb2-admin-password deleted file mode 100644 index e69de29..0000000 diff --git a/__swarm/home-assistant/.env.influxdb2-admin-token b/__swarm/home-assistant/.env.influxdb2-admin-token deleted file mode 100644 index 2adc89a..0000000 --- a/__swarm/home-assistant/.env.influxdb2-admin-token +++ /dev/null @@ -1 +0,0 @@ -l4c1j4yd33Du5lo \ No newline at end of file diff --git a/__swarm/home-assistant/.env.influxdb2-admin-username b/__swarm/home-assistant/.env.influxdb2-admin-username deleted file mode 100644 index e69de29..0000000 diff --git a/__swarm/home-assistant/home-assistant-swarm.yml b/__swarm/home-assistant/home-assistant-swarm.yml deleted file mode 100644 index 5b11153..0000000 --- a/__swarm/home-assistant/home-assistant-swarm.yml +++ /dev/null @@ -1,230 +0,0 @@ -version: '3.9' -services: - homeassistant: - network_mode: host - image: ${DOCKER_REGISTRY:-}ghcr.io/home-assistant/home-assistant:latest - volumes: - - /share/docker_data/ha/:/config - - /var/run/docker.sock:/var/run/docker.sock - - /run/dbus:/run/dbus:ro - privileged: true - environment: - - DISABLE_JEMALLOC=value - - TZ=Europe/Bratislava - dns: - - 192.168.77.101 - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - com.centurylinklabs.watchtower.enable: true - homepage.group: Smarthome - homepage.name: Home Assistant - homepage.weight: 1 - homepage.icon: home-assistant.png - homepage.href: https://ha.sectorq.eu - homepage.description: 3D Printing - homepage.server: my-docker - homepage.container: HomeAssistant - homepage.widget.type: homeassistant - homepage.widget.url: https://ha.sectorq.eu - homepage.widget.key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzOTk5NGJjYjIzYjk0YzExYmM5OWZiNTBlNzU0N2M2YyIsImlhdCI6MTc0MDM5OTY4NCwiZXhwIjoyMDU1NzU5Njg0fQ.LDebvPGreyZzlWT1CylHSdSt8i_cWO72HnNCsCAIaG8 - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager - esphome: - image: ${DOCKER_REGISTRY:-}esphome/esphome:latest - volumes: - - /share/docker_data/esphome/config:/config - - /etc/localtime:/etc/localtime:ro - privileged: true - network_mode: host - environment: - - USERNAME=jaydee - - PASSWORD=jaydee1 - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - com.centurylinklabs.watchtower.enable: true - homepage.group: Smarthome - homepage.name: ESPHome - homepage.weight: 1 - homepage.icon: esphome.png - homepage.href: https://esphome.sectorq.eu - homepage.description: 3D Printing - homepage.server: my-docker - homepage.container: esphome - homepage.widget.type: esphome - homepage.widget.url: https://esphome.sectorq.eu - homepage.widget.username: jaydee - homepage.widget.password: jaydee1 - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager - wyoming-piper-en: - image: ${DOCKER_REGISTRY:-}rhasspy/wyoming-piper - ports: - - 10200:10200 - volumes: - - /share/docker_data/piper/english:/data - command: --data-dir /data --voice en_US-lessac-medium - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - com.centurylinklabs.watchtower.enable: true - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager - wyoming-whisper-en: - image: ${DOCKER_REGISTRY:-}rhasspy/wyoming-whisper - ports: - - 10300:10300 - volumes: - - /share/docker_data/whisper/english:/data - command: --data-dir /data --model tiny-int8 --language en - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - com.centurylinklabs.watchtower.enable: true - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager - openwakeword: - image: ${DOCKER_REGISTRY:-}rhasspy/wyoming-openwakeword:latest - command: --preload-model 'ok_nabu' --custom-model-dir /custom --model 'ok nabu' - --model 'ok_nabu' --uri 'tcp://0.0.0.0:10400' --threshold 0.7 --trigger-level - 2 --debug - volumes: - - /share/docker_data/openwakeword-data:/data - - /share/docker_data/openwakeword-data:/custom - environment: - - TZ=Europe/Bratislava - ports: - - 10400:10400 - - 10400:10400/udp - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - com.centurylinklabs.watchtower.enable: true - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager - matter-server: - image: ${DOCKER_REGISTRY:-}ghcr.io/home-assistant-libs/python-matter-server:stable - security_opt: - - apparmor=unconfined - volumes: - - /share/docker_data/matter-server:/data - - /run/dbus:/run/dbus:ro - network_mode: host - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - com.centurylinklabs.watchtower.enable: true - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager - music-assistant-server: - image: ${DOCKER_REGISTRY:-}ghcr.io/music-assistant/server:latest - network_mode: host - volumes: - - /share/docker_data/music-assistant-server/data:/data/ - cap_add: - - SYS_ADMIN - - DAC_READ_SEARCH - security_opt: - - apparmor:unconfined - environment: - - LOG_LEVEL=info - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - com.centurylinklabs.watchtower.enable: true - wud.watch: true - wud.watch.digest: true - homepage.group: Smarthome - homepage.name: music-assistant - homepage.weight: 1 - homepage.icon: music-assistant.png - homepage.href: https://music.sectorq.eu - homepage.description: Music - homepage.server: my-docker - homepage.container: music-assistant-server - placement: - constraints: - - node.role == manager - influxdb: - ports: - - 8086:8086 - volumes: - - /share/docker_data/influxdb/data:/var/lib/influxdb2 - - /share/docker_data/influxdb/config:/etc/influxdb2 - secrets: - - influxdb2-admin-username - - influxdb2-admin-password - - influxdb2-admin-token - environment: - - DOCKER_INFLUXDB_INIT_MODE=setup - - DOCKER_INFLUXDB_INIT_USERNAME=ha - - DOCKER_INFLUXDB_INIT_PASSWORD=haHAhaHA - - DOCKER_INFLUXDB_INIT_ORG=ha - - DOCKER_INFLUXDB_INIT_BUCKET=ha - - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=mytoken123 - - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN_FILE=/run/secrets/influxdb2-admin-token - image: ${DOCKER_REGISTRY:-}influxdb:2 - healthcheck: - test: echo test > /var/lib/influxdb2/hc || exit 1 - interval: 10s - timeout: 3s - retries: 2 - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - com.centurylinklabs.watchtower.enable: true - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager -secrets: - influxdb2-admin-username: - file: .env.influxdb2-admin-username - influxdb2-admin-password: - file: .env.influxdb2-admin-password - influxdb2-admin-token: - file: .env.influxdb2-admin-token diff --git a/backup_volumes copy.py b/backup_volumes copy.py deleted file mode 100644 index 8eee0eb..0000000 --- a/backup_volumes copy.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -import docker -import os -import datetime -import argparse -import subprocess -import sys - -BACKUP_DIR = "/backup/docker_volumes" - -import requests - -PORTAINER_URL = "https://port.sectorq.eu/api" -API_KEY = "ptr_/5RkMCT/j3BTaL32vMSDtXFi76yOXRKVFOrUtzMsl5Y=" -HEADERS = {"X-API-Key": f"{API_KEY}"} -ENDPOINT_ID = 4 -def get_stack_by_name(stack_name): - url = f"{PORTAINER_URL}/stacks" - r = requests.get(url, headers=HEADERS) - r.raise_for_status() - stacks = r.json() - for s in stacks: - if s['Name'] == stack_name: - return s - return None - -def stop_stack(stack_name): - stack = get_stack_by_name(stack_name) - if not stack: - print(f"[INFO] Stack {stack_name} not found.") - return - - print(f"Stopping stack {stack_name} via Portainer API...") - url = f"{PORTAINER_URL}/stacks/{stack['Id']}/stop?endpointId={ENDPOINT_ID}" - print("URL:", url) - r = requests.post(url, headers=HEADERS) - if r.status_code == 200: - print(f"[OK] Stack {stack_name} stopped.") - else: - print(f"[ERROR] Failed to stop stack: {r.status_code} {r.text}") - -def start_stack(stack_name): - stack = get_stack_by_name(stack_name) - if not stack: - print(f"[INFO] Stack {stack_name} not found.") - return - - print(f"Starting stack {stack_name} via Portainer API...") - url = f"{PORTAINER_URL}/stacks/{stack['Id']}/start?endpointId={ENDPOINT_ID}" - r = requests.post(url, headers=HEADERS) - if r.status_code == 200: - print(f"[OK] Stack {stack_name} started.") - else: - print(f"[ERROR] Failed to start stack: {r.status_code} {r.text}") - -def to_timestamp(): - return datetime.datetime.now().strftime("%Y%m%d-%H%M%S") - -def get_containers_using_volume(client, vol_name): - containers = [] - for c in client.containers.list(): - for m in c.attrs['Mounts']: - if m.get('Name') == vol_name: - containers.append(c) - return containers - -def backup_volumes(client): - os.makedirs(BACKUP_DIR, exist_ok=True) - timestamp = to_timestamp() - volumes = client.volumes.list() - if not volumes: - print("No Docker volumes found.") - return - - for vol in volumes: - vol_name = vol.name - backup_file = os.path.join(BACKUP_DIR, f"{vol_name}-{timestamp}.tar.gz") - containers = get_containers_using_volume(client, vol_name) - - # Track stacks that need to be stopped - stacks_to_restart = {} - - for c in containers: - stack = c.labels.get('com.docker.stack.namespace') - if stack: - # Stop stack instead of individual container - if stack not in stacks_to_restart: - stacks_to_restart[stack] = True - stop_stack(stack) - else: - # Stop individual container - print(f"Stopping container {c.name} for backup...") - c.stop() - - # Backup using busybox - try: - print(f"Backing up volume {vol_name} → {backup_file}") - client.containers.run( - image="busybox", - command=f"tar czf /backup/{vol_name}-{timestamp}.tar.gz -C /volume .", - remove=True, - volumes={ - vol_name: {'bind': '/volume', 'mode': 'ro'}, - BACKUP_DIR: {'bind': '/backup', 'mode': 'rw'} - }, - ) - print(f"[OK] Backup completed: {backup_file}") - except Exception as e: - print(f"[ERROR] Failed to backup {vol_name}: {e}") - - # Restart individual containers - for c in containers: - stack = c.labels.get('com.docker.stack.namespace') - if not stack: - print(f"Starting container {c.name} after backup...") - c.start() - - # Restart stacks - for stack in stacks_to_restart.keys(): - start_stack(stack) - - -def restore_volume(client, vol_name, backup_file): - containers = get_containers_using_volume(client, vol_name) - stacks_to_restart = {} - - for c in containers: - stack = c.labels.get('com.docker.stack.namespace') - if stack: - if stack not in stacks_to_restart: - stacks_to_restart[stack] = True - stop_stack(stack) - else: - print(f"Stopping container {c.name} for restore...") - c.stop() - - try: - print(f"Restoring volume {vol_name} from {backup_file}") - client.containers.run( - image="busybox", - command=f"tar xzf /backup/{os.path.basename(backup_file)} -C /volume", - remove=True, - volumes={ - vol_name: {'bind': '/volume', 'mode': 'rw'}, - os.path.dirname(backup_file): {'bind': '/backup', 'mode': 'rw'} - }, - ) - print(f"[OK] Restore completed: {vol_name}") - except Exception as e: - print(f"[ERROR] Failed to restore {vol_name}: {e}") - - # Restart containers - for c in containers: - stack = c.labels.get('com.docker.stack.namespace') - if not stack: - print(f"Starting container {c.name} after restore...") - c.start() - - # Restart stacks - for stack in stacks_to_restart.keys(): - stack_file = f"/deployments/{stack}.yml" - if os.path.exists(stack_file): - start_stack(stack_file) - else: - print(f"[WARNING] Stack file {stack_file} not found. Start manually.") - -def main(): - parser = argparse.ArgumentParser(description="Backup or restore Docker volumes.") - parser.add_argument("action", choices=["backup", "restore"], help="Action to perform") - parser.add_argument("--volume", help="Volume name (required for restore)") - parser.add_argument("--file", help="Backup file (required for restore)") - args = parser.parse_args() - - client = docker.from_env() - - if args.action == "backup": - backup_volumes(client) - elif args.action == "restore": - if not args.volume or not args.file: - print("[ERROR] --volume and --file are required for restore") - sys.exit(1) - restore_volume(client, args.volume, args.file) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/backup_volumes.py b/backup_volumes.py deleted file mode 100644 index fd444f1..0000000 --- a/backup_volumes.py +++ /dev/null @@ -1,92 +0,0 @@ -import docker -import requests -import os -import time -PORTAINER_URL = "https://port.sectorq.eu/api" -API_KEY = "ptr_/5RkMCT/j3BTaL32vMSDtXFi76yOXRKVFOrUtzMsl5Y=" -HEADERS = {"X-API-Key": f"{API_KEY}"} -ENDPOINT_ID = 4 -client = docker.from_env() - -# Keep track of which stacks we already stopped -stopped_stacks = {} - -def get_stack_by_name(stack_name): - url = f"{PORTAINER_URL}/stacks" - r = requests.get(url, headers=HEADERS) - r.raise_for_status() - for s in r.json(): - if s['Name'] == stack_name: - return s - return None - -def stop_stack(stack_name): - if stack_name in stopped_stacks: - return - stack = get_stack_by_name(stack_name) - if not stack: - print(f"[INFO] Stack {stack_name} not found, skipping stop") - return - url = f"{PORTAINER_URL}/stacks/{stack['Id']}/stop?endpointId={ENDPOINT_ID}" - print(f"url: {url}") - r = requests.post(url, headers=HEADERS) - if r.status_code == 200: - print(f"[OK] Stack {stack_name} stopped") - else: - print(f"[ERROR] Failed to stop stack {stack_name}: {r.text}") - stopped_stacks[stack_name] = True - -def start_stack(stack_name): - stack = get_stack_by_name(stack_name) - if not stack: - print(f"[INFO] Stack {stack_name} not found, skipping start") - return - url = f"{PORTAINER_URL}/stacks/{stack['Id']}/start?endpointId={ENDPOINT_ID}" - r = requests.post(url, headers=HEADERS) - if r.status_code == 200: - print(f"[OK] Stack {stack_name} started") - else: - print(f"[ERROR] Failed to start stack {stack_name}: {r.text}") - -def backup_volume(vol): - vol_name = vol.name - backup_file = f"/backup/{vol_name}.tar" - print(f"[INFO] Backing up volume {vol_name} → {backup_file}") - # Use a temporary container to archive the volume - client.containers.run( - image="alpine", - command=f"tar czf /backup/{vol_name}.tar -C /data .", - volumes={vol_name: {'bind': '/data', 'mode': 'ro'}, - "/backup": {'bind': '/backup', 'mode': 'rw'}}, - remove=True - ) - -def main(): - volumes = client.volumes.list() - stack_volumes = {} - normal_volumes = [] - - # Categorize volumes by stack - for vol in volumes: - labels = vol.attrs.get('Labels', {}) - stack_name = labels.get('com.docker.stack.namespace') - if stack_name: - stack_volumes.setdefault(stack_name, []).append(vol) - else: - normal_volumes.append(vol) - - # Backup stacks - for stack_name, vols in stack_volumes.items(): - stop_stack(stack_name) - #input("Press Enter to continue with backup...") - for v in vols: - backup_volume(v) - time.sleep(10) # Small delay to ensure stack is fully stopped - start_stack(stack_name) - - # Backup normal volumes - for v in normal_volumes: - backup_volume(v) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/converted.yml b/converted.yml deleted file mode 100644 index d1ad0ce..0000000 --- a/converted.yml +++ /dev/null @@ -1,148 +0,0 @@ -version: '3.9' -services: - authentik_ldap: - environment: - AUTHENTIK_HOST: https://auth.sectorq.eu - AUTHENTIK_INSECURE: 'false' - AUTHENTIK_TOKEN: EfLokorVuj1woeO0p1he3mRJvVfGfvdKM8Bdew3DtDZZ3To6bVpFSDI7GOqY - TZ: Europe/Bratislava - image: ${DOCKER_REGISTRY:-}ghcr.io/goauthentik/ldap:${AUTHENTIK_TAG:-2024.6.1} - ports: - - 2389:3389 - - 2636:6636 - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager - postgresql: - environment: - AUTHENTIK_SECRET_KEY: $AUTHENTIK_SECRET_KEY - POSTGRES_DB: ${PG_DB:-authentik} - POSTGRES_PASSWORD: ${PG_PASS:?database password required} - POSTGRES_USER: ${PG_USER:-authentik} - TZ: Europe/Bratislava - healthcheck: - interval: 30s - retries: 5 - start_period: 20s - test: - - CMD-SHELL - - pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER} - timeout: 5s - image: ${DOCKER_REGISTRY:-docker.io/library/}postgres:16-alpine - volumes: - - /share/docker_data/authentik/database:/var/lib/postgresql/data - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - wud.watch: false - placement: - constraints: - - node.role == manager - redis: - command: --save 60 1 --loglevel warning - healthcheck: - interval: 30s - retries: 5 - start_period: 20s - test: - - CMD-SHELL - - redis-cli ping | grep PONG - timeout: 3s - image: ${DOCKER_REGISTRY:-docker.io/library/}redis:alpine - volumes: - - redis:/data - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager - server: - command: server - environment: - AUTHENTIK_POSTGRESQL__HOST: postgresql - AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} - AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} - AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} - AUTHENTIK_REDIS__HOST: redis - AUTHENTIK_SECRET_KEY: $AUTHENTIK_SECRET_KEY - TZ: Europe/Bratislava - image: ${DOCKER_REGISTRY:-}ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2024.6.1} - ports: - - ${COMPOSE_PORT_HTTP:-9003}:9000 - - ${COMPOSE_PORT_HTTPS:-9453}:9443 - volumes: - - /share/docker_data/authentik/media:/media - - /share/docker_data/authentik/custom-templates:/templates - - /var/run/docker.sock:/var/run/docker.sock - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - homepage.container: authentik-server-1 - homepage.description: Authentification server - homepage.group: Utilities - homepage.href: https://auth.sectorq.eu - homepage.icon: authentik.png - homepage.name: Authentik - homepage.server: my-docker - homepage.weight: '10' - homepage.widget.key: sVOwPPInTue7ZnvolmKG15hkE9gCyLcuAelLOQny6OIVn7JUilny9loPTG0v - homepage.widget.type: authentik - homepage.widget.url: https://auth.sectorq.eu - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager - worker: - command: worker - environment: - AUTHENTIK_POSTGRESQL__HOST: postgresql - AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} - AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} - AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} - AUTHENTIK_REDIS__HOST: redis - AUTHENTIK_SECRET_KEY: $AUTHENTIK_SECRET_KEY - TZ: Europe/Bratislava - image: ${DOCKER_REGISTRY:-}ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2024.6.1} - user: root - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /share/docker_data/authentik/media:/media - - /share/docker_data/authentik/certs:/certs - - /share/docker_data/authentik/custom-templates:/templates - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: any - labels: - wud.watch: true - wud.watch.digest: true - placement: - constraints: - - node.role == manager -volumes: - database: - driver: local - redis: - driver: local diff --git a/copu_to_volume.py b/copu_to_volume.py deleted file mode 100644 index df96801..0000000 --- a/copu_to_volume.py +++ /dev/null @@ -1,63 +0,0 @@ -import docker -import os -import argparse - -parser = argparse.ArgumentParser(description="Portainer helper - use env vars or pass credentials.") - -parser.add_argument("--volume_name","-v", type=str, default=None, help="Volume name") -parser.add_argument("--source_dir","-s", type=str, default=None, help="Source directory to copy from") -args = parser.parse_args() - - -def copy_to_volume(volume_name, source_dir, container_path="/data", image="busybox:latest"): - """ - Copy all files from source_dir into a Docker volume using a temporary container. - Creates the volume if it does not exist. - - :param volume_name: Name of the Docker volume - :param source_dir: Local directory to copy - :param container_path: Path inside the container where volume is mounted - :param image: Temporary container image - """ - client = docker.from_env() - - if not os.path.isdir(source_dir): - raise ValueError(f"Source directory {source_dir} does not exist") - if not os.listdir(source_dir): - print("Folder is empty") - return 1 - else: - print("Folder is not empty") - # Check if volume exists - try: - volume = client.volumes.get(volume_name) - print(f"Volume '{volume_name}' exists.") - except docker.errors.NotFound: - print(f"Volume '{volume_name}' does not exist. Creating...") - volume = client.volumes.create(name=volume_name) - print(f"Volume '{volume_name}' created.") - - print(f"Copying files from {source_dir} to volume '{volume_name}'...") - - # Run temporary container to copy files - print(container_path) - client.containers.run( - image, - command=f"sh -c 'cp -r /tmp/* {container_path}/'", - volumes={ - volume_name: {"bind": container_path, "mode": "rw"}, - os.path.abspath(source_dir): {"bind": "/tmp", "mode": "ro"} - }, - remove=True, - detach=False - ) - - print("Files copied successfully.") - -if __name__ == "__main__": - # Example usage - copy_to_volume( - volume_name=args.volume_name, - source_dir=args.source_dir, - container_path="/data" - ) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 75d9842..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pyyaml -docker diff --git a/yaml_convert.py b/yaml_convert.py deleted file mode 100644 index 30cd3ae..0000000 --- a/yaml_convert.py +++ /dev/null @@ -1,124 +0,0 @@ -import yaml -import sys -import copy - -def default_deploy(): - return { - "mode": "replicated", - "replicas": 1, - "restart_policy": {"condition": "any"}, - "labels": {}, - "placement": { - "constraints": [ - "node.role == manager" - ] - } - } - - -def convert_service(service): - swarm_service = {} - - # Create a fresh deploy section each time (avoids YAML anchors) - deploy_section = default_deploy() - - for key, value in service.items(): - #print(key, value) - # Unsupported in Swarm - - - # Move labels → deploy.labels - #print(f"Labels: {deploy_section['labels']}") - if key == "labels": - input(f"Key: {key} Value: {value}") - #print("Processing Labels:") - if isinstance(value, dict): - deploy_section["labels"].update({k: str(v).lower() for k, v in value.items()}) - - - elif isinstance(value, list): - for item in value: - if "=" in item: - k, v = item.split("=", 1) - deploy_section["labels"][k] = str(v).lower() - - continue - - swarm_service[key] = value - envir = [] - for en in swarm_service['environment']: - #print(f"Environment Variable: {en} : {swarm_service['environment'][en]}") - if "=" in en: - e = en.split("=",1)[0] - envir.append(e) - print(en) - print(swarm_service['environment'][en]) - swarm_service['environment'].appeendstr(swarm_service['environment'][en]).lower() - #print("Deploy Section:") - #print(swarm_service) - # Merge user deploy section if present - #input(service) - if "deploy" in service: - user_deploy = service["deploy"] - #print("User Deploy Section:") - # merge deploy.labels - if "labels" in user_deploy: - ##print("User Deploy Labels:") - labels = user_deploy["labels"] - if isinstance(labels, dict): - deploy_section["labels"].update(labels) - elif isinstance(labels, list): - for item in labels: - #print(f"Label Item: {item}") - if "=" in item: - k, v = item.split("=", 1) - deploy_section["labels"][k] = str(v).lower() - - # merge placement constraints - if "placement" in user_deploy: - if "constraints" in user_deploy["placement"]: - deploy_section["placement"]["constraints"].extend( - user_deploy["placement"]["constraints"] - ) - - # merge other keys - for dk, dv in user_deploy.items(): - if dk not in ["labels", "placement"]: - deploy_section[dk] = copy.deepcopy(dv) - - swarm_service["deploy"] = deploy_section - return swarm_service - - -def convert_compose_to_swarm(app): - output_file = "__swarm/" + app + "/" + app + "-swarm.yml" - input_file = app + "/docker-compose.yml" - with open(input_file, "r") as f: - compose = yaml.safe_load(f) - - swarm = {"version": "3.9", "services": {}} - - for name, service in compose.get("services", {}).items(): - swarm["services"][name] = convert_service(service) - - for section in ["networks", "volumes", "configs", "secrets"]: - if section in compose: - swarm[section] = compose[section] - - # Prevent PyYAML from creating anchors - class NoAliasDumper(yaml.SafeDumper): - def ignore_aliases(self, data): - return True - - with open(output_file, "w") as f: - yaml.dump(swarm, f, sort_keys=False, Dumper=NoAliasDumper) - - print(f"✔ Swarm file written to: {output_file}") - - -if __name__ == "__main__": - if len(sys.argv) != 2: - print("Usage: python convert_to_swarm.py app_name") - sys.exit(1) - - convert_compose_to_swarm(sys.argv[1]) \ No newline at end of file diff --git a/yaml_convert2.py b/yaml_convert2.py deleted file mode 100644 index cf21219..0000000 --- a/yaml_convert2.py +++ /dev/null @@ -1,172 +0,0 @@ -import yaml -import sys -import re -import os -stack_name = sys.argv[1] -INPUT_FILE = f"{stack_name}/docker-compose.yml" -OUTPUT_FILE = f"__swarm/{stack_name}/{stack_name}-swarm.yml" - - - - -def fix_env_file(filepath): - """Convert YAML-style env (KEY: value) → Docker env (KEY=value).""" - fixed_lines = [] - changed = False - - with open(filepath, "r") as f: - for raw_line in f: - line = raw_line.rstrip("\n") - stripped = line.strip() - - # Preserve comments and blank lines - if not stripped or stripped.startswith("#"): - fixed_lines.append(raw_line) - continue - - # Detect YAML-style: KEY: value - # MUST convert - if ":" in stripped and "=" not in stripped.split(":")[0]: - key, value = stripped.split(":", 1) - key = key.strip() - value = value.strip() - - # Validate env key - if not re.match(r"^[A-Za-z0-9_]+$", key): - raise ValueError(f"Invalid variable name: {key}") - - fixed_lines.append(f"{key}={value}\n") - changed = True - continue - - # Detect valid Docker-style: KEY=value - if "=" in stripped: - key, value = stripped.split("=", 1) - - # Validate key - if not re.match(r"^[A-Za-z0-9_]+$", key): - raise ValueError(f"Invalid environment variable name: {key}") - - # Value may contain anything - fixed_lines.append(raw_line) - continue - - # Anything else is invalid - raise ValueError(f"Invalid env line: {stripped}") - - # Write file if modified - if changed: - with open(filepath, "w") as f: - f.writelines(fixed_lines) - print(f"[FIXED] Converted YAML → Docker env format in {filepath}") - else: - print(f"[OK] .env file valid: {filepath}") - -def convert_ports(ports): - """Convert short port syntax to Swarm long syntax.""" - result = [] - print(f"Converting ports: {ports}") - for p in ports: - print(f"Port entry: {p}") - 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", {}) - #input(services) - for name, svc in services.items(): - print(f"Converting service: {name} , svc: {svc}") - if name in ["container_name", "restart", "depends_on"]: - continue - - svc.pop('restart', None) - svc.pop('depends_on', None) - svc.pop('container_name', None) - # 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"] - labels = deploy.get("labels", {}) - if "homepage.server" in labels and labels["homepage.server"] == "my-docker": - labels["homepage.server"] = "my-docker-swarm" - # 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: - #input(svc) - svc["ports"] = convert_ports(svc["ports"]) - - # 7) Remove container_name (not allowed in Swarm) - svc.pop("container_name", None) - - return data - -def main(): - if os.path.exists(f"__swarm/{stack_name}/stack.env"): - fix_env_file(f"__swarm/{stack_name}/stack.env") # NEW FIX STEP - with open(INPUT_FILE, "r") as f: - compose = yaml.safe_load(f) - #input(compose) - 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