remove submodule

This commit is contained in:
CJ_Clippy 2025-02-21 19:33:39 -08:00
parent ed13626f83
commit ad6788639e
40 changed files with 373 additions and 368 deletions

View File

@ -19,19 +19,6 @@ jobs:
with:
submodules: recursive
# IDK if I need this
# - name: Set docker metadata
# id: meta
# uses: docker/metadata-action@v5
# with:
# images: |
# gitea.futureporn.net/futureporn/tracker-helper:latest
# tags: |
# type=ref,event=branch
# type=ref,event=pr
# type=semver,pattern={{version}}
# type=semver,pattern={{major}}.{{minor}}
- name: Login to Gitea Docker Registry
uses: docker/login-action@v3
with:
@ -39,31 +26,30 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build futureporn/tracker-helper
uses: docker/build-push-action@v6
with:
context: ./services/tracker-helper
push: true
tags: gitea.futureporn.net/futureporn/tracker-helper:latest
labels: |
org.opencontainers.image.description=Opentracker helper service. Adds info_hash whitelisting via HTTP
org.opencontainers.image.title=tracker-helper
org.opencontainers.image.created={{commit_date 'YYYY-MM-DDTHH:mm:ss.SSS[Z]'}}
org.opencontainers.image.licenses=unlicense
org.opencontainers.image.source=https://gitea.futureporn.net/futureporn/fp
org.opencontainers.image.url=https://gitea.futureporn.net/futureporn/-/packages/container/tracker-helper
secrets: |
WL_USERNAME=${{ secrets.WL_USERNAME }}
WL_PASSWORD=${{ secrets.WL_PASSWORD }}
- name: Build futureporn/aquatic
uses: docker/build-push-action@v6
with:
context: ./apps/aquatic-tracker
file: ./apps/aquatic-tracker/docker/aquatic_udp.Dockerfile
context: ./apps/aquatic
file: ./apps/aquatic/docker/aquatic_udp.Dockerfile
push: true
tags: gitea.futureporn.net/futureporn/aquatic:latest
- name: Build futureporn/tracker
uses: docker/build-push-action@v6
with:
context: ./services/tracker
push: true
tags: gitea.futureporn.net/futureporn/tracker:latest
labels: |
org.opencontainers.image.description=Aquatic tracker with custom helper service, adding info_hash accesslist operations via HTTP
org.opencontainers.image.title=tracker
org.opencontainers.image.licenses=unlicense
org.opencontainers.image.source=https://gitea.futureporn.net/futureporn/fp
org.opencontainers.image.url=https://gitea.futureporn.net/futureporn/-/packages/container/tracker
secrets: |
TRACKER_HELPER_USERNAME=${{ secrets.TRACKER_HELPER_USERNAME }}
TRACKER_HELPER_PASSWORD=${{ secrets.TRACKER_HELPER_PASSWORD }}
- name: Build futureporn/bright
uses: docker/build-push-action@v6
with:
@ -80,18 +66,3 @@ jobs:
org.opencontainers.image.licenses=unlicense
org.opencontainers.image.source=https://gitea.futureporn.net/futureporn/fp
org.opencontainers.image.url=https://gitea.futureporn.net/futureporn/-/packages/container/bright
# - name: Build futureporn/opentracker
# uses: docker/build-push-action@v6
# with:
# context: ./apps/opentracker
# push: true
# tags: gitea.futureporn.net/futureporn/opentracker:latest
# labels: |
# org.opencontainers.image.description=opentracker is an open and free bittorrent tracker project.
# org.opencontainers.image.title=opentracker
# org.opencontainers.image.created={{commit_date 'YYYY-MM-DDTHH:mm:ss.SSS[Z]'}}
# org.opencontainers.image.version={{version}}
# org.opencontainers.image.licenses=beerware
# org.opencontainers.image.source=https://erdgeist.org/arts/software/opentracker
# org.opencontainers.image.url=https://gitea.futureporn.net/futureporn/-/packages/container/opentracker

View File

