This commit is contained in:
2026-02-07 16:34:17 -08:00
commit 11ebd95956
6 changed files with 437 additions and 0 deletions

25
cleanup.nomad Normal file
View File

@@ -0,0 +1,25 @@
job "cleanup-litefs-all" {
datacenters = ["dc1"]
type = "batch"
group "cleanup" {
count = 2
constraint {
attribute = "${attr.unique.hostname}"
operator = "regexp"
value = "odroid7|odroid8"
}
task "clean" {
driver = "docker"
config {
image = "busybox"
volumes = [
"/mnt/configs/navidrome_litefs:/mnt/data"
]
command = "sh"
args = ["-c", "rm -rf /mnt/data/* && echo \"Cleaned $(hostname)\""]
}
}
}
}

38
juicefs-controller.nomad Normal file
View File

@@ -0,0 +1,38 @@
job "jfs-controller" {
datacenters = ["dc1"]
type = "system"
group "controller" {
task "plugin" {
driver = "docker"
config {
image = "juicedata/juicefs-csi-driver:v0.31.1"
args = [
"--endpoint=unix://csi/csi.sock",
"--logtostderr",
"--nodeid=test",
"--v=5",
"--by-process=true"
]
privileged = true
}
csi_plugin {
id = "juicefs0"
type = "controller"
mount_dir = "/csi"
}
resources {
cpu = 100
memory = 512
}
env {
POD_NAME = "csi-controller"
POD_NAMESPACE = "default"
}
}
}
}

63
juicefs-node.nomad Normal file
View File

@@ -0,0 +1,63 @@
job "jfs-node" {
datacenters = ["dc1"]
type = "system"
group "nodes" {
network {
port "metrics" {
static = 9567
to = 8080
}
}
service {
name = "juicefs-metrics"
port = "metrics"
tags = ["prometheus"]
check {
type = "http"
path = "/metrics"
interval = "10s"
timeout = "2s"
}
}
task "juicefs-plugin" {
driver = "docker"
config {
image = "juicedata/juicefs-csi-driver:v0.31.1"
memory_hard_limit = 2048
ports = ["metrics"]
args = [
"--endpoint=unix://csi/csi.sock",
"--logtostderr",
"--v=5",
"--nodeid=${node.unique.name}",
"--by-process=true",
]
privileged = true
}
csi_plugin {
id = "juicefs0"
type = "node"
mount_dir = "/csi"
health_timeout = "3m"
}
resources {
cpu = 100
memory = 100
}
env {
POD_NAME = "csi-node"
POD_NAMESPACE = "default"
# Aggregates metrics from children onto the 8080 port
JFS_METRICS = "0.0.0.0:8080"
# Ensures mounts run as background processes managed by the driver
JFS_MOUNT_MODE = "process"
}
}
}
}

92
navidrome-juice.nomad Normal file
View File

@@ -0,0 +1,92 @@
job "navidrome" {
datacenters = ["dc1"]
type = "service"
constraint {
attribute = "${attr.unique.hostname}"
operator = "regexp"
value = "odroid.*"
}
group "navidrome" {
count = 1
volume "navidrome-csi-vol" {
type = "csi"
source = "navidrome-volume" # This must match the 'id' in your volume registration
attachment_mode = "file-system"
access_mode = "multi-node-multi-writer"
}
# Main Navidrome task
task "navidrome" {
driver = "docker"
volume_mount {
volume = "navidrome-csi-vol" # Matches the name in the volume block above
destination = "/data" # Where it appears inside the container
read_only = false
}
config {
image = "ghcr.io/navidrome/navidrome:latest"
memory_hard_limit = "2048"
ports = ["http"]
volumes = [
"/mnt/Public/Downloads/Clean_Music:/music/CleanMusic:ro",
"/mnt/Public/Downloads/news/slskd/downloads:/music/slskd:ro",
"/mnt/Public/Downloads/incoming_music:/music/incomingmusic:ro"
]
}
env {
ND_DATAFOLDER = "/data"
ND_CACHEFOLDER = "/data/cache"
ND_CONFIGFILE= "/data/navidrome.toml"
ND_DBPATH = "/data/navidrome.db?_busy_timeout=30000&_journal_mode=DELETE&_foreign_keys=on&synchronous=NORMAL&cache=shared&nolock=1"
ND_SCANSCHEDULE = "32 8-20 * * *"
ND_LOGLEVEL = "trace"
ND_REVERSEPROXYWHITELIST = "0.0.0.0/0"
ND_REVERSEPROXYUSERHEADER = "X-Forwarded-User"
ND_SCANNER_GROUPALBUMRELEASES = "False"
ND_BACKUP_PATH = "/data/backups"
ND_BACKUP_SCHEDULE = "0 0 * * *"
ND_BACKUP_COUNT = "7"
}
resources {
cpu = 100
memory = 128
}
service {
name = "navidrome"
tags = [
"navidrome",
"web",
"urlprefix-/navidrome",
"tools",
"traefik.http.routers.navidromelan.rule=Host(`navidrome.service.dc1.consul`)",
"traefik.http.routers.navidromewan.rule=Host(`m.fbleagh.duckdns.org`)",
"traefik.http.routers.navidromewan.middlewares=dex@consulcatalog",
"traefik.http.routers.navidromewan.tls=true",
]
port = "http"
check {
type = "tcp"
interval = "10s"
timeout = "2s"
}
}
}
network {
port "http" {
static = 4533
to = 4533
}
}
}
}

184
navidrome-litefs.nomad Normal file
View File

