From 5ebea988faa8d4526ab91e861c010b09530c344d Mon Sep 17 00:00:00 2001 From: CJ_Clippy Date: Fri, 31 Jan 2025 01:18:15 -0800 Subject: [PATCH] opentracker dockerfile created --- config/deploy.yml | 27 ++- devbox.json | 3 +- devbox.lock | 48 ++++++ docker-compose.yml | 54 +++++- dockerfiles/artisan.dockerfile | 28 ---- dockerfiles/bot.dockerfile | 32 ---- dockerfiles/bright.dockerfile | 4 +- dockerfiles/capture.dockerfile | 57 ------- dockerfiles/factory.dockerfile | 63 ------- dockerfiles/htmx.dockerfile | 41 ----- dockerfiles/link2cid.dockerfile | 14 -- dockerfiles/mailbox.dockerfile | 58 ------- dockerfiles/migrations-data.dockerfile | 20 --- dockerfiles/migrations-schema.dockerfile | 20 --- dockerfiles/next.dockerfile | 55 ------ dockerfiles/opentracker.dockerfile | 75 +++++++++ dockerfiles/scout.dockerfile | 43 ----- dockerfiles/uppy.dockerfile | 15 -- packages/opentracker/opentracker.conf | 156 ++++++++++++++++++ .../init-opentracker/dependencies.d/base | 0 .../s6-rc.d/init-opentracker/script | 10 ++ .../s6-overlay/s6-rc.d/init-opentracker/type | 1 + .../s6-overlay/s6-rc.d/init-opentracker/up | 1 + .../dependencies.d/init-opentracker | 0 .../s6-overlay/s6-rc.d/svc-opentracker/run | 2 + .../s6-overlay/s6-rc.d/svc-opentracker/type | 1 + .../svc-socat/dependencies.d/init-opentracker | 0 .../root/etc/s6-overlay/s6-rc.d/svc-socat/run | 2 + .../etc/s6-overlay/s6-rc.d/svc-socat/type | 1 + .../s6-rc.d/user/contents.d/init-opentracker | 0 .../s6-rc.d/user/contents.d/svc-opentracker | 0 .../s6-rc.d/user/contents.d/svc-socat | 0 packages/opentracker/whitelist | 1 + services/bright/config/runtime.exs | 4 +- services/bright/config/test.exs | 1 + services/bright/lib/bright/b2.ex | 29 ++-- services/bright/lib/bright/cache.ex | 7 - .../lib/bright/oban_workers/create_torrent.ex | 34 ++++ .../lib/bright/oban_workers/process_vod.ex | 5 +- services/bright/lib/bright/rss.ex | 24 +++ services/bright/lib/bright/streams.ex | 10 ++ services/bright/lib/bright/streams/vod.ex | 5 +- services/bright/lib/bright/torrent.ex | 44 +++++ services/bright/lib/bright/torrentfile.ex | 93 +++++++++++ services/bright/lib/bright/torrents.ex | 104 ++++++++++++ .../bright/lib/bright/torrents/torrent.ex | 20 +++ services/bright/lib/bright/tracker.ex | 39 +++++ .../components/layouts/root.html.heex | 1 + .../controllers/page_html/about.html.heex | 14 +- .../bright_web/controllers/rss_controller.ex | 62 +++++++ .../controllers/rss_xml/index.xml.eex | 26 +++ services/bright/lib/bright_web/router.ex | 5 + services/bright/mix.exs | 1 + services/bright/mix.lock | 4 + .../20250130015306_add_magnet_link.exs | 10 ++ .../20250131034913_add_info_hash_v2.exs | 11 ++ .../20250131091405_create_torrent.exs | 14 ++ services/bright/test/bright/b2_test.exs | 11 ++ services/bright/test/bright/cache_test.ex | 25 ++- .../oban_workers/create_torrent_test.exs | 30 ++-- services/bright/test/bright/torrent_test.exs | 49 ++++++ .../bright/test/bright/torrentfile_test.ex | 95 +++++++++++ services/bright/test/bright/torrents_test.exs | 65 ++++++++ services/bright/test/bright/tracker_test.exs | 46 ++++++ .../bright/test/fixtures/123-test-fixture.ts | Bin 0 -> 1832436 bytes .../support/fixtures/torrents_fixtures.ex | 23 +++ 66 files changed, 1248 insertions(+), 495 deletions(-) delete mode 100644 dockerfiles/artisan.dockerfile delete mode 100644 dockerfiles/bot.dockerfile delete mode 100644 dockerfiles/capture.dockerfile delete mode 100644 dockerfiles/factory.dockerfile delete mode 100644 dockerfiles/htmx.dockerfile delete mode 100644 dockerfiles/link2cid.dockerfile delete mode 100644 dockerfiles/mailbox.dockerfile delete mode 100644 dockerfiles/migrations-data.dockerfile delete mode 100644 dockerfiles/migrations-schema.dockerfile delete mode 100644 dockerfiles/next.dockerfile create mode 100644 dockerfiles/opentracker.dockerfile delete mode 100644 dockerfiles/scout.dockerfile delete mode 100644 dockerfiles/uppy.dockerfile create mode 100644 packages/opentracker/opentracker.conf create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/dependencies.d/base create mode 100755 packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/script create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/type create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/up create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/dependencies.d/init-opentracker create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/run create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/type create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/dependencies.d/init-opentracker create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/run create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/type create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-opentracker create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-opentracker create mode 100644 packages/opentracker/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-socat create mode 100644 packages/opentracker/whitelist create mode 100644 services/bright/lib/bright/oban_workers/create_torrent.ex create mode 100644 services/bright/lib/bright/rss.ex create mode 100644 services/bright/lib/bright/torrent.ex create mode 100644 services/bright/lib/bright/torrentfile.ex create mode 100644 services/bright/lib/bright/torrents.ex create mode 100644 services/bright/lib/bright/torrents/torrent.ex create mode 100644 services/bright/lib/bright/tracker.ex create mode 100644 services/bright/lib/bright_web/controllers/rss_controller.ex create mode 100644 services/bright/lib/bright_web/controllers/rss_xml/index.xml.eex create mode 100644 services/bright/priv/repo/migrations/20250130015306_add_magnet_link.exs create mode 100644 services/bright/priv/repo/migrations/20250131034913_add_info_hash_v2.exs create mode 100644 services/bright/priv/repo/migrations/20250131091405_create_torrent.exs create mode 100644 services/bright/test/bright/torrent_test.exs create mode 100644 services/bright/test/bright/torrentfile_test.ex create mode 100644 services/bright/test/bright/torrents_test.exs create mode 100644 services/bright/test/bright/tracker_test.exs create mode 100644 services/bright/test/fixtures/123-test-fixture.ts create mode 100644 services/bright/test/support/fixtures/torrents_fixtures.ex diff --git a/config/deploy.yml b/config/deploy.yml index b203dc4..8e5dd33 100644 --- a/config/deploy.yml +++ b/config/deploy.yml @@ -94,12 +94,27 @@ ssh: # accessories: - # superstreamer: - # host: 45.76.57.101 - # env: - # clear: - # PUBLIC_API_ENDPOINT: https://api.superstreamer.futureporn.net - # PUBLIC_STITCHER_ENDPOINT: http://localhost:52002 + qbittorrent: + image: lscr.io/linuxserver/qbittorrent:latest + host: 45.76.57.101 + port: "127.0.0.1:8080:8080" + env: + clear: + PUID: "1000" + PGID: "1000" + TZ: "Etc/UTC" + WEBUI_PORT: "8080" + TORRENTING_PORT: "6881" + proxy: + ssl: true + forward_headers: true + app_port: 8080 + host: qbittorrent.futureporn.net + healthcheck: + path: / + volumes: + - /root/.cache/futureporn:/root/.cache/futureporn + db: image: postgres:15 diff --git a/devbox.json b/devbox.json index af53baf..0cada25 100644 --- a/devbox.json +++ b/devbox.json @@ -11,7 +11,8 @@ "ruby@latest", "chisel@latest", "bento4@latest", - "shaka-packager@latest" + "shaka-packager@latest", + "mktorrent@latest" ], "env": { "DEVBOX_COREPACK_ENABLED": "true", diff --git a/devbox.lock b/devbox.lock index 65fd043..7e19039 100644 --- a/devbox.lock +++ b/devbox.lock @@ -341,6 +341,54 @@ } } }, + "mktorrent@latest": { + "last_modified": "2025-01-25T23:17:58Z", + "resolved": "github:NixOS/nixpkgs/b582bb5b0d7af253b05d58314b85ab8ec46b8d19#mktorrent", + "source": "devbox-search", + "version": "1.1", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/lwa8h4w9jicy7c67bhnmv78vlix19ma1-mktorrent-1.1", + "default": true + } + ], + "store_path": "/nix/store/lwa8h4w9jicy7c67bhnmv78vlix19ma1-mktorrent-1.1" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/iq1mqwjl37dlzaxli3dnj4lv1bhi6vaf-mktorrent-1.1", + "default": true + } + ], + "store_path": "/nix/store/iq1mqwjl37dlzaxli3dnj4lv1bhi6vaf-mktorrent-1.1" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/di0fgl55xp7pwjfi0zgxywn8ky36ijar-mktorrent-1.1", + "default": true + } + ], + "store_path": "/nix/store/di0fgl55xp7pwjfi0zgxywn8ky36ijar-mktorrent-1.1" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/rrdq0l681zc8ljlymq7i5jsq7sp2xrrr-mktorrent-1.1", + "default": true + } + ], + "store_path": "/nix/store/rrdq0l681zc8ljlymq7i5jsq7sp2xrrr-mktorrent-1.1" + } + } + }, "nodejs@20": { "last_modified": "2024-12-23T21:10:33Z", "plugin_version": "0.0.2", diff --git a/docker-compose.yml b/docker-compose.yml index cc7b53e..f4249fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,52 @@ services: opentracker: - image: anthonyzou/opentracker:latest + build: + context: . + dockerfile: dockerfiles/opentracker.dockerfile + container_name: opentracker + environment: + - WHITELIST_FEED_URL=http://bright:4000/torrents/whitelist?type=json ports: - "6969:6969/tcp" - "6969:6969/udp" + - "8666:8666/tcp" volumes: - - ./packages/opentracker/opentracker.conf:/etc/opentracker.conf:ro + - opentracker-etc:/etc/opentracker + - opentracker-var:/var/run/opentracker + + # qbittorrent: + # build: + # context: . + # dockerfile: dockerfiles/qbittorrent.dockerfile + # environment: + # - PUID=1000 + # - PGID=1000 + # - UMASK=002 + # - TZ=Etc/UTC + # - WEBUI_PORT=8181 + # - WEBUI_PASSWORD=passwordpassword + # volumes: + # - cache:/root/.cache/futureporn + # container_name: qbittorrent + # ports: + # - "8181:8181/tcp" + + # ## socat for exposing opentracker's named pipe (adder.fifo) to the docker network + # ## we use the named pipe to update the list of whitelisted torrents without having to reload the entire (huge) whitelist + # opentracker-socat: + # build: + # context: . + # dockerfile: dockerfiles/opentracker-socat.dockerfile + # container_name: opentracker-socat + # ports: + # - '8666:8666/tcp' + # volumes: + # # we use this volume to share adder.fifo + # - opentracker-var:/var/run/opentracker + # depends_on: + # - opentracker + bright: container_name: bright @@ -22,6 +62,8 @@ services: DATABASE_HOSTNAME: db SUPERSTREAMER_URL: http://superstreamer-api:52001 PUBLIC_S3_ENDPOINT: https://fp-dev.b-cdn.net + BT_TRACKER_URL: https://tracker.futureporn.net/announce + SITE_URL: https://futureporn.net env_file: - .kamal/secrets.development ports: @@ -29,8 +71,8 @@ services: depends_on: - db # volumes: + # - cache:/root/.cache/futureporn # - ./services/bright/lib:/app/lib - # volumes: # - /home/cj/Documents/ueberauth_patreon:/app/contrib/ueberauth_patreon develop: watch: @@ -74,6 +116,7 @@ services: - '5432:5432' pgadmin: + container_name: pgadmin image: dpage/pgadmin4 ports: - '5050:5050' @@ -86,4 +129,7 @@ services: volumes: pg_data: - redis_data: \ No newline at end of file + redis_data: + cache: + opentracker-var: + opentracker-etc: \ No newline at end of file diff --git a/dockerfiles/artisan.dockerfile b/dockerfiles/artisan.dockerfile deleted file mode 100644 index afbade9..0000000 --- a/dockerfiles/artisan.dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -## Important! Build context is the ROOT of the project. -## this keeps the door open for future possibility of shared code between pnpm workspace packages - - -FROM oven/bun:1 AS base -RUN apt-get update && apt-get install -y \ - curl - -RUN mkdir -p /tmp/dev -WORKDIR /tmp/dev -COPY ./contrib/superstreamer . -RUN ls -la - -# Install ffmpeg, ffprobe -RUN bun run install-bin - - -FROM oven/bun:1 AS install -RUN bun install -RUN bun run test -RUN bun run build -USER bun -EXPOSE 7991/tcp -WORKDIR /tmp/dev/packages/artisan -RUN ls -la ./dist -ENTRYPOINT [ "bun", "run", "./dist/index.js" ] - - diff --git a/dockerfiles/bot.dockerfile b/dockerfiles/bot.dockerfile deleted file mode 100644 index 2779490..0000000 --- a/dockerfiles/bot.dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM node:20 AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -WORKDIR /app -RUN corepack enable && corepack prepare pnpm@9.6.0 --activate -ENTRYPOINT ["pnpm"] - -FROM base AS install -COPY pnpm-lock.yaml .npmrc package.json . -COPY ./services/bot/ ./services/bot/ -COPY ./packages/types/ ./packages/types/ -COPY ./packages/utils/ ./packages/utils/ -COPY ./packages/fetchers/ ./packages/fetchers/ - -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline - - -FROM install AS build -RUN pnpm -r build -RUN pnpm deploy --filter=bot --prod /prod/bot - - -FROM install AS dev -WORKDIR /app/services/bot -CMD ["run", "dev"] - - -FROM base AS bot -COPY --from=build /prod/bot . -CMD ["run", "start"] - diff --git a/dockerfiles/bright.dockerfile b/dockerfiles/bright.dockerfile index a9e6573..435782e 100644 --- a/dockerfiles/bright.dockerfile +++ b/dockerfiles/bright.dockerfile @@ -22,9 +22,11 @@ ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" FROM ${BUILDER_IMAGE} AS builder # install build dependencies -RUN apt-get update -y && apt-get install -y build-essential git inotify-tools ffmpeg \ +RUN apt-get update -y && apt-get install -y build-essential git inotify-tools ffmpeg python3 python3-pip \ + && pip install torrentfile \ && apt-get clean && rm -f /var/lib/apt/lists/*_* + # prepare build dir WORKDIR /app diff --git a/dockerfiles/capture.dockerfile b/dockerfiles/capture.dockerfile deleted file mode 100644 index e665e4d..0000000 --- a/dockerfiles/capture.dockerfile +++ /dev/null @@ -1,57 +0,0 @@ -FROM node:20-alpine AS base -## Install dependencies only when needed -## Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat -RUN corepack enable && corepack prepare pnpm@9.6.0 --activate - -## Enable `pnpm add --global` on Alpine Linux by setting -## home location environment variable to a location already in $PATH -## https://github.com/pnpm/pnpm/issues/784#issuecomment-1518582235 -ENV PNPM_HOME=/usr/local/bin - -# update and install latest dependencies, add dumb-init package -# add a non root user -RUN apk update && apk upgrade && apk add dumb-init ffmpeg make gcc g++ python3 - -## install yt-dlp -RUN wget -O /usr/local/bin/yt-dlp https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp && chmod +x /usr/local/bin/yt-dlp - -WORKDIR /app - -FROM base AS build -## Copy the manifests and lockfiles into the build context -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc . -COPY ./services/capture/package.json ./services/capture/pnpm-lock.yaml ./services/capture/ -COPY ./packages/types/package.json ./packages/types/pnpm-lock.yaml ./packages/types/ -COPY ./packages/utils/package.json ./packages/utils/pnpm-lock.yaml ./packages/utils/ -COPY ./packages/fetchers/package.json ./packages/fetchers/pnpm-lock.yaml ./packages/fetchers/ - -## install npm packages -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --prefer-offline - -## Copy in all project files -COPY ./services/capture/ ./services/capture/ -COPY ./packages/types/ ./packages/types/ -COPY ./packages/utils/ ./packages/utils/ -COPY ./packages/fetchers/ ./packages/fetchers/ - -## Run the build process and generate the artifacts -RUN pnpm run -r build -RUN mkdir -p /prod/capture -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@futureporn/capture deploy --prod /prod/capture - - - -FROM build AS dev -WORKDIR /app/services/capture -ENTRYPOINT ["pnpm", "run", "dev"] - - -## start the app with dumb init to spawn the Node.js runtime process -## with signal support -## The mode @futureporn/capture uses when starting is determined by FUNCTION environment variable. (worker|api) -FROM base AS capture -ENV HOSTNAME=0.0.0.0 NODE_ENV=production -COPY --from=build /prod/capture . -CMD [ "dumb-init", "node", "dist/index.js" ] \ No newline at end of file diff --git a/dockerfiles/factory.dockerfile b/dockerfiles/factory.dockerfile deleted file mode 100644 index 9e1311d..0000000 --- a/dockerfiles/factory.dockerfile +++ /dev/null @@ -1,63 +0,0 @@ -## factory.dockerfile -## -## @futureporn/factory is the system component which processes video segments into a VOD. -## factory does tasks such as thumbnail generation, video encoding, file transfers, strapi record creation, etc. - - -FROM node:20 AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -WORKDIR /app -COPY --from=mwader/static-ffmpeg:7.0.2 /ffmpeg /usr/local/bin/ -COPY --from=mwader/static-ffmpeg:7.0.2 /ffprobe /usr/local/bin/ -RUN corepack enable && corepack prepare pnpm@9.6.0 --activate -ENTRYPOINT ["pnpm"] - -FROM base AS install -WORKDIR /app -RUN mkdir -p /app/services/factory && mkdir -p /prod/factory - -## Copy manfiests, lockfiles, and configs into docker context -COPY package.json pnpm-lock.yaml .npmrc . -COPY ./packages/utils/pnpm-lock.yaml ./packages/utils/package.json ./packages/utils/ -COPY ./packages/fetchers/package.json ./packages/fetchers/pnpm-lock.yaml ./packages/fetchers/ -COPY ./packages/storage/pnpm-lock.yaml ./packages/storage/package.json ./packages/storage/ -COPY ./packages/types/pnpm-lock.yaml ./packages/types/package.json ./packages/types/ -COPY ./services/factory/pnpm-lock.yaml ./services/factory/package.json ./services/factory/ - -## Install npm packages -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch -## we install node-gyp explicitly in order for sharp to install properly -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install -g node-gyp --prefer-offline -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline -## Copy package code into docker context -COPY ./packages/utils/ ./packages/utils/ -COPY ./packages/fetchers/ ./packages/fetchers/ -RUN ls -la /app/packages/utils/node_modules/prevvy/ -RUn cat ./packages/utils/package.json -COPY ./packages/storage/ ./packages/storage/ -COPY ./packages/types/ ./packages/types/ -COPY ./services/factory/ ./services/factory/ -# we are grabbing the mp4 files from capture so we can run tests with them -COPY ./services/capture/src/fixtures ./services/capture/src/fixtures - - -FROM install AS build -## Transpile TS into JS -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm -r build - -## Copy all production code into one place -## `pnpm deploy` copies all dependencies into an isolated node_modules directory inside the target dir -## @see https://pnpm.io/cli/deploy -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm deploy --filter=@futureporn/factory --prod /prod/factory - -FROM install AS dev -WORKDIR /app/services/factory -RUN ls -lash -CMD ["run", "dev"] - -FROM base AS prod -COPY --from=build /prod/factory . -RUN ls -la . -CMD ["start"] - diff --git a/dockerfiles/htmx.dockerfile b/dockerfiles/htmx.dockerfile deleted file mode 100644 index ab876c5..0000000 --- a/dockerfiles/htmx.dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -## Important! Build context is the ROOT of the project. -## this keeps the door open for future possibility of shared code between pnpm workspace packages - -# use the official Bun image -# see all versions at https://hub.docker.com/r/oven/bun/tags -FROM oven/bun:1 AS base -WORKDIR /usr/src/app - -# install dependencies into temp directory -# this will cache them and speed up future builds -FROM base AS install -RUN mkdir -p /temp/dev -COPY ./services/htmx/package.json ./services/htmx/bun.lockb /temp/dev/ -RUN cd /temp/dev && bun install --frozen-lockfile - -# install with --production (exclude devDependencies) -RUN mkdir -p /temp/prod -COPY ./services/htmx/package.json ./services/htmx/bun.lockb /temp/prod/ -RUN cd /temp/prod && bun install --frozen-lockfile --production - -# copy node_modules from temp directory -# then copy all (non-ignored) project files into the image -FROM base AS prerelease -COPY --from=install /temp/dev/node_modules node_modules -COPY . . - -# [optional] tests & build -ENV NODE_ENV=production -RUN bun test -RUN bun run build - -# copy production dependencies and source code into final image -FROM base AS release -COPY --from=install /temp/prod/node_modules node_modules -COPY --from=prerelease /usr/src/app/index.ts . -COPY --from=prerelease /usr/src/app/package.json . - -# run the app -USER bun -EXPOSE 7991/tcp -ENTRYPOINT [ "bun", "run", "index.ts" ] \ No newline at end of file diff --git a/dockerfiles/link2cid.dockerfile b/dockerfiles/link2cid.dockerfile deleted file mode 100644 index 01a6a3b..0000000 --- a/dockerfiles/link2cid.dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -# Reference-- https://pnpm.io/docker - -FROM node:20-alpine AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable -WORKDIR /app -COPY ./packages/link2cid/package.json /app -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod -COPY ./packages/link2cid/index.js /app -COPY ./packages/link2cid/src /app/src -ENTRYPOINT ["pnpm"] -CMD ["start"] - diff --git a/dockerfiles/mailbox.dockerfile b/dockerfiles/mailbox.dockerfile deleted file mode 100644 index cb0b00b..0000000 --- a/dockerfiles/mailbox.dockerfile +++ /dev/null @@ -1,58 +0,0 @@ -## d.mailbox.dockerfile -## -## @todo future improvement might be merging the dockerfiles for the various monorepo packages. -## this is not an easy task, so I'm not doing it right now. -## "make it work, make it right, make it fast" (in that order) -## Right now we are making things work with separate dockerfiles for each package. -## One thing to determine is build speed. If we're developing in Tilt and have to wait 20 minutes for the build to complete -## every time we change a file in any dependent package, then merging dockerfiles is not desirable. -## One of the slow parts of the docker build is copying all package directories into the build context. -## If we have a lot of packages, it takes a long time. -## I have yet to determine performance benchmarks, so it's unclear if merging dockerfiles is desirable. -## -## @todo another performance improvement would almost certainly be to move strapi, next, and similar packages from `packages/*` into `services/*` -## this way, when we're building the various @futureporn library-type packages, we don't have to filter and COPY the dependency packages one-by-one. -## instead, we add the entire `packages/*` directory and then move on to the next step. - -FROM node:20 AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -WORKDIR /app -RUN corepack enable && corepack prepare pnpm@9.6.0 --activate - -FROM base AS build -WORKDIR /app -RUN mkdir -p /app/services/mailbox && mkdir -p /prod/mailbox - -## Copy manfiests, lockfiles, and configs into docker context -COPY package.json pnpm-lock.yaml .npmrc . -COPY ./packages/storage/pnpm-lock.yaml ./packages/storage/package.json ./packages/storage/ -COPY ./packages/types/pnpm-lock.yaml ./packages/types/package.json ./packages/types/ -COPY ./packages/utils/pnpm-lock.yaml ./packages/utils/package.json ./packages/utils/ -COPY ./services/mailbox/pnpm-lock.yaml ./services/mailbox/package.json ./services/mailbox/ - -## Install npm packages -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline - -## Copy package code into docker context -COPY ./packages/storage/ ./packages/storage/ -COPY ./packages/types/ ./packages/types/ -COPY ./packages/utils/ ./packages/utils/ -COPY ./services/mailbox/ ./services/mailbox/ - -## Transpile TS into JS -RUN pnpm --filter=@futureporn/mailbox build -# RUN pnpm -r build - -## Copy all production code into one place -## `pnpm deploy` copies all dependencies into an isolated node_modules directory inside the target dir -## @see https://pnpm.io/cli/deploy -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm deploy --filter=@futureporn/mailbox --prod /prod/mailbox - - -FROM base AS mailbox -COPY --from=build /prod/mailbox . -RUN ls -la . -ENTRYPOINT ["pnpm", "start"] - diff --git a/dockerfiles/migrations-data.dockerfile b/dockerfiles/migrations-data.dockerfile deleted file mode 100644 index 8c9773e..0000000 --- a/dockerfiles/migrations-data.dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM node:20-alpine AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -WORKDIR /app -RUN corepack enable && corepack prepare pnpm@9.6.0 --activate - -FROM base AS build -COPY ./pnpm-workspace.yaml ./.npmrc . -COPY ./services/migrations-data/package.json ./services/migrations-data/pnpm-lock.yaml ./services/migrations-data/ -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --prefer-offline -COPY ./services/migrations-data/ ./services/migrations-data/ -RUN pnpm --filter=@futureporn/migrations-data deploy --prod /prod/migrations-data -RUN ls -las /prod/migrations-data - -FROM base AS migrations-data -ENV NODE_ENV=production -COPY --from=build /prod/migrations-data . -ENTRYPOINT ["pnpm", "start"] - diff --git a/dockerfiles/migrations-schema.dockerfile b/dockerfiles/migrations-schema.dockerfile deleted file mode 100644 index 106a162..0000000 --- a/dockerfiles/migrations-schema.dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM node:20-alpine AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -WORKDIR /app -RUN corepack enable && corepack prepare pnpm@9.6.0 --activate - -FROM base AS build -COPY ./pnpm-workspace.yaml ./.npmrc . -COPY ./services/migrations-schema/package.json ./services/migrations-schema/pnpm-lock.yaml ./services/migrations-schema/ -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --prefer-offline -COPY ./services/migrations-schema/ ./services/migrations-schema/ -RUN pnpm --filter=@futureporn/migrations-schema deploy --prod /prod/migrations-schema -RUN ls -las /prod/migrations-schema - -FROM base AS migrations-schema -ENV NODE_ENV=production -COPY --from=build /prod/migrations-schema . -ENTRYPOINT ["pnpm", "start"] - diff --git a/dockerfiles/next.dockerfile b/dockerfiles/next.dockerfile deleted file mode 100644 index f8a3afb..0000000 --- a/dockerfiles/next.dockerfile +++ /dev/null @@ -1,55 +0,0 @@ -## Important! Build context is the ROOT of the project. -## this keeps the door open for future possibility of shared code between pnpm workspace packages - -FROM node:20-slim AS base - -FROM base AS deps -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable && corepack prepare pnpm@9.6.0 --activate -WORKDIR /app - - -FROM deps AS install -ARG NEXT_PUBLIC_SITE_URL=https://futureporn.net -ARG NEXT_PUBLIC_STRAPI_URL=https://portal.futureporn.net -ARG NEXT_PUBLIC_UPPY_COMPANION_URL=https://uppy.futureporn.net -ENV NEXT_PUBLIC_SITE_URL ${NEXT_PUBLIC_SITE_URL} -ENV NEXT_PUBLIC_STRAPI_URL ${NEXT_PUBLIC_STRAPI_URL} -ENV NEXT_PUBLIC_UPPY_COMPANION_URL ${NEXT_PUBLIC_UPPY_COMPANION_URL} -ENV NEXT_TELEMETRY_DISABLED 1 -COPY ./certs ./certs -COPY pnpm-lock.yaml ./ -RUN pnpm fetch -# COPY pnpm-lock.yaml .npmrc package.json . -COPY ./services/next ./services/next -COPY ./packages/types ./packages/types -COPY ./packages/fetchers ./packages/fetchers -COPY ./packages/utils ./packages/utils -# COPY ./packages/strapi ./packages/strapi - -RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline - - -FROM install AS dev -WORKDIR /app/services/next -CMD ["pnpm", "run", "dev"] - -FROM install AS build -RUN pnpm run -r build -# RUN pnpm --filter=next deploy --prod /prod/next ## using `pnpm deploy` doesn't work and I haven't worked out why -RUN echo "next we are next we are" -RUN ls -lash /app - -FROM deps AS next -RUN apt-get update && apt-get install -y -qq --no-install-recommends dumb-init -COPY --chown=node:node --from=build /app/services/next/package.json /app/services/next/pnpm-lock.yaml ./ -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile -COPY --chown=node:node --from=build /app/services/next/public ./public -COPY --chown=node:node --from=build /app/services/next/.next/standalone ./ -COPY --chown=node:node --from=build /app/services/next/.next/static ./.next/static -ENV TZ=UTC -ENV NODE_ENV=production -ENV HOSTNAME="0.0.0.0" -CMD [ "dumb-init", "node", "server.js" ] - diff --git a/dockerfiles/opentracker.dockerfile b/dockerfiles/opentracker.dockerfile new file mode 100644 index 0000000..bca2a18 --- /dev/null +++ b/dockerfiles/opentracker.dockerfile @@ -0,0 +1,75 @@ +# +# Based on wiltonsr/opentracker-docker @see https://github.com/wiltonsr/opentracker-docker/tree/main +# ours uses -DWANT_DYNAMIC_ACCESSLIST for incremental whitelist updates via named pipe +# +FROM gcc:14 AS compile-stage + +RUN apt update ; \ + apt install cvs -y + +RUN adduser \ + --system --disabled-login \ + --uid 6969 --group \ + --home /etc/opentracker \ + farmhand + +WORKDIR /usr/src + +# Run libowfat compilation in separated layer to benefit from docker layer cache +RUN cvs -d :pserver:cvs@cvs.fefe.de:/cvs -z9 co libowfat ; \ + git clone git://erdgeist.org/opentracker ; \ + cd /usr/src/libowfat ; \ + make + +# http://erdgeist.org/arts/software/opentracker/#build-instructions +RUN cd /usr/src/opentracker ; \ + # Build opentracker statically to use it in scratch image + LDFLAGS=-static make \ + FEATURES+=-DWANT_FULLSCRAPE \ + FEATURES+=-DWANT_FULLLOG_NETWORKS \ + FEATURES+=-DWANT_LOG_NUMWANT \ + FEATURES+=-DWANT_MODEST_FULLSCRAPES \ + FEATURES+=-DWANT_SPOT_WOODPECKER \ + FEATURES+=-DWANT_ACCESSLIST_WHITE \ + FEATURES+=-DWANT_DYNAMIC_ACCESSLIST \ + ;\ + bash -c 'mkdir -pv /tmp/stage/{etc/opentracker,bin}' ; \ + bash -c 'touch /tmp/stage/etc/opentracker/{white,black}list' ; \ + cp -v opentracker.conf.sample /tmp/stage/etc/opentracker/opentracker.conf ; \ + # Opentrack configuration file + sed -ri \ + -e 's!(.*)(tracker.user)(.*)!\2 farmhand!g;' \ + -e 's!(.*)(access.whitelist)(.*)!\2 /etc/opentracker/whitelist!g;' \ + /tmp/stage/etc/opentracker/opentracker.conf ; \ + install -m 755 opentracker.debug /tmp/stage/bin ; \ + make DESTDIR=/tmp/stage BINDIR="/bin" install + + + +FROM alpine + +RUN apk add --no-cache curl bash socat +ARG S6_OVERLAY_VERSION=v3.2.0.2 + + +COPY --from=compile-stage /tmp/stage / +COPY --from=compile-stage /etc/passwd /etc/passwd +COPY ./packages/opentracker/opentracker.conf /etc/opentracker/opentracker.conf +COPY ./packages/opentracker/root/ / + +WORKDIR /etc/opentracker + + + + +EXPOSE 6969/udp +EXPOSE 6969/tcp + +## use s6-overlay +ADD https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp +RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz +ADD https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp +RUN tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz +ENTRYPOINT /init +# CMD ["/etc/s6-overlay/s6-rc.d/svc-opentracker/run"] # IDK if this is correct +# USER 6969 # I think we can instead drop privs via s6 \ No newline at end of file diff --git a/dockerfiles/scout.dockerfile b/dockerfiles/scout.dockerfile deleted file mode 100644 index 18f6cd3..0000000 --- a/dockerfiles/scout.dockerfile +++ /dev/null @@ -1,43 +0,0 @@ -FROM node:20 AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -WORKDIR /app -RUN curl -s https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest | grep "browser_download_url.*yt-dlp_linux\"" | cut -d : -f 2,3 | tr -d "\"" | wget -q -O /usr/local/bin/yt-dlp -i - && chmod +x /usr/local/bin/yt-dlp -## @important If pnpm is downloading node during the build, that's a bandwidth-expensive mistake. -## Node already exists in the docker image at /usr/local/bin/node. -## We should use the node version that exists in the docker image. -## The only thing that should be downloaded by corepack is pnpm. -## The reason we explicitly set a pnpm version here is because we want to have pnpm cached. -## We haven't copied any .npmrc or package.json files at this point in the build, so corepack has no way of knowing which version to get. -## There might be a more optimal way of doing this that doesn't require syncing this version with the version in package.json -## but I'm not sure what that would look like. -## -## @important match the pnpm version between all pnpm workspace packages or multiple versions of pnpm will get installed (slow) -RUN corepack enable && corepack prepare pnpm@9.6.0 --activate -ENTRYPOINT ["pnpm"] - - -FROM base AS install -COPY pnpm-lock.yaml .npmrc package.json . -COPY ./services/scout/ ./services/scout/ -COPY ./packages/types/ ./packages/types/ -COPY ./packages/utils/ ./packages/utils/ -COPY ./packages/fetchers/ ./packages/fetchers/ -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline - - -FROM install AS build -RUN pnpm -r build -RUN pnpm deploy --filter=scout --prod /prod/scout - - -FROM install AS dev -WORKDIR /app/services/scout -CMD ["run", "dev"] - - -FROM base AS prod -COPY --from=build /prod/scout . -CMD ["run", "start"] - diff --git a/dockerfiles/uppy.dockerfile b/dockerfiles/uppy.dockerfile deleted file mode 100644 index b90b86d..0000000 --- a/dockerfiles/uppy.dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:20-alpine as base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -WORKDIR /app -RUN corepack enable - -FROM base as build -COPY ./packages/uppy/package.json ./packages/uppy/pnpm-lock.yaml /app -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile -COPY ./packages/uppy/ . - -FROM build as run -ENTRYPOINT ["pnpm"] -CMD ["start"] - diff --git a/packages/opentracker/opentracker.conf b/packages/opentracker/opentracker.conf new file mode 100644 index 0000000..918dc7b --- /dev/null +++ b/packages/opentracker/opentracker.conf @@ -0,0 +1,156 @@ +# opentracker config file +# + +# I) Address opentracker will listen on, using both, tcp AND udp family +# (note, that port 6969 is implicit if omitted). +# +# If no listen option is given (here or on the command line), opentracker +# listens on 0.0.0.0:6969 tcp and udp. +# +# The next variable determines if udp sockets are handled in the event +# loop (set it to 0, the default) or are handled in blocking reads in +# dedicated worker threads. You have to set this value before the +# listen.tcp_udp or listen.udp statements before it takes effect, but you +# can re-set it for each listen statement. Normally you should keep it at +# the top of the config file. +# +# listen.udp.workers 4 +# +listen.tcp_udp 0.0.0.0 +# listen.tcp_udp 192.168.0.1:80 +# listen.tcp_udp 10.0.0.5:6969 +# +# To only listen on tcp or udp family ports, list them this way: +# +# listen.tcp 0.0.0.0 +# listen.udp 192.168.0.1:6969 +# listen.tcp 127.0.0.1 +# listen.udp 127.0.0.1:6969 +# +# Note, that using 0.0.0.0 for udp sockets may yield surprising results. +# An answer packet sent on that socket will not necessarily have the +# source address that the requesting client may expect, but any address +# on that interface. +# + +# II) If opentracker runs in a non-open mode, point it to files containing +# all torrent hashes that it will serve (shell option -w) +# +access.whitelist /etc/opentracker/whitelist +# +# or, if opentracker was compiled to allow blacklisting (shell option -b) +# +# access.blacklist ./blacklist +# +# It is pointless and hence not possible to compile black AND white +# listing, so choose one of those options at compile time. File format +# is straight forward: "\n\n..." +# +# IIa) You can enable dynamic changesets to accesslists by enabling +# WANT_DYNAMIC_ACCESSLIST. +# +# The suggested way to work with dynamic changeset lists is to keep a +# main accesslist file that is loaded when opentracker (re)starts and +# reloaded infrequently (hourly or daily). +# +# All changes to the accesslist (e.g. from a web frontend) should be +# both appended to or removed from that file and sent to opentracker. By +# keeping dynamic changeset lists, you can avoid reloading huge +# accesslists whenever just a single entry is added or removed. +# +# Any info_hash (format see above) written to the fifo_add file will be +# kept on a dynamic add-changeset, removed from the dynamic +# delete-changeset and treated as if it was in the main accesslist file. +# The semantic of the respective dynamic changeset depends on whether +# WANT_ACCESSLIST_WHITE or WANT_ACCESSLIST_BLACK is enabled. +# +access.fifo_add /var/run/opentracker/adder.fifo +# +# Any info_hash (format see above) written to the fifo_delete file will +# be kept on a dynamic delete-changeset, removed from the dynamic +# add-changeset and treated as if it was not in the main accesslist +# file. +# +# access.fifo_delete /var/run/opentracker/deleter.fifo +# +# If you reload the accesslist by sending SIGHUP to the tracker process, +# the dynamic lists are flushed, as opentracker assumes thoses lists are +# merged into the main accesslist. +# +# NOTE: While you can have multiple writers sending lines to the fifos, +# any writes larger than PIPE_BUF (see your limits.h, minimally 512 +# bytes but usually 4096) may be interleaved with data sent by other +# writers. This can lead to unparsable lines of info_hashes. +# +# IIb) +# If you do not want to grant anyone access to your stats, enable the +# WANT_RESTRICT_STATS option in Makefile and bless the ip addresses +# or network allowed to fetch stats here. +# +# access.stats 192.168.0.23 +# access.stats 10.1.1.23 +# +# There is another way of hiding your stats. You can obfuscate the path +# to them. Normally it is located at /stats but you can configure it to +# appear anywhere on your tracker. +# +# access.stats_path stats +# +# II +# If opentracker lives behind one or multiple reverse proxies, +# every http connection appears to come from these proxies. In order to +# take the X-Forwarded-For address instead, compile opentracker with the +# WANT_IP_FROM_PROXY option and set your proxy addresses or networkss here. +# +# access.proxy 10.0.1.23 +# access.proxy 192.0.0.0/8 +# + +# III) Live sync uses udp multicast packets to keep a cluster of opentrackers +# synchronized. This option tells opentracker which port to listen for +# incoming live sync packets. The ip address tells opentracker, on which +# interface to join the multicast group, those packets will arrive. +# (shell option -i 192.168.0.1 -s 9696), port 9696 is default. +# +# livesync.cluster.listen 192.168.0.1:9696 +# +# Note that two udp sockets will be opened. One on ip address 0.0.0.0 +# port 9696, that will join the multicast group 224.0.42.23 for incoming +# udp packets and one on ip address 192.168.0.1 port 9696 for outgoing +# udp packets. +# +# As of now one and only one ip address must be given, if opentracker +# was built with the WANT_SYNC_LIVE feature. +# + +# IV) Sync between trackers running in a cluster is restricted to packets +# coming from trusted ip addresses. While source ip verification is far +# from perfect, the authors of opentracker trust in the correct +# application of tunnels, filters and LAN setups (shell option -A). +# +# livesync.cluster.node_ip 192.168.0.4 +# livesync.cluster.node_ip 192.168.0.5 +# livesync.cluster.node_ip 192.168.0.6 +# +# This is the admin ip address for old style (HTTP based) asynchronus +# tracker syncing. +# +# batchsync.cluster.admin_ip 10.1.1.1 +# + +# V) Control privilege drop behaviour. +# Put in the directory opentracker will chroot/chdir to. All black/white +# list files must be put in that directory (shell option -d). +# +# +# tracker.rootdir /usr/local/etc/opentracker +# +# Tell opentracker which user to setuid to. +# +tracker.user farmhand +# + +# VI) opentracker can be told to answer to a "GET / HTTP"-request with a +# redirect to another location (shell option -r). +# +# tracker.redirect_url https://your.tracker.local/ diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/dependencies.d/base b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/dependencies.d/base new file mode 100644 index 0000000..e69de29 diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/script b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/script new file mode 100755 index 0000000..a71d4b6 --- /dev/null +++ b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/script @@ -0,0 +1,10 @@ +#!/command/with-contenv sh + +if [ -z "$WHITELIST_FEED_URL" ]; then + echo "Error: WHITELIST_FEED_URL is not set" >&2 + exit 1 +fi + +mkdir -p /var/run/opentracker +mkfifo -m a+rw /var/run/opentracker/adder.fifo +curl -sS "$WHITELIST_FEED_URL" -o /etc/opentracker/whitelist \ No newline at end of file diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/type b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/type new file mode 100644 index 0000000..3d92b15 --- /dev/null +++ b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/type @@ -0,0 +1 @@ +oneshot \ No newline at end of file diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/up b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/up new file mode 100644 index 0000000..ec63558 --- /dev/null +++ b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/init-opentracker/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-opentracker/script \ No newline at end of file diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/dependencies.d/init-opentracker b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/dependencies.d/init-opentracker new file mode 100644 index 0000000..e69de29 diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/run b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/run new file mode 100644 index 0000000..e62a52d --- /dev/null +++ b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/run @@ -0,0 +1,2 @@ +#!/bin/sh +exec s6-setuidgid farmhand /bin/opentracker -f /etc/opentracker/opentracker.conf \ No newline at end of file diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/type b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/type new file mode 100644 index 0000000..1780f9f --- /dev/null +++ b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-opentracker/type @@ -0,0 +1 @@ +longrun \ No newline at end of file diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/dependencies.d/init-opentracker b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/dependencies.d/init-opentracker new file mode 100644 index 0000000..e69de29 diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/run b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/run new file mode 100644 index 0000000..de2731e --- /dev/null +++ b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/run @@ -0,0 +1,2 @@ +#!/bin/sh +exec s6-setuidgid farmhand socat -v -u TCP-LISTEN:8666,fork OPEN:/var/run/opentracker/adder.fifo,append diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/type b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/type new file mode 100644 index 0000000..1780f9f --- /dev/null +++ b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/svc-socat/type @@ -0,0 +1 @@ +longrun \ No newline at end of file diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-opentracker b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-opentracker new file mode 100644 index 0000000..e69de29 diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-opentracker b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-opentracker new file mode 100644 index 0000000..e69de29 diff --git a/packages/opentracker/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-socat b/packages/opentracker/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-socat new file mode 100644 index 0000000..e69de29 diff --git a/packages/opentracker/whitelist b/packages/opentracker/whitelist new file mode 100644 index 0000000..9a5c683 --- /dev/null +++ b/packages/opentracker/whitelist @@ -0,0 +1 @@ +723886c0b0d9d41bfaa5276a9b2552d84ba09dd8a77d9ddcab5c9fa16cdb9770 \ No newline at end of file diff --git a/services/bright/config/runtime.exs b/services/bright/config/runtime.exs index 8b76c73..c9382a1 100644 --- a/services/bright/config/runtime.exs +++ b/services/bright/config/runtime.exs @@ -29,7 +29,9 @@ config :bright, superstreamer_url: System.get_env("SUPERSTREAMER_URL"), superstreamer_auth_token: System.get_env("SUPERSTREAMER_AUTH_TOKEN"), public_s3_endpoint: System.get_env("PUBLIC_S3_ENDPOINT"), - s3_cdn_endpoint: System.get_env("PUBLIC_S3_ENDPOINT") + s3_cdn_endpoint: System.get_env("PUBLIC_S3_ENDPOINT"), + bittorrent_tracker_url: System.get_env("BT_TRACKER_URL"), + site_url: System.get_env("SITE_URL") config :bright, :buckets, media: System.get_env("AWS_BUCKET") diff --git a/services/bright/config/test.exs b/services/bright/config/test.exs index 932dc3f..cdac7a2 100644 --- a/services/bright/config/test.exs +++ b/services/bright/config/test.exs @@ -15,6 +15,7 @@ config :bright, Bright.Repo, username: "postgres", password: "password", hostname: System.cmd("docker", ["inspect", "--format", "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}", "futureporn-db"]) |> elem(0) |> String.trim(), + # hostname: "futureporn-db", database: "bright_test#{System.get_env("MIX_TEST_PARTITION")}", pool: Ecto.Adapters.SQL.Sandbox, pool_size: System.schedulers_online() * 2 diff --git a/services/bright/lib/bright/b2.ex b/services/bright/lib/bright/b2.ex index 28cf7b7..535b461 100644 --- a/services/bright/lib/bright/b2.ex +++ b/services/bright/lib/bright/b2.ex @@ -6,11 +6,12 @@ defmodule Bright.B2 do require Logger alias ExAws.S3 - - alias Bright.Repo - alias Bright.Cache - - alias Bright.B2 + alias Bright.{ + Repo, + Cache, + B2 + } + alias Bright.Streams.Vod @doc """ Put a file from local disk to Backblaze. This function uses the filename as the S3 key. Use put/2 if you want to specify the key @@ -52,26 +53,28 @@ defmodule Bright.B2 do end + def get(%Vod{} = vod) do + object_key = + vod.s3_cdn_url + |> URI.parse() + |> Map.get(:path) + |> String.trim_leading("/") + local_file = Cache.generate_filename(object_key) + IO.puts "get/1 object_key=#{object_key} local_file=#{local_file}" + get(object_key, local_file) + end @doc """ Download a file from Backblaze to local disk """ def get(object_key, local_file) do - # B2.get("test/SampleVideo_1280x720_1mb.mp4", local_file) - bucket = Application.get_env(:bright, :aws_bucket) - - S3.download_file(bucket, object_key, local_file) |> ExAws.request |> case do {:ok, :done} -> {:ok, local_file} {:error, reason} -> {:error, reason} end - - - - end diff --git a/services/bright/lib/bright/cache.ex b/services/bright/lib/bright/cache.ex index ec32a92..df58374 100644 --- a/services/bright/lib/bright/cache.ex +++ b/services/bright/lib/bright/cache.ex @@ -33,13 +33,6 @@ defmodule Bright.Cache do def generate_filename(input, ext) do Path.join(@cache_dir, generate_basename(input, ext)) - - # @cache_dir - # input - # |> generate_basename - # |> Path.join(@cache_dir) - # |> Path.rootname - # |> Kernel.<>(".#{ext}") end def get_cache_dir do diff --git a/services/bright/lib/bright/oban_workers/create_torrent.ex b/services/bright/lib/bright/oban_workers/create_torrent.ex new file mode 100644 index 0000000..f9c30b7 --- /dev/null +++ b/services/bright/lib/bright/oban_workers/create_torrent.ex @@ -0,0 +1,34 @@ +defmodule Bright.ObanWorkers.CreateTorrent do + use Oban.Worker, queue: :default, max_attempts: 3 + + alias Bright.Streams + alias Bright.Streams.Vod + alias Bright.{ + Repo, + Downloader, + B2, + Images, + Cache, + Torrent, + Tracker + } + require Logger + import Ecto.Query, warn: false + + + + def perform(%Oban.Job{args: %{"vod_id" => vod_id}}) do + vod = Streams.get_vod!(vod_id) + with {:ok, filename} <- B2.get(vod), + {:ok, torrent} <- Torrent.create_torrent(vod), + {:ok, %{cdn_url: cdn_url}} <- B2.put(torrent.local_path, torrent.basename), + :ok <- Tracker.whitelist_info_hash(torrent.info_hash), + :ok <- Tracker.announce_torrent(torrent.magnet_link), + {:ok, updated_vod} <- Streams.update_vod(vod, %{torrent: cdn_url, magnet_link: torrent.magnet_link}) do + {:ok, updated_vod} + end + end + + + +end diff --git a/services/bright/lib/bright/oban_workers/process_vod.ex b/services/bright/lib/bright/oban_workers/process_vod.ex index d05d226..19f3b09 100644 --- a/services/bright/lib/bright/oban_workers/process_vod.ex +++ b/services/bright/lib/bright/oban_workers/process_vod.ex @@ -8,7 +8,8 @@ defmodule Bright.ObanWorkers.ProcessVod do alias Bright.ObanWorkers.{ CreateHlsPlaylist, CreateS3Asset, - CreateThumbnail + CreateThumbnail, + CreateTorrent, } @impl Oban.Worker @@ -24,6 +25,8 @@ defmodule Bright.ObanWorkers.ProcessVod do unless vod.thumbnail_url, do: queue_create_thumbnail(vod) end + Oban.insert!(CreateTorrent.new(%{vod_id: vod_id})) + :ok diff --git a/services/bright/lib/bright/rss.ex b/services/bright/lib/bright/rss.ex new file mode 100644 index 0000000..189de8d --- /dev/null +++ b/services/bright/lib/bright/rss.ex @@ -0,0 +1,24 @@ +defmodule Bright.RSS do + + use Timex + + def to_rfc822(date) do + date + |> Timezone.convert("UTC") + |> Timex.format!("{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} {Zname}") + end +end + + + +# which is better? + +# date +# |> Timezone.convert("GMT") +# |> Timex.format!("{RFC822}") +# # Wed, 27 Aug 20 11:37:46 +0000 + +# date +# |> Timezone.convert("GMT") +# |> Timex.format!("{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} {Zname}") +# # Wed, 27 Aug 2020 11:37:46 GMT diff --git a/services/bright/lib/bright/streams.ex b/services/bright/lib/bright/streams.ex index 21ff355..3d06dbb 100644 --- a/services/bright/lib/bright/streams.ex +++ b/services/bright/lib/bright/streams.ex @@ -166,6 +166,16 @@ defmodule Bright.Streams do Repo.all(Vod) end + @doc """ + Returns the most recently updated vod + """ + def most_recently_updated_vod do + Vod + |> order_by([v], desc: v.updated_at) + |> limit(1) + |> Repo.one + end + @doc """ Gets a single vod. diff --git a/services/bright/lib/bright/streams/vod.ex b/services/bright/lib/bright/streams/vod.ex index 87d1cb6..bf20e2d 100644 --- a/services/bright/lib/bright/streams/vod.ex +++ b/services/bright/lib/bright/streams/vod.ex @@ -13,6 +13,9 @@ defmodule Bright.Streams.Vod do field :notes, :string field :thumbnail_url, :string field :local_path, :string + field :magnet_link, :string + field :info_hash_v1, :string + field :info_hash_v2, :string belongs_to :stream, Bright.Streams.Stream # belongs_to :uploader, Bright.Accounts.User, foreign_key: :uploaded_by_id # Metadata for uploader @@ -23,7 +26,7 @@ defmodule Bright.Streams.Vod do @doc false def changeset(vod, attrs) do vod - |> cast(attrs, [:local_path, :s3_cdn_url, :mux_asset_id, :mux_playback_id, :ipfs_cid, :torrent, :stream_id, :origin_temp_input_url, :playlist_url, :thumbnail_url]) + |> cast(attrs, [:magnet_link, :info_hash_v1, :info_hash_v2, :local_path, :s3_cdn_url, :mux_asset_id, :mux_playback_id, :ipfs_cid, :torrent, :stream_id, :origin_temp_input_url, :playlist_url, :thumbnail_url]) |> validate_required([:stream_id]) end diff --git a/services/bright/lib/bright/torrent.ex b/services/bright/lib/bright/torrent.ex new file mode 100644 index 0000000..8e5beba --- /dev/null +++ b/services/bright/lib/bright/torrent.ex @@ -0,0 +1,44 @@ +defmodule Bright.Torrent do + + + alias Bright.Streams.Vod + alias Bright.{Cache,Torrentfile,B2} + + + def bittorrent_tracker_url do + Application.fetch_env!(:bright, :bittorrent_tracker_url) + end + + def site_url do + Application.fetch_env!(:bright, :site_url) + end + + + + def create_torrent(input_path, output_path, web_seed_url, vod_id) do + IO.puts "site_url=#{site_url()}" + IO.puts "bittorrent_tracker_url=#{bittorrent_tracker_url()}" + tracker_url = bittorrent_tracker_url() + source_url = URI.append_path(URI.parse(site_url()), "/vods/#{vod_id}") |> URI.to_string() + comment = site_url() + meta_version = 3 # hybrid BT v1 & v2 + + {:ok, %{btih: btih, btmh: btmh, magnet: magnet, save_path: save_path} = torrentfile} = Torrentfile.create(input_path, output_path, tracker_url, source_url, comment, web_seed_url, meta_version) + + + # upload to s3 + basename = Path.basename(save_path) + {:ok, asset} = B2.put(save_path, basename) + + {:ok, %{basename: basename, local_path: save_path, magnet_link: magnet, info_hash_v1: btih, info_hash_v2: btmh}} + + + end + + + + + + + +end diff --git a/services/bright/lib/bright/torrentfile.ex b/services/bright/lib/bright/torrentfile.ex new file mode 100644 index 0000000..04248d0 --- /dev/null +++ b/services/bright/lib/bright/torrentfile.ex @@ -0,0 +1,93 @@ +defmodule Bright.Torrentfile do + @moduledoc """ + Provides functions to work with [torrentfile](https://github.com/alexpdev/torrentfile) CLI program + """ + + alias Bright.Cache + + # @spec execute(command :: Command.t) :: {:ok, binary()} | {:error, {Collectable.t, exit_status :: non_neg_integer}} + # def execute(%Command{} = command) do + # {executable, args} = prepare(command) + + # Rambo.run(executable, args, log: false) + # |> format_output() + # end + + # @spec prepare(command :: Command.t) :: {binary() | nil, list(binary)} + # def prepare(%Command{files: files, global_options: options}) do + # options = Enum.map(options, &arg_for_option/1) + # cmd_args = List.flatten([options, options_list(files)]) + # {ffmpeg_path(), cmd_args} + # end + + # $ torrentfile \ + # create \ + # --prog 0 \ + # -o test-fixture.torrent \ + # -a https://tracker.futureporn.net/announce \ + # --source https://futureporn.net \ + # --web-seed=https://futureporn-b2.b-cdn.net/test-fixture.ts \ + # --meta-version 2 \ + # /home/cj/Documents/futureporn-monorepo/services/bright/test/fixtures/test-fixture.ts + + def version do + case Rambo.run(torrentfile_path(), ["-V"]) do + {:ok, %Rambo{status: 0, out: output}} -> + case Regex.run(~r/(v[\d.]+)/, output) do + [_, version] -> {:ok, version} + _ -> {:error, "Version not found"} + end + + {:error, reason} -> {:error, reason} + end + end + + + # def parse_output(output) do + # magnet = extract_last(Regex.run(~r/(magnet:\?[^\s]+)/, output)) + # save_path = extract_last(Regex.run(~r/Torrent Save Path:\s+(.+)/, output)) + # btih = extract_last(Regex.run(~r/\burn:btih:([A-F\d]+)\b/i, magnet)) + # btmh = extract_last(Regex.run(~r/\burn:btmh:([A-F\d]+)\b/i, magnet)) + # %{magnet: magnet, save_path: save_path, btih: btih, btmh: btmh} + # end + + def parse_output(output) do + magnet = extract_last(Regex.run(~r/(magnet:\?[^\s]+)/, output)) + save_path = extract_last(Regex.run(~r/Torrent Save Path:\s+(.+)/, output)) + + btih = if magnet, do: extract_last(Regex.run(~r/\burn:btih:([A-F\d]+)\b/i, magnet)), else: nil + btmh = if magnet, do: extract_last(Regex.run(~r/\burn:btmh:([A-F\d]+)\b/i, magnet)), else: nil + + %{magnet: magnet, save_path: save_path, btih: btih, btmh: btmh} + end + + + + defp extract_last(nil), do: nil + defp extract_last(list) when is_list(list), do: List.last(list) + + def create(input_path, output_path, tracker_url, source_url, comment, web_seed_url, meta_version) do + case Rambo.run(torrentfile_path(), [ + "--magnet", + "--prog", "0", + "--out", output_path, + "-a", tracker_url, + "--source", source_url, + "--comment", comment, + "--web-seed", web_seed_url, + "--meta-version", to_string(meta_version), + input_path + ]) do + {:error, reason} -> {:error, reason} + {:ok, %Rambo{status: 0, out: out, err: ""}} -> {:ok, parse_output(out)} + end + end + + def torrentfile_path do + case Application.get_env(:bright, :torrentfile_path, nil) do + nil -> System.find_executable("torrentfile") + path -> path + end + end + +end diff --git a/services/bright/lib/bright/torrents.ex b/services/bright/lib/bright/torrents.ex new file mode 100644 index 0000000..5b5bb91 --- /dev/null +++ b/services/bright/lib/bright/torrents.ex @@ -0,0 +1,104 @@ +defmodule Bright.Torrents do + @moduledoc """ + The Torrents context. + """ + + import Ecto.Query, warn: false + alias Bright.Repo + + alias Bright.Torrents.Torrent + + @doc """ + Returns the list of torrent. + + ## Examples + + iex> list_torrent() + [%Torrent{}, ...] + + """ + def list_torrent do + Repo.all(Torrent) + end + + @doc """ + Gets a single torrent. + + Raises `Ecto.NoResultsError` if the Torrent does not exist. + + ## Examples + + iex> get_torrent!(123) + %Torrent{} + + iex> get_torrent!(456) + ** (Ecto.NoResultsError) + + """ + def get_torrent!(id), do: Repo.get!(Torrent, id) + + @doc """ + Creates a torrent. + + ## Examples + + iex> create_torrent(%{field: value}) + {:ok, %Torrent{}} + + iex> create_torrent(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_torrent(attrs \\ %{}) do + %Torrent{} + |> Torrent.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a torrent. + + ## Examples + + iex> update_torrent(torrent, %{field: new_value}) + {:ok, %Torrent{}} + + iex> update_torrent(torrent, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_torrent(%Torrent{} = torrent, attrs) do + torrent + |> Torrent.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a torrent. + + ## Examples + + iex> delete_torrent(torrent) + {:ok, %Torrent{}} + + iex> delete_torrent(torrent) + {:error, %Ecto.Changeset{}} + + """ + def delete_torrent(%Torrent{} = torrent) do + Repo.delete(torrent) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking torrent changes. + + ## Examples + + iex> change_torrent(torrent) + %Ecto.Changeset{data: %Torrent{}} + + """ + def change_torrent(%Torrent{} = torrent, attrs \\ %{}) do + Torrent.changeset(torrent, attrs) + end +end diff --git a/services/bright/lib/bright/torrents/torrent.ex b/services/bright/lib/bright/torrents/torrent.ex new file mode 100644 index 0000000..93abe6e --- /dev/null +++ b/services/bright/lib/bright/torrents/torrent.ex @@ -0,0 +1,20 @@ +defmodule Bright.Torrents.Torrent do + use Ecto.Schema + import Ecto.Changeset + + schema "torrent" do + field :info_hash_v1, :string + field :info_hash_v2, :string + field :cdn_url, :string + field :magnet, :string + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(torrent, attrs) do + torrent + |> cast(attrs, [:info_hash_v1, :info_hash_v2, :cdn_url, :magnet]) + |> validate_required([:info_hash_v1, :info_hash_v2, :cdn_url, :magnet]) + end +end diff --git a/services/bright/lib/bright/tracker.ex b/services/bright/lib/bright/tracker.ex new file mode 100644 index 0000000..79ea707 --- /dev/null +++ b/services/bright/lib/bright/tracker.ex @@ -0,0 +1,39 @@ +defmodule Bright.Tracker do + + + alias Bright.Streams.Vod + alias Bright.{Cache,Torrentfile,B2} + + def tracker_url do + Application.get_env!(:bright, :bt_tracker_url) + end + + + def announce_torrent(info_hash) do + + end + + def whitelist_info_hash(info_hash) do + + server = "tcp://ncat:8666" + port = 8666 + + # Open a TCP connection + {:ok, socket} = :gen_tcp.connect(server, port, [:binary, packet: :raw, active: false]) + + # Send the "hello world" data to the server + :gen_tcp.send(socket, "#{info_hash}\n") + + # Close the connection + :gen_tcp.close(socket) + + + # url = "http://ncat:6868" + # body = [ + + # ] + # headers = [] + # HTTPoison.post(url, body, headers) + end + +end diff --git a/services/bright/lib/bright_web/components/layouts/root.html.heex b/services/bright/lib/bright_web/components/layouts/root.html.heex index 6aac60c..df3d376 100644 --- a/services/bright/lib/bright_web/components/layouts/root.html.heex +++ b/services/bright/lib/bright_web/components/layouts/root.html.heex @@ -17,6 +17,7 @@ + diff --git a/services/bright/lib/bright_web/controllers/page_html/about.html.heex b/services/bright/lib/bright_web/controllers/page_html/about.html.heex index ae9e19f..f3c959a 100644 --- a/services/bright/lib/bright_web/controllers/page_html/about.html.heex +++ b/services/bright/lib/bright_web/controllers/page_html/about.html.heex @@ -11,7 +11,6 @@
-