@ -34,9 +34,9 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
PUBLIC_S3_ENDPOINT: ${{ vars.PUBLIC_S3_ENDPOINT }}
SITE_URL: https://futureporn.net
WL_URL: ${{ vars.WL_URL }}
WL_USERNAME: ${{ secrets.WL_USERNAME }}
WL_PASSWORD: ${{ secrets.WL_PASSWORD }}
TRACKER_HELPER_URL: ${{ vars.TRACKER_HELPER_URL }}
TRACKER_HELPER_USERNAME: ${{ secrets.TRACKER_HELPER_USERNAME }}
TRACKER_HELPER_PASSWORD: ${{ secrets.TRACKER_HELPER_PASSWORD }}
DB_NAME: ${{ vars.DB_NAME }}
DB_HOST: ${{ vars.DB_HOST }}
DB_PORT: ${{ vars.DB_PORT }}
@ -72,29 +72,17 @@ jobs:
--health-timeout 5s
--health-retries 5
tracker-helper:
image: gitea.futureporn.net/futureporn/tracker-helper:latest
tracker:
image: gitea.futureporn.net/futureporn/tracker:latest
ports:
- 5063:5063
env:
WL_FILE_PATH: /var/lib/aquatic/whitelist
WL_USERNAME: ${{ secrets.WL_USERNAME }}
WL_PASSWORD: ${{ secrets.WL_PASSWORD }}
WL_PORT: 5063
volumes:
- aquatic:/var/lib/aquatic
- /tmp/aquatic:/tmp/aquatic
- /tmp/test:/root/test
- /test2:/root/test2
aquatic:
image: gitea.futureporn.net/futureporn/aquatic:latest
ports:
- 3003:3003
- 9000:9000
volumes:
- aquatic:/var/lib/aquatic
env:
TRACKER_HELPER_ACCESSLIST_PATH: /var/lib/aquatic/whitelist
TRACKER_HELPER_USERNAME: ${{ secrets.TRACKER_HELPER_USERNAME }}
TRACKER_HELPER_PASSWORD: ${{ secrets.TRACKER_HELPER_PASSWORD }}
TRACKER_HELPER_PORT: 5063
ACCESS_LIST_CONTENTS: ""
CONFIG_FILE_CONTENTS: |
log_level = 'debug'
@ -105,12 +93,12 @@ jobs:
address_ipv6 = "[::]:3003"
[statistics]
interval = 5
print_to_stdout = true
print_to_stdout = false
run_prometheus_endpoint = true
prometheus_endpoint_address = "0.0.0.0:9000"
[access_list]
mode = "allow"
path = "/var/lib/aquatic/whitelist"
path = "/var/lib/aquatic/accesslist"
[privileges]
# Chroot and switch group and user after binding to sockets
drop_privileges = true

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "apps/aquatic-tracker"]
path = apps/aquatic-tracker
url = https://github.com/insanity54/aquatic

@ -1 +0,0 @@
Subproject commit 96e69754510c82ad2bbb9d0a2967c8bca0696bbd

View File

@ -32,9 +32,9 @@ config :bright,
config :bright, :torrent,
tracker_url: System.get_env("TRACKER_URL"),
whitelist_url: System.get_env("WL_URL"),
whitelist_username: System.get_env("WL_USERNAME"),
whitelist_password: System.get_env("WL_PASSWORD")
tracker_helper_url: System.get_env("TRACKER_HELPER_URL"),
tracker_helper_username: System.get_env("TRACKER_HELPER_USERNAME"),
tracker_helper_password: System.get_env("TRACKER_HELPER_PASSWORD")
config :bright, :buckets, media: System.get_env("AWS_BUCKET")

View File

@ -26,8 +26,8 @@ defmodule Bright.ObanWorkers.CreateTorrent do
cdn_url: cdn_url,
magnet: tf.magnet
}),
{_, _} <- Tracker.whitelist_info_hash(torrent.info_hash_v1),
{_, _} <- Tracker.whitelist_info_hash(torrent.info_hash_v2),
{_, _} <- Tracker.accesslist_info_hash(torrent.info_hash_v1),
{_, _} <- Tracker.accesslist_info_hash(torrent.info_hash_v2),
{:ok, _updated_vod} <- Streams.update_vod(vod, %{}) do
{:ok, torrent}
end

View File

