Compare commits

...
8 Commits
6 changed files with 61 additions and 43 deletions

No files matched your search

+5 -2
View File
@@ -97,16 +97,19 @@ jobs:
bun build server/index.ts --compile --target ${{ matrix.bun-target }} --outfile ${{ matrix.binary }} bun build server/index.ts --compile --target ${{ matrix.bun-target }} --outfile ${{ matrix.binary }}
shell: pwsh shell: pwsh
- name: Build worker
run: bun build server/organize-worker.ts --target ${{ matrix.bun-target }} --outfile organize-worker.js
- name: Package archive (Windows) - name: Package archive (Windows)
if: matrix.target == 'windows-x64' if: matrix.target == 'windows-x64'
run: Compress-Archive -Path ${{ matrix.binary }}, public -DestinationPath ${{ matrix.archive }} run: Compress-Archive -Path ${{ matrix.binary }}, organize-worker.js, public -DestinationPath ${{ matrix.archive }}
shell: pwsh shell: pwsh
- name: Package archive (Unix) - name: Package archive (Unix)
if: matrix.target != 'windows-x64' if: matrix.target != 'windows-x64'
run: | run: |
chmod +x ${{ matrix.binary }} chmod +x ${{ matrix.binary }}
zip -r ${{ matrix.archive }} ${{ matrix.binary }} public/ zip -r ${{ matrix.archive }} ${{ matrix.binary }} organize-worker.js public/
- name: Upload archive to Release - name: Upload archive to Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
+14
View File
@@ -1,5 +1,19 @@
# Changelog # Changelog
## [1.9.4](https://github.com/Masutayunikon/FanKarr/compare/v1.9.3...v1.9.4) (2026-03-13)
### Bug Fixes
* worker outside the exe ([a47c3a4](https://github.com/Masutayunikon/FanKarr/commit/a47c3a43fb5dbbedd87c9ac2d584c3290fcc6097))
## [1.9.3](https://github.com/Masutayunikon/FanKarr/compare/v1.9.2...v1.9.3) (2026-03-13)
### Bug Fixes
* use bcryptjs for compilation ([6a6981e](https://github.com/Masutayunikon/FanKarr/commit/6a6981e10a3a9019f49982d7680e562872ee95a5))
## [1.9.2](https://github.com/Masutayunikon/FanKarr/compare/v1.9.1...v1.9.2) (2026-03-13) ## [1.9.2](https://github.com/Masutayunikon/FanKarr/compare/v1.9.1...v1.9.2) (2026-03-13)
+7 -7
View File
@@ -8,16 +8,15 @@
"dev:server": "tsx watch server/index.ts", "dev:server": "tsx watch server/index.ts",
"dev:client": "vite", "dev:client": "vite",
"build": "vite build && tsc -p tsconfig.server.json", "build": "vite build && tsc -p tsconfig.server.json",
"build:bun:linux": "bun build server/index.ts --compile --target bun-linux-x64 --outfile binaries/fankarr-linux-x64", "build:bun:linux": "bun build server/index.ts --compile --target bun-linux-x64 --outfile binaries/fankarr-linux-x64 && bun build server/organize-worker.ts --target bun-linux-x64 --outfile binaries/organize-worker.js",
"build:bun:windows": "bun build server/index.ts --compile --target bun-windows-x64 --outfile binaries/fankarr-windows-x64.exe", "build:bun:windows": "bun build server/index.ts --compile --target bun-windows-x64 --outfile binaries/fankarr-windows-x64.exe && bun build server/organize-worker.ts --target bun-windows-x64 --outfile binaries/organize-worker.js",
"build:bun:macos": "bun build server/index.ts --compile --target bun-darwin-arm64 --outfile binaries/fankarr-macos", "build:bun:macos": "bun build server/index.ts --compile --target bun-darwin-arm64 --outfile binaries/fankarr-macos && bun build server/organize-worker.ts --target bun-darwin-arm64 --outfile binaries/organize-worker.js",
"build:bun:all": "pnpm build:bun:linux && pnpm build:bun:windows && pnpm build:bun:macos", "build:bun:all": "pnpm build:bun:linux && pnpm build:bun:windows && pnpm build:bun:macos",
"start": "node dist/server/index.js", "start": "node dist/server/index.js",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.1",
"bcrypt": "^6.0.0",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"express": "^5.2.1", "express": "^5.2.1",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
@@ -25,10 +24,10 @@
"pinia": "^3.0.4", "pinia": "^3.0.4",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"vue": "^3.5.25", "vue": "^3.5.25",
"vue-router": "^5.0.3" "vue-router": "^5.0.3",
"bcryptjs": "^3.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/cookie-parser": "^1.4.8", "@types/cookie-parser": "^1.4.8",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.9", "@types/jsonwebtoken": "^9.0.9",
@@ -39,6 +38,7 @@
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.3.1", "vite": "^7.3.1",
"vue-tsc": "^3.1.5" "vue-tsc": "^3.1.5",
"@types/bcrypt": "^5.0.2"
} }
} }
+7 -22
View File
@@ -11,9 +11,9 @@ importers:
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.2.1 specifier: ^4.2.1
version: 4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) version: 4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))
bcrypt: bcryptjs:
specifier: ^6.0.0 specifier: ^3.0.3
version: 6.0.0 version: 3.0.3
cookie-parser: cookie-parser:
specifier: ^1.4.7 specifier: ^1.4.7
version: 1.4.7 version: 1.4.7
@@ -650,9 +650,9 @@ packages:
resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==} resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
bcrypt@6.0.0: bcryptjs@3.0.3:
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==}
engines: {node: '>= 18'} hasBin: true
birpc@2.9.0: birpc@2.9.0:
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
@@ -1087,10 +1087,6 @@ packages:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
node-addon-api@8.6.0:
resolution: {integrity: sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==}
engines: {node: ^18 || ^20 || >= 21}
node-domexception@1.0.0: node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'} engines: {node: '>=10.5.0'}
@@ -1100,10 +1096,6 @@ packages:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
node-gyp-build@4.8.4:
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
object-inspect@1.13.4: object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1919,10 +1911,7 @@ snapshots:
'@babel/parser': 7.29.0 '@babel/parser': 7.29.0
ast-kit: 2.2.0 ast-kit: 2.2.0
bcrypt@6.0.0: bcryptjs@3.0.3: {}
dependencies:
node-addon-api: 8.6.0
node-gyp-build: 4.8.4
birpc@2.9.0: {} birpc@2.9.0: {}
@@ -2351,8 +2340,6 @@ snapshots:
negotiator@1.0.0: {} negotiator@1.0.0: {}
node-addon-api@8.6.0: {}
node-domexception@1.0.0: {} node-domexception@1.0.0: {}
node-fetch@3.3.2: node-fetch@3.3.2:
@@ -2361,8 +2348,6 @@ snapshots:
fetch-blob: 3.2.0 fetch-blob: 3.2.0
formdata-polyfill: 4.0.10 formdata-polyfill: 4.0.10
node-gyp-build@4.8.4: {}
object-inspect@1.13.4: {} object-inspect@1.13.4: {}
on-finished@2.4.1: on-finished@2.4.1:
+1 -1
View File
@@ -1,6 +1,6 @@
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import bcrypt from 'bcrypt' import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import type { Request, Response, NextFunction } from 'express' import type { Request, Response, NextFunction } from 'express'
import { JWT_SECRET } from './secret.js' import { JWT_SECRET } from './secret.js'
+27 -11
View File
@@ -13,6 +13,18 @@ import { logger } from './logger.js'
// Un seul worker à la fois // Un seul worker à la fois
let workerRunning = false let workerRunning = false
// Résolution du chemin du worker
// En binaire Bun compilé : le worker est à côté de l'exécutable
// En Docker/dev : à côté du fichier courant
function resolveWorkerPath(): string {
const _isBunBinary = typeof (globalThis as any).Bun !== 'undefined'
&& path.dirname((process as any).execPath) !== process.cwd()
if (_isBunBinary) {
return path.join(path.dirname((process as any).execPath), 'organize-worker.js')
}
return path.join(path.dirname(new URL(import.meta.url).pathname), 'organize-worker.js')
}
export interface OrganizeResult { export interface OrganizeResult {
total : number total : number
skipped : number skipped : number
@@ -112,7 +124,7 @@ export async function autoOrganizeAll(
onResult?: (r: { hash: string; name: string; done: number; skipped: number; errors: number; errorFiles: { file: string; error: string }[] }) => void onResult?: (r: { hash: string; name: string; done: number; skipped: number; errors: number; errorFiles: { file: string; error: string }[] }) => void
): Promise<void> { ): Promise<void> {
if (workerRunning) { if (workerRunning) {
logger.debug('organize', 'Worker déjà en cours, skip') console.log('[organize] Worker déjà en cours, skip')
return return
} }
@@ -120,7 +132,7 @@ export async function autoOrganizeAll(
try { try {
torrents = await listFn() torrents = await listFn()
} catch (err) { } catch (err) {
logger.error('organize', "Impossible de récupérer la liste des torrents: " + (err instanceof Error ? err.message : String(err))) console.error('[organize] Impossible de récupérer la liste des torrents:', err)
return return
} }
@@ -129,18 +141,20 @@ export async function autoOrganizeAll(
const seedingCount = torrents.filter(t => t.state === 'seeding').length const seedingCount = torrents.filter(t => t.state === 'seeding').length
if (seedingCount === 0) return if (seedingCount === 0) return
logger.info('organize', `Lancement worker (${seedingCount} torrents en seeding)`) console.log(`[organize] Lancement worker (${seedingCount} torrents en seeding)`)
workerRunning = true workerRunning = true
const workerPath = path.join(path.dirname(new URL(import.meta.url).pathname), 'organize-worker.js') const workerPath = resolveWorkerPath()
const worker = new Worker(workerPath) const worker = new Worker(workerPath)
worker.on('message', (msg: any) => { worker.on('message', (msg: any) => {
if (msg.type === 'log') { if (msg.type === 'log') {
logger[msg.level as 'debug'|'info'|'warn'|'error']('organize', msg.msg) if (msg.level === 'error') console.error(msg.msg)
else if (msg.level === 'warn') console.warn(msg.msg)
else console.log(msg.msg)
} else if (msg.type === 'result') { } else if (msg.type === 'result') {
logger.info('organize', `${msg.name} → ${msg.done} OK, ${msg.skipped} skippés, ${msg.errors.length} erreurs`) console.log(`[organize] ${msg.name} → ${msg.done} OK, ${msg.skipped} skippés, ${msg.errors.length} erreurs`)
onResult?.({ onResult?.({
hash : msg.hash, hash : msg.hash,
name : msg.name, name : msg.name,
@@ -150,19 +164,19 @@ export async function autoOrganizeAll(
errorFiles : msg.errors, errorFiles : msg.errors,
}) })
} else if (msg.type === 'done') { } else if (msg.type === 'done') {
logger.debug('organize', 'Worker terminé') console.log('[organize] Worker terminé')
workerRunning = false workerRunning = false
worker.terminate() worker.terminate()
} }
}) })
worker.on('error', (err) => { worker.on('error', (err) => {
logger.error('organize', `Worker erreur: ${err instanceof Error ? err.message : err}`) console.error('[organize] Worker erreur:', err)
workerRunning = false workerRunning = false
}) })
worker.on('exit', (code) => { worker.on('exit', (code) => {
if (code !== 0) logger.error('organize', `Worker exit code ${code}`) if (code !== 0) console.error(`[organize] Worker exit code ${code}`)
workerRunning = false workerRunning = false
}) })
@@ -180,7 +194,7 @@ export async function organizeTorrent(
savePath: string savePath: string
): Promise<OrganizeResult> { ): Promise<OrganizeResult> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const workerPath = path.join(path.dirname(new URL(import.meta.url).pathname), 'organize-worker.js') const workerPath = resolveWorkerPath()
const worker = new Worker(workerPath) const worker = new Worker(workerPath)
// On envoie un seul torrent "fictif seeding" // On envoie un seul torrent "fictif seeding"
@@ -189,7 +203,9 @@ export async function organizeTorrent(
worker.on('message', (msg: any) => { worker.on('message', (msg: any) => {
if (msg.type === 'log') { if (msg.type === 'log') {
logger[msg.level as 'debug'|'info'|'warn'|'error']('organize', msg.msg) if (msg.level === 'error') console.error(msg.msg)
else if (msg.level === 'warn') console.warn(msg.msg)
else console.log(msg.msg)
} else if (msg.type === 'result') { } else if (msg.type === 'result') {
result.total = msg.total result.total = msg.total
result.skipped = msg.skipped result.skipped = msg.skipped