Skip to content

Commit

Permalink
update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
benjick committed Sep 25, 2024
1 parent 6782500 commit ba1a31c
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 89 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Docker Publish

on:
workflow_dispatch:
inputs:
tag:
description: "Tag for the Docker image"
required: true
default: "latest"

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build Docker image
run: docker build -t ghcr.io/${{ github.repository }}:${{ github.event.inputs.tag }} .

- name: Push Docker image
run: docker push ghcr.io/${{ github.repository }}:${{ github.event.inputs.tag }}
42 changes: 0 additions & 42 deletions .github/workflows/push-main.yml

This file was deleted.

128 changes: 103 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,119 @@

## Setup

1. Create a Discord bot and get the **bot** token: https://discord.com/developers/applications

2. Create a `.env` file with the following:

```
DISCORD_BOT_TOKEN=<your token>
```

3. Create a `config.yml` file

```yml
mediaManagers:
- id: "radarr"
enabled: true
type: "radarr"
apiUrl: "http://radarr:7878/radarr"
apiKey: "xxx"
- id: "sonarr"
enabled: true
type: "sonarr"
apiUrl: "http://sonarr:8989/sonarr"
apiKey: "xxx"
votingPeriod: "1 week"
gracePeriod: "1 week"
immunityPeriod: "3 months"
discord:
channelId: "123456" # Enable Discord developer mode and right-click the channel then press "Copy Channel ID"
```
4. `docker-compose.yml`

```yml
services:
byedarr:
image: ghcr.io/benjick/byedarr:latest
env_file:
- .env
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
- CONFIG_PATH=/config/config.yml
volumes:
- ./config.yml:/config/config.yml
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres_data:
```

> ✋ The config is read on app start, so any changes will require a restart.

## Full docs

### Environment variables

| Variable | Type | Comment |
| ----------------- | ------------ | --------------------------------------------------------- |
| DISCORD_BOT_TOKEN | string | https://discord.com/developers/applications |
| CONFIG_PATH | string | Path to the configuration file, e.g. `/config/config.yml` |
| DATABASE_URL | string (URL) | PostgreSQL database connection URL |
| DRY_RUN | boolean | If true, nothing is actually deleted from sonarr/radarr |
| DEBUG | boolean | If true, debug messages are printed to the console |
| SKIP_MIGRATIONS | boolean | If true, migrations are skipped |

### `config.yml`

```yaml
cron:
findMedia: "0 18 * * *" # Cron string for how often to check for media to remove (default: "0 18 * * *")
processVotes: "0 * * * *" # Cron string for how often to check for ended votes and remove media if necessary (default: "0 * * * *")

mediaManagers: # List of media managers to use
- id: string # Unique identifier for the media manager, can be anything
apiUrl: string # API URL for the media manager, without /api
apiKey: string # API key for the media manager
type: "radarr" | "sonarr" # Type of media manager
weights: # Weights for each media attribute
age: number # (default: 0.1)
size: number # (default: 0.3)
imdb: number # (default: 0.5)
tmdb: number # (default: 0.5)
addImportExclusionOnDelete: boolean # Whether to add an import exclusion on delete for this manager (default: false)

ignoredPaths: [string] # Paths to ignore (default: [])

discord: # Discord settings
channelId: string # ID of the channel to send messages to

votingPeriod: string # How long a vote lasts (default: "1 week")

gracePeriod: string # How long to wait after a vote ends before removing media (default: "2 weeks")

onDraw: "keep" | "delete" # What to do if a vote ends in a draw (default: "delete")
# How often to check for media to remove (default: daily at 6 PM)
findMedia: "0 18 * * *"
# How often to check for ended votes and process results (default: hourly)
processVotes: "0 * * * *"
# List of media managers to use
mediaManagers:
- id: "unique_identifier" # Unique identifier for the media manager (don't change later)
enabled: true # Should look for media in this manager
apiUrl: "http://radarr:7878/radarr" # Base URL for the media manager's API (excluding '/api')
apiKey: "xxx"
type: "radarr" # Specifies the media manager software (radarr or sonarr)
count: 1 # Number of media items to include in each voting round
weights: # Weights for each media attribute
age: 0.1 # Weight based on time since added to the media manager
size: 0.3 # Weight based on media size
rating: 1 # Weight based on media rating
addImportExclusionOnDelete: false # If true, adds deleted items to import exclusion list
# Paths to ignore when processing media
ignoredPaths:
- "/path/to/ignore"
- "/another/path"
# Discord settings
discord:
channelId: "discord_channel_id" # ID of the channel to send messages to
# Voting and grace period settings
votingPeriod: "1 week" # How long a vote lasts
gracePeriod: "2 weeks" # Wait time after vote ends before removing/whitelisting media
immunityPeriod: "3 months" # Time a new media item is immune from voting
# What to do if a vote ends in a draw (keep or delete, default delete)
onDraw: "delete" | "keep"
# If true, no media will actually be deleted from managers
dryRun: false
```
24 changes: 24 additions & 0 deletions docker-compose.pull.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
app:
image: ghcr.io/benjick/byedarr:latest
env_file:
- .env
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
- CONFIG_PATH=/config/config.yml
volumes:
- ./config.yml:/config/config.yml
restart: unless-stopped

db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped

