From c341d79e6b10eccbd129d39a2a3b8d4177a4a44f Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Sat, 9 Aug 2025 13:52:28 -0600 Subject: [PATCH 1/7] Pre-commit hooks --- .github/scripts/ensure_compose_image.py | 57 +++++++++++++++++++++++++ .pre-commit-config.yaml | 34 +++++---------- docker-compose.yaml | 2 +- 3 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 .github/scripts/ensure_compose_image.py diff --git a/.github/scripts/ensure_compose_image.py b/.github/scripts/ensure_compose_image.py new file mode 100644 index 0000000..54825eb --- /dev/null +++ b/.github/scripts/ensure_compose_image.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +import sys +from pathlib import Path + +try: + import yaml +except Exception as e: + sys.stderr.write("PyYAML is required to run this hook.\n") + raise + +EXPECTED_IMAGE = "cooldockerizer93/spotizerr" + + +def validate_compose_image(path: Path) -> int: + if not path.exists(): + sys.stderr.write(f"File not found: {path}\n") + return 1 + + try: + with path.open("r", encoding="utf-8") as f: + data = yaml.safe_load(f) + except Exception as e: + sys.stderr.write(f"Failed to parse YAML from {path}: {e}\n") + return 1 + + image = ( + (data or {}) + .get("services", {}) + .get("spotizerr", {}) + .get("image") + ) + + errors = [] + if not isinstance(image, str): + errors.append("services.spotizerr.image is missing or not a string") + else: + if image != EXPECTED_IMAGE: + errors.append( + f"services.spotizerr.image must be '{EXPECTED_IMAGE}' (found '{image}')" + ) + + if errors: + sys.stderr.write("docker-compose.yaml validation failed:\n") + for err in errors: + sys.stderr.write(f" - {err}\n") + return 1 + + return 0 + + +def main(argv: list[str]) -> int: + compose_path = Path(argv[1]) if len(argv) > 1 else Path("docker-compose.yaml") + return validate_compose_image(compose_path) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70035fc..6c4123d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,32 +1,11 @@ repos: # Various general + format-specific helpers - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v4.6.0 hooks: - - id: check-symlinks - exclude: ^spotizerr-ui/ - - id: trailing-whitespace - exclude: ^spotizerr-ui/ - - id: mixed-line-ending - args: [--fix=lf] - exclude: ^spotizerr-ui/ - id: check-yaml - exclude: 'mkdocs.yml|^spotizerr-ui/' - - id: check-toml - exclude: ^spotizerr-ui/ - - id: check-json - exclude: ^spotizerr-ui/ - - id: check-ast - exclude: ^spotizerr-ui/ - - id: debug-statements - exclude: ^spotizerr-ui/ - - id: check-merge-conflict - exclude: ^spotizerr-ui/ - - id: check-shebang-scripts-are-executable - exclude: ^spotizerr-ui/ - - id: check-added-large-files - args: [--maxkb=10000] - exclude: ^spotizerr-ui/ + - id: end-of-file-fixer + - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema rev: '0.33.0' hooks: @@ -53,3 +32,10 @@ repos: exclude: ^spotizerr-ui/ # NOTE: you might need to add some deps here: additional_dependencies: [waitress==3.0.2, types-waitress, types-requests] + - repo: local + hooks: + - id: ensure-compose-image + name: Ensure docker-compose image is cooldockerizer93/spotizerr + entry: python3 .github/scripts/ensure_compose_image.py docker-compose.yaml + language: system + files: ^docker-compose\.ya?ml$ diff --git a/docker-compose.yaml b/docker-compose.yaml index 18a57b7..66efef9 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,7 +2,7 @@ name: spotizerr services: spotizerr: - image: cooldockerizer93/spotizerr:beta + image: cooldockerizer93/spotizerr:eee volumes: - ./data:/app/data - ./downloads:/app/downloads # <-- Change this for your music library dir From acfa7ce10ecb5362754ecff191cbc31cb6ac5f6a Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Sat, 9 Aug 2025 13:52:52 -0600 Subject: [PATCH 2/7] Test --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 66efef9..27a556e 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,7 +2,7 @@ name: spotizerr services: spotizerr: - image: cooldockerizer93/spotizerr:eee + image: cooldockerizer93/spotizerr:ee volumes: - ./data:/app/data - ./downloads:/app/downloads # <-- Change this for your music library dir From e267549aecdeaa791d5c64b97dff20b945d6b125 Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Sat, 9 Aug 2025 13:56:06 -0600 Subject: [PATCH 3/7] Image guard --- .pre-commit-config.yaml | 34 ++++++++++++++++++++++++---------- docker-compose.yaml | 2 +- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c4123d..70035fc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,32 @@ repos: # Various general + format-specific helpers - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - - id: check-yaml - - id: end-of-file-fixer + - id: check-symlinks + exclude: ^spotizerr-ui/ - id: trailing-whitespace + exclude: ^spotizerr-ui/ + - id: mixed-line-ending + args: [--fix=lf] + exclude: ^spotizerr-ui/ + - id: check-yaml + exclude: 'mkdocs.yml|^spotizerr-ui/' + - id: check-toml + exclude: ^spotizerr-ui/ + - id: check-json + exclude: ^spotizerr-ui/ + - id: check-ast + exclude: ^spotizerr-ui/ + - id: debug-statements + exclude: ^spotizerr-ui/ + - id: check-merge-conflict + exclude: ^spotizerr-ui/ + - id: check-shebang-scripts-are-executable + exclude: ^spotizerr-ui/ + - id: check-added-large-files + args: [--maxkb=10000] + exclude: ^spotizerr-ui/ - repo: https://github.com/python-jsonschema/check-jsonschema rev: '0.33.0' hooks: @@ -32,10 +53,3 @@ repos: exclude: ^spotizerr-ui/ # NOTE: you might need to add some deps here: additional_dependencies: [waitress==3.0.2, types-waitress, types-requests] - - repo: local - hooks: - - id: ensure-compose-image - name: Ensure docker-compose image is cooldockerizer93/spotizerr - entry: python3 .github/scripts/ensure_compose_image.py docker-compose.yaml - language: system - files: ^docker-compose\.ya?ml$ diff --git a/docker-compose.yaml b/docker-compose.yaml index 27a556e..66efef9 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,7 +2,7 @@ name: spotizerr services: spotizerr: - image: cooldockerizer93/spotizerr:ee + image: cooldockerizer93/spotizerr:eee volumes: - ./data:/app/data - ./downloads:/app/downloads # <-- Change this for your music library dir From 58220485aae75abe88330923247ac9020bac839a Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Sat, 9 Aug 2025 13:59:07 -0600 Subject: [PATCH 4/7] Image guard --- .github/scripts/ensure_compose_image.py | 33 +++++++-------------- .github/workflows/compose-image-guard.yml | 36 +++++++++++++++++++++++ .pre-commit-config.yaml | 2 +- 3 files changed, 47 insertions(+), 24 deletions(-) mode change 100644 => 100755 .github/scripts/ensure_compose_image.py create mode 100644 .github/workflows/compose-image-guard.yml diff --git a/.github/scripts/ensure_compose_image.py b/.github/scripts/ensure_compose_image.py old mode 100644 new mode 100755 index 54825eb..cd15b88 --- a/.github/scripts/ensure_compose_image.py +++ b/.github/scripts/ensure_compose_image.py @@ -4,9 +4,9 @@ from pathlib import Path try: import yaml -except Exception as e: - sys.stderr.write("PyYAML is required to run this hook.\n") - raise +except Exception: + sys.stderr.write("PyYAML is required to run this check.\n") + sys.exit(2) EXPECTED_IMAGE = "cooldockerizer93/spotizerr" @@ -23,28 +23,15 @@ def validate_compose_image(path: Path) -> int: sys.stderr.write(f"Failed to parse YAML from {path}: {e}\n") return 1 - image = ( - (data or {}) - .get("services", {}) - .get("spotizerr", {}) - .get("image") - ) + image = (data or {}).get("services", {}).get("spotizerr", {}).get("image") - errors = [] - if not isinstance(image, str): - errors.append("services.spotizerr.image is missing or not a string") - else: - if image != EXPECTED_IMAGE: - errors.append( - f"services.spotizerr.image must be '{EXPECTED_IMAGE}' (found '{image}')" - ) - - if errors: - sys.stderr.write("docker-compose.yaml validation failed:\n") - for err in errors: - sys.stderr.write(f" - {err}\n") + if image != EXPECTED_IMAGE: + sys.stderr.write( + f"services.spotizerr.image must be '{EXPECTED_IMAGE}' (found '{image}')\n" + ) return 1 + print(f"OK: docker-compose image is '{EXPECTED_IMAGE}'") return 0 @@ -54,4 +41,4 @@ def main(argv: list[str]) -> int: if __name__ == "__main__": - sys.exit(main(sys.argv)) \ No newline at end of file + sys.exit(main(sys.argv)) diff --git a/.github/workflows/compose-image-guard.yml b/.github/workflows/compose-image-guard.yml new file mode 100644 index 0000000..a63f297 --- /dev/null +++ b/.github/workflows/compose-image-guard.yml @@ -0,0 +1,36 @@ +name: Compose Image Guard + +on: + push: + branches: [ dev, main, master ] + paths: + - 'docker-compose.yaml' + - '.github/workflows/compose-image-guard.yml' + - '.github/scripts/ensure_compose_image.py' + pull_request: + branches: [ dev, main, master ] + paths: + - 'docker-compose.yaml' + - '.github/workflows/compose-image-guard.yml' + - '.github/scripts/ensure_compose_image.py' + +jobs: + validate-compose-image: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pyyaml + + - name: Validate docker-compose image + run: | + python .github/scripts/ensure_compose_image.py docker-compose.yaml \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70035fc..821bd17 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,4 +52,4 @@ repos: args: [--no-strict-optional, --ignore-missing-imports] exclude: ^spotizerr-ui/ # NOTE: you might need to add some deps here: - additional_dependencies: [waitress==3.0.2, types-waitress, types-requests] + additional_dependencies: [waitress==3.0.2, types-waitress, types-requests, types-PyYAML] From b26c85ad66c2ceb66e0f7cc2c2004e4d99b68216 Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Sat, 9 Aug 2025 14:03:01 -0600 Subject: [PATCH 5/7] Image guard (? --- .github/scripts/ensure_compose_image.py | 93 +++++++++++++++++++---- .github/workflows/compose-image-guard.yml | 2 +- 2 files changed, 80 insertions(+), 15 deletions(-) diff --git a/.github/scripts/ensure_compose_image.py b/.github/scripts/ensure_compose_image.py index cd15b88..00c1ee0 100755 --- a/.github/scripts/ensure_compose_image.py +++ b/.github/scripts/ensure_compose_image.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import sys +import subprocess from pathlib import Path +from typing import Tuple try: import yaml @@ -11,34 +13,97 @@ except Exception: EXPECTED_IMAGE = "cooldockerizer93/spotizerr" -def validate_compose_image(path: Path) -> int: - if not path.exists(): - sys.stderr.write(f"File not found: {path}\n") +def load_compose(path: Path): + with path.open("r", encoding="utf-8") as f: + return yaml.safe_load(f) or {} + + +def save_compose(path: Path, data) -> None: + with path.open("w", encoding="utf-8") as f: + yaml.safe_dump(data, f, sort_keys=False) + + +def ensure_image_unversioned(data) -> Tuple[bool, str, str]: + """ + Returns (changed, old_image, new_image) + """ + services = (data or {}).get("services", {}) + svc = services.get("spotizerr", {}) + image = svc.get("image") + if image == EXPECTED_IMAGE: + return False, image, image + + # Normalize to expected image if it has a tag/digest or is different + svc["image"] = EXPECTED_IMAGE + services["spotizerr"] = svc + data["services"] = services + return True, image, EXPECTED_IMAGE + + +def git(*args: str) -> subprocess.CompletedProcess: + return subprocess.run(["git", *args], check=False, text=True, capture_output=True) + + +def autocommit(file_path: str) -> None: + # Configure git identity if missing + git("config", "user.name").stdout + if git("config", "user.name").stdout.strip() == "": + git("config", "user.name", "github-actions[bot]") + if git("config", "user.email").stdout.strip() == "": + git("config", "user.email", "github-actions[bot]@users.noreply.github.com") + # Stage and commit + git("add", file_path) + status = git("status", "--porcelain").stdout.strip() + if status: + msg = "chore: normalize docker-compose image to cooldockerizer93/spotizerr" + commit_res = git("commit", "-m", msg) + if commit_res.returncode != 0: + sys.stderr.write(f"Git commit failed: {commit_res.stderr}\n") + sys.exit(1) + push_res = git("push") + if push_res.returncode != 0: + sys.stderr.write(f"Git push failed: {push_res.stderr}\n") + sys.exit(1) + print("Pushed normalization commit") + else: + print("No changes to commit") + + +def main(argv: list[str]) -> int: + # Usage: ensure_compose_image.py [docker-compose.yaml] [--autocommit] + compose_path = ( + Path(argv[1]) + if len(argv) > 1 and not argv[1].startswith("-") + else Path("docker-compose.yaml") + ) + do_autocommit = any(arg == "--autocommit" for arg in argv[1:]) + + if not compose_path.exists(): + sys.stderr.write(f"File not found: {compose_path}\n") return 1 try: - with path.open("r", encoding="utf-8") as f: - data = yaml.safe_load(f) + data = load_compose(compose_path) except Exception as e: - sys.stderr.write(f"Failed to parse YAML from {path}: {e}\n") + sys.stderr.write(f"Failed to parse YAML from {compose_path}: {e}\n") return 1 - image = (data or {}).get("services", {}).get("spotizerr", {}).get("image") + changed, old_image, new_image = ensure_image_unversioned(data) - if image != EXPECTED_IMAGE: + if changed: + save_compose(compose_path, data) sys.stderr.write( - f"services.spotizerr.image must be '{EXPECTED_IMAGE}' (found '{image}')\n" + f"Normalized services.spotizerr.image from '{old_image}' to '{new_image}'\n" ) + # For pre-commit: exit non-zero to force user to re-stage + if do_autocommit: + autocommit(str(compose_path)) + return 0 return 1 print(f"OK: docker-compose image is '{EXPECTED_IMAGE}'") return 0 -def main(argv: list[str]) -> int: - compose_path = Path(argv[1]) if len(argv) > 1 else Path("docker-compose.yaml") - return validate_compose_image(compose_path) - - if __name__ == "__main__": sys.exit(main(sys.argv)) diff --git a/.github/workflows/compose-image-guard.yml b/.github/workflows/compose-image-guard.yml index a63f297..8a5d406 100644 --- a/.github/workflows/compose-image-guard.yml +++ b/.github/workflows/compose-image-guard.yml @@ -33,4 +33,4 @@ jobs: - name: Validate docker-compose image run: | - python .github/scripts/ensure_compose_image.py docker-compose.yaml \ No newline at end of file + python .github/scripts/ensure_compose_image.py docker-compose.yaml --autocommit \ No newline at end of file From 440dc5cd87ec56133ea6adaa9099a2e54bf0901e Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Sat, 9 Aug 2025 14:04:41 -0600 Subject: [PATCH 6/7] Image guard x2(? --- .github/workflows/compose-image-guard.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/compose-image-guard.yml b/.github/workflows/compose-image-guard.yml index 8a5d406..5c5113c 100644 --- a/.github/workflows/compose-image-guard.yml +++ b/.github/workflows/compose-image-guard.yml @@ -14,6 +14,10 @@ on: - '.github/workflows/compose-image-guard.yml' - '.github/scripts/ensure_compose_image.py' +permissions: + contents: write + pull-requests: write + jobs: validate-compose-image: runs-on: ubuntu-latest @@ -31,6 +35,12 @@ jobs: python -m pip install --upgrade pip pip install pyyaml - - name: Validate docker-compose image - run: | - python .github/scripts/ensure_compose_image.py docker-compose.yaml --autocommit \ No newline at end of file + # On pushes to this repo: normalize and push + - name: Validate and normalize (auto-commit on push) + if: github.event_name == 'push' && github.repository == 'Xoconoch/spotizerr' + run: python .github/scripts/ensure_compose_image.py docker-compose.yaml --autocommit + + # On PRs (including forks): validate only, no push + - name: Validate (no auto-commit on PR) + if: github.event_name != 'push' || github.repository != 'Xoconoch/spotizerr' + run: python .github/scripts/ensure_compose_image.py docker-compose.yaml \ No newline at end of file From dd620d6770077ab2fdb5f76fc4f2f5f85238ba6e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 9 Aug 2025 20:04:53 +0000 Subject: [PATCH 7/7] chore: normalize docker-compose image to cooldockerizer93/spotizerr --- docker-compose.yaml | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 66efef9..d7bba01 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,32 +1,28 @@ name: spotizerr - services: spotizerr: - image: cooldockerizer93/spotizerr:eee + image: cooldockerizer93/spotizerr volumes: - - ./data:/app/data - - ./downloads:/app/downloads # <-- Change this for your music library dir - - ./logs:/app/logs # <-- Volume for persistent logs + - ./data:/app/data + - ./downloads:/app/downloads + - ./logs:/app/logs ports: - - 7171:7171 + - 7171:7171 container_name: spotizerr-app restart: unless-stopped env_file: - - .env - + - .env depends_on: - - redis - + - redis redis: image: redis:alpine container_name: spotizerr-redis restart: unless-stopped env_file: - - .env + - .env volumes: - - redis-data:/data + - redis-data:/data command: sh -c 'redis-server --requirepass "$REDIS_PASSWORD" --appendonly yes' - volumes: redis-data: driver: local