initial commit

This commit is contained in:
dvirlabs 2025-07-10 02:59:55 +03:00
commit 669d865ffb
9 changed files with 219 additions and 0 deletions

65
.woodpecker.yaml Normal file
View File

@ -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

13
backend/Dockerfile Normal file
View File

@ -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"]

11
backend/config.py Normal file
View File

@ -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()

49
backend/main.py Normal file
View File

@ -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
}

4
backend/requirements.txt Normal file
View File

@ -0,0 +1,4 @@
fastapi
uvicorn
pydantic
requests

View File

@ -0,0 +1,9 @@
apiVersion: v2
name: tunedrop
version: 0.1.0
appVersion: "1.0.0"
description: Tunedrop - Music Downloader API
dependencies: []
type: application

View File

@ -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

View File

@ -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 }}

View File

@ -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