volumes:
postgres_data:
5 changes: 2 additions & 3 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
version: "3.8"
services:
app:
build:
Expand All @@ -7,10 +6,10 @@ services:
env_file:
- .env
environment:
- NODE_ENV=production
# - NODE_ENV=production
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
- CONFIG_PATH=/config/config.yml
# - DEBUG=true
- DEBUG=true
volumes:
- ./config.yml:/config/config.yml
restart: unless-stopped
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
"start": "tsx src/index.ts",
"dev": "NODE_ENV=development tsx watch src/index.ts",
"dev:db": "docker compose -f docker-compose.dev.yml up",
"test:docker": "docker compose -f docker-compose.test.yml up",
"test:docker:down": "docker compose -f docker-compose.test.yml down -v",
"test:docker": "docker compose -f docker-compose.test.yml -p byedarr-test up",
"test:docker:down": "docker compose -f docker-compose.test.yml -p byedarr-test down -v",
"pull:docker": "docker compose -f docker-compose.pull.yml -p byedarr-pull up",
"pull:docker:down": "docker compose -f docker-compose.pull.yml -p byedarr-pull down -v",
"test": "vitest",
"db:push": "dotenv drizzle-kit push",
"db:migrate": "dotenv drizzle-kit migrate",
Expand Down
16 changes: 1 addition & 15 deletions src/env.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
import "dotenv/config";
import { z } from "zod";

const envSchema = z.object({
DISCORD_BOT_TOKEN: z.string(),
CONFIG_PATH: z.string(),
DATABASE_URL: z
.string()
.url()
.default("postgres://postgres:postgres@localhost:5432/postgres"),
SKIP_MIGRATIONS: z.coerce.boolean().default(false),
DRY_RUN: z.coerce.boolean().default(false),
DEBUG: z.coerce.boolean().default(false),
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
});
import { envSchema } from "./envSchema";

const envMap = {
DISCORD_BOT_TOKEN: process.env.DISCORD_BOT_TOKEN,
Expand Down
32 changes: 32 additions & 0 deletions src/envSchema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { desc } from "drizzle-orm";
import { describe, expect, it } from "vitest";

import { boolean, envSchema } from "./envSchema";

describe("boolean", () => {
it("should parse strings properly", () => {
expect(boolean.parse("true")).toBe(true);
expect(boolean.parse("false")).toBe(false);
expect(boolean.parse("")).toBe(false);
expect(boolean.parse("anything")).toBe(false);
expect(boolean.parse(1)).toBe(true);
expect(boolean.parse(0)).toBe(false);
expect(boolean.parse(null)).toBe(false);
expect(boolean.parse(undefined)).toBe(false);
expect(boolean.parse(true)).toBe(true);
expect(boolean.parse(false)).toBe(false);
});
});

describe("envSchema", () => {
it("should have defaults properly set", () => {
const res = envSchema.parse({
DISCORD_BOT_TOKEN: "process.env.DISCORD_BOT_TOKEN",
CONFIG_PATH: "process.env.CONFIG_PATH",
});
expect(res.NODE_ENV).toBe("development");
expect(res.DRY_RUN).toBe(false);
expect(res.DEBUG).toBe(false);
expect(res.SKIP_MIGRATIONS).toBe(false);
});
});
21 changes: 21 additions & 0 deletions src/envSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from "zod";

export const boolean = z.preprocess((val) => {
if (typeof val === "string" && val !== "true") return false;
return val;
}, z.coerce.boolean());

export const envSchema = z.object({
DISCORD_BOT_TOKEN: z.string(),
CONFIG_PATH: z.string(),
DATABASE_URL: z
.string()
.url()
.default("postgres://postgres:postgres@localhost:5432/postgres"),
SKIP_MIGRATIONS: boolean.default(false),
DRY_RUN: boolean.default(false),
DEBUG: boolean.default(false),
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
});
2 changes: 1 addition & 1 deletion src/lib/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const bot = `
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▓▓▓▓▓▓▓▓▓███████▓▒▒░░░░░░░░█████▓▓▓▓▓▓▒░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓████████▒░░░░░░░░░░▒▓▓▒▒▓▓▓▓▓▓▓▓░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓█▓▓▓██████████████▓░░░░░░░▒▒▒▒░▒▒▒▒▓▓▓▓▓▓░░░░░░░
░░░░░░░░░░░░░ʙʏᴇᴅᴀʀʀ░░░░░░░░░░░░░░░░░░░████▓▓▓▓▓▓▓████▓▓████▓░░░░░░░▒▒▒░▒▒▒▒▓▓▓▓▓▓▓░░░░░░
░░░░░░░░░░░░░ʙʏᴇᴅᴀʀʀ░░░░░░░░░░░░░░░░░░░████▓▓▓▓▓▓▓████▓▓████▓░░░░░░░▒▒▒░▒▒▒▒▓▓▓▓▓▓▓░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒████▓▓▓▓█████████▓▓▓▓░░░░░░░▒▒▒▒▒▓▓▓█████▓░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▓▓▒▒▒▓▓███████▓▓▓▓▒▒▒▓▒░░░░░░▒▓▓███████████▓▒░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓█████░░░░░░░▒▒▒░░░░░░▒▓▓███▒░░░░░░░▓▓▓█▓▓▓▓█████░░░░░
Expand Down
1 change: 0 additions & 1 deletion src/services/deletionService.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// src/__tests__/calculate-deletion-score.test.ts
import { describe, expect, it } from "vitest";

import { MediaItemInsertSchema } from "../lib/types";
Expand Down

0 comments on commit ba1a31c

Please sign in to comment.