diff --git a/app/api/tus/hooks/route.ts b/app/api/tus/hooks/route.ts new file mode 100644 index 00000000..997fd711 --- /dev/null +++ b/app/api/tus/hooks/route.ts @@ -0,0 +1,124 @@ +import {NextResponse} from "next/server"; +import fs from "fs"; +import path from "path"; + + +// +// export async function POST(request: Request) { +// try { +// const body = await request.json(); +// console.log("tusd hook payload:", body); +// +// return NextResponse.json({ +// "Upload-Defer-Length": true, +// "Upload-Metadata": "name somefilename", +// }); +// } catch (error) { +// console.error("Error in POST handler:", error); +// return NextResponse.json( +// { error: "Internal server error" }, +// { status: 500 } +// ); +// } +// } + +// export async function POST(request: Request) { +// try { +// const body = await request.json(); +// console.log("TUSD hook payload:", body); +// +// // Pre-create hook: must return Upload-Defer-Length or Upload-Length +// if (body.event === "pre-create") { +// return NextResponse.json({ +// "Upload-Defer-Length": true, // defer the upload length +// "Upload-Metadata": "name somefilename" // optional metadata +// }); +// } +// +// // Post-receive hook: handle finished uploads +// if (body.event === "post-receive") { +// console.log(`Upload finished: ${body.id}, size: ${body.upload_length}`); +// // process the file, AES encryption, move, etc. +// } +// +// return NextResponse.json({ ok: true }); // safe fallback for other events +// } catch (error) { +// console.error("Hook error:", error); +// return NextResponse.json({ error: "Internal server error" }, { status: 500 }); +// } +// } + +export async function POST(request: Request) { + try { + const body = await request.json(); + console.log("TUSD hook payload:", body); + const event = body.Event + const headers = event.HTTPRequest.Header + + // if (body.event === "pre-create") { + // return NextResponse.json({ + // "Upload-Defer-Length": true + // }); + // } + console.log("TUSD hook payload:", body.Type); + console.log("TUSD hook payload:", headers); + + if ( + body.Type === "post-receive" && + event.Upload.SizeIsDeferred === false && + event.Upload.Offset === event.Upload.Size + ) { + const id = event.Upload.ID; + // const extensionFile = headers["X-Extension"]?.[0] ?? ".bin"; + const extensionFile = ".enc"; + const filePath = path.join(process.cwd(), "/private/uploads/backups/"); + + fs.renameSync( + `${filePath}${id}`, + `${filePath}${id}${extensionFile}` + ); + + } + + + + // if (body.Type === "post-receive"){ + // // const id = event.Upload.ID + // // const extensionFile = headers["X-Extension"]?.[0] ?? ".bin"; + // // const extensionFile = ".enc"; + // // const filePath = path.join(process.cwd(), "/private/uploads/backups/"); + // // + // // fs.renameSync( + // // `${filePath}${id}`, + // // `${filePath}${id}${extensionFile}` + // // ); + // } + + // Post-receive hook: handle finished uploads + if (body.event === "post-receive") { + + + + console.log(`Upload finished: ${body.id}, size: ${body.upload_length}`); + // process the file, AES encryption, move, etc. + + const id = body.id; + const ext = body.upload.metaData?.extension ?? ".bin"; + console.log(body) + console.log("Upload finished: ", id); + console.log("Ext: ", ext); + + // fs.renameSync( + // `/data/uploads/backups/${id}`, + // `/data/uploads/backups/${id}${ext}` + // ); + + } + + + return NextResponse.json({}); // no Upload-Defer-Length + } catch (error) { + console.error("Hook error:", error); + return NextResponse.json({error: "Internal server error"}, {status: 500}); + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 28637b2b..f6e66795 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,5 +17,22 @@ services: timeout: 5s retries: 5 + tusd: + image: tusproject/tusd:v2.8.0 + ports: + - "1080:8080" + command: > + -upload-dir /data/uploads/backups + -hooks-http http://localhost:8887/api/tus/hooks + -max-size 21474836480 + + # -max-size 54975581388 + # -max-size 54975581388 + + extra_hosts: + - "localhost:host-gateway" + volumes: + - ./private/uploads/backups:/data/uploads/backups + volumes: postgres-data: diff --git a/docker/dockerfile/Dockerfile b/docker/dockerfile/Dockerfile index 6ac85701..798fe656 100644 --- a/docker/dockerfile/Dockerfile +++ b/docker/dockerfile/Dockerfile @@ -1,53 +1,177 @@ -FROM node:22-alpine AS base +#FROM node:22-alpine AS base +# +#RUN apk add --update --no-cache \ +# libc6-compat \ +# openssl \ +# tzdata +# +#FROM base AS tusd-base +# +#RUN apk add --no-cache curl ca-certificates bash \ +# && TUSD_VERSION=1.28.0 \ +# && curl -L -o /usr/local/bin/tusd https://github.com/tus/tusd/releases/download/v${TUSD_VERSION}/tusd-${TUSD_VERSION}-linux-amd64 \ +# && chmod +x /usr/local/bin/tusd +# +# +#FROM base AS build-env +# +#RUN apk add --update --no-cache \ +# build-base \ +# g++ \ +# make \ +# libtool \ +# autoconf \ +# automake +# +#RUN corepack enable && corepack prepare pnpm@latest --activate +# +# +#FROM build-env AS deps +# +#WORKDIR /app +# +#COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +# +#RUN pnpm i --frozen-lockfile +# +# +# +# +#FROM build-env AS dev +# +#COPY --from=tusd-base /usr/local/bin/tusd /usr/local/bin/tusd +# +# +#WORKDIR /app +# +#COPY --from=deps /app/node_modules ./node_modules +#COPY . . +# +#USER root +# +#RUN chmod +x /app/docker/entrypoints/app-dev-entrypoint.sh +#ENTRYPOINT ["sh","/app/docker/entrypoints/app-dev-entrypoint.sh"] +# +# +#FROM build-env AS builder +# +#WORKDIR /app +#COPY --from=deps /app/node_modules ./node_modules +#COPY . . +# +#ENV NEXT_TELEMETRY_DISABLED=1 +# +#RUN pnpm run build +# +# +#FROM base AS prod +# +#COPY --from=tusd-base /usr/local/bin/tusd /usr/local/bin/tusd +# +#WORKDIR /app +# +#ENV NODE_ENV=production +#ENV NEXT_TELEMETRY_DISABLED=1 +# +#RUN addgroup --system --gid 1001 nodejs +#RUN adduser --system --uid 1001 nextjs +# +#COPY --from=builder /app/public ./public +# +#RUN mkdir .next +#RUN chown nextjs:nodejs .next +#RUN mkdir -p /app/private/uploads +#RUN chown -R nextjs:nodejs /app/private +#RUN chown -R nextjs:nodejs /app/public +# +#COPY --from=builder /app/next.config.ts ./ +# +#COPY --from=builder /app/portabase.config.ts ./ +#COPY --from=builder /app/drizzle.config.ts ./ +#COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +#COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +#COPY --chown=nextjs:nodejs src/db ./src/db +# +#USER root +# +#COPY ./docker/entrypoints/app-prod-entrypoint.sh /app/app-prod-entrypoint.sh +#RUN chmod +x /app/app-prod-entrypoint.sh +# +#EXPOSE 80 +# +#ENV PORT=80 +# +#ENV HOSTNAME="0.0.0.0" +# +#USER nextjs +# +#ENTRYPOINT ["sh","/app/app-prod-entrypoint.sh"] -RUN apk add --update --no-cache \ - libc6-compat \ - openssl \ - tzdata + + +FROM node:22-bullseye AS base + +RUN apt-get update && apt-get install -y \ + curl \ + ca-certificates \ + bash \ + tzdata \ + libc6 \ + build-essential \ + g++ \ + make \ + autoconf \ + automake \ + libtool \ + git \ + && rm -rf /var/lib/apt/lists/* + +FROM base AS tusd-base + +ENV TUSD_VERSION=2.8.0 + +RUN apt-get update && apt-get install -y \ + curl \ + ca-certificates \ + bash \ + tar \ + gzip \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Download and extract tusd +RUN curl -L -o /tmp/tusd.tar.gz \ + https://github.com/tus/tusd/releases/download/v${TUSD_VERSION}/tusd_linux_amd64.tar.gz \ + && mkdir -p /usr/local/bin \ + && tar -xzf /tmp/tusd.tar.gz -C /tmp \ + # Move the actual binary, whatever its name/folder inside tar + && mv /tmp/*/tusd /usr/local/bin/tusd \ + && chmod +x /usr/local/bin/tusd \ + && /usr/local/bin/tusd --version FROM base AS build-env -RUN apk add --update --no-cache \ - build-base \ - g++ \ - make \ - libtool \ - autoconf \ - automake - RUN corepack enable && corepack prepare pnpm@latest --activate - - - FROM build-env AS deps WORKDIR /app COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ - RUN pnpm i --frozen-lockfile - - - FROM build-env AS dev -WORKDIR /app - COPY --from=deps /app/node_modules ./node_modules +COPY --from=tusd-base /usr/local/bin/tusd /usr/local/bin/tusd +WORKDIR /app COPY . . USER root - RUN chmod +x /app/docker/entrypoints/app-dev-entrypoint.sh + ENTRYPOINT ["sh","/app/docker/entrypoints/app-dev-entrypoint.sh"] - - - - - FROM build-env AS builder WORKDIR /app @@ -55,57 +179,37 @@ COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 - RUN pnpm run build - - - - FROM base AS prod WORKDIR /app - ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 - -RUN corepack enable && corepack prepare pnpm@latest --activate - +ENV PORT=80 +ENV HOSTNAME="0.0.0.0" RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public - -RUN mkdir .next -RUN chown nextjs:nodejs .next -RUN mkdir -p /app/private/uploads -RUN chown -R nextjs:nodejs /app/private -RUN chown -R nextjs:nodejs /app/public - COPY --from=builder /app/next.config.ts ./ -COPY --from=builder /app/server ./server -COPY --from=builder /app/src ./src - COPY --from=builder /app/portabase.config.ts ./ COPY --from=builder /app/drizzle.config.ts ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static -COPY --chown=nextjs:nodejs src/db ./src/db - +COPY --from=builder --chown=1001:1001 /app/.next/standalone ./ +COPY --from=builder --chown=1001:1001 /app/.next/static ./.next/static +COPY --chown=1001:1001 src/db ./src/db COPY --from=deps /app/node_modules ./node_modules -USER root +COPY --from=tusd-base /usr/local/bin/tusd /usr/local/bin/tusd +RUN mkdir -p .next /app/private/uploads \ + && chown -R nextjs:nodejs .next /app/private /app/public + +USER root COPY ./docker/entrypoints/app-prod-entrypoint.sh /app/app-prod-entrypoint.sh RUN chmod +x /app/app-prod-entrypoint.sh EXPOSE 80 - -ENV PORT=80 - -ENV HOSTNAME="0.0.0.0" - USER nextjs - ENTRYPOINT ["sh","/app/app-prod-entrypoint.sh"] diff --git a/docker/entrypoints/app-prod-entrypoint.sh b/docker/entrypoints/app-prod-entrypoint.sh index 901588b8..7becd81f 100644 --- a/docker/entrypoints/app-prod-entrypoint.sh +++ b/docker/entrypoints/app-prod-entrypoint.sh @@ -7,8 +7,12 @@ else echo "[WARN] No TZ provided, using default container timezone" fi -pnpm run start -#node server.js +# Start tusd as background process +mkdir -p /app/private/uploads +echo "▶ Starting tusd server..." +tusd --dir /app/private/uploads --hooks-http http://localhost:80/api/tus/hooks --port 1080 & + +node server.js exec "$@" diff --git a/next.config.ts b/next.config.ts index ffb58319..242dbb27 100644 --- a/next.config.ts +++ b/next.config.ts @@ -47,6 +47,20 @@ const nextConfig: NextConfig = { }, proxyClientMaxBodySize: '10gb', }, + async rewrites() { + return [ + { + source: '/tus/metrics', + destination: 'http://localhost:1080/metrics', + }, + { + source: '/tus/files', + destination: 'http://localhost:1080/files', + }, + ] + }, + + async headers() { return [ { diff --git a/package.json b/package.json index 94c40ae7..ecef4faf 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,10 @@ "version": "1.2.5-rc.8", "private": true, "scripts": { - "dev": "NODE_ENV=development tsx watch server/server.ts", + "dev": "next dev --turbopack -p 8887", "build": "next build --experimental-build-mode compile", - "start": "NODE_ENV=production tsx server/server.ts", + "start": "next start", + "lint": "next lint", "email": "email dev --dir ./src/components/emails", diff --git a/proxy.ts b/proxy.ts index 9529890f..0541851d 100644 --- a/proxy.ts +++ b/proxy.ts @@ -56,6 +56,7 @@ function checkRouteExists(pathname: string) { /^\/api\/agent\/[^/]+\/restore\/?$/, /^\/api\/files\/images\/[^/]+\/?$/, /^\/api\/files\/backups\/?$/, + /^\/api\/tus\/hooks\/?$/, /^\/api\/events\/?$/, /^\/api\/init\/?$/, /^\/api\/config\/?$/, diff --git a/src/features/api/upload/helpers/common.ts b/src/features/api/upload/helpers/common.ts index 45d473c3..cebbc76d 100644 --- a/src/features/api/upload/helpers/common.ts +++ b/src/features/api/upload/helpers/common.ts @@ -77,6 +77,7 @@ export default async function uploadTempFileToProviders( ? await dispatchStorage(input, policy.id) : await dispatchStorage(input, undefined, policy.storageChannelId); } catch (err: any) { + console.error(err); result = {success: false, provider: null, error: err.message}; } await db.update(drizzleDb.schemas.backupStorage)