@ -13,72 +13,55 @@ defmodule Bright.Tracker do
end
@doc """
get the URL to our Bittorrent tracker accesslist URL, which is part of our opentracker docker container.
This is where we send infohashes that end up in opentracker's adder.fifo
Usually it's port 8666, path /whitelist
get the URL to our tracker helper accesslist URL, which is part of our tracker docker container.
This is where we send infohashes that end up in aquatic's accesslist
Usually it's port 5063, path /accesslist
"""
@spec whitelist_url() :: binary()
def whitelist_url do
@spec accesslist_url() :: binary()
def accesslist_url do
url =
case Application.fetch_env!(:bright, :torrent)[:whitelist_url] do
nil -> raise "whitelist_url missing or empty in app config"
"" -> raise "whitelist_url missing or empty in app config"
case Application.fetch_env!(:bright, :torrent)[:tracker_helper_accesslist_url] do
nil -> raise "accesslist_url missing or empty in app config"
"" -> raise "accesslist_url missing or empty in app config"
url -> url
end
Logger.debug("whitelist_url=#{url}")
Logger.debug("accesslist_url=#{url}")
url
end
@spec whitelist_username() :: binary()
def whitelist_username do
case Application.fetch_env!(:bright, :torrent)[:whitelist_username] do
nil -> raise "whitelist_username missing or empty in app config"
"" -> raise "whitelist_username missing or empty in app config"
@spec tracker_helper_username() :: binary()
def tracker_helper_username do
case Application.fetch_env!(:bright, :torrent)[:tracker_helper_username] do
nil -> raise "tracker_helper_username missing or empty in app config"
"" -> raise "tracker_helper_username missing or empty in app config"
username -> username
end
end
@spec whitelist_password() :: binary()
def whitelist_password do
case Application.fetch_env!(:bright, :torrent)[:whitelist_password] do
nil -> raise "whitelist_password missing or empty in app config"
"" -> raise "whitelist_password missing or empty in app config"
@spec tracker_helper_password() :: binary()
def tracker_helper_password do
case Application.fetch_env!(:bright, :torrent)[:tracker_helper_password] do
nil -> raise "tracker_helper_password missing or empty in app config"
"" -> raise "tracker_helper_password missing or empty in app config"
password -> password
end
end
@spec whitelist_feed_url() :: binary()
def whitelist_feed_url do
case Application.fetch_env!(:bright, :torrent)[:whitelist_feed_url] do
nil -> raise "whitelist_feed_url missing or empty in app config"
"" -> raise "whitelist_feed_url missing or empty in app config"
feed_url -> feed_url
end
end
def whitelist_info_hash(info_hash) do
whitelist_url = whitelist_url()
username = whitelist_username()
password = whitelist_password()
def accesslist_info_hash(info_hash) do
accesslist_url = accesslist_url()
username = tracker_helper_username()
password = tracker_helper_password()
Logger.debug(
"Attempting to whitelist info_hash=#{info_hash}, whitelist_url=#{whitelist_url}, whitelist_username=#{username}, whitelist_password=#{password}"
"Attempting to accesslist info_hash=#{info_hash}, accesslist_url=#{accesslist_url}, tracker_helper_username=#{username}, tracker_helper_password=#{password}"
)
send_whitelist_request(whitelist_url, info_hash, username, password)
# case URI.parse(whitelist_url) do
# %URI{scheme: scheme, host: host, port: port} when host not in [nil, ""] and port != nil ->
# full_url = "#{scheme}://#{host}:#{port}"
# _ ->
# Logger.error("Invalid whitelist_url: #{whitelist_url}")
# {:error, :invalid_url}
# end
send_accesslist_request(accesslist_url, info_hash, username, password)
end
defp send_whitelist_request(url, info_hash, username, password) do
Logger.debug("send_whitelist_request to url=#{url}")
defp send_accesslist_request(url, info_hash, username, password) do
Logger.debug("send_accesslist_request to url=#{url}")
headers = [
{"Content-Type", "text/plain"},
@ -87,12 +70,12 @@ defmodule Bright.Tracker do
case HTTPoison.post(url, info_hash, headers) do
{:ok, %HTTPoison.Response{status_code: 201, body: response_body}} ->
Logger.info("Successfully whitelisted info_hash=#{info_hash}")
Logger.info("Successfully accesslisted info_hash=#{info_hash}")
{:ok, response_body}
{:ok, %HTTPoison.Response{status_code: status_code, body: error_body}} ->
Logger.warning(
"Whitelist failed: info_hash=#{info_hash}, status=#{status_code}, error=#{error_body}"
"accesslist failed: info_hash=#{info_hash}, status=#{status_code}, error=#{error_body}"
)
{:error, %{status: status_code, body: error_body}}

View File

@ -0,0 +1,56 @@
# syntax=docker/dockerfile:1
FROM debian:stable-slim AS release
ENV PATH=/command:$PATH
RUN apt-get update -qq \
&& apt-get install -y -qq --no-install-recommends \
xz-utils \
unzip \
ca-certificates \
curl \
&& export BUN_INSTALL=/home/bun \
&& curl -fsSL https://bun.sh/install | bash -s "bun-v1.2.2" \
&& groupadd bun \
&& useradd bun \
--gid bun \
--shell /bin/sh \
--create-home \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& chown -R bun:bun /home/bun
# copy tracker-helper app
COPY --from=gitea.futureporn.net/futureporn/tracker-helper:latest /usr/src/app/node_modules /usr/src/tracker-helper/node_modules
COPY --from=gitea.futureporn.net/futureporn/tracker-helper:latest /usr/src/app/index.ts /usr/src/tracker-helper/
COPY --from=gitea.futureporn.net/futureporn/tracker-helper:latest /usr/src/app/app.ts /usr/src/tracker-helper/
COPY --from=gitea.futureporn.net/futureporn/tracker-helper:latest /usr/src/app/package.json /usr/src/tracker-helper/
# copy aquatic app
COPY --from=gitea.futureporn.net/futureporn/aquatic:latest /usr/local/bin/aquatic_udp /usr/local/bin/aquatic_udp
# copy caddy
COPY --from=caddy:alpine /usr/bin/caddy /usr/bin/caddy
# copy our app (s6-overlay dir structure)
COPY ./root /
# 9000/tcp for aquatic_udp metrics /metrics
# and tracker-helper api /helper/*
# Caddy handles the routing for us
EXPOSE 9000/tcp
# 6969/udp for aquatic_udp
EXPOSE 6969/udp
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
CMD /etc/tracker/healthcheck.sh
ADD https://github.com/just-containers/s6-overlay/releases/download/v3.2.0.2/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/v3.2.0.2/s6-overlay-x86_64.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz
ENTRYPOINT ["/init"]

View File

@ -0,0 +1,27 @@
log_level = 'debug'
[network]
use_ipv4 = true
use_ipv6 = true
address_ipv4 = "0.0.0.0:3003"
address_ipv6 = "[::]:3003"
[statistics]
interval = 5
print_to_stdout = false
run_prometheus_endpoint = true
prometheus_endpoint_address = "0.0.0.0:9000"
[access_list]
mode = "allow"
path = "/var/lib/aquatic/accesslist"
[privileges]
# Chroot and switch group and user after binding to sockets
drop_privileges = true
# Chroot to this path
chroot_path = "/var/lib/aquatic"
# Group to switch to after chrooting
group = "nogroup"
# User to switch to after chrooting
user = "nobody"

View File

@ -0,0 +1,18 @@
{
auto_https off
admin off
http_port 9000
}
:9000 {
# Route for tracker-helper service
handle_path /helper/* {
reverse_proxy localhost:5063
}
# Route for Prometheus metrics
handle_path /metrics/* {
reverse_proxy localhost:9000
}
}

View File

@ -0,0 +1,3 @@
#!/command/with-contenv sh
exec /usr/local/bin/aquatic_udp -c /etc/aquatic/config.toml

View File

@ -0,0 +1 @@
longrun

View File

@ -0,0 +1,3 @@
#!/command/with-contenv sh
/usr/bin/caddy run --config /etc/caddy/Caddyfile

View File

@ -0,0 +1 @@
longrun

View File

@ -0,0 +1,7 @@
#!/command/with-contenv sh
export PORT=5063
exec 2>&1
exec s6-setuidgid bun \
/home/bun/bin/bun run /usr/src/tracker-helper/index.ts

View File

@ -0,0 +1 @@
longrun

View File

@ -0,0 +1,5 @@
#!/bin/sh
pidof aquatic_udp || exit 111
curl -f http://localhost:5063/helper || exit 112
curl -f http://localhost:9000/metrics || exit 113

View File

@ -107,49 +107,24 @@ accessories:
- opentracker-etc:/etc/opentracker
- opentracker-var:/var/run/opentracker
# opentracker:
# image: gitea.futureporn.net/futureporn/opentracker:latest
# host: 45.76.57.101
# port: "127.0.0.1:6969:6969"
# env:
# clear:
# WHITELIST_FEED_URL: https://bright.futureporn.net/torrents
# secret:
# - WHITELIST_USERNAME
# - WHITELIST_PASSWORD
# proxy:
# ssl: true
# forward_headers: true
# app_port: 6969
# host: tracker.futureporn.net
# healthcheck:
# path: /stats
# volumes:
# - opentracker-etc:/etc/opentracker
# - opentracker-var:/var/run/opentracker
aquatic:
image: gitea.futureporn.net/futureporn/aquatic:latest
tracker:
image: gitea.futureporn.net/futureporn/tracker:latest
host: 45.76.57.101
port: "127.0.0.1:3000:3000"
port: "0.0.0.0:5063:5063"
env:
clear:
WHITELIST_FEED_URL: https://bright.futureporn.net/torrents
HELPER_ACCESSLIST_PATH: "/var/lib/aquatic/accesslist"
secret:
- WHITELIST_USERNAME
- WHITELIST_PASSWORD
- HELPER_USERNAME
- HELPER_PASSWORD
proxy:
ssl: true
forward_headers: true
app_port: 3000
app_port: 5063 # note: tracker also uses port 6969/udp and 9000/tcp, but the api at 5063/tcp is what we specify here. # @todo @blocking https://github.com/basecamp/kamal-proxy/issues/48
host: tracker.futureporn.net
## we can't do the healthcheck on the prometheus port because kamal only allows one port per container
## @blocking https://github.com/basecamp/kamal-proxy/issues/48
# healthcheck:
# path: /stats
volumes:
- opentracker-etc:/etc/opentracker
- opentracker-var:/var/run/opentracker
healthcheck:
path: /health
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest

View File

@ -9,38 +9,6 @@ services:
volumes:
- aquatic
# opentracker:
# build:
# context: ./apps/opentracker
# container_name: opentracker
# environment:
# - WHITELIST_FEED_URL=http://bright:4000/torrents
# env_file:
# - .kamal/secrets.development
# ports:
# - "6969:6969/tcp"
# - "6969:6969/udp"
# volumes:
# - opentracker-etc:/etc/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"
# bright:
# container_name: bright

View File

@ -1,158 +0,0 @@
import {
describe
, expect
, it
, beforeEach
} from 'bun:test'
import {
Elysia
} from 'elysia'
import {
treaty
} from '@elysiajs/eden'
import app from '../app.ts'
import Docker from 'dockerode'
if (!process.env.WL_FILE_PATH) throw new Error("WL_FILE_PATH is missing in env");
if (!process.env.WL_USERNAME) throw new Error("WL_USERNAME is missing in env.");
if (!process.env.WL_PASSWORD) throw new Error("WL_PASSWORD is missing in env.");
const whitelistFilePath = process.env.WL_FILE_PATH!
const fixture = "3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01"
const username = process.env.WL_USERNAME!
const password = process.env.WL_PASSWORD!
const opts = {
headers: {
authorization: "Basic " + btoa(username + ':' + password)
}
}
const api = treaty(app)
describe
('tracker-helper', () => {
beforeEach(() => {
let whitelistFilePath = process.env.WL_FILE_PATH!
console.log(`Asserting existance of whitelist at ${whitelistFilePath}`)
// create whitelist file if it doesn't exist
const assertWhitelistExists = async function assertWhitelistExists(whitelistFilePath: string) {
const wlFile = Bun.file(whitelistFilePath);
const exists = await wlFile.exists()
if (!exists) {
console.log(`creating whitelist file at ${whitelistFilePath}`)
await wlFile.write("")
}
}
const clearWhitelist = async function clearWhitelist(whitelistFilePath: string) {
const wlFile = Bun.file(whitelistFilePath);
await wlFile.write("")
}
assertWhitelistExists(whitelistFilePath)
clearWhitelist(whitelistFilePath)
});
it('return a health response', async () => {
const { data, status } = await api.health.get()
expect(status).toBe(200)
expect(data).toBe("OK")
})
it('return a version', async () => {
const { data, status } = await api.version.get(opts)
expect(status).toBe(200)
expect(data).toContain("version")
})
it('return a whitelist', async () => {
const seedWhitelist = async function clearWhitelist(p: string, f: string) {
const wlFile = Bun.file(p);
await wlFile.write(f)
}
await seedWhitelist(whitelistFilePath, fixture)
const { data, status } = await api.whitelist.get(opts)
expect(status).toBe(200)
expect(data).toContain("3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01")
})
it('expects the whitelist to already exist', async () => {
const whitelist = Bun.file(whitelistFilePath)
const whitelistExists = await whitelist.exists()
expect(whitelistExists).toBe(true)
})
it('appends a new info_hash to the whitelist file', async () => {
// make an api call which is supposed to add an entry to the whitelist
const { data, status } = await api.whitelist.post(fixture, opts)
// assert that the entry has been added to the whitelist
const w = Bun.file(whitelistFilePath)
const whitelistAfter = await w.text()
console.log('whitelistAfter as follows')
console.log(whitelistAfter)
expect(status).toBe(201)
expect(data).toMatch(fixture)
expect(whitelistAfter).toMatch(fixture)
})
// it('sends a SIGHUP to opentracker', async () => {
// const { data, status } = await api.whitelist.post(fixture, opts)
// const containerId = "act-ci-Tests-Checks-6e6f12196682961041a41a25b9d0dcf00e4d0f8e58f-7cb37eebfe9e1670328d58ad1f7c7bdf0fa078298ca6dd299e67d0141a4b9579"
// // await docker.getContainer(containerId).kill({ signal: 'SIGHUP' })
// let container = await docker.getContainer(containerId)
// container.inspect
// })
// // This is skipped because I couldn't figure out opentracker's whitelist add/delete via FIFO functionality.
// // I got as far as writing to the FIFO, and seeing opentracker acknowledge the line in it's logs.
// // Despite this, requests from qbittorrent to opentracker responded with,
// // "Requested download is not authorized for use with this tracker"
// // About a week on this problem, and I give up! Using the whitelist reloading strat instead.
// it.skip('writes a new info_hash to a fifo', async () => {
// const fifoFilePath = process.env.WL_FIFO_PATH!
// const fifo = Bun.file(fifoFilePath)
// const fifoExists = await fifo.exists();
// // create fifo if it doesn't exist
// if (!fifoExists) {
// await Bun.spawn(["mkfifo", fifoFilePath]).exited;
// }
// // Start a process to read from the FIFO
// const reader = Bun.spawn(["cat", fifoFilePath], { stdout: "pipe" });
// const { data, status } = await api.whitelist.post("3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01", opts)
// const text = await new Response(reader.stdout).text();
// expect(text).toBe("3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01\n")
// expect(status).toBe(200)
// expect(data).toBe("3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01")
// })
it('returns 401 when username/password is missing from GET /whitelist ', async () => {
const { status } = await api.whitelist.get()
expect(status).toBe(401)
})
it('returns 401 when username/password is missing from POST /whitelist ', async () => {
const { status } = await api.whitelist.post()
expect(status).toBe(401)
})
})

View File

@ -0,0 +1,4 @@
b22ea43e4c0a7f73fc706b5faf1c35bb078d3722
3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01
3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01
3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01

View File

@ -28,8 +28,8 @@ COPY . .
# [optional] tests & build
ENV NODE_ENV=test WL_FILE_PATH=/tmp/whitelist
RUN --mount=type=secret,id=WL_USERNAME,env=WL_USERNAME,required=true \
--mount=type=secret,id=WL_PASSWORD,env=WL_PASSWORD,required=true \
RUN --mount=type=secret,id=TRACKER_HELPER_USERNAME,env=TRACKER_HELPER_USERNAME,required=true \
--mount=type=secret,id=TRACKER_HELPER_PASSWORD,env=TRACKER_HELPER_PASSWORD,required=true \
bun test
# copy production dependencies and source code into final image

View File

@ -4,12 +4,12 @@ import { basicAuth } from '@eelkevdbos/elysia-basic-auth'
import net from 'net'
import { appendFile } from "node:fs/promises";
if (!process.env.WL_USERNAME) throw new Error('WL_USERNAME missing in env');
if (!process.env.WL_PASSWORD) throw new Error('WL_PASSWORD missing in env');
if (!process.env.TRACKER_HELPER_USERNAME) throw new Error('TRACKER_HELPER_USERNAME missing in env');
if (!process.env.TRACKER_HELPER_PASSWORD) throw new Error('TRACKER_HELPER_PASSWORD missing in env');
const whitelistFilePath = process.env.WL_FILE_PATH || "/etc/opentracker/whitelist"
const username = process.env.WL_USERNAME!
const password = process.env.WL_PASSWORD!
const accesslistFilePath = process.env.TRACKER_HELPER_ACCESSLIST_PATH || "/var/lib/aquatic/accesslist"
const username = process.env.TRACKER_HELPER_USERNAME!
const password = process.env.TRACKER_HELPER_PASSWORD!
interface DockerContainer {
Id: string;
@ -18,7 +18,7 @@ interface DockerContainer {
const authOpts = {
scope: [
"/whitelist",
"/accesslist",
"/version"
],
credentials: [
@ -32,8 +32,8 @@ const authOpts = {
const startupChecks = async function startupChecks() {
if (!process.env.WL_FILE_PATH) {
console.warn(`WL_FILE_PATH is missing in env. Using default ${whitelistFilePath}`)
if (!process.env.TRACKER_HELPER_ACCESSLIST_PATH) {
console.warn(`TRACKER_HELPER_ACCESSLIST_PATH is missing in env. Using default ${accesslistFilePath}`)
}
}
@ -41,9 +41,9 @@ const startupChecks = async function startupChecks() {
const getWhitelist = async function getWhitelist(ctx: Context) {
const wl = Bun.file(whitelistFilePath); // relative to cwd
console.debug(`read from whitelist file at ${whitelistFilePath}. size=${wl.size}, type=${wl.type}`)
const getAccesslist = async function getAccesslist(ctx: Context) {
const wl = Bun.file(accesslistFilePath); // relative to cwd
console.debug(`read from accesslist file at ${accesslistFilePath}. size=${wl.size}, type=${wl.type}`)
return wl.text()
}
@ -127,11 +127,11 @@ const maybeKillContainer = async function maybeKillContainer(signal: string = "S
}
const postWhitelist = async function postWhitelist(ctx: Context) {
const postAccesslist = async function postAccesslist(ctx: Context) {
let body = ctx.body
console.debug('appending to whitelist at ' + whitelistFilePath)
await appendFile(whitelistFilePath, body + "\n");
console.debug('appending to accesslist at ' + accesslistFilePath)
await appendFile(accesslistFilePath, body + "\n");
await maybeKillContainer("SIGUSR1")
@ -148,8 +148,8 @@ const app = new Elysia()
.use(basicAuth(authOpts))
.get('/health', () => 'OK')
.get('/version', () => `version ${version}`)
.get('/whitelist', getWhitelist)
.post('/whitelist', postWhitelist, {
.get('/accesslist', getAccesslist)
.post('/accesslist', postAccesslist, {
body: t.String()
})

View File

@ -0,0 +1,156 @@
import {
describe
, expect
, it
, beforeEach
} from 'bun:test'
import {
Elysia
} from 'elysia'
import {
treaty
} from '@elysiajs/eden'
import app from '../app.ts'
import Docker from 'dockerode'
import { mkdir } from "node:fs/promises";
import path from 'node:path'
if (!process.env.TRACKER_HELPER_USERNAME) throw new Error("TRACKER_HELPER_USERNAME is missing in env.");
if (!process.env.TRACKER_HELPER_PASSWORD) throw new Error("TRACKER_HELPER_PASSWORD is missing in env.");
const accesslistFilePath = process.env.TRACKER_HELPER_ACCESSLIST_PATH || "/var/lib/aquatic/accesslist"
const fixture = "3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01" // ubuntustudio-22.04.5-dvd-amd64.iso
const fixture2 = "b22ea43e4c0a7f73fc706b5faf1c35bb078d3722" // Solus-GNOME-Release-2025-01-26.iso
const username = process.env.TRACKER_HELPER_USERNAME!
const password = process.env.TRACKER_HELPER_PASSWORD!
const opts = {
headers: {
authorization: "Basic " + btoa(username + ':' + password)
}
}
const api = treaty(app)
describe
('tracker-helper', () => {
beforeEach(async () => {
console.log(`Asserting existence of accesslist at ${accesslistFilePath}`);
const assertAccesslistExists = async function (accesslistFilePath: string) {
const wlFile = Bun.file(accesslistFilePath);
const exists = await wlFile.exists();
if (!exists) {
console.log(`Creating accesslist file at ${accesslistFilePath}`);
// Ensure the parent directory exists
await mkdir(path.dirname(accesslistFilePath), { recursive: true });
await wlFile.write(`${fixture2}\n${fixture}\n`);
}
};
await assertAccesslistExists(accesslistFilePath);
});
it('return a health response', async () => {
const { data, status } = await api.health.get()
expect(status).toBe(200)
expect(data).toBe("OK")
})
it('return a version', async () => {
const { data, status } = await api.version.get(opts)
expect(status).toBe(200)
expect(data).toContain("version")
})
it('return a accesslist', async () => {
const { data, status } = await api.accesslist.get(opts)
expect(status).toBe(200)
expect(data).toContain(fixture)
})
it('expects the accesslist to already exist', async () => {
const accesslist = Bun.file(accesslistFilePath)
const accesslistExists = await accesslist.exists()
expect(accesslistExists).toBe(true)
})
it('appends a new info_hash to the accesslist file', async () => {
// make an api call which is supposed to add an entry to the accesslist
const { data, status } = await api.accesslist.post(fixture, opts)
// assert that the entry has been added to the accesslist
const w = Bun.file(accesslistFilePath)
const accesslistAfter = await w.text()
console.log('accesslistAfter as follows')
console.log(accesslistAfter)
expect(status).toBe(201)
expect(data).toMatch(fixture)
expect(accesslistAfter).toMatch(fixture)
})
// it('sends a SIGHUP to opentracker', async () => {
// const { data, status } = await api.accesslist.post(fixture, opts)
// const containerId = "act-ci-Tests-Checks-6e6f12196682961041a41a25b9d0dcf00e4d0f8e58f-7cb37eebfe9e1670328d58ad1f7c7bdf0fa078298ca6dd299e67d0141a4b9579"
// // await docker.getContainer(containerId).kill({ signal: 'SIGHUP' })
// let container = await docker.getContainer(containerId)
// container.inspect
// })
// // This is skipped because I couldn't figure out opentracker's accesslist add/delete via FIFO functionality.
// // I got as far as writing to the FIFO, and seeing opentracker acknowledge the line in it's logs.
// // Despite this, requests from qbittorrent to opentracker responded with,
// // "Requested download is not authorized for use with this tracker"
// // About a week on this problem, and I give up! Using the accesslist reloading strat instead.
// it.skip('writes a new info_hash to a fifo', async () => {
// const fifoFilePath = process.env.TRACKER_HELPER_FIFO_PATH!
// const fifo = Bun.file(fifoFilePath)
// const fifoExists = await fifo.exists();
// // create fifo if it doesn't exist
// if (!fifoExists) {
// await Bun.spawn(["mkfifo", fifoFilePath]).exited;
// }
// // Start a process to read from the FIFO
// const reader = Bun.spawn(["cat", fifoFilePath], { stdout: "pipe" });
// const { data, status } = await api.accesslist.post("3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01", opts)
// const text = await new Response(reader.stdout).text();
// expect(text).toBe("3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01\n")
// expect(status).toBe(200)
// expect(data).toBe("3aa5ad5e62eaffd148cff3dbe93ff2e1e9cbcf01")
// })
it('returns 401 when username/password is missing from GET /accesslist ', async () => {
const { status } = await api.accesslist.get()
expect(status).toBe(401)
})
it('returns 401 when username/password is missing from POST /accesslist ', async () => {
const { status } = await api.accesslist.post()
expect(status).toBe(401)
})
})