@@ -0,0 +1,184 @@
job "navidrome-litefs" {
datacenters = ["dc1"]
type = "service"
# We pin to Linux because LiteFS requires FUSE
constraint {
attribute = "${attr.kernel.name}"
value = "linux"
}
group "navidrome" {
count = 2
constraint {
distinct_hosts = true
}
network {
mode = "host"
port "http" {}
}
# --- Setup Task ---
task "setup" {
driver = "docker"
lifecycle {
hook = "prestart"
sidecar = false
}
config {
image = "busybox"
command = "mkdir"
args = ["-p", "/alloc/sqlite"]
network_mode = "host"
}
}
# --- LiteFS Task ---
task "litefs" {
driver = "docker"
config {
image = "flyio/litefs:0.5"
privileged = true # Needed for FUSE
ports = ["http"]
network_mode = "host"
# 1. Bind mount for LiteFS internal data (chunks/WAL)
# 2. Bind mount for the config
# 3. Mount the shared alloc dir so we can mount FUSE on it
volumes = [
"/mnt/configs/navidrome_litefs:/var/lib/litefs",
"local/litefs.yml:/etc/litefs.yml"
]
mounts = [
{
type = "bind"
source = "../alloc/sqlite"
target = "/mnt/sqlite"
bind_options = {
propagation = "shared"
}
}
]
}
# Create the config file
template {
left_delimiter = "[["
right_delimiter = "]]"
data = <<EOF
fuse:
# This matches the internal mount point in the container
dir: "/mnt/sqlite"
data:
# Internal data storage
dir: "/var/lib/litefs"
# Use Consul for leader election
lease:
type: "consul"
consul:
url: "http://[[ env `attr.unique.network.ip-address` ]]:8500"
key: "litefs/navidrome"
# The HTTP Proxy routes traffic
proxy:
addr: ":[[ env `NOMAD_PORT_http` ]]"
target: "127.0.0.1:4533" # Navidrome's internal port
db: "navidrome.db" # The DB to track for transaction consistency
passthrough: # Paths that don't need write-forwarding (optional optimizations)
- "*.js"
- "*.css"
- "*.png"
EOF
destination = "local/litefs.yml"
}
resources {
cpu = 200
memory = 256
}
}
# --- Navidrome Task (The App) ---
task "navidrome" {
driver = "docker"
config {
image = "ghcr.io/navidrome/navidrome:latest"
memory_hard_limit = "2048"
ports = [] # No ports exposed directly!
network_mode = "host"
# We mount the sqlite dir from the allocation directory
mounts = [
{
type = "bind"
source = "../alloc/sqlite"
target = "/data"
bind_options = {
propagation = "shared"
}
}
]
# Shared Music and Configs
volumes = [
"/mnt/Public/Downloads/Clean_Music:/music/CleanMusic:ro",
"/mnt/Public/Downloads/news/slskd/downloads:/music/slskd:ro",
"/mnt/Public/Downloads/incoming_music:/music/incomingmusic:ro",
"/mnt/Public/configs/navidrome:/shared_data"
]
}
env {
ND_DATAFOLDER = "/local/data"
ND_CACHEFOLDER = "/shared_data/cache"
ND_CONFIGFILE= "/local/data/navidrome.toml"
# Important: LiteFS handles locking, but we still want WAL mode.
ND_DBPATH = "/data/navidrome.db?_busy_timeout=30000&_journal_mode=WAL&_foreign_keys=on&synchronous=NORMAL"
# Disable internal scheduling to prevent redundant scans on secondary nodes.
ND_SCANSCHEDULE = "0"
ND_SCANNER_FSWATCHER_ENABLED = "false"
ND_LOGLEVEL = "info"
ND_REVERSEPROXYWHITELIST = "0.0.0.0/0"
ND_REVERSEPROXYUSERHEADER = "X-Forwarded-User"
}
service {
name = "navidrome"
tags = [
"navidrome",
"web",
"traefik.enable=true",
"urlprefix-/navidrome",
"tools",
"traefik.http.routers.navidromelan.rule=Host(`navidrome.service.dc1.consul`)",
"traefik.http.routers.navidromewan.rule=Host(`m.fbleagh.duckdns.org`)",
"traefik.http.routers.navidromewan.middlewares=dex@consulcatalog",
"traefik.http.routers.navidromewan.tls=true",
]
port = "http" # This maps to the LiteFS proxy port defined in network block
check {
type = "http"
path = "/app" # LiteFS proxy passes this through
interval = "10s"
timeout = "2s"
}
}
resources {
cpu = 500
memory = 512
}
}
}
}

35
navidrome-vol.nomad Normal file
View File

@@ -0,0 +1,35 @@
type = "csi"
id = "navidrome-volume"
name = "navidrome-volume"
# This UUID was generated during the Postgres storage format
external_id = "56783f1f-d9c6-45fd-baec-56fa6c33776b"
capacity_min = "10GiB"
capacity_max = "10GiB"
capability {
access_mode = "multi-node-multi-writer"
attachment_mode = "file-system"
}
plugin_id = "juicefs0"
context {
writeback = "false"
delayed-write = "true"
upload-delay = "1m"
cache-size = "1024"
buffer-size = "128"
attr-cache = "60"
entry-cache = "60"
enable-mmap = "true"
metacache = "true"
}
secrets {
name = "navidrome-volume"
metaurl = "postgres://postgres:postgres@master.postgres.service.dc1.consul:5432/juicefs-navidrome"
storage = "postgres"
bucket = "postgres://postgres:postgres@master.postgres.service.dc1.consul:5432/juicefs-navidrome-storage"
}