Initial workspace scaffold
Cargo workspace with 5 crates: buh-entity (pure data structs), buh-data (Turso/libsql data access), buh-util (scraper, rules, processor, sync modules), buh-cli (binary "buh" with client/daemon subcommands), and buh-ws (axum WebSocket server). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
53
CLAUDE.md
Normal file
53
CLAUDE.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## What is buh
|
||||
|
||||
buh is a media automation engine that scrapes torrent indexers, indexes torrents and metadata, selects downloads via operator-defined ingestion/discard rules, queues downloads, processes downloaded files (renaming), and syncs them to LAN targets (e.g., Jellyfin media folders). Media types: shows, movies, music, books, audio-books.
|
||||
|
||||
## Build & Test Commands
|
||||
|
||||
```bash
|
||||
cargo check --workspace # fast type-check all crates
|
||||
cargo build --workspace # full build
|
||||
cargo test --workspace # run all tests
|
||||
cargo test -p buh-entity # test a single crate
|
||||
cargo run -p buh-cli -- --help # run the CLI
|
||||
cargo run -p buh-cli -- daemon --config daemon.toml # run daemon mode
|
||||
cargo run -p buh-ws # start the WebSocket server
|
||||
```
|
||||
|
||||
The CLI binary is named `buh` (not `buh-cli`). When no subcommand is given, it defaults to `client` mode.
|
||||
|
||||
## Architecture
|
||||
|
||||
Cargo workspace with edition 2024, resolver 3. All shared dependencies are declared in the root `Cargo.toml` under `[workspace.dependencies]` and inherited by crates.
|
||||
|
||||
### Dependency graph (arrows = "depends on")
|
||||
|
||||
```
|
||||
buh-entity ← leaf crate, no internal deps, serde-only
|
||||
↑
|
||||
buh-data ← Turso/libsql data access layer
|
||||
↑
|
||||
buh-util ← domain logic (scraper, rules, processor, sync modules)
|
||||
↑
|
||||
buh-cli, buh-ws ← binaries
|
||||
```
|
||||
|
||||
### Crate responsibilities
|
||||
|
||||
- **buh-entity** — Pure data structs. No business logic, no database awareness. Only depends on `serde`. Every other crate imports this.
|
||||
- **buh-data** — All database access goes through `Db` struct wrapping a libsql `Connection`. Uses `thiserror` for typed errors. Schema migrations live in `Db::migrate()`.
|
||||
- **buh-util** — Domain logic organized as modules: `scraper` (indexer scraping), `rules` (ingestion/discard evaluation), `processor` (file renaming), `sync` (LAN file sync).
|
||||
- **buh-cli** — Binary `buh` with two clap subcommands: `client` (default, interactive) and `daemon` (reads TOML config, runs routines). Config path defaults to `/etc/buh/daemon.toml`.
|
||||
- **buh-ws** — Axum-based WebSocket server on port 9000, endpoint `/ws`.
|
||||
|
||||
### Error handling convention
|
||||
|
||||
Libraries (`buh-data`, `buh-util`, `buh-entity`) use `thiserror` for typed errors. Binaries (`buh-cli`, `buh-ws`) use `anyhow` for error erasure.
|
||||
|
||||
### Config format
|
||||
|
||||
Daemon configuration is TOML. The schema is defined in `buh-entity::config`. See `daemon.toml` at the repo root for an example.
|
||||
3561
Cargo.lock
generated
Normal file
3561
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
Cargo.toml
Normal file
40
Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/*"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
version = "0.1.0"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
# internal
|
||||
buh-entity = { path = "crates/buh-entity" }
|
||||
buh-data = { path = "crates/buh-data" }
|
||||
buh-util = { path = "crates/buh-util" }
|
||||
|
||||
# async runtime
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
# serialization
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
toml = "0.8"
|
||||
|
||||
# http / websocket
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
axum = { version = "0.8", features = ["ws"] }
|
||||
|
||||
# database
|
||||
libsql = "0.9"
|
||||
|
||||
# cli
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
# observability
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
# errors
|
||||
thiserror = "2"
|
||||
anyhow = "1"
|
||||
79
README.md
Normal file
79
README.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# buh
|
||||
|
||||
A media automation engine that scrapes torrent indexers, indexes torrents and their metadata, selects interesting downloads using operator-defined rules, queues downloads, processes downloaded files using renaming rules, and syncs renamed files to configured LAN targets.
|
||||
|
||||
The end goal is to populate a Jellyfin (or similar) media server with content — shows, movies, music, books, and audio-books — based on ingestion rules, and to later discard content based on operator discard rules.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
buh-entity pure data structs (serde only, no logic)
|
||||
↑
|
||||
buh-data data access layer (Turso/libsql)
|
||||
↑
|
||||
buh-util domain logic (scraper, rules, processor, sync)
|
||||
↑
|
||||
buh-cli CLI binary ("buh")
|
||||
buh-ws WebSocket server (axum)
|
||||
```
|
||||
|
||||
All crates live under `crates/` in a single Cargo workspace.
|
||||
|
||||
## Usage
|
||||
|
||||
### CLI
|
||||
|
||||
```bash
|
||||
# interactive client mode (default)
|
||||
buh
|
||||
|
||||
# run daemon routines from config
|
||||
buh daemon --config /etc/buh/daemon.toml
|
||||
```
|
||||
|
||||
The `client` subcommand is assumed when no subcommand is provided.
|
||||
|
||||
### WebSocket server
|
||||
|
||||
```bash
|
||||
cargo run -p buh-ws
|
||||
# listens on 0.0.0.0:9000, endpoint /ws
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The daemon reads a TOML configuration file. See [`daemon.toml`](daemon.toml) for an example.
|
||||
|
||||
```toml
|
||||
[database]
|
||||
url = "libsql://your-db.turso.io"
|
||||
auth_token = "your-token"
|
||||
|
||||
[[indexers]]
|
||||
name = "example-indexer"
|
||||
url = "https://example.com/api"
|
||||
media_types = ["show", "movie"]
|
||||
|
||||
[[sync]]
|
||||
name = "jellyfin-shows"
|
||||
media_type = "show"
|
||||
path = "/mnt/media/shows"
|
||||
|
||||
[routines]
|
||||
scrape = true
|
||||
process = true
|
||||
sync = true
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
cargo build --workspace
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
Requires Rust edition 2024 (stable 1.85+).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
20
crates/buh-cli/Cargo.toml
Normal file
20
crates/buh-cli/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "buh-cli"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "buh"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
buh-entity = { workspace = true }
|
||||
buh-data = { workspace = true }
|
||||
buh-util = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
4
crates/buh-cli/src/client.rs
Normal file
4
crates/buh-cli/src/client.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub async fn run() -> anyhow::Result<()> {
|
||||
tracing::info!("client mode not yet implemented");
|
||||
Ok(())
|
||||
}
|
||||
24
crates/buh-cli/src/daemon.rs
Normal file
24
crates/buh-cli/src/daemon.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use std::path::Path;
|
||||
|
||||
use buh_entity::config::DaemonConfig;
|
||||
|
||||
pub async fn run(config_path: &Path) -> anyhow::Result<()> {
|
||||
let raw = std::fs::read_to_string(config_path)?;
|
||||
let config: DaemonConfig = toml::from_str(&raw)?;
|
||||
|
||||
let db = buh_data::Db::connect(&config.database.url, config.database.auth_token.as_deref())
|
||||
.await?;
|
||||
db.migrate().await?;
|
||||
|
||||
if config.routines.scrape {
|
||||
tracing::info!("scrape routine: not yet implemented");
|
||||
}
|
||||
if config.routines.process {
|
||||
tracing::info!("process routine: not yet implemented");
|
||||
}
|
||||
if config.routines.sync {
|
||||
tracing::info!("sync routine: not yet implemented");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
37
crates/buh-cli/src/main.rs
Normal file
37
crates/buh-cli/src/main.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
mod client;
|
||||
mod daemon;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "buh", version, about = "Media automation engine")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
/// Interactive client (default when no subcommand is given)
|
||||
Client,
|
||||
/// Run daemon routines from a configuration file
|
||||
Daemon {
|
||||
/// Path to daemon TOML configuration file
|
||||
#[arg(long, default_value = "/etc/buh/daemon.toml")]
|
||||
config: std::path::PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command.unwrap_or(Command::Client) {
|
||||
Command::Client => client::run().await,
|
||||
Command::Daemon { config } => daemon::run(&config).await,
|
||||
}
|
||||
}
|
||||
11
crates/buh-data/Cargo.toml
Normal file
11
crates/buh-data/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "buh-data"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
buh-entity = { workspace = true }
|
||||
libsql = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
77
crates/buh-data/src/lib.rs
Normal file
77
crates/buh-data/src/lib.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use libsql::Connection;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DataError {
|
||||
#[error("database error: {0}")]
|
||||
Database(#[from] libsql::Error),
|
||||
|
||||
#[error("not found: {0}")]
|
||||
NotFound(String),
|
||||
}
|
||||
|
||||
pub struct Db {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl Db {
|
||||
pub async fn connect(url: &str, auth_token: Option<&str>) -> Result<Self, DataError> {
|
||||
let db = match auth_token {
|
||||
Some(token) => {
|
||||
libsql::Builder::new_remote(url.to_string(), token.to_string())
|
||||
.build()
|
||||
.await?
|
||||
}
|
||||
None => {
|
||||
libsql::Builder::new_local(url)
|
||||
.build()
|
||||
.await?
|
||||
}
|
||||
};
|
||||
let conn = db.connect()?;
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
pub async fn migrate(&self) -> Result<(), DataError> {
|
||||
self.conn
|
||||
.execute_batch(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS torrents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
info_hash TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
media_type TEXT,
|
||||
state TEXT NOT NULL DEFAULT 'discovered',
|
||||
source_url TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
media_type TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
pattern TEXT NOT NULL,
|
||||
priority INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS media_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
media_type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
year INTEGER,
|
||||
torrent_id INTEGER REFERENCES torrents(id),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
",
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("database migrations applied");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn conn(&self) -> &Connection {
|
||||
&self.conn
|
||||
}
|
||||
}
|
||||
7
crates/buh-entity/Cargo.toml
Normal file
7
crates/buh-entity/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "buh-entity"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
39
crates/buh-entity/src/config.rs
Normal file
39
crates/buh-entity/src/config.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::media::MediaType;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct DaemonConfig {
|
||||
pub database: DatabaseConfig,
|
||||
pub indexers: Vec<IndexerConfig>,
|
||||
pub sync: Vec<SyncTarget>,
|
||||
pub routines: RoutineConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct DatabaseConfig {
|
||||
pub url: String,
|
||||
pub auth_token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct IndexerConfig {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
pub api_key: Option<String>,
|
||||
pub media_types: Vec<MediaType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct SyncTarget {
|
||||
pub name: String,
|
||||
pub media_type: MediaType,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct RoutineConfig {
|
||||
pub scrape: bool,
|
||||
pub process: bool,
|
||||
pub sync: bool,
|
||||
}
|
||||
4
crates/buh-entity/src/lib.rs
Normal file
4
crates/buh-entity/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod media;
|
||||
pub mod rule;
|
||||
pub mod torrent;
|
||||
19
crates/buh-entity/src/media.rs
Normal file
19
crates/buh-entity/src/media.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum MediaType {
|
||||
Show,
|
||||
Movie,
|
||||
Music,
|
||||
Book,
|
||||
AudioBook,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MediaItem {
|
||||
pub id: Option<i64>,
|
||||
pub media_type: MediaType,
|
||||
pub title: String,
|
||||
pub year: Option<u16>,
|
||||
}
|
||||
20
crates/buh-entity/src/rule.rs
Normal file
20
crates/buh-entity/src/rule.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::media::MediaType;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum RuleAction {
|
||||
Ingest,
|
||||
Discard,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Rule {
|
||||
pub id: Option<i64>,
|
||||
pub name: String,
|
||||
pub media_type: MediaType,
|
||||
pub action: RuleAction,
|
||||
pub pattern: String,
|
||||
pub priority: i32,
|
||||
}
|
||||
25
crates/buh-entity/src/torrent.rs
Normal file
25
crates/buh-entity/src/torrent.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::media::MediaType;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum TorrentState {
|
||||
Discovered,
|
||||
Queued,
|
||||
Downloading,
|
||||
Downloaded,
|
||||
Processed,
|
||||
Synced,
|
||||
Discarded,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Torrent {
|
||||
pub id: Option<i64>,
|
||||
pub info_hash: String,
|
||||
pub name: String,
|
||||
pub media_type: Option<MediaType>,
|
||||
pub state: TorrentState,
|
||||
pub source_url: String,
|
||||
}
|
||||
14
crates/buh-util/Cargo.toml
Normal file
14
crates/buh-util/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "buh-util"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
buh-entity = { workspace = true }
|
||||
buh-data = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
4
crates/buh-util/src/lib.rs
Normal file
4
crates/buh-util/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod processor;
|
||||
pub mod rules;
|
||||
pub mod scraper;
|
||||
pub mod sync;
|
||||
1
crates/buh-util/src/processor.rs
Normal file
1
crates/buh-util/src/processor.rs
Normal file
@@ -0,0 +1 @@
|
||||
//! Post-download file processing and renaming.
|
||||
1
crates/buh-util/src/rules.rs
Normal file
1
crates/buh-util/src/rules.rs
Normal file
@@ -0,0 +1 @@
|
||||
//! Rule evaluation engine for torrent selection and discard.
|
||||
1
crates/buh-util/src/scraper.rs
Normal file
1
crates/buh-util/src/scraper.rs
Normal file
@@ -0,0 +1 @@
|
||||
//! Scraper routines for torrent indexers.
|
||||
1
crates/buh-util/src/sync.rs
Normal file
1
crates/buh-util/src/sync.rs
Normal file
@@ -0,0 +1 @@
|
||||
//! File synchronization to LAN targets.
|
||||
16
crates/buh-ws/Cargo.toml
Normal file
16
crates/buh-ws/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "buh-ws"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
buh-entity = { workspace = true }
|
||||
buh-data = { workspace = true }
|
||||
buh-util = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
34
crates/buh-ws/src/main.rs
Normal file
34
crates/buh-ws/src/main.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use axum::{
|
||||
Router,
|
||||
extract::ws::{WebSocket, WebSocketUpgrade},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
};
|
||||
|
||||
async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
|
||||
ws.on_upgrade(handle_socket)
|
||||
}
|
||||
|
||||
async fn handle_socket(mut socket: WebSocket) {
|
||||
while let Some(Ok(msg)) = socket.recv().await {
|
||||
tracing::debug!(?msg, "received");
|
||||
if socket.send(msg).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let app = Router::new().route("/ws", get(ws_handler));
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:9000").await?;
|
||||
tracing::info!("buh-ws listening on {}", listener.local_addr()?);
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
38
daemon.toml
Normal file
38
daemon.toml
Normal file
@@ -0,0 +1,38 @@
|
||||
[database]
|
||||
url = "libsql://your-db.turso.io"
|
||||
auth_token = "your-token"
|
||||
|
||||
[[indexers]]
|
||||
name = "example-indexer"
|
||||
url = "https://example.com/api"
|
||||
media_types = ["show", "movie"]
|
||||
|
||||
[[sync]]
|
||||
name = "jellyfin-shows"
|
||||
media_type = "show"
|
||||
path = "/mnt/media/shows"
|
||||
|
||||
[[sync]]
|
||||
name = "jellyfin-movies"
|
||||
media_type = "movie"
|
||||
path = "/mnt/media/movies"
|
||||
|
||||
[[sync]]
|
||||
name = "jellyfin-music"
|
||||
media_type = "music"
|
||||
path = "/mnt/media/music"
|
||||
|
||||
[[sync]]
|
||||
name = "jellyfin-books"
|
||||
media_type = "book"
|
||||
path = "/mnt/media/books"
|
||||
|
||||
[[sync]]
|
||||
name = "jellyfin-audiobooks"
|
||||
media_type = "audio-book"
|
||||
path = "/mnt/media/audiobooks"
|
||||
|
||||
[routines]
|
||||
scrape = true
|
||||
process = true
|
||||
sync = true
|
||||
Reference in New Issue
Block a user