A platform built by fans, for fans, dedicated to preserving the moments that matter in the world of R-18 VTuber live streaming. It all started with a simple need: capturing ProjektMelody's streams on Chaturbate. Chaturbate doesn’t save VODs, and sometimes we missed the magic. Other times, creators like ProjektMelody faced unnecessary de-platforming for simply being unique. We wanted to create a space where this content could endure, unshaken by the tides of censorship or fleeting platforms.

@@ -26,4 +25,17 @@

Join us as we redefine archiving and fandom, ensuring that no stream is ever lost again. Together, we can create a platform that stands as a testament to creativity, individuality, and the fans who make it all possible.

+
+

Goals

+
    +
  • Preserve lewdtuber history
  • +
  • Grow the lewdtuber fanbase
  • +
  • Introduce groundbreaking interactivity features
  • +
  • Beam VODs to LEO, the Moon & Mars base (literally)
  • +
+
+ + + + diff --git a/services/bright/lib/bright_web/controllers/rss_controller.ex b/services/bright/lib/bright_web/controllers/rss_controller.ex new file mode 100644 index 0000000..03cdb1d --- /dev/null +++ b/services/bright/lib/bright_web/controllers/rss_controller.ex @@ -0,0 +1,62 @@ +# defmodule BrightWeb.RssController do +# use BrightWeb, :controller +# plug :put_layout, false + +# alias BrightWeb.Streams.Vod + +# def index(conn, _params) do +# vods = Vod.list_vods() +# updated_at = Vod.most_recently_updated_vod.updated_at + +# conn +# |> put_resp_content_type("text/xml") +# |> render("index.xml", vods: vods, updated_at: updated_at) +# end +# end + + +defmodule BrightWeb.RssController do + use BrightWeb, :controller + + alias Bright.Streams + alias Bright.Streams.Vod + alias Atomex.{Feed, Entry} + + @author "CJ_Clippy" + @email "cj@futureporn.net" + + def vods(conn, _params) do + vods = Streams.list_vods() + feed = build_feed(vods, conn) + + conn + |> put_resp_content_type("text/xml") + |> send_resp(200, feed) + end + + def build_feed(vods, conn) do + Feed.new(~p"/", DateTime.utc_now(), "Futureporn VOD RSS") + |> Feed.author(@author, email: @email) + |> Feed.link(~p"/feeds/vods.xml", rel: "self") + |> Feed.entries(Enum.map(vods, &get_entry(conn, &1))) + |> Feed.build() + |> Atomex.generate_document() + end + + defp get_entry( + conn, + %Vod{id: id, torrent: torrent, origin_temp_input_url: origin_temp_input_url, updated_at: updated_at, playlist_url: playlist_url} + ) do + Entry.new( + # Routes.post_url(conn, :show, kind, slug), + id, + DateTime.from_naive!(updated_at, "Etc/UTC"), + "vod #{id}" + ) + # |> Entry.link(Routes.post_url(conn, :show, kind, slug)) + |> Entry.link("https://futureporn.net/vods/#{id}") + |> Entry.content(playlist_url, type: "text") + |> Feed.add_field(:guid, %{isPermalink: false}, torrent) + |> Entry.build() + end +end diff --git a/services/bright/lib/bright_web/controllers/rss_xml/index.xml.eex b/services/bright/lib/bright_web/controllers/rss_xml/index.xml.eex new file mode 100644 index 0000000..dbc08a4 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/rss_xml/index.xml.eex @@ -0,0 +1,26 @@ + + + + VOD Title + <%= Routes.blog_url(@conn, :index) %> + + Your blog's description... + en + Copyright <%= DateTime.utc_now.year %> Your Name + <%= @last_build_date |> to_rfc822 %> + IT/Internet/Web development + 60 + + <%= for post <- @posts do %> + + <%= post.title %> + <%= Routes.post_url(@conn, :show, post) %> + <%= Routes.post_url(@conn, :show, post) %> + ]]> + <%= post.category.name %> + <%= post.inserted_at |> to_rfc822 %> + Blog Title + + <% end %> + + diff --git a/services/bright/lib/bright_web/router.ex b/services/bright/lib/bright_web/router.ex index 259c61f..bd2e741 100644 --- a/services/bright/lib/bright_web/router.ex +++ b/services/bright/lib/bright_web/router.ex @@ -77,6 +77,7 @@ defmodule BrightWeb.Router do get("/", PageController, :home) + get("/profile", PageController, :profile) get("/patrons", PatronController, :index) @@ -114,6 +115,10 @@ defmodule BrightWeb.Router do end + scope "/feed", BrightWeb do + get "/vods.xml", RssController, :vods + end + # Other scopes may use custom stacks. scope "/api", BrightWeb do pipe_through(:api) diff --git a/services/bright/mix.exs b/services/bright/mix.exs index a52f834..e65c091 100644 --- a/services/bright/mix.exs +++ b/services/bright/mix.exs @@ -66,6 +66,7 @@ defmodule Bright.MixProject do {:ffmpex, "~> 0.11.0"}, {:sweet_xml, "~> 0.6"}, {:ex_m3u8, "~> 0.14.2"}, + {:atomex, "~> 0.3.0"}, # {:membrane_core, "~> 1.0"}, # {:membrane_mpeg_ts_plugin, "~> 1.0.3"}, # {:membrane_file_plugin, "~> 0.17.2"}, diff --git a/services/bright/mix.lock b/services/bright/mix.lock index caff7b3..1751585 100644 --- a/services/bright/mix.lock +++ b/services/bright/mix.lock @@ -1,4 +1,5 @@ %{ + "atomex": {:hex, :atomex, "0.3.0", "19b5d1a2aef8706dbd307385f7d5d9f6f273869226d317492c396c7bacf26402", [:mix], [{:xml_builder, "~> 2.0.0", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "025dbc3a3e99380894791a093019f535d0ef6cf1916f6ec1b778ac107fcfc3e4"}, "bandit": {:hex, :bandit, "1.6.6", "f2019a95261d400579075df5bc15641ba8e446cc4777ede6b4ec19e434c3340d", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "ceb19bf154bc2c07ee0c9addf407d817c48107e36a66351500846fc325451bf9"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"}, "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, @@ -8,6 +9,7 @@ "bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, + "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, @@ -88,6 +90,7 @@ "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, + "pythonx": {:hex, :pythonx, "0.2.5", "05660dc8548a4ab5da5b7f7977c6a5a3fa16eefadbe54077f9a176c9d386be27", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "66d2179e37be527cbecf47097c15aea28a1dbcb2c6e965407c89ca1e1ac74d17"}, "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, "rambo": {:hex, :rambo, "0.3.4", "8962ac3bd1a633ee9d0e8b44373c7913e3ce3d875b4151dcd060886092d2dce7", [:mix], [], "hexpm", "0cc54ed089fbbc84b65f4b8a774224ebfe60e5c80186fafc7910b3e379ad58f1"}, "ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"}, @@ -114,5 +117,6 @@ "unifex": {:hex, :unifex, "1.2.1", "6841c170a6e16509fac30b19e4e0a19937c33155a59088b50c15fc2c36251b6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "8c9d2e3c48df031e9995dd16865bab3df402c0295ba3a31f38274bb5314c7d37"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, + "xml_builder": {:hex, :xml_builder, "2.0.0", "371ed27bb63bf0598dbaf3f0c466e5dc7d16cb4ecb68f06a67f953654062e21b", [:mix], [], "hexpm", "baeb5c8d42204bac2b856ffd50e8cda42d63b622984538d18d92733e4e790fbd"}, "zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"}, } diff --git a/services/bright/priv/repo/migrations/20250130015306_add_magnet_link.exs b/services/bright/priv/repo/migrations/20250130015306_add_magnet_link.exs new file mode 100644 index 0000000..ea28f0d --- /dev/null +++ b/services/bright/priv/repo/migrations/20250130015306_add_magnet_link.exs @@ -0,0 +1,10 @@ +defmodule Bright.Repo.Migrations.AddMagnetLink do + use Ecto.Migration + + def change do + alter table(:vods) do + add :magnet_link, :string + add :info_hash, :string + end + end +end diff --git a/services/bright/priv/repo/migrations/20250131034913_add_info_hash_v2.exs b/services/bright/priv/repo/migrations/20250131034913_add_info_hash_v2.exs new file mode 100644 index 0000000..e5a2d1f --- /dev/null +++ b/services/bright/priv/repo/migrations/20250131034913_add_info_hash_v2.exs @@ -0,0 +1,11 @@ +defmodule Bright.Repo.Migrations.AddInfoHashV2 do + use Ecto.Migration + + def change do + alter table(:vods) do + remove :info_hash + add :info_hash_v1, :string + add :info_hash_v2, :string + end + end +end diff --git a/services/bright/priv/repo/migrations/20250131091405_create_torrent.exs b/services/bright/priv/repo/migrations/20250131091405_create_torrent.exs new file mode 100644 index 0000000..66c665d --- /dev/null +++ b/services/bright/priv/repo/migrations/20250131091405_create_torrent.exs @@ -0,0 +1,14 @@ +defmodule Bright.Repo.Migrations.CreateTorrent do + use Ecto.Migration + + def change do + create table(:torrent) do + add :info_hash_v1, :text + add :info_hash_v2, :text + add :cdn_url, :text + add :magnet, :text + + timestamps(type: :utc_datetime) + end + end +end diff --git a/services/bright/test/bright/b2_test.exs b/services/bright/test/bright/b2_test.exs index 1168995..8592bd7 100644 --- a/services/bright/test/bright/b2_test.exs +++ b/services/bright/test/bright/b2_test.exs @@ -9,6 +9,7 @@ defmodule Bright.B2Test do alias Bright.B2 alias Bright.Cache + import Bright.StreamsFixtures @tag :acceptance test "put/1" do @@ -27,6 +28,16 @@ defmodule Bright.B2Test do end + @tag :integration + test "get/1 with %Vod{}" do + stream = stream_fixture() + vod = vod_fixture(%{stream_id: stream.id, s3_cdn_url: "https://futureporn-b2.b-cdn.net/test-fixture.ts"}) + {:ok, filename} = B2.get(vod) + assert :ok + assert Regex.match?(~r/\.cache\/futureporn.*\.ts/, filename) + end + + @tag :acceptance test "get/2" do local_file = "/tmp/SampleVideo_1280x720_1mb.mp4" diff --git a/services/bright/test/bright/cache_test.ex b/services/bright/test/bright/cache_test.ex index 970cb3c..ace02c2 100644 --- a/services/bright/test/bright/cache_test.ex +++ b/services/bright/test/bright/cache_test.ex @@ -5,6 +5,30 @@ defmodule Bright.CacheTest do @sample_url "https://example.com/my_video.mp4" + + ## IDK what I'm doing here. Ideally I want a redis-like k/v store where I can temporarily put VODs and they expire after 12 hours or so. + ## this would potentially speed up vod processing because it would prevent having to download the VOD from S3 during every Oban worker performance. + ## BUT I don't want to implement it myself because of the idiom, "There are only two unsolved problems in CS. Naming things and cache invalidation" + ## Meaning I don't think I can do any better than the experts in the field. + ## Anyway, this is FEATURE CREEP! Solve the problem without caching and LET IT BE SLOW. + ## To implement this cache before the system works is pre-mature optimization! + + # describe "cache k/v" do + # test "get/1 with string cache key" do + + # end + + # test "get/1 with %Vod{}" do + # stream = stream_fixture() + # vod = vod_fixture(%{stream_id: stream.id}) + # Cache.get(vod) + # end + + # test "put/2" do + + # end + # end + describe "cache" do @tag :unit @@ -12,7 +36,6 @@ defmodule Bright.CacheTest do assert Regex.match?(~r/.cache\/futureporn/, Cache.get_cache_dir()) end - @tag :unit test "generate_basename/1" do # Test with a URL diff --git a/services/bright/test/bright/oban_workers/create_torrent_test.exs b/services/bright/test/bright/oban_workers/create_torrent_test.exs index d16aa78..8adc4fc 100644 --- a/services/bright/test/bright/oban_workers/create_torrent_test.exs +++ b/services/bright/test/bright/oban_workers/create_torrent_test.exs @@ -8,33 +8,39 @@ defmodule Bright.CreateTorrentTest do alias Bright.ObanWorkers.{ProcessVod, CreateTorrent} alias Bright.Streams - alias Bright.Streams.Stream + alias Bright.Streams.{Stream,Vod} describe "CreateTorrent" do import Bright.StreamsFixtures + @test_video_url "https://futureporn-b2.b-cdn.net/test-fixture.ts" + + @tag :integration + test "torrent creation" do + stream = stream_fixture() + vod = vod_fixture(%{torrent: nil, stream_id: stream.id, origin_temp_input_url: @test_video_url}) + {:ok, %Vod{torrent: torrent, magnet_link: magnet_link, info_hash_v1: info_hash_v1, info_hash_v2: info_hash_v2}} + = perform_job(Bright.ObanWorkers.CreateTorrent, %{vod_id: vod.id}) + assert Regex.match?(~r/^https:\/\/.*\.torrent$/, torrent) + assert Regex.match?(~r/^magnet:/, magnet_link) + assert Regex.match?(~r/([A-F\d]+)\b/i, info_hash_v1) + assert Regex.match?(~r/([A-F\d]+)\b/i, info_hash_v2) + end + + @tag :integration test "sheduling upon vod creation" do - example_video = "http://example.com/video.ts" stream_attrs = %{date: ~U[2024-12-28 03:31:00Z], title: "some title", notes: "some notes"} {:ok, %Stream{} = stream} = Streams.create_stream(stream_attrs) - {:ok, _vod} = Streams.create_vod(%{stream_id: stream.id, origin_temp_input_url: example_video}) + {:ok, _vod} = Streams.create_vod(%{stream_id: stream.id, origin_temp_input_url: @test_video_url}) assert_enqueued worker: ProcessVod, queue: :default - assert %{success: 1} = Oban.drain_queue(queue: :default) # ProcessVod is what queues CreateThumbnail so we need to make it run + assert %{success: 1} = Oban.drain_queue(queue: :default) # ProcessVod is what queues CreateTorrent so we need to make it run assert_enqueued [worker: CreateTorrent, queue: :default], 1000 end - @tag :integration - test "not scheduled when origin_temp_input_url is missing" do - stream_attrs = %{date: ~U[2024-12-28 03:31:00Z], title: "some title", notes: "some notes"} - {:ok, %Stream{} = stream} = Streams.create_stream(stream_attrs) - {:ok, _vod} = Streams.create_vod(%{stream_id: stream.id}) - refute_enqueued worker: CreateTorrent - end - end diff --git a/services/bright/test/bright/torrent_test.exs b/services/bright/test/bright/torrent_test.exs new file mode 100644 index 0000000..6da7361 --- /dev/null +++ b/services/bright/test/bright/torrent_test.exs @@ -0,0 +1,49 @@ +defmodule Bright.TorrentTest do + use Bright.DataCase + + alias Bright.Torrent + + + describe "torrent" do + + import Bright.StreamsFixtures + alias Bright.{Downloader,Cache} + + @test_fixture "https://futureporn-b2.b-cdn.net/test-fixture.ts" + + # @tag :integration + # test "create_torrent/1" do + # stream = stream_fixture() + # vod = vod_fixture(%{stream_id: stream.id, s3_cdn_url: "https://futureporn-b2.b-cdn.net/test-fixture.ts"}) + # {:ok, _} = Torrent.create_torrent(vod) + # assert :ok + # end + + @tag :integration + test "create_torrent/7" do + stream = stream_fixture() + vod = vod_fixture(%{stream_id: stream.id, s3_cdn_url: "https://futureporn-b2.b-cdn.net/test-fixture.ts"}) + input_path = Path.absname("./test/fixtures/test-fixture.ts") + output_path = Cache.generate_filename("test", "torrent") + tracker_url = "https://tracker.futureporn.net/announce" + source_url = "https://futureporn.net/vods/69" + comment = "https://futureporn.net" + web_seed_url = @test_fixture + meta_version = 3 + IO.puts "input_path=#{input_path} output_path=#{output_path} tracker_url=#{tracker_url} source_url=#{source_url}" + {:ok, %{local_path: local_path, magnet_link: magnet_link, basename: basename, info_hash_v1: info_hash_v1, info_hash_v2: info_hash_v2}} + = Torrent.create_torrent(input_path, output_path, web_seed_url, vod.id) + assert :ok + assert local_path === output_path + assert File.exists?(output_path) + assert String.starts_with?(magnet_link, "magnet:") + assert String.ends_with?(basename, ".torrent") + assert is_binary(info_hash_v1) + assert is_binary(info_hash_v2) + end + + + + + end +end diff --git a/services/bright/test/bright/torrentfile_test.ex b/services/bright/test/bright/torrentfile_test.ex new file mode 100644 index 0000000..6077bd0 --- /dev/null +++ b/services/bright/test/bright/torrentfile_test.ex @@ -0,0 +1,95 @@ +defmodule Bright.TorrentfileTest do + use Bright.DataCase + + alias Bright.Torrentfile + alias Bright.Cache + + @test_ts_fixture "./test/fixtures/test-fixture.ts" + @test_tracker_url "http://localhost:6969/announce" + @test_web_seed_url "https://futureporn-b2.b-cdn.net/test-fixture.ts" + @test_source_url "https://futureporn.net/vods/69" + @test_comment "https://futureporn.net" + + describe "torrentfile" do + + test "version/0" do + {:ok, ver_num} = Torrentfile.version() + assert :ok + assert Regex.match?(~r"v\d\.\d\.\d", ver_num) + end + + @tag :unit + test "torrentfile_path" do + assert Regex.match?(~r"\/torrentfile", Torrentfile.torrentfile_path()) + end + + + test "create/7" do + input_path = @test_ts_fixture + output_path = Cache.generate_filename("test", "torrent") + tracker_url = @test_tracker_url + comment = @test_comment + source_url = @test_source_url + web_Seed_url = @test_web_seed_url + meta_version = 3 + {:ok, output} = Torrentfile.create(input_path, output_path, tracker_url, comment, source_url, web_Seed_url, meta_version) + + assert :ok + assert is_binary(output.save_path) + assert output.save_path === output_path + assert is_binary(output.btih) + assert is_binary(output.btmh) + assert File.exists?(output_path) + + end + + @tag :unit + test "parses magnet link, save path, btih and btmh correctly" do + output = """ + magnet:?xt=urn:btih:157835a64d398fd63d83b5fd6dac5612bd60b6c6&xt=urn:btmh:12201bf9590518d84900ca3e4a88a7fe5f6a246deff2cf37d3acc24b7f64a8b0b572&dn=test-fixture.ts&tr=https%3A%2F%2Ftracker.futureporn.net%2Fannounce + + Torrent Save Path: /home/cj/Downloads/test-fixture.torrent + """ + + expected = %{ + magnet: "magnet:?xt=urn:btih:157835a64d398fd63d83b5fd6dac5612bd60b6c6&xt=urn:btmh:12201bf9590518d84900ca3e4a88a7fe5f6a246deff2cf37d3acc24b7f64a8b0b572&dn=test-fixture.ts&tr=https%3A%2F%2Ftracker.futureporn.net%2Fannounce", + save_path: "/home/cj/Downloads/test-fixture.torrent", + btih: "157835a64d398fd63d83b5fd6dac5612bd60b6c6", + btmh: "12201bf9590518d84900ca3e4a88a7fe5f6a246deff2cf37d3acc24b7f64a8b0b572" + } + + assert Torrentfile.parse_output(output) == expected + end + + + @tag :unit + test "returns nil values when output is empty" do + assert Torrentfile.parse_output("") == %{magnet: nil, save_path: nil, btih: nil, btmh: nil} + end + + @tag :unit + test "handles missing save path" do + output = "magnet:?xt=urn:btih:12345" + assert Torrentfile.parse_output(output) == %{magnet: "magnet:?xt=urn:btih:12345", save_path: nil, btih: "12345", btmh: nil} + end + + @tag :unit + test "handles missing magnet link" do + output = "Torrent Save Path: /downloads/test.torrent" + assert Torrentfile.parse_output(output) == %{magnet: nil, save_path: "/downloads/test.torrent", btih: nil, btmh: nil} + end + + @tag :unit + test "handles missing btih" do + output = "Torrent Save Path: /downloads/test.torrent" + assert Torrentfile.parse_output(output) == %{btih: nil, magnet: nil, btmh: nil, save_path: "/downloads/test.torrent"} + end + + @tag :unit + test "handles missing btmh" do + output = "Torrent Save Path: /downloads/test.torrent" + assert Torrentfile.parse_output(output) == %{btmh: nil, magnet: nil, btih: nil, save_path: "/downloads/test.torrent"} + end + + end +end diff --git a/services/bright/test/bright/torrents_test.exs b/services/bright/test/bright/torrents_test.exs new file mode 100644 index 0000000..23ce85c --- /dev/null +++ b/services/bright/test/bright/torrents_test.exs @@ -0,0 +1,65 @@ +defmodule Bright.TorrentsTest do + use Bright.DataCase + + alias Bright.Torrents + + describe "torrent" do + alias Bright.Torrents.Torrent + + import Bright.TorrentsFixtures + + @invalid_attrs %{info_hash_v1: nil, info_hash_v2: nil, cdn_url: nil, magnet: nil} + + test "list_torrent/0 returns all torrent" do + torrent = torrent_fixture() + assert Torrents.list_torrent() == [torrent] + end + + test "get_torrent!/1 returns the torrent with given id" do + torrent = torrent_fixture() + assert Torrents.get_torrent!(torrent.id) == torrent + end + + test "create_torrent/1 with valid data creates a torrent" do + valid_attrs = %{info_hash_v1: "some info_hash_v1", info_hash_v2: "some info_hash_v2", cdn_url: "some cdn_url", magnet: "some magnet"} + + assert {:ok, %Torrent{} = torrent} = Torrents.create_torrent(valid_attrs) + assert torrent.info_hash_v1 == "some info_hash_v1" + assert torrent.info_hash_v2 == "some info_hash_v2" + assert torrent.cdn_url == "some cdn_url" + assert torrent.magnet == "some magnet" + end + + test "create_torrent/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Torrents.create_torrent(@invalid_attrs) + end + + test "update_torrent/2 with valid data updates the torrent" do + torrent = torrent_fixture() + update_attrs = %{info_hash_v1: "some updated info_hash_v1", info_hash_v2: "some updated info_hash_v2", cdn_url: "some updated cdn_url", magnet: "some updated magnet"} + + assert {:ok, %Torrent{} = torrent} = Torrents.update_torrent(torrent, update_attrs) + assert torrent.info_hash_v1 == "some updated info_hash_v1" + assert torrent.info_hash_v2 == "some updated info_hash_v2" + assert torrent.cdn_url == "some updated cdn_url" + assert torrent.magnet == "some updated magnet" + end + + test "update_torrent/2 with invalid data returns error changeset" do + torrent = torrent_fixture() + assert {:error, %Ecto.Changeset{}} = Torrents.update_torrent(torrent, @invalid_attrs) + assert torrent == Torrents.get_torrent!(torrent.id) + end + + test "delete_torrent/1 deletes the torrent" do + torrent = torrent_fixture() + assert {:ok, %Torrent{}} = Torrents.delete_torrent(torrent) + assert_raise Ecto.NoResultsError, fn -> Torrents.get_torrent!(torrent.id) end + end + + test "change_torrent/1 returns a torrent changeset" do + torrent = torrent_fixture() + assert %Ecto.Changeset{} = Torrents.change_torrent(torrent) + end + end +end diff --git a/services/bright/test/bright/tracker_test.exs b/services/bright/test/bright/tracker_test.exs new file mode 100644 index 0000000..062f3bd --- /dev/null +++ b/services/bright/test/bright/tracker_test.exs @@ -0,0 +1,46 @@ +defmodule Bright.TrackerTest do + use Bright.DataCase + + alias Bright.Tracker + + describe "tracker" do + + import Bright.StreamsFixtures + + @info_hash_fixture "723886c0b0d9d41bfaa5276a9b2552d84ba09dd8a77d9ddcab5c9fa16cdb9770" # test-fixture.ts (BT info_hash v2) + + @tag :integration + test "whitelist_info_hash/1 using a string info_hash" do + :ok = Tracker.whitelist_info_hash(@info_hash_fixture) + assert :ok + end + + @tag :integration + test "whitelist_info_hash/1 using a %Vod{}" do + stream = stream_fixture() + vod = vod_fixture(%{stream_id: stream.id}) + :ok = Tracker.whitelist_info_hash(vod) + assert :ok + end + + @tag :integration + test "announce_torrent/1 using a string info_hash" do + :ok = Tracker.announce_torrent(@info_hash_fixture) + assert :ok + end + + @tag :integration + test "announce_torrent/1 using a %Vod{}" do + stream = stream_fixture() + vod = vod_fixture(%{stream_id: stream.id, info_hash: @info_hash_fixture}) + :ok = Tracker.announce_torrent(vod) + assert :ok + end + + end + + + + + +end diff --git a/services/bright/test/fixtures/123-test-fixture.ts b/services/bright/test/fixtures/123-test-fixture.ts new file mode 100644 index 0000000000000000000000000000000000000000..43d67d9b9b27498d2761af45ebebac7867792829 GIT binary patch literal 1832436 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