commit 669d865ffb2d472165dc98b45e5a01862e89458f Author: dvirlabs Date: Thu Jul 10 02:59:55 2025 +0300 initial commit diff --git a/.woodpecker.yaml b/.woodpecker.yaml new file mode 100644 index 0000000..e5d17f1 --- /dev/null +++ b/.woodpecker.yaml @@ -0,0 +1,65 @@ +steps: + build-backend: + name: Build & Push Backend + image: woodpeckerci/plugin-kaniko + when: + branch: [ master, develop ] + event: [ push, pull_request, tag ] + path: + include: [ backend/** ] + settings: + registry: harbor.dvirlabs.com + repo: my-apps/${CI_REPO_NAME}-backend + dockerfile: backend/Dockerfile + context: backend + tags: + - latest + - ${CI_COMMIT_TAG:-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:7}} + username: + from_secret: DOCKER_USERNAME + password: + from_secret: DOCKER_PASSWORD + + update-values-backend: + name: Update backend tag in values.yaml + image: alpine:3.19 + when: + branch: [ master, develop ] + event: [ push ] + path: + include: [ backend/** ] + environment: + GIT_USERNAME: + from_secret: GIT_USERNAME + GIT_TOKEN: + from_secret: GIT_TOKEN + commands: + - apk add --no-cache git yq + - git config --global user.name "woodpecker-bot" + - git config --global user.email "ci@dvirlabs.com" + - cd my-apps + - | + TAG="${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:7}" + echo "💡 Setting backend tag to: $TAG" + yq -i ".backend.tag = \"$TAG\"" manifests/${CI_REPO_NAME}/values.yaml + git add manifests/${CI_REPO_NAME}/values.yaml + git commit -m "backend: update tag to $TAG" || echo "No changes" + git push origin HEAD + + trigger-gitops-via-push: + name: Trigger apps-gitops via Git push + image: alpine/git + environment: + GIT_USERNAME: + from_secret: GIT_USERNAME + GIT_TOKEN: + from_secret: GIT_TOKEN + commands: | + git config --global user.name "woodpecker-bot" + git config --global user.email "ci@dvirlabs.com" + git clone "https://$${GIT_USERNAME}:$${GIT_TOKEN}@git.dvirlabs.com/dvirlabs/apps-gitops.git" + cd apps-gitops + echo "# trigger at $(date) by $${CI_REPO_NAME}" >> .trigger + git add .trigger + git commit -m "ci: trigger apps-gitops build" || echo "no changes" + git push origin HEAD diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..2cb042a --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.11-slim + +RUN apt update && apt install -y curl ffmpeg && \ + curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp && \ + chmod a+rx /usr/local/bin/yt-dlp + +WORKDIR /app +COPY . . +RUN pip install --no-cache-dir -r requirements.txt + +ENV MUSIC_DIR=/music + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/backend/config.py b/backend/config.py new file mode 100644 index 0000000..8deafdd --- /dev/null +++ b/backend/config.py @@ -0,0 +1,11 @@ +import os +from pydantic import BaseSettings + +class Settings(BaseSettings): + MUSIC_DIR: str = "/music" + NAVIDROME_SCAN_URL: str = "" + + class Config: + env_file = ".env" + +settings = Settings() \ No newline at end of file diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..69266a7 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,49 @@ +from fastapi import FastAPI, Query, HTTPException +from fastapi.responses import JSONResponse +from downloader import download_song +from config import settings + +app = FastAPI(title="Tunedrop") + +@app.get("/download") +def download(query: str = Query(..., description="Song name or YouTube search term")): + try: + result = download_song(query) + return JSONResponse(content={"status": "success", **result}) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +# downloader.py +import subprocess +import os +from config import settings +import uuid +import requests + +def download_song(query: str): + output_template = os.path.join(settings.MUSIC_DIR, "%(title)s.%(ext)s") + + command = [ + "yt-dlp", + f"ytsearch1:{query}", + "--extract-audio", + "--audio-format", "mp3", + "--output", output_template + ] + + result = subprocess.run(command, capture_output=True, text=True) + + if result.returncode != 0: + raise Exception(f"Download failed: {result.stderr}") + + # Optional: trigger navidrome scan + if settings.NAVIDROME_SCAN_URL: + try: + requests.post(settings.NAVIDROME_SCAN_URL, timeout=5) + except Exception as e: + pass # non-critical + + return { + "query": query, + "log": result.stdout + } \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..24b4f82 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,4 @@ +fastapi +uvicorn +pydantic +requests \ No newline at end of file diff --git a/tunedrop-chart/Chart.yaml b/tunedrop-chart/Chart.yaml new file mode 100644 index 0000000..3894611 --- /dev/null +++ b/tunedrop-chart/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: tunedrop +version: 0.1.0 +appVersion: "1.0.0" +description: Tunedrop - Music Downloader API + +dependencies: [] + +type: application \ No newline at end of file diff --git a/tunedrop-chart/templates/deployment.yaml b/tunedrop-chart/templates/deployment.yaml new file mode 100644 index 0000000..ff9474d --- /dev/null +++ b/tunedrop-chart/templates/deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tunedrop +spec: + replicas: 1 + selector: + matchLabels: + app: tunedrop + template: + metadata: + labels: + app: tunedrop + spec: + containers: + - name: tunedrop + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: MUSIC_DIR + value: {{ .Values.env.MUSIC_DIR | quote }} + - name: NAVIDROME_SCAN_URL + value: {{ .Values.env.NAVIDROME_SCAN_URL | quote }} + volumeMounts: + - name: music + mountPath: {{ .Values.persistence.mountPath }} + volumes: + - name: music + persistentVolumeClaim: + claimName: tunedrop-pvc \ No newline at end of file diff --git a/tunedrop-chart/templates/pvc.yaml b/tunedrop-chart/templates/pvc.yaml new file mode 100644 index 0000000..4d256aa --- /dev/null +++ b/tunedrop-chart/templates/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: tunedrop-pvc +spec: + accessModes: + - {{ .Values.persistence.accessMode }} + resources: + requests: + storage: {{ .Values.persistence.size }} + storageClassName: {{ .Values.persistence.storageClass }} \ No newline at end of file diff --git a/tunedrop-chart/values.yaml b/tunedrop-chart/values.yaml new file mode 100644 index 0000000..74db52c --- /dev/null +++ b/tunedrop-chart/values.yaml @@ -0,0 +1,27 @@ +image: + repository: my-apps/tunedrop + tag: latest + pullPolicy: IfNotPresent + +ingress: + enabled: true + className: traefik + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + hosts: + - host: tunedrop.dvirlabs.com + paths: + - path: / + pathType: Prefix + +env: + MUSIC_DIR: /music + NAVIDROME_SCAN_URL: http://navidrome.my-apps.svc.cluster.local:4533/api/rescan + +persistence: + enabled: true + mountPath: /music + size: 100Gi + accessMode: ReadWriteMany + storageClass: nfs-client \ No newline at end of file