diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml new file mode 100644 index 0000000..924f5ac --- /dev/null +++ b/.github/workflows/build-binaries.yml @@ -0,0 +1,109 @@ +name: Build Binaries + +on: + release: + types: [created] + workflow_dispatch: + +jobs: + build-frontend: + name: Build Frontend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v3 + with: + version: latest + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build Vue frontend + run: pnpm build + + - name: Upload frontend dist + uses: actions/upload-artifact@v4 + with: + name: frontend-dist + path: dist/ + retention-days: 1 + + build-binaries: + name: Build ${{ matrix.target }} + needs: build-frontend + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - target: windows-x64 + os: windows-latest + bun-target: bun-windows-x64 + binary: binaries/fankarr.exe + archive: fankarr-windows-x64.zip + - target: linux-x64 + os: ubuntu-latest + bun-target: bun-linux-x64 + binary: binaries/fankarr + archive: fankarr-linux-x64.zip + - target: macos + os: macos-latest + bun-target: bun-darwin-arm64 + binary: binaries/fankarr + archive: fankarr-macos.zip + + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - uses: pnpm/action-setup@v3 + with: + version: latest + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Download frontend dist + uses: actions/download-artifact@v4 + with: + name: frontend-dist + path: public/ + + - name: Build binary + run: | + mkdir -p binaries + bun build server/index.ts \ + --compile \ + --target ${{ matrix.bun-target }} \ + --outfile ${{ matrix.binary }} + + - name: Package archive (Windows) + if: matrix.target == 'windows-x64' + run: Compress-Archive -Path ${{ matrix.binary }}, public -DestinationPath ${{ matrix.archive }} + shell: pwsh + + - name: Package archive (Unix) + if: matrix.target != 'windows-x64' + run: | + chmod +x ${{ matrix.binary }} + zip -r ${{ matrix.archive }} ${{ matrix.binary }} public/ + + - name: Upload archive to Release + uses: softprops/action-gh-release@v2 + with: + files: ${{ matrix.archive }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index a547bf3..a0de374 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +binaries # Editor directories and files .vscode/* diff --git a/README.md b/README.md index 5f69d33..bbee0ee 100644 --- a/README.md +++ b/README.md @@ -69,14 +69,9 @@ Fankarr récupère ce fichier au démarrage et via le bouton **Mettre à jour** ## Installation -### Prérequis +### Docker Compose (recommandé) -- Docker + Docker Compose -- qBittorrent accessible en réseau -- Un dossier de téléchargements complétés (`/downloads/complete` ou équivalent) -- Un dossier médiathèque pour Jellyfin (`/media/Kai` ou équivalent) - -### Docker Compose +**Prérequis** : Docker + Docker Compose, qBittorrent accessible en réseau. ```yaml services: @@ -88,8 +83,8 @@ services: - PGID=1000 # GID de votre utilisateur (id -g) - TZ=Europe/Paris # Votre timezone volumes: - - ./data:/app/data # Config, logs, base de données - - /votre/chemin/media:/media/Kai # Destination Jellyfin + - ./data:/app/data # Config, logs, base de données + - /votre/chemin/media:/media/Kai # Destination Jellyfin - /votre/chemin/complete:/downloads/complete # Dossier complétés qBittorrent ports: - 3001:3001 @@ -100,15 +95,41 @@ services: docker compose up -d ``` -Fankarr sera accessible sur `http://localhost:3001`. +### Binaire autonome (Windows / Linux / macOS) + +Téléchargez l'archive correspondant à votre système depuis les [Releases GitHub](https://github.com/masutayunikon/fankarr/releases/latest) : + +| Système | Archive | +|---|---| +| Windows x64 | `fankarr-windows-x64.zip` | +| Linux x64 | `fankarr-linux-x64.zip` | +| macOS (Apple Silicon) | `fankarr-macos.zip` | + +Extrayez l'archive — elle contient le binaire **et** le dossier `public/` qui doivent rester ensemble : + +``` +fankarr-windows-x64/ +├── fankarr.exe ← lancer ce fichier +└── public/ ← ne pas déplacer séparément +``` + +**Linux / macOS** : rendre le binaire exécutable avant le premier lancement : + +```bash +chmod +x fankarr +./fankarr +``` + +Fankarr sera accessible sur `http://localhost:3001`. Les données (config, logs, base de données) sont sauvegardées dans un dossier `data/` créé automatiquement à côté du binaire. ### Variables d'environnement | Variable | Défaut | Description | |---|---|---| -| `PUID` | `1000` | UID utilisateur pour les permissions fichiers | -| `PGID` | `1000` | GID utilisateur pour les permissions fichiers | +| `PUID` | `1000` | UID utilisateur pour les permissions fichiers (Docker) | +| `PGID` | `1000` | GID utilisateur pour les permissions fichiers (Docker) | | `TZ` | — | Timezone (ex: `Europe/Paris`) | +| `PORT` | `3001` | Port d'écoute du serveur | | `JWT_SECRET` | auto-généré | Secret JWT — généré automatiquement dans `data/secret.key` si absent | | `GITHUB_RAW_URL` | repo scraper | URL du `torrent_final.json` à utiliser | @@ -130,7 +151,7 @@ Fankarr organise automatiquement les fichiers terminés toutes les **30 secondes ``` /media/Kai/ └── Black Lagoon Henshū/ - └── Saison 001/ + └── Saison 1/ ├── Black Lagoon Henshū S01E01.mkv ├── Black Lagoon Henshū S01E02.mkv └── ... @@ -157,8 +178,8 @@ Fankarr est disponible dans l'appstore personnalisé. Pour l'installer : | Frontend | Vue 3 + Vite + Tailwind CSS | | Backend | Express 5 + TypeScript | | Auth | JWT + bcrypt | -| Packaging | Docker (`node:20-slim`) + pnpm | -| Permissions | gosu (PUID/PGID) | +| Packaging Docker | `node:20-slim` + pnpm + gosu (PUID/PGID) | +| Packaging binaire | Bun (self-contained, pas de Node requis) | --- diff --git a/package.json b/package.json index e4fe7bb..0b21d2b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,12 @@ "dev": "concurrently \"vite\" \"tsx watch server/index.ts\"", "dev:server": "tsx watch server/index.ts", "dev:client": "vite", - "build": "vite build --outDir dist/client && tsc -p tsconfig.server.json", + "build": "vite build", + "build:bun:linux": "bun build server/index.ts --compile --target bun-linux-x64 --outfile binaries/fankarr-linux-x64", + "build:bun:windows": "bun build server/index.ts --compile --target bun-windows-x64 --outfile binaries/fankarr-windows-x64.exe", + "build:bun:macos": "bun build server/index.ts --compile --target bun-darwin-arm64 --outfile binaries/fankarr-macos", + "build:bun:all": "pnpm build:bun:linux && pnpm build:bun:windows && pnpm build:bun:macos", + "start": "node dist/server/index.js", "preview": "vite preview" }, "dependencies": { diff --git a/server/index.ts b/server/index.ts index d80aa39..9110f9f 100644 --- a/server/index.ts +++ b/server/index.ts @@ -15,10 +15,19 @@ import { logger, readLogs, clearLogs, logsFileSize } from './logger.js' registerDriver(qbittorrentDriver) +// ── Base directory ───────────────────────────────────────────── +// Binaire Bun compilé : data/ et public/ sont à côté de l'exécutable +// Docker / dev : relatif au cwd (comportement inchangé) +const _isBunBinary = typeof (globalThis as any).Bun !== 'undefined' + && path.dirname((process as any).execPath) !== process.cwd() +export const BASE_DIR = _isBunBinary + ? path.dirname((process as any).execPath) + : process.cwd() + const app = express() -const PORT = 3001 +const PORT = Number(process.env.PORT) || 3001 const FANKAI_API = 'https://metadata.fankai.fr' -const TORRENTS_PATH = path.join(process.cwd(), 'data', 'torrent_final.json') +const TORRENTS_PATH = path.join(BASE_DIR, 'data', 'torrent_final.json') const GITHUB_RAW_URL = process.env.GITHUB_RAW_URL ?? 'https://raw.githubusercontent.com/masutayunikon/fankarr-scraper/main/data/torrent_final.json' @@ -26,7 +35,7 @@ app.use(express.json()) app.use(cookieParser()) // ── Static frontend (prod) ───────────────────────────────────── -const PUBLIC_PATH = path.join(process.cwd(), 'public') +const PUBLIC_PATH = path.join(BASE_DIR, 'public') if (fs.existsSync(PUBLIC_PATH)) { app.use(express.static(PUBLIC_PATH)) } diff --git a/server/organize-worker.ts b/server/organize-worker.ts index 9fd1936..f7d9de1 100644 --- a/server/organize-worker.ts +++ b/server/organize-worker.ts @@ -16,8 +16,15 @@ import fsp from 'fs/promises' import path from 'path' import { parentPort } from 'worker_threads' -const TORRENTS_PATH = path.join(process.cwd(), 'data', 'torrent_final.json') -const ORGANIZED_PATH = path.join(process.cwd(), 'data', 'organized.json') +// Base directory : binaire Bun ou cwd (Docker/dev) +const _isBunBinary = typeof (globalThis as any).Bun !== 'undefined' + && path.dirname((process as any).execPath) !== process.cwd() +const BASE_DIR = _isBunBinary + ? path.dirname((process as any).execPath) + : process.cwd() + +const TORRENTS_PATH = path.join(BASE_DIR, 'data', 'torrent_final.json') +const ORGANIZED_PATH = path.join(BASE_DIR, 'data', 'organized.json') // ─── Utils log ──────────────────────────────────────────────── // Le worker tourne dans un thread séparé — il passe les logs au thread principal diff --git a/server/settings.ts b/server/settings.ts index ab02bf3..4aafa96 100644 --- a/server/settings.ts +++ b/server/settings.ts @@ -1,7 +1,12 @@ import fs from 'fs' import path from 'path' -const DATA_PATH = path.join(process.cwd(), 'data', 'settings.json') +const _isBunBinary = typeof (globalThis as any).Bun !== 'undefined' + && path.dirname((process as any).execPath) !== process.cwd() +const BASE_DIR = _isBunBinary + ? path.dirname((process as any).execPath) + : process.cwd() +const DATA_PATH = path.join(BASE_DIR, 'data', 'settings.json') export interface Settings { mediaPath : string