Initial commit of Docker files
This commit is contained in:
76
.env.template
Executable file
76
.env.template
Executable file
@@ -0,0 +1,76 @@
|
||||
# ===========================================
|
||||
# DOCKER COMPOSE ENVIRONMENT CONFIGURATION
|
||||
# ===========================================
|
||||
# Copy this file to .env and fill in your values
|
||||
# DO NOT commit .env to version control
|
||||
|
||||
# ===== SYSTEM CONFIGURATION =====
|
||||
TZ=Pacific/Auckland
|
||||
PUID=1000
|
||||
PGID=1000
|
||||
|
||||
# ===== DOMAINS & URLS =====
|
||||
DOMAIN=kansaigaijin.com
|
||||
JELLYFIN_URL=https://jellyfin.${DOMAIN}
|
||||
SCROBBLE_URL=https://scrobble.${DOMAIN}
|
||||
MALOJA_URL=https://maloja.${DOMAIN}
|
||||
PAPERLESS_URL=https://paperless.${DOMAIN}
|
||||
PANGOLIN_URL=https://png.${DOMAIN}
|
||||
|
||||
# ===== DIRECTORY PATHS =====
|
||||
DATA_ROOT=/data
|
||||
DOCKER_CONFIG_ROOT=../docker-local
|
||||
TORRENTS_PATH=${DATA_ROOT}/torrents
|
||||
|
||||
# ===== DATABASE CREDENTIALS =====
|
||||
# Speedtest Tracker Database
|
||||
SPEEDTEST_DB_NAME=speedtest_tracker
|
||||
SPEEDTEST_DB_USER=speedtest_tracker
|
||||
SPEEDTEST_DB_PASSWORD=CHANGE_ME_SPEEDTEST_DB_PASSWORD
|
||||
|
||||
# Paperless Database
|
||||
PAPERLESS_DB_NAME=paperless
|
||||
PAPERLESS_DB_USER=paperless
|
||||
PAPERLESS_DB_PASSWORD=CHANGE_ME_PAPERLESS_DB_PASSWORD
|
||||
PAPERLESS_DB_ROOT_PASSWORD=CHANGE_ME_PAPERLESS_ROOT_PASSWORD
|
||||
|
||||
# ===== APPLICATION SECRETS =====
|
||||
# Maloja
|
||||
MALOJA_FORCE_PASSWORD=CHANGE_ME_MALOJA_PASSWORD
|
||||
|
||||
# Speedtest Tracker
|
||||
SPEEDTEST_APP_KEY=CHANGE_ME_BASE64_APP_KEY
|
||||
|
||||
# Spotify Integration
|
||||
SPOTIFY_CLIENT_ID=CHANGE_ME_SPOTIFY_CLIENT_ID
|
||||
SPOTIFY_CLIENT_SECRET=CHANGE_ME_SPOTIFY_CLIENT_SECRET
|
||||
SPOTIFY_REDIRECT_URI=${SCROBBLE_URL}/callback
|
||||
|
||||
# Maloja API
|
||||
MALOJA_API_KEY=CHANGE_ME_MALOJA_API_KEY
|
||||
|
||||
# Newt Service
|
||||
NEWT_ID=CHANGE_ME_NEWT_ID
|
||||
NEWT_SECRET=CHANGE_ME_NEWT_SECRET
|
||||
|
||||
# ===== OPTIONAL CONFIGURATIONS =====
|
||||
# Homepage
|
||||
HOMEPAGE_ALLOWED_HOSTS=${DOMAIN}
|
||||
|
||||
# Speedtest
|
||||
SPEEDTEST_SERVERS=7317
|
||||
SPEEDTEST_SCHEDULE=30 * * * *
|
||||
|
||||
# Docker Proxy (security settings)
|
||||
DOCKER_PROXY_CONTAINERS=1
|
||||
DOCKER_PROXY_SERVICES=1
|
||||
DOCKER_PROXY_TASKS=1
|
||||
DOCKER_PROXY_POST=0
|
||||
|
||||
# Watchtower
|
||||
WATCHTOWER_CLEANUP=true
|
||||
WATCHTOWER_POLL_INTERVAL=86400
|
||||
|
||||
# ===== PROFILE CONTROL =====
|
||||
# Uncomment to enable specific service groups
|
||||
# COMPOSE_PROFILES=media,utilities,documents,monitoring
|
103
.gitignore
vendored
Executable file
103
.gitignore
vendored
Executable file
@@ -0,0 +1,103 @@
|
||||
# ===========================================
|
||||
# DOCKER COMPOSE SECURITY & DATA FILES
|
||||
# ===========================================
|
||||
|
||||
# Environment files with secrets
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
.env.development
|
||||
|
||||
# Service configuration and data directories
|
||||
# (Keeps service folders but ignores data/config inside them)
|
||||
*/config/
|
||||
*/data/
|
||||
*/logs/
|
||||
*/cache/
|
||||
*/db/
|
||||
*/media/
|
||||
|
||||
# Specific service data patterns
|
||||
qBittorrent/config/
|
||||
Homepage/config/
|
||||
Homepage/data/
|
||||
filebrowser/config/
|
||||
filebrowser/data/
|
||||
maloja/config/
|
||||
maloja/data/
|
||||
maloja/logs/
|
||||
scrobble/config/
|
||||
scrobble/data/
|
||||
stirling/*/config/
|
||||
stirling/*/data/
|
||||
stirling/*/logs/
|
||||
paperless/data/
|
||||
paperless/media/
|
||||
paperless/export/
|
||||
paperless/consume/
|
||||
gramps/gramps_*/
|
||||
obsidian/vaults/
|
||||
obsidian/config/
|
||||
syncthing/
|
||||
rustdesk/data/
|
||||
racknerd-converter/data/
|
||||
racknerd-converter/logs/
|
||||
|
||||
# Docker volumes and bind mounts from external paths
|
||||
../docker-local/
|
||||
docker-local/
|
||||
|
||||
# Database files
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
||||
*~
|
||||
|
||||
# OS specific files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Docker specific
|
||||
.dockerignore
|
||||
docker-compose.override.yml
|
||||
|
||||
# SSL certificates and keys
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
*.p12
|
||||
*.pfx
|
||||
|
||||
# SSH keys
|
||||
id_rsa
|
||||
id_rsa.pub
|
||||
known_hosts
|
||||
|
||||
# Runtime files
|
||||
*.pid
|
||||
*.sock
|
1
Arrs/Bazarr/config/config/analytics_visitor_id.txt
Executable file
1
Arrs/Bazarr/config/config/analytics_visitor_id.txt
Executable file
@@ -0,0 +1 @@
|
||||
4964985276.1738142532
|
28
Arrs/Bazarr/config/config/announcements.json
Executable file
28
Arrs/Bazarr/config/config/announcements.json
Executable file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"text": "Welcome to the new announcement section. It will keep you up-to-date with important news. You can dismiss this announcement by clicking on the button at the end of this line.",
|
||||
"link": "",
|
||||
"hash": "",
|
||||
"dismissible": true,
|
||||
"timestamp": 1676235999,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"text": "Opensubtitles.org is now only accessible to VIP users. If you're still using it and do not plan to get a VIP subscription, you should consider disabling it and move to opensubtitles.com.",
|
||||
"link": "",
|
||||
"hash": "",
|
||||
"dismissible": true,
|
||||
"timestamp": 1700791126,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"text": "We're about to drop support for legacy Sonarr and Radarr versions (prior to v3). If you're still using those legacy versions, please consider upgrading ASAP.",
|
||||
"link": "",
|
||||
"hash": "",
|
||||
"dismissible": true,
|
||||
"timestamp": 1731247748,
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
274
Arrs/Bazarr/config/config/config.yaml
Executable file
274
Arrs/Bazarr/config/config/config.yaml
Executable file
@@ -0,0 +1,274 @@
|
||||
---
|
||||
addic7ed:
|
||||
cookies: ''
|
||||
password: ''
|
||||
user_agent: ''
|
||||
username: ''
|
||||
vip: false
|
||||
analytics:
|
||||
enabled: true
|
||||
anidb:
|
||||
api_client: ''
|
||||
api_client_ver: 1
|
||||
animetosho:
|
||||
anidb_api_client: ''
|
||||
anidb_api_client_ver: 1
|
||||
search_threshold: 6
|
||||
anticaptcha:
|
||||
anti_captcha_key: ''
|
||||
assrt:
|
||||
token: ''
|
||||
auth:
|
||||
apikey: f4e633d900a236934a951cede0e9176a
|
||||
password: ''
|
||||
type: null
|
||||
username: ''
|
||||
avistaz:
|
||||
cookies: ''
|
||||
user_agent: ''
|
||||
backup:
|
||||
day: 6
|
||||
folder: /config/backup
|
||||
frequency: Weekly
|
||||
hour: 3
|
||||
retention: 31
|
||||
betaseries:
|
||||
token: ''
|
||||
cinemaz:
|
||||
cookies: ''
|
||||
user_agent: ''
|
||||
cors:
|
||||
enabled: false
|
||||
deathbycaptcha:
|
||||
password: ''
|
||||
username: ''
|
||||
embeddedsubtitles:
|
||||
fallback_lang: en
|
||||
hi_fallback: false
|
||||
included_codecs: []
|
||||
timeout: 600
|
||||
unknown_as_fallback: false
|
||||
general:
|
||||
adaptive_searching: true
|
||||
adaptive_searching_delay: 3w
|
||||
adaptive_searching_delta: 1w
|
||||
anti_captcha_provider: null
|
||||
auto_update: true
|
||||
base_url: ''
|
||||
branch: master
|
||||
chmod: '0640'
|
||||
chmod_enabled: false
|
||||
days_to_upgrade_subs: 7
|
||||
debug: false
|
||||
default_und_audio_lang: ''
|
||||
default_und_embedded_subtitles_lang: ''
|
||||
dont_notify_manual_actions: false
|
||||
embedded_subs_show_desired: true
|
||||
embedded_subtitles_parser: ffprobe
|
||||
enabled_integrations: []
|
||||
enabled_providers: []
|
||||
flask_secret_key: 2cea2a0dfa96a753c70ac9b5c6160ccb
|
||||
hi_extension: hi
|
||||
ignore_ass_subs: false
|
||||
ignore_pgs_subs: false
|
||||
ignore_vobsub_subs: false
|
||||
ip: '*'
|
||||
language_equals: []
|
||||
minimum_score: 90
|
||||
minimum_score_movie: 70
|
||||
movie_default_enabled: false
|
||||
movie_default_profile: ''
|
||||
movie_tag_enabled: false
|
||||
multithreading: true
|
||||
page_size: 25
|
||||
parse_embedded_audio_track: false
|
||||
path_mappings: []
|
||||
path_mappings_movie: []
|
||||
port: 6767
|
||||
postprocessing_cmd: ''
|
||||
postprocessing_threshold: 90
|
||||
postprocessing_threshold_movie: 70
|
||||
remove_profile_tags: []
|
||||
serie_default_enabled: false
|
||||
serie_default_profile: ''
|
||||
serie_tag_enabled: false
|
||||
single_language: false
|
||||
skip_hashing: false
|
||||
subfolder: current
|
||||
subfolder_custom: ''
|
||||
subzero_mods: ''
|
||||
theme: auto
|
||||
upgrade_frequency: 12
|
||||
upgrade_manual: true
|
||||
upgrade_subs: true
|
||||
use_embedded_subs: true
|
||||
use_postprocessing: false
|
||||
use_postprocessing_threshold: false
|
||||
use_postprocessing_threshold_movie: false
|
||||
use_radarr: false
|
||||
use_scenename: true
|
||||
use_sonarr: false
|
||||
utf8_encode: true
|
||||
wanted_search_frequency: 6
|
||||
wanted_search_frequency_movie: 6
|
||||
hdbits:
|
||||
passkey: ''
|
||||
username: ''
|
||||
jimaku:
|
||||
api_key: ''
|
||||
enable_ai_subs: false
|
||||
enable_archives_download: false
|
||||
enable_name_search_fallback: true
|
||||
karagarga:
|
||||
f_password: ''
|
||||
f_username: ''
|
||||
password: ''
|
||||
username: ''
|
||||
ktuvit:
|
||||
email: ''
|
||||
hashed_password: ''
|
||||
legendasdivx:
|
||||
password: ''
|
||||
skip_wrong_fps: false
|
||||
username: ''
|
||||
legendasnet:
|
||||
password: ''
|
||||
username: ''
|
||||
log:
|
||||
exclude_filter: ''
|
||||
ignore_case: false
|
||||
include_filter: ''
|
||||
use_regex: false
|
||||
movie_scores:
|
||||
audio_codec: 3
|
||||
edition: 1
|
||||
hash: 119
|
||||
hearing_impaired: 1
|
||||
release_group: 13
|
||||
resolution: 2
|
||||
source: 7
|
||||
streaming_service: 1
|
||||
title: 60
|
||||
video_codec: 2
|
||||
year: 30
|
||||
napiprojekt:
|
||||
only_authors: false
|
||||
only_real_names: false
|
||||
napisy24:
|
||||
password: ''
|
||||
username: ''
|
||||
opensubtitles:
|
||||
password: ''
|
||||
skip_wrong_fps: false
|
||||
ssl: false
|
||||
timeout: 15
|
||||
use_tag_search: false
|
||||
username: ''
|
||||
vip: false
|
||||
opensubtitlescom:
|
||||
include_ai_translated: false
|
||||
password: ''
|
||||
use_hash: true
|
||||
username: ''
|
||||
podnapisi:
|
||||
verify_ssl: true
|
||||
postgresql:
|
||||
database: ''
|
||||
enabled: false
|
||||
host: localhost
|
||||
password: ''
|
||||
port: 5432
|
||||
username: ''
|
||||
proxy:
|
||||
exclude:
|
||||
- localhost
|
||||
- 127.0.0.1
|
||||
password: ''
|
||||
port: ''
|
||||
type: null
|
||||
url: ''
|
||||
username: ''
|
||||
radarr:
|
||||
apikey: ''
|
||||
base_url: /
|
||||
defer_search_signalr: false
|
||||
excluded_tags: []
|
||||
full_update: Daily
|
||||
full_update_day: 6
|
||||
full_update_hour: 4
|
||||
http_timeout: 60
|
||||
ip: 127.0.0.1
|
||||
movies_sync: 60
|
||||
only_monitored: false
|
||||
port: 7878
|
||||
ssl: false
|
||||
sync_only_monitored_movies: false
|
||||
use_ffprobe_cache: true
|
||||
series_scores:
|
||||
audio_codec: 3
|
||||
episode: 30
|
||||
hash: 359
|
||||
hearing_impaired: 1
|
||||
release_group: 14
|
||||
resolution: 2
|
||||
season: 30
|
||||
series: 180
|
||||
source: 7
|
||||
streaming_service: 1
|
||||
video_codec: 2
|
||||
year: 90
|
||||
sonarr:
|
||||
apikey: ''
|
||||
base_url: /
|
||||
defer_search_signalr: false
|
||||
exclude_season_zero: false
|
||||
excluded_series_types: []
|
||||
excluded_tags: []
|
||||
full_update: Daily
|
||||
full_update_day: 6
|
||||
full_update_hour: 4
|
||||
http_timeout: 60
|
||||
ip: 127.0.0.1
|
||||
only_monitored: false
|
||||
port: 8989
|
||||
series_sync: 60
|
||||
ssl: false
|
||||
sync_only_monitored_episodes: false
|
||||
sync_only_monitored_series: false
|
||||
use_ffprobe_cache: true
|
||||
subdl:
|
||||
api_key: ''
|
||||
subf2m:
|
||||
user_agent: ''
|
||||
verify_ssl: true
|
||||
subsync:
|
||||
checker:
|
||||
blacklisted_languages: []
|
||||
blacklisted_providers: []
|
||||
debug: false
|
||||
force_audio: false
|
||||
gss: true
|
||||
max_offset_seconds: 60
|
||||
no_fix_framerate: true
|
||||
subsync_movie_threshold: 70
|
||||
subsync_threshold: 90
|
||||
use_subsync: false
|
||||
use_subsync_movie_threshold: false
|
||||
use_subsync_threshold: false
|
||||
titlovi:
|
||||
password: ''
|
||||
username: ''
|
||||
titulky:
|
||||
approved_only: false
|
||||
password: ''
|
||||
skip_wrong_fps: false
|
||||
username: ''
|
||||
whisperai:
|
||||
endpoint: http://127.0.0.1:9000
|
||||
loglevel: INFO
|
||||
pass_video_name: false
|
||||
response: 5
|
||||
timeout: 3600
|
||||
xsubs:
|
||||
password: ''
|
||||
username: ''
|
1
Arrs/Bazarr/config/config/releases.txt
Executable file
1
Arrs/Bazarr/config/config/releases.txt
Executable file
File diff suppressed because one or more lines are too long
16
Arrs/Prowlarr/config/asp/key-076c8fc0-d072-456f-a8c8-b5d4722d497d.xml
Executable file
16
Arrs/Prowlarr/config/asp/key-076c8fc0-d072-456f-a8c8-b5d4722d497d.xml
Executable file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<key id="076c8fc0-d072-456f-a8c8-b5d4722d497d" version="1">
|
||||
<creationDate>2025-01-29T09:22:08.4496837Z</creationDate>
|
||||
<activationDate>2025-01-29T09:22:08.4381256Z</activationDate>
|
||||
<expirationDate>2025-04-29T09:22:08.4381256Z</expirationDate>
|
||||
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||
<descriptor>
|
||||
<encryption algorithm="AES_256_CBC" />
|
||||
<validation algorithm="HMACSHA256" />
|
||||
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||
<!-- Warning: the key below is in an unencrypted form. -->
|
||||
<value>0i5M9BhZYxyRZUnt26LMNZ4ukvPyWz1P2CVr4iqIfZFTBANQrCeRHQL9RXc0dbhU7Gi2ECvniDLHgNUfcvZfaA==</value>
|
||||
</masterKey>
|
||||
</descriptor>
|
||||
</descriptor>
|
||||
</key>
|
16
Arrs/Prowlarr/config/config.xml
Executable file
16
Arrs/Prowlarr/config/config.xml
Executable file
@@ -0,0 +1,16 @@
|
||||
<Config>
|
||||
<BindAddress>*</BindAddress>
|
||||
<Port>9696</Port>
|
||||
<SslPort>6969</SslPort>
|
||||
<EnableSsl>False</EnableSsl>
|
||||
<LaunchBrowser>True</LaunchBrowser>
|
||||
<ApiKey>2e061a290fb24d8792ebb2d70b15bf67</ApiKey>
|
||||
<AuthenticationMethod>None</AuthenticationMethod>
|
||||
<AuthenticationRequired>Enabled</AuthenticationRequired>
|
||||
<Branch>master</Branch>
|
||||
<LogLevel>debug</LogLevel>
|
||||
<SslCertPath></SslCertPath>
|
||||
<SslCertPassword></SslCertPassword>
|
||||
<UrlBase></UrlBase>
|
||||
<InstanceName>Prowlarr</InstanceName>
|
||||
</Config>
|
16
Arrs/Radarr/config/asp/key-4bdf6bfd-92c6-4723-8e40-a1abcb19db17.xml
Executable file
16
Arrs/Radarr/config/asp/key-4bdf6bfd-92c6-4723-8e40-a1abcb19db17.xml
Executable file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<key id="4bdf6bfd-92c6-4723-8e40-a1abcb19db17" version="1">
|
||||
<creationDate>2025-01-29T09:22:08.5915717Z</creationDate>
|
||||
<activationDate>2025-01-29T09:22:08.5787744Z</activationDate>
|
||||
<expirationDate>2025-04-29T09:22:08.5787744Z</expirationDate>
|
||||
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||
<descriptor>
|
||||
<encryption algorithm="AES_256_CBC" />
|
||||
<validation algorithm="HMACSHA256" />
|
||||
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||
<!-- Warning: the key below is in an unencrypted form. -->
|
||||
<value>u+w7PRy5v7u9F6/9RnBwbm0FeKyLyyR9/PPgCKe9Hvgapo2NBpjXgQOUO+mXRsYeTM8aL9sAMd80BJPAeQWZPA==</value>
|
||||
</masterKey>
|
||||
</descriptor>
|
||||
</descriptor>
|
||||
</key>
|
16
Arrs/Radarr/config/config.xml
Executable file
16
Arrs/Radarr/config/config.xml
Executable file
@@ -0,0 +1,16 @@
|
||||
<Config>
|
||||
<BindAddress>*</BindAddress>
|
||||
<Port>7878</Port>
|
||||
<SslPort>9898</SslPort>
|
||||
<EnableSsl>False</EnableSsl>
|
||||
<LaunchBrowser>True</LaunchBrowser>
|
||||
<ApiKey>6c4a2d0e6cd547ad9681d428786d45f7</ApiKey>
|
||||
<AuthenticationMethod>None</AuthenticationMethod>
|
||||
<AuthenticationRequired>Enabled</AuthenticationRequired>
|
||||
<Branch>master</Branch>
|
||||
<LogLevel>debug</LogLevel>
|
||||
<SslCertPath></SslCertPath>
|
||||
<SslCertPassword></SslCertPassword>
|
||||
<UrlBase></UrlBase>
|
||||
<InstanceName>Radarr</InstanceName>
|
||||
</Config>
|
16
Arrs/Sonarr/config/asp/key-7fe5740c-65be-4e37-9015-402fa9a48df0.xml
Executable file
16
Arrs/Sonarr/config/asp/key-7fe5740c-65be-4e37-9015-402fa9a48df0.xml
Executable file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<key id="7fe5740c-65be-4e37-9015-402fa9a48df0" version="1">
|
||||
<creationDate>2025-01-29T09:22:08.4520862Z</creationDate>
|
||||
<activationDate>2025-01-29T09:22:08.440317Z</activationDate>
|
||||
<expirationDate>2025-04-29T09:22:08.440317Z</expirationDate>
|
||||
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||
<descriptor>
|
||||
<encryption algorithm="AES_256_CBC" />
|
||||
<validation algorithm="HMACSHA256" />
|
||||
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||
<!-- Warning: the key below is in an unencrypted form. -->
|
||||
<value>C1ExEujkCoR0uN5myc8nRpZboUVQDdEeu2EkGqISG1/m8hC/LiDeQj7GMEIZFTjxRUSB4JSfU2CvjsvYGGEt4A==</value>
|
||||
</masterKey>
|
||||
</descriptor>
|
||||
</descriptor>
|
||||
</key>
|
16
Arrs/Sonarr/config/config.xml
Executable file
16
Arrs/Sonarr/config/config.xml
Executable file
@@ -0,0 +1,16 @@
|
||||
<Config>
|
||||
<BindAddress>*</BindAddress>
|
||||
<Port>8989</Port>
|
||||
<SslPort>9898</SslPort>
|
||||
<EnableSsl>False</EnableSsl>
|
||||
<LaunchBrowser>True</LaunchBrowser>
|
||||
<ApiKey>c9dba8cdc00741f299e96c518372118f</ApiKey>
|
||||
<AuthenticationMethod>None</AuthenticationMethod>
|
||||
<AuthenticationRequired>Enabled</AuthenticationRequired>
|
||||
<Branch>main</Branch>
|
||||
<LogLevel>trace</LogLevel>
|
||||
<SslCertPath></SslCertPath>
|
||||
<SslCertPassword></SslCertPassword>
|
||||
<UrlBase></UrlBase>
|
||||
<InstanceName>Sonarr</InstanceName>
|
||||
</Config>
|
204
README.md
Executable file
204
README.md
Executable file
@@ -0,0 +1,204 @@
|
||||
# Docker Media & Utility Stack
|
||||
|
||||
A comprehensive Docker Compose setup for media management, document processing, and various utility services.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <your-repo-url>
|
||||
cd <repo-name>
|
||||
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
|
||||
# Or start specific service groups
|
||||
docker compose --profile media up -d
|
||||
```
|
||||
|
||||
## 📋 Services Overview
|
||||
|
||||
### 🎬 Media Stack (`media` profile)
|
||||
- **Sonarr** (8989) - TV series management
|
||||
- **Radarr** (7878) - Movie management
|
||||
- **Lidarr** (8686) - Music management
|
||||
- **Bazarr** (6767) - Subtitle management
|
||||
- **Prowlarr** (9696) - Indexer management
|
||||
- **FlareSolverr** (8191) - Cloudflare bypass
|
||||
- **Jellyfin** (8096) - Media streaming server
|
||||
- **Jellyseerr** (5055) - Media request management
|
||||
- **qBittorrent** (7070) - Download client
|
||||
- **Multi-Scrobbler** (9078) - Music scrobbling
|
||||
- **Maloja** (42010) - Music statistics
|
||||
|
||||
### 🛠️ Utilities (`utilities` profile)
|
||||
- **Homepage** (7575) - Service dashboard
|
||||
- **FileBrowser** (6633) - Web file manager
|
||||
- **Syncthing** (8384) - File synchronization
|
||||
- **Obsidian Remote** (8181) - Note-taking
|
||||
- **Stirling PDF** (8090) - PDF processing
|
||||
- **RustDesk** - Remote desktop server
|
||||
- **Gramps** (5511) - Genealogy management
|
||||
- **Newt** - Notification service
|
||||
- **RackNerd API Converter** (5000) - VPS monitoring API
|
||||
|
||||
### 📄 Documents (`documents` profile)
|
||||
- **Paperless NGX** (8100) - Document management
|
||||
- **Paperless AI** (3040) - AI document enhancement
|
||||
- **Gotenberg** - Document conversion
|
||||
- **Tika** - Content extraction
|
||||
|
||||
### 📊 Monitoring (`monitoring` profile)
|
||||
- **Speedtest Tracker** (8180) - Network monitoring
|
||||
- **Watchtower** - Container updates
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
The stack is organized into logical service groups:
|
||||
|
||||
```
|
||||
├── docker-compose.yml # Main orchestration
|
||||
├── compose/
|
||||
│ ├── media-stack.yml # *arr services & Jellyfin
|
||||
│ ├── utilities.yml # General utilities
|
||||
│ ├── document-management.yml # Paperless stack
|
||||
│ └── monitoring.yml # Monitoring services
|
||||
├── .env # Environment variables
|
||||
└── .env.template # Template for new deployments
|
||||
```
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Key configuration is handled through `.env`:
|
||||
|
||||
- `DOMAIN` - Your domain name
|
||||
- `TZ` - Timezone (Pacific/Auckland)
|
||||
- `DATA_ROOT` - Media storage path (/data)
|
||||
- Database credentials for各服务
|
||||
|
||||
### Service Profiles
|
||||
|
||||
Control which services start:
|
||||
|
||||
```bash
|
||||
# Start only media services
|
||||
export COMPOSE_PROFILES=media
|
||||
docker compose up -d
|
||||
|
||||
# Start multiple profiles
|
||||
export COMPOSE_PROFILES=media,utilities
|
||||
docker compose up -d
|
||||
|
||||
# Start everything
|
||||
export COMPOSE_PROFILES=all
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 📁 Directory Structure
|
||||
|
||||
Ensure these directories exist:
|
||||
|
||||
```
|
||||
/data/ # Media storage
|
||||
├── movies/
|
||||
├── tv/
|
||||
├── music/
|
||||
└── torrents/
|
||||
|
||||
../docker-local/ # Container configs
|
||||
├── Arrs/
|
||||
│ ├── Sonarr/config/
|
||||
│ ├── Radarr/config/
|
||||
│ └── ...
|
||||
└── [service-name]/config/
|
||||
```
|
||||
|
||||
## 🔒 Security Features
|
||||
|
||||
- **No hardcoded secrets** - All sensitive data in `.env`
|
||||
- **Least privilege** - `no-new-privileges` security options
|
||||
- **Network isolation** - Separate networks for different stacks
|
||||
- **Health checks** - Automatic service monitoring
|
||||
- **Read-only mounts** - Docker socket proxy with restricted access
|
||||
|
||||
## 🌐 Network Configuration
|
||||
|
||||
- **arr_network** (172.20.0.0/16) - Media services
|
||||
- **database_network** (172.21.0.0/16) - Database services
|
||||
|
||||
Static IPs assigned for reliable service discovery.
|
||||
|
||||
## 📝 Service URLs
|
||||
|
||||
Once running, access services at:
|
||||
|
||||
- **Homepage**: http://localhost:7575
|
||||
- **Jellyfin**: http://localhost:8096
|
||||
- **Sonarr**: http://localhost:8989
|
||||
- **Radarr**: http://localhost:7878
|
||||
- **qBittorrent**: http://localhost:7070
|
||||
- **Paperless**: http://localhost:8100
|
||||
- **FileBrowser**: http://localhost:6633
|
||||
- **RackNerd API**: http://localhost:5000
|
||||
|
||||
## 🔄 Management Commands
|
||||
|
||||
```bash
|
||||
# View running services
|
||||
docker compose ps
|
||||
|
||||
# View logs
|
||||
docker compose logs [service-name]
|
||||
|
||||
# Update a service
|
||||
docker compose pull [service-name]
|
||||
docker compose up -d [service-name]
|
||||
|
||||
# Stop all services
|
||||
docker compose down
|
||||
|
||||
# Stop and remove volumes (⚠️ DATA LOSS)
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Port conflicts**: Check if ports are already in use
|
||||
2. **Permission issues**: Ensure PUID/PGID match your user
|
||||
3. **Volume mounts**: Verify directory paths exist
|
||||
4. **Network issues**: Check firewall settings
|
||||
|
||||
### Health Checks
|
||||
|
||||
Most services include health checks. View status:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
# Shows health status for each service
|
||||
```
|
||||
|
||||
## 🔐 Security Notes
|
||||
|
||||
- `.env` contains sensitive data - keep it secure
|
||||
- Consider using Docker secrets for production
|
||||
- Regular updates via Watchtower
|
||||
- Monitor access logs
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
1. Follow the existing structure when adding services
|
||||
2. Add health checks to new services
|
||||
3. Use environment variables for configuration
|
||||
4. Update documentation for new services
|
||||
|
||||
## 📄 License
|
||||
|
||||
[Add your license here]
|
||||
|
||||
---
|
||||
|
||||
**⚠️ Important**: This setup includes production credentials. Ensure `.env` is never committed to version control.
|
54
adventurelog/docker-compose.yaml
Executable file
54
adventurelog/docker-compose.yaml
Executable file
@@ -0,0 +1,54 @@
|
||||
services:
|
||||
web:
|
||||
#build: ./frontend/
|
||||
image: ghcr.io/seanmorley15/adventurelog-frontend:latest
|
||||
container_name: adventurelog-frontend
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUBLIC_SERVER_URL=http://server:8000 # Should be the service name of the backend with port 8000, even if you change the port in the backend service
|
||||
- ORIGIN=https://adventure.kansaigaijin.com
|
||||
- BODY_SIZE_LIMIT=Infinity
|
||||
ports:
|
||||
- "8015:3000"
|
||||
depends_on:
|
||||
- server
|
||||
|
||||
db:
|
||||
image: postgis/postgis:15-3.3
|
||||
container_name: adventurelog-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: database
|
||||
POSTGRES_USER: adventure
|
||||
POSTGRES_PASSWORD: Figureitout0313
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data/
|
||||
|
||||
server:
|
||||
#build: ./backend/
|
||||
image: ghcr.io/seanmorley15/adventurelog-backend:latest
|
||||
container_name: adventurelog-backend
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PGHOST=db
|
||||
- PGDATABASE=database
|
||||
- PGUSER=adventure
|
||||
- PGPASSWORD=Figureitout0313
|
||||
- SECRET_KEY=Figureitout0313
|
||||
- DJANGO_ADMIN_USERNAME=admin
|
||||
- DJANGO_ADMIN_PASSWORD=admin
|
||||
- DJANGO_ADMIN_EMAIL=jamie@kansaigaijin.com
|
||||
- PUBLIC_URL=https://adventureadmin.kansaigaijin.com # Match the outward port, used for the creation of image urls
|
||||
- CSRF_TRUSTED_ORIGINS=http://localhost:8016,http://localhost:8015,https://adventure.kansaigaijin.com,https://adventureadmin.kansaigaijin.com # Comma separated list of trusted origins for CSRF
|
||||
- DEBUG=False
|
||||
- FRONTEND_URL=https://adventure.kansaigaijin.com # Used for email generation. This should be the url of the frontend
|
||||
ports:
|
||||
- "8016:80"
|
||||
depends_on:
|
||||
- db
|
||||
volumes:
|
||||
- adventurelog_media:/code/media/
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
adventurelog_media:
|
150
compose/document-management.yml
Executable file
150
compose/document-management.yml
Executable file
@@ -0,0 +1,150 @@
|
||||
# Document Management Stack - Paperless NGX
|
||||
services:
|
||||
# ===== PAPERLESS INFRASTRUCTURE =====
|
||||
paperless-broker:
|
||||
container_name: paperless-broker
|
||||
image: redis:8-alpine
|
||||
profiles: ["documents", "all"]
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
networks:
|
||||
- database_network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
# ===== DOCUMENT PROCESSING SERVICES =====
|
||||
paperless-gotenberg:
|
||||
container_name: paperless-gotenberg
|
||||
image: gotenberg/gotenberg:8.20
|
||||
profiles: ["documents", "all"]
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- "gotenberg"
|
||||
- "--chromium-disable-javascript=true"
|
||||
- "--chromium-allow-list=file:///tmp/.*"
|
||||
networks:
|
||||
- database_network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
paperless-tika:
|
||||
container_name: paperless-tika
|
||||
image: apache/tika:latest
|
||||
profiles: ["documents", "all"]
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- database_network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9998/version"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ===== MAIN PAPERLESS APPLICATION =====
|
||||
paperless-webserver:
|
||||
container_name: paperless-webserver
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
profiles: ["documents", "all"]
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
paperless-db:
|
||||
condition: service_healthy
|
||||
paperless-broker:
|
||||
condition: service_started
|
||||
paperless-gotenberg:
|
||||
condition: service_started
|
||||
paperless-tika:
|
||||
condition: service_started
|
||||
ports:
|
||||
- "8100:8000"
|
||||
volumes:
|
||||
- ./paperless/data:/usr/src/paperless/data
|
||||
- ./paperless/media:/usr/src/paperless/media
|
||||
- ./paperless/export:/usr/src/paperless/export
|
||||
- ./paperless/consume:/usr/src/paperless/consume
|
||||
env_file: ./paperless/docker-compose.env
|
||||
environment:
|
||||
- PAPERLESS_REDIS=redis://paperless-broker:6379
|
||||
- PAPERLESS_DBENGINE=mariadb
|
||||
- PAPERLESS_DBHOST=paperless-db
|
||||
- PAPERLESS_DBUSER=${PAPERLESS_DB_USER}
|
||||
- PAPERLESS_DBPASS=${PAPERLESS_DB_PASSWORD}
|
||||
- PAPERLESS_DBPORT=3306
|
||||
- PAPERLESS_URL=${PAPERLESS_URL}
|
||||
- PAPERLESS_TIKA_ENABLED=1
|
||||
- PAPERLESS_TIKA_GOTENBERG_ENDPOINT=http://paperless-gotenberg:3000
|
||||
- PAPERLESS_TIKA_ENDPOINT=http://paperless-tika:9998
|
||||
networks:
|
||||
- database_network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ===== AI ENHANCEMENT =====
|
||||
paperless-ai:
|
||||
image: clusterzx/paperless-ai:latest
|
||||
container_name: paperless-ai
|
||||
profiles: ["documents", "all"]
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- paperless-webserver
|
||||
cap_drop:
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- RAG_SERVICE_URL=http://localhost:8000
|
||||
- RAG_SERVICE_ENABLED=true
|
||||
ports:
|
||||
- "3040:3000"
|
||||
volumes:
|
||||
- aidata:/app/data
|
||||
|
||||
networks:
|
||||
database_network:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
redisdata:
|
||||
external: true
|
||||
dbdata:
|
||||
external: true
|
||||
aidata:
|
||||
external: true
|
||||
|
||||
paperless-db:
|
||||
container_name: paperless-db
|
||||
image: mariadb:11
|
||||
profiles: ["documents", "all"]
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- dbdata:/var/lib/mysql
|
||||
environment:
|
||||
- MARIADB_HOST=paperless
|
||||
- MARIADB_DATABASE=${PAPERLESS_DB_NAME}
|
||||
- MARIADB_USER=${PAPERLESS_DB_USER}
|
||||
- MARIADB_PASSWORD=${PAPERLESS_DB_PASSWORD}
|
||||
- MARIADB_ROOT_PASSWORD=${PAPERLESS_DB_ROOT_PASSWORD}
|
||||
networks:
|
||||
- database_network
|
||||
healthcheck:
|
||||
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-u", "root", "-p$$MARIADB_ROOT_PASSWORD"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
253
compose/media-stack.yml
Executable file
253
compose/media-stack.yml
Executable file
@@ -0,0 +1,253 @@
|
||||
# Media Stack - *arr services, Jellyfin, torrent client
|
||||
services:
|
||||
# ===== MEDIA MANAGEMENT =====
|
||||
sonarr:
|
||||
image: lscr.io/linuxserver/sonarr:latest
|
||||
container_name: sonarr
|
||||
profiles: ["media", "all"]
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/Arrs/Sonarr/config:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${TORRENTS_PATH}:/downloads
|
||||
ports:
|
||||
- "8989:8989"
|
||||
networks:
|
||||
arr_network:
|
||||
ipv4_address: 172.20.0.3
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8989/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
radarr:
|
||||
image: lscr.io/linuxserver/radarr:latest
|
||||
container_name: radarr
|
||||
profiles: ["media", "all"]
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/Arrs/Radarr/config:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${TORRENTS_PATH}:/downloads
|
||||
ports:
|
||||
- "7878:7878"
|
||||
networks:
|
||||
arr_network:
|
||||
ipv4_address: 172.20.0.5
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:7878/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
lidarr:
|
||||
image: lscr.io/linuxserver/lidarr:latest
|
||||
container_name: lidarr
|
||||
profiles: ["media", "all"]
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/Arrs/Lidarr/config:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
- ${TORRENTS_PATH}:/downloads
|
||||
ports:
|
||||
- "8686:8686"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
arr_network:
|
||||
ipv4_address: 172.20.0.7
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8686/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
bazarr:
|
||||
image: lscr.io/linuxserver/bazarr:latest
|
||||
container_name: bazarr
|
||||
profiles: ["media", "all"]
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/Arrs/Bazarr/config:/config
|
||||
- ${DATA_ROOT}:/data
|
||||
networks:
|
||||
arr_network:
|
||||
ipv4_address: 172.20.0.6
|
||||
ports:
|
||||
- "6767:6767"
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:6767/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
prowlarr:
|
||||
image: lscr.io/linuxserver/prowlarr:latest
|
||||
container_name: prowlarr
|
||||
profiles: ["media", "all"]
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/Arrs/Prowlarr/config:/config
|
||||
ports:
|
||||
- "9696:9696"
|
||||
networks:
|
||||
arr_network:
|
||||
ipv4_address: 172.20.0.4
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9696/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
flaresolverr:
|
||||
image: ghcr.io/flaresolverr/flaresolverr:latest
|
||||
container_name: flaresolverr
|
||||
profiles: ["media", "all"]
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
ports:
|
||||
- "8191:8191"
|
||||
networks:
|
||||
arr_network:
|
||||
ipv4_address: 172.20.0.8
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8191/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ===== MEDIA STREAMING =====
|
||||
jellyfin:
|
||||
image: jellyfin/jellyfin:latest
|
||||
container_name: jellyfin
|
||||
profiles: ["media", "all"]
|
||||
ports:
|
||||
- "8096:8096"
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/Arrs/Jellyfin/config:/config
|
||||
- ${DOCKER_CONFIG_ROOT}/Arrs/Jellyfin/cache:/cache
|
||||
- ${DATA_ROOT}:/data
|
||||
- /dev/dri/renderD128:/dev/dri/renderD128
|
||||
group_add:
|
||||
- "104"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- JELLYFIN_PublishedServerUrl=${JELLYFIN_URL}
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8096/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
jellyseerr:
|
||||
image: fallenbagel/jellyseerr:latest
|
||||
container_name: jellyseerr
|
||||
profiles: ["media", "all"]
|
||||
environment:
|
||||
- LOG_LEVEL=info
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/Arrs/Jellyseerr/config:/app/config
|
||||
- ${TORRENTS_PATH}:/downloads
|
||||
ports:
|
||||
- "5055:5055"
|
||||
networks:
|
||||
arr_network:
|
||||
ipv4_address: 172.20.0.14
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5055/api/v1/status"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ===== DOWNLOAD CLIENT =====
|
||||
qbittorrent:
|
||||
container_name: qbittorrent
|
||||
image: lscr.io/linuxserver/qbittorrent:latest
|
||||
profiles: ["media", "all"]
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=${TZ}
|
||||
- WEBUI_PORT=8080
|
||||
ports:
|
||||
- "7070:8080"
|
||||
- "56881:6881"
|
||||
- "56881:6881/udp"
|
||||
volumes:
|
||||
- ./qBittorrent/config:/config
|
||||
- ${TORRENTS_PATH}:/downloads
|
||||
networks:
|
||||
arr_network:
|
||||
ipv4_address: 172.20.0.2
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ===== MUSIC SCROBBLING =====
|
||||
multi-scrobbler:
|
||||
image: foxxmd/multi-scrobbler:latest
|
||||
container_name: multi-scrobbler
|
||||
profiles: ["media", "all"]
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- BASE_URL=${SCROBBLE_URL}
|
||||
- SPOTIFY_CLIENT_ID=${SPOTIFY_CLIENT_ID}
|
||||
- SPOTIFY_CLIENT_SECRET=${SPOTIFY_CLIENT_SECRET}
|
||||
- SPOTIFY_REDIRECT_URI=${SPOTIFY_REDIRECT_URI}
|
||||
- MALOJA_URL=${MALOJA_URL}
|
||||
- MALOJA_API_KEY=${MALOJA_API_KEY}
|
||||
volumes:
|
||||
- "./scrobble/config:/config"
|
||||
ports:
|
||||
- "9078:9078"
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- maloja
|
||||
|
||||
maloja:
|
||||
image: krateng/maloja:latest
|
||||
container_name: maloja
|
||||
profiles: ["media", "all"]
|
||||
ports:
|
||||
- "42010:42010"
|
||||
volumes:
|
||||
- "./maloja/config:/etc/maloja"
|
||||
- "./maloja/data:/var/lib/maloja"
|
||||
- "./maloja/logs:/var/log/maloja"
|
||||
environment:
|
||||
- MALOJA_FORCE_PASSWORD=${MALOJA_FORCE_PASSWORD}
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
arr_network:
|
||||
external: true
|
84
compose/monitoring.yml
Executable file
84
compose/monitoring.yml
Executable file
@@ -0,0 +1,84 @@
|
||||
# Monitoring & Maintenance Services
|
||||
services:
|
||||
# ===== NETWORK MONITORING =====
|
||||
speedtest-tracker:
|
||||
image: lscr.io/linuxserver/speedtest-tracker:latest
|
||||
restart: unless-stopped
|
||||
container_name: speedtest-tracker
|
||||
profiles: ["monitoring", "all"]
|
||||
ports:
|
||||
- "8180:80"
|
||||
- "8143:443"
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=${TZ}
|
||||
- APP_KEY=${SPEEDTEST_APP_KEY}
|
||||
- DB_CONNECTION=mariadb
|
||||
- DB_HOST=speedtest-db
|
||||
- DB_PORT=3306
|
||||
- DB_DATABASE=${SPEEDTEST_DB_NAME}
|
||||
- DB_USERNAME=${SPEEDTEST_DB_USER}
|
||||
- DB_PASSWORD=${SPEEDTEST_DB_PASSWORD}
|
||||
- APP_DEBUG=false
|
||||
- SPEEDTEST_SCHEDULE=${SPEEDTEST_SCHEDULE}
|
||||
- SPEEDTEST_SERVERS=${SPEEDTEST_SERVERS}
|
||||
- PUBLIC_DASHBOARD=true
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/speedtest-tracker/data:/config
|
||||
depends_on:
|
||||
speedtest-db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- database_network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost/api/healthcheck"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
speedtest-db:
|
||||
image: mariadb:11
|
||||
container_name: speedtest-db
|
||||
profiles: ["monitoring", "all"]
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MYSQL_DATABASE=${SPEEDTEST_DB_NAME}
|
||||
- MYSQL_USER=${SPEEDTEST_DB_USER}
|
||||
- MYSQL_PASSWORD=${SPEEDTEST_DB_PASSWORD}
|
||||
- MYSQL_RANDOM_ROOT_PASSWORD=true
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/speedtest-tracker/db:/var/lib/mysql
|
||||
networks:
|
||||
- database_network
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
# ===== CONTAINER MAINTENANCE =====
|
||||
watchtower:
|
||||
image: containrrr/watchtower:latest
|
||||
container_name: watchtower
|
||||
profiles: ["monitoring", "all"]
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- WATCHTOWER_CLEANUP=${WATCHTOWER_CLEANUP}
|
||||
- WATCHTOWER_POLL_INTERVAL=${WATCHTOWER_POLL_INTERVAL}
|
||||
- WATCHTOWER_INCLUDE_RESTARTING=true
|
||||
- WATCHTOWER_INCLUDE_STOPPED=false
|
||||
- WATCHTOWER_REVIVE_STOPPED=false
|
||||
- WATCHTOWER_NO_STARTUP_MESSAGE=true
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
networks:
|
||||
database_network:
|
||||
external: true
|
271
compose/utility-stack.yml
Executable file
271
compose/utility-stack.yml
Executable file
@@ -0,0 +1,271 @@
|
||||
# Utility Services - Dashboard, file management, sync, etc.
|
||||
services:
|
||||
# ===== DASHBOARD & MONITORING =====
|
||||
homepage:
|
||||
image: ghcr.io/gethomepage/homepage:latest
|
||||
container_name: homepage
|
||||
profiles: ["utilities", "all"]
|
||||
volumes:
|
||||
- ./Homepage/config/images:/app/public/images
|
||||
- ./Homepage/config/icons:/app/public/icons
|
||||
- ./Homepage/config:/app/config
|
||||
ports:
|
||||
- "7575:3000"
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=${TZ}
|
||||
- HOMEPAGE_ALLOWED_HOSTS=${HOMEPAGE_ALLOWED_HOSTS}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- dockerproxy
|
||||
|
||||
dockerproxy:
|
||||
image: ghcr.io/tecnativa/docker-socket-proxy:latest
|
||||
container_name: dockerproxy
|
||||
profiles: ["utilities", "all"]
|
||||
environment:
|
||||
- CONTAINERS=${DOCKER_PROXY_CONTAINERS}
|
||||
- SERVICES=${DOCKER_PROXY_SERVICES}
|
||||
- TASKS=${DOCKER_PROXY_TASKS}
|
||||
- POST=${DOCKER_PROXY_POST}
|
||||
ports:
|
||||
- "127.0.0.1:2375:2375"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
# ===== FILE MANAGEMENT =====
|
||||
filebrowser:
|
||||
image: hurlenko/filebrowser:latest
|
||||
container_name: filebrowser
|
||||
profiles: ["utilities", "all"]
|
||||
user: "${PUID}:${PGID}"
|
||||
ports:
|
||||
- "6633:8080"
|
||||
volumes:
|
||||
- ./filebrowser/data:/data
|
||||
- ./filebrowser/config:/config
|
||||
- /home/jamie:/data/home
|
||||
- ${DATA_ROOT}:/data/media
|
||||
environment:
|
||||
- FB_BASEURL=/filebrowser
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ===== SYNCHRONIZATION =====
|
||||
syncthing:
|
||||
image: syncthing/syncthing:latest
|
||||
container_name: syncthing
|
||||
hostname: syncthing
|
||||
profiles: ["utilities", "all"]
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/syncthing:/var/syncthing
|
||||
- ${DOCKER_CONFIG_ROOT}/obsidian/vaults:/var/syncthing/obsidian
|
||||
ports:
|
||||
- "8384:8384" # Web UI
|
||||
- "22000:22000/tcp" # TCP file transfers
|
||||
- "22000:22000/udp" # QUIC file transfers
|
||||
- "21027:21027/udp" # Receive local discovery broadcasts
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8384/rest/system/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ===== PRODUCTIVITY =====
|
||||
obsidian:
|
||||
image: ghcr.io/sytone/obsidian-remote:latest
|
||||
container_name: obsidian-remote
|
||||
profiles: ["utilities", "all"]
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8181:8080"
|
||||
- "8443:8443"
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/obsidian/vaults:/vaults
|
||||
- ${DOCKER_CONFIG_ROOT}/obsidian/config:/config
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=${TZ}
|
||||
- DOCKER_MODS=linuxserver/mods:universal-git
|
||||
depends_on:
|
||||
- syncthing
|
||||
|
||||
stirling-pdf:
|
||||
container_name: stirling-pdf
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
|
||||
profiles: ["utilities", "all"]
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
ports:
|
||||
- "8090:8080"
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
- DISABLE_ADDITIONAL_FEATURES=true
|
||||
- SECURITY_ENABLELOGIN=false
|
||||
- LANGS=en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID
|
||||
- SYSTEM_DEFAULTLOCALE=en-US
|
||||
- UI_APPNAME=Stirling-PDF
|
||||
- UI_HOMEDESCRIPTION=Stirling-PDF
|
||||
- UI_APPNAMENAVBAR=Stirling-PDF
|
||||
- SYSTEM_MAXFILESIZE=100
|
||||
- METRICS_ENABLED=true
|
||||
- SYSTEM_GOOGLEVISIBILITY=true
|
||||
- SHOW_SURVEY=true
|
||||
restart: unless-stopped
|
||||
|
||||
# ===== REMOTE ACCESS =====
|
||||
hbbr:
|
||||
container_name: hbbr
|
||||
image: rustdesk/rustdesk-server:latest
|
||||
profiles: ["remote", "all"]
|
||||
command: hbbr
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/rustdesk/data:/root
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
|
||||
hbbs:
|
||||
container_name: hbbs
|
||||
image: rustdesk/rustdesk-server:latest
|
||||
profiles: ["remote", "all"]
|
||||
command: hbbs
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/rustdesk/data:/root
|
||||
network_mode: "host"
|
||||
depends_on:
|
||||
- hbbr
|
||||
restart: unless-stopped
|
||||
|
||||
# ===== GENEALOGY =====
|
||||
grampsweb:
|
||||
container_name: gramps
|
||||
image: ghcr.io/gramps-project/grampsweb:latest
|
||||
profiles: ["genealogy", "all"]
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5511:5000"
|
||||
environment:
|
||||
- GRAMPSWEB_TREE=Gramps Web
|
||||
- GRAMPSWEB_CELERY_CONFIG__broker_url=redis://grampsweb_redis:6379/0
|
||||
- GRAMPSWEB_CELERY_CONFIG__result_backend=redis://grampsweb_redis:6379/0
|
||||
- GRAMPSWEB_RATELIMIT_STORAGE_URI=redis://grampsweb_redis:6379/1
|
||||
depends_on:
|
||||
- grampsweb_redis
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_users:/app/users
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_index:/app/indexdir
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_thumb_cache:/app/thumbnail_cache
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_cache:/app/cache
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_secret:/app/secret
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_db:/root/.gramps/grampsdb
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_media:/app/media
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_tmp:/tmp
|
||||
|
||||
grampsweb_celery:
|
||||
container_name: grampsweb_celery
|
||||
image: ghcr.io/gramps-project/grampsweb:latest
|
||||
profiles: ["genealogy", "all"]
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- GRAMPSWEB_TREE=Gramps Web
|
||||
- GRAMPSWEB_CELERY_CONFIG__broker_url=redis://grampsweb_redis:6379/0
|
||||
- GRAMPSWEB_CELERY_CONFIG__result_backend=redis://grampsweb_redis:6379/0
|
||||
- GRAMPSWEB_RATELIMIT_STORAGE_URI=redis://grampsweb_redis:6379/1
|
||||
depends_on:
|
||||
- grampsweb_redis
|
||||
volumes:
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_users:/app/users
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_index:/app/indexdir
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_thumb_cache:/app/thumbnail_cache
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_cache:/app/cache
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_secret:/app/secret
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_db:/root/.gramps/grampsdb
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_media:/app/media
|
||||
- ${DOCKER_CONFIG_ROOT}/gramps/gramps_tmp:/tmp
|
||||
command: celery -A gramps_webapi.celery worker --loglevel=INFO --concurrency=2
|
||||
|
||||
grampsweb_redis:
|
||||
image: redis:7.2.4-alpine
|
||||
container_name: grampsweb_redis
|
||||
profiles: ["genealogy", "all"]
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- grampsweb_redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# ===== API SERVICES =====
|
||||
racknerd-api-converter:
|
||||
build: ./racknerd-converter
|
||||
container_name: racknerd-api-converter
|
||||
profiles: ["utilities", "all"]
|
||||
ports:
|
||||
- "5000:5000"
|
||||
environment:
|
||||
- RACKNERD_API_KEY=${RACKNERD_API_KEY}
|
||||
- RACKNERD_API_HASH=${RACKNERD_API_HASH}
|
||||
- RACKNERD_VSERVER_ID=${RACKNERD_VSERVER_ID}
|
||||
- RACKNERD_BASE_URL=${RACKNERD_BASE_URL}
|
||||
- UPDATE_INTERVAL=${RACKNERD_UPDATE_INTERVAL}
|
||||
- HOST=0.0.0.0
|
||||
- PORT=5000
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ===== MISC SERVICES =====
|
||||
newt:
|
||||
image: fosrl/newt:latest
|
||||
container_name: newt
|
||||
profiles: ["utilities", "all"]
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PANGOLIN_ENDPOINT=${PANGOLIN_URL}
|
||||
- NEWT_ID=${NEWT_ID}
|
||||
- NEWT_SECRET=${NEWT_SECRET}
|
||||
|
||||
volumes:
|
||||
grampsweb_redis_data:
|
||||
driver: local
|
31
docker-compose.yml
Executable file
31
docker-compose.yml
Executable file
@@ -0,0 +1,31 @@
|
||||
# Main orchestration file - includes all service stacks
|
||||
# Use profiles to control which services run in different environments
|
||||
|
||||
include:
|
||||
- compose/media-stack.yml
|
||||
- compose/utilities.yml
|
||||
- compose/document-management.yml
|
||||
- compose/monitoring.yml
|
||||
|
||||
# Global networks
|
||||
networks:
|
||||
arr_network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.20.0.0/16
|
||||
|
||||
database_network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.21.0.0/16
|
||||
|
||||
# Global volumes
|
||||
volumes:
|
||||
aidata:
|
||||
driver: local
|
||||
dbdata:
|
||||
driver: local
|
||||
redisdata:
|
||||
driver: local
|
BIN
open-webui/@eaDir/Dockerfile@SynoEAStream
Executable file
BIN
open-webui/@eaDir/Dockerfile@SynoEAStream
Executable file
Binary file not shown.
179
open-webui/Dockerfile
Executable file
179
open-webui/Dockerfile
Executable file
@@ -0,0 +1,179 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Initialize device type args
|
||||
# use build args in the docker build command with --build-arg="BUILDARG=true"
|
||||
ARG USE_CUDA=false
|
||||
ARG USE_OLLAMA=false
|
||||
# Tested with cu117 for CUDA 11 and cu121 for CUDA 12 (default)
|
||||
ARG USE_CUDA_VER=cu128
|
||||
# any sentence transformer model; models to use can be found at https://huggingface.co/models?library=sentence-transformers
|
||||
# Leaderboard: https://huggingface.co/spaces/mteb/leaderboard
|
||||
# for better performance and multilangauge support use "intfloat/multilingual-e5-large" (~2.5GB) or "intfloat/multilingual-e5-base" (~1.5GB)
|
||||
# IMPORTANT: If you change the embedding model (sentence-transformers/all-MiniLM-L6-v2) and vice versa, you aren't able to use RAG Chat with your previous documents loaded in the WebUI! You need to re-embed them.
|
||||
ARG USE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
||||
ARG USE_RERANKING_MODEL=""
|
||||
|
||||
# Tiktoken encoding name; models to use can be found at https://huggingface.co/models?library=tiktoken
|
||||
ARG USE_TIKTOKEN_ENCODING_NAME="cl100k_base"
|
||||
|
||||
ARG BUILD_HASH=dev-build
|
||||
# Override at your own risk - non-root configurations are untested
|
||||
ARG UID=0
|
||||
ARG GID=0
|
||||
|
||||
######## WebUI frontend ########
|
||||
FROM --platform=$BUILDPLATFORM node:22-alpine3.20 AS build
|
||||
ARG BUILD_HASH
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# to store git revision in build
|
||||
RUN apk add --no-cache git
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
ENV APP_BUILD_HASH=${BUILD_HASH}
|
||||
RUN npm run build
|
||||
|
||||
######## WebUI backend ########
|
||||
FROM python:3.11-slim-bookworm AS base
|
||||
|
||||
# Use args
|
||||
ARG USE_CUDA
|
||||
ARG USE_OLLAMA
|
||||
ARG USE_CUDA_VER
|
||||
ARG USE_EMBEDDING_MODEL
|
||||
ARG USE_RERANKING_MODEL
|
||||
ARG UID
|
||||
ARG GID
|
||||
|
||||
## Basis ##
|
||||
ENV ENV=prod \
|
||||
PORT=8080 \
|
||||
# pass build args to the build
|
||||
USE_OLLAMA_DOCKER=${USE_OLLAMA} \
|
||||
USE_CUDA_DOCKER=${USE_CUDA} \
|
||||
USE_CUDA_DOCKER_VER=${USE_CUDA_VER} \
|
||||
USE_EMBEDDING_MODEL_DOCKER=${USE_EMBEDDING_MODEL} \
|
||||
USE_RERANKING_MODEL_DOCKER=${USE_RERANKING_MODEL}
|
||||
|
||||
## Basis URL Config ##
|
||||
ENV OLLAMA_BASE_URL="/ollama" \
|
||||
OPENAI_API_BASE_URL=""
|
||||
|
||||
## API Key and Security Config ##
|
||||
ENV OPENAI_API_KEY="" \
|
||||
WEBUI_SECRET_KEY="" \
|
||||
SCARF_NO_ANALYTICS=true \
|
||||
DO_NOT_TRACK=true \
|
||||
ANONYMIZED_TELEMETRY=false
|
||||
|
||||
#### Other models #########################################################
|
||||
## whisper TTS model settings ##
|
||||
ENV WHISPER_MODEL="base" \
|
||||
WHISPER_MODEL_DIR="/app/backend/data/cache/whisper/models"
|
||||
|
||||
## RAG Embedding model settings ##
|
||||
ENV RAG_EMBEDDING_MODEL="$USE_EMBEDDING_MODEL_DOCKER" \
|
||||
RAG_RERANKING_MODEL="$USE_RERANKING_MODEL_DOCKER" \
|
||||
SENTENCE_TRANSFORMERS_HOME="/app/backend/data/cache/embedding/models"
|
||||
|
||||
## Tiktoken model settings ##
|
||||
ENV TIKTOKEN_ENCODING_NAME="cl100k_base" \
|
||||
TIKTOKEN_CACHE_DIR="/app/backend/data/cache/tiktoken"
|
||||
|
||||
## Hugging Face download cache ##
|
||||
ENV HF_HOME="/app/backend/data/cache/embedding/models"
|
||||
|
||||
## Torch Extensions ##
|
||||
# ENV TORCH_EXTENSIONS_DIR="/.cache/torch_extensions"
|
||||
|
||||
#### Other models ##########################################################
|
||||
|
||||
WORKDIR /app/backend
|
||||
|
||||
ENV HOME=/root
|
||||
# Create user and group if not root
|
||||
RUN if [ $UID -ne 0 ]; then \
|
||||
if [ $GID -ne 0 ]; then \
|
||||
addgroup --gid $GID app; \
|
||||
fi; \
|
||||
adduser --uid $UID --gid $GID --home $HOME --disabled-password --no-create-home app; \
|
||||
fi
|
||||
|
||||
RUN mkdir -p $HOME/.cache/chroma
|
||||
RUN echo -n 00000000-0000-0000-0000-000000000000 > $HOME/.cache/chroma/telemetry_user_id
|
||||
|
||||
# Make sure the user has access to the app and root directory
|
||||
RUN chown -R $UID:$GID /app $HOME
|
||||
|
||||
RUN if [ "$USE_OLLAMA" = "true" ]; then \
|
||||
apt-get update && \
|
||||
# Install pandoc and netcat
|
||||
apt-get install -y --no-install-recommends git build-essential pandoc netcat-openbsd curl && \
|
||||
apt-get install -y --no-install-recommends gcc python3-dev && \
|
||||
# for RAG OCR
|
||||
apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
|
||||
# install helper tools
|
||||
apt-get install -y --no-install-recommends curl jq && \
|
||||
# install ollama
|
||||
curl -fsSL https://ollama.com/install.sh | sh && \
|
||||
# cleanup
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
else \
|
||||
apt-get update && \
|
||||
# Install pandoc, netcat and gcc
|
||||
apt-get install -y --no-install-recommends git build-essential pandoc gcc netcat-openbsd curl jq && \
|
||||
apt-get install -y --no-install-recommends gcc python3-dev && \
|
||||
# for RAG OCR
|
||||
apt-get install -y --no-install-recommends ffmpeg libsm6 libxext6 && \
|
||||
# cleanup
|
||||
rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# install python dependencies
|
||||
COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt
|
||||
|
||||
RUN pip3 install --no-cache-dir uv && \
|
||||
if [ "$USE_CUDA" = "true" ]; then \
|
||||
# If you use CUDA the whisper and embedding model will be downloaded on first use
|
||||
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
|
||||
uv pip install --system -r requirements.txt --no-cache-dir && \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
|
||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
|
||||
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
|
||||
else \
|
||||
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
|
||||
uv pip install --system -r requirements.txt --no-cache-dir && \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
|
||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
|
||||
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
|
||||
fi; \
|
||||
chown -R $UID:$GID /app/backend/data/
|
||||
|
||||
|
||||
|
||||
# copy embedding weight from build
|
||||
# RUN mkdir -p /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2
|
||||
# COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onnx
|
||||
|
||||
# copy built frontend files
|
||||
COPY --chown=$UID:$GID --from=build /app/build /app/build
|
||||
COPY --chown=$UID:$GID --from=build /app/CHANGELOG.md /app/CHANGELOG.md
|
||||
COPY --chown=$UID:$GID --from=build /app/package.json /app/package.json
|
||||
|
||||
# copy backend files
|
||||
COPY --chown=$UID:$GID ./backend .
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK CMD curl --silent --fail http://localhost:${PORT:-8080}/health | jq -ne 'input.status == true' || exit 1
|
||||
|
||||
USER $UID:$GID
|
||||
|
||||
ARG BUILD_HASH
|
||||
ENV WEBUI_BUILD_VERSION=${BUILD_HASH}
|
||||
ENV DOCKER=true
|
||||
|
||||
CMD [ "bash", "start.sh"]
|
34
racknerd-converter/Dockerfile
Executable file
34
racknerd-converter/Dockerfile
Executable file
@@ -0,0 +1,34 @@
|
||||
# Use Python 3.11 slim image
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy requirements file
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install Python dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy the application
|
||||
COPY kvmapiconv.py .
|
||||
|
||||
# Create a non-root user
|
||||
RUN useradd --create-home --shell /bin/bash appuser && \
|
||||
chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
# Expose port
|
||||
EXPOSE 5000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:5000/health || exit 1
|
||||
|
||||
# Run the application
|
||||
CMD ["python", "kvmapiconv.py"]
|
152
racknerd-converter/README.md
Executable file
152
racknerd-converter/README.md
Executable file
@@ -0,0 +1,152 @@
|
||||
# RackNerd KVM API Converter
|
||||
|
||||
A Python Flask service that converts RackNerd KVM XML API responses to JSON format for Homepage widgets.
|
||||
|
||||
## Features
|
||||
|
||||
- Converts RackNerd XML API responses to clean JSON format
|
||||
- Caches data to reduce API calls (configurable interval, default 30 minutes)
|
||||
- RESTful API endpoints for Homepage integration
|
||||
- Docker containerized for easy deployment
|
||||
- Health checks and error handling
|
||||
- Automatic background data updates
|
||||
|
||||
## API Endpoints
|
||||
|
||||
- `GET /api/kvm` - Get cached KVM data in JSON format
|
||||
- `GET /api/kvm/raw` - Get fresh data (bypass cache)
|
||||
- `GET /api/kvm/status` - Service status and metadata
|
||||
- `GET /health` - Health check endpoint
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using Docker Compose (Recommended)
|
||||
|
||||
1. Clone/download the files
|
||||
2. Update the environment variables in `docker-compose.yml` with your RackNerd API credentials
|
||||
3. Run:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Using Docker
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker build -t racknerd-api-converter .
|
||||
|
||||
# Run the container
|
||||
docker run -d \
|
||||
-p 5000:5000 \
|
||||
-e RACKNERD_API_KEY=your_api_key \
|
||||
-e RACKNERD_API_HASH=your_api_hash \
|
||||
-e RACKNERD_VSERVER_ID=your_vserver_id \
|
||||
-e UPDATE_INTERVAL=1800 \
|
||||
--name racknerd-converter \
|
||||
racknerd-api-converter
|
||||
```
|
||||
|
||||
### Manual Python Setup
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
export RACKNERD_API_KEY=your_api_key
|
||||
export RACKNERD_API_HASH=your_api_hash
|
||||
export RACKNERD_VSERVER_ID=your_vserver_id
|
||||
python app.py
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure via environment variables:
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `RACKNERD_API_KEY` | Your RackNerd API key | A0ZJA-FSJXW-QXV7R |
|
||||
| `RACKNERD_API_HASH` | Your RackNerd API hash | fce545debdab0edf2565788277d3670e1afd8823 |
|
||||
| `RACKNERD_VSERVER_ID` | Your VServer ID | 476515 |
|
||||
| `RACKNERD_BASE_URL` | RackNerd API base URL | https://nerdvm.racknerd.com/api/client/command.php |
|
||||
| `UPDATE_INTERVAL` | Cache update interval (seconds) | 1800 (30 minutes) |
|
||||
| `HOST` | Server host | 0.0.0.0 |
|
||||
| `PORT` | Server port | 5000 |
|
||||
|
||||
## Homepage Widget Configuration
|
||||
|
||||
Add this to your Homepage `services.yaml`:
|
||||
|
||||
```yaml
|
||||
- KVM Server:
|
||||
icon: server
|
||||
href: http://your-server:5000
|
||||
ping: http://your-server:5000
|
||||
widget:
|
||||
type: customapi
|
||||
url: http://your-server:5000/api/kvm
|
||||
refreshInterval: 30000
|
||||
mappings:
|
||||
- field: hostname
|
||||
label: Hostname
|
||||
- field: status
|
||||
label: Status
|
||||
- field: uptime
|
||||
label: Uptime
|
||||
- field: cpu_usage
|
||||
label: CPU Usage
|
||||
suffix: "%"
|
||||
- field: memory_usage
|
||||
label: Memory
|
||||
suffix: "%"
|
||||
```
|
||||
|
||||
## API Response Format
|
||||
|
||||
The service converts XML responses to JSON. Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"vserver": {
|
||||
"hostname": "your-server",
|
||||
"status": "online",
|
||||
"uptime": "15 days",
|
||||
"cpu_usage": 25.5,
|
||||
"memory": {
|
||||
"total": "4096MB",
|
||||
"used": "2048MB",
|
||||
"free": "2048MB"
|
||||
},
|
||||
"bandwidth": {
|
||||
"used": "150GB",
|
||||
"total": "1000GB"
|
||||
}
|
||||
},
|
||||
"_metadata": {
|
||||
"last_updated": "2024-01-15T10:30:00Z",
|
||||
"source": "racknerd_api",
|
||||
"vserver_id": "476515"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
- Check service health: `GET /health`
|
||||
- Monitor logs: `docker logs racknerd-api-converter`
|
||||
- Service status: `GET /api/kvm/status`
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Keep your API credentials secure
|
||||
- Consider using Docker secrets for production
|
||||
- The service runs as a non-root user
|
||||
- Network access is limited to necessary ports
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
1. **No data returned**: Check API credentials and network connectivity
|
||||
2. **XML parsing errors**: Verify API response format hasn't changed
|
||||
3. **Container won't start**: Check environment variables and port conflicts
|
||||
4. **Homepage not updating**: Verify URL and check service logs
|
||||
|
||||
## Contributing
|
||||
|
||||
Feel free to submit issues and enhancement requests!
|
315
racknerd-converter/kvmapiconv.py
Executable file
315
racknerd-converter/kvmapiconv.py
Executable file
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
import time
|
||||
import threading
|
||||
from flask import Flask, jsonify, request
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import os
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
class RackNerdAPIConverter:
|
||||
def __init__(self):
|
||||
# Load configuration from environment variables
|
||||
self.api_key = os.getenv('RACKNERD_API_KEY', 'A0ZJA-FSJXW-QXV7R')
|
||||
self.api_hash = os.getenv('RACKNERD_API_HASH', 'fce545debdab0edf2565788277d3670e1afd8823')
|
||||
self.vserver_id = os.getenv('RACKNERD_VSERVER_ID', '476515')
|
||||
self.base_url = os.getenv('RACKNERD_BASE_URL', 'https://nerdvm.racknerd.com/api/client/command.php')
|
||||
self.update_interval = int(os.getenv('UPDATE_INTERVAL', '1800')) # 30 minutes default
|
||||
|
||||
self.cached_data = {}
|
||||
self.last_update = None
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def fetch_kvm_data(self) -> Optional[Dict[str, Any]]:
|
||||
"""Fetch data from RackNerd API and convert XML to JSON"""
|
||||
try:
|
||||
# Construct the API URL
|
||||
params = {
|
||||
'key': self.api_key,
|
||||
'hash': self.api_hash,
|
||||
'action': 'info',
|
||||
'vserverid': self.vserver_id,
|
||||
'bw': 'true'
|
||||
}
|
||||
|
||||
logger.info(f"Fetching data from RackNerd API for vserver {self.vserver_id}")
|
||||
|
||||
# Make the API request
|
||||
response = requests.get(self.base_url, params=params, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
# Clean up the response text
|
||||
xml_text = response.text.strip()
|
||||
|
||||
# Handle potential HTML/error responses
|
||||
if xml_text.lower().startswith('<!doctype') or xml_text.lower().startswith('<html'):
|
||||
logger.error("Received HTML response instead of XML - check API credentials")
|
||||
return {
|
||||
'error': 'Invalid API Response',
|
||||
'message': 'Received HTML instead of XML. Check your API credentials.',
|
||||
'raw_response': xml_text[:500]
|
||||
}
|
||||
|
||||
# RackNerd returns XML fragments without root element, so wrap them
|
||||
if not xml_text.startswith('<?xml') and not xml_text.startswith('<root>'):
|
||||
xml_text = f"<root>{xml_text}</root>"
|
||||
logger.info("Wrapped XML fragments in root element")
|
||||
|
||||
# Parse XML response
|
||||
root = ET.fromstring(xml_text)
|
||||
|
||||
# Convert XML to JSON
|
||||
json_data = self.xml_to_dict(root)
|
||||
|
||||
# Parse bandwidth data if present
|
||||
if 'bw' in json_data and isinstance(json_data['bw'], str):
|
||||
bw_parts = json_data['bw'].split(',')
|
||||
if len(bw_parts) >= 4:
|
||||
json_data['bandwidth'] = {
|
||||
'total_bytes': int(bw_parts[0]) if bw_parts[0].isdigit() else bw_parts[0],
|
||||
'used_bytes': int(bw_parts[1]) if bw_parts[1].isdigit() else bw_parts[1],
|
||||
'total_formatted': self.format_bytes(int(bw_parts[0])) if bw_parts[0].isdigit() else bw_parts[0],
|
||||
'used_formatted': self.format_bytes(int(bw_parts[1])) if bw_parts[1].isdigit() else bw_parts[1],
|
||||
'remaining_bytes': int(bw_parts[0]) - int(bw_parts[1]) if bw_parts[0].isdigit() and bw_parts[1].isdigit() else 0,
|
||||
'usage_percent': round((int(bw_parts[1]) / int(bw_parts[0])) * 100, 2) if bw_parts[0].isdigit() and bw_parts[1].isdigit() and int(bw_parts[0]) > 0 else 0
|
||||
}
|
||||
if json_data['bandwidth']['remaining_bytes'] > 0:
|
||||
json_data['bandwidth']['remaining_formatted'] = self.format_bytes(json_data['bandwidth']['remaining_bytes'])
|
||||
|
||||
# Add metadata
|
||||
json_data['_metadata'] = {
|
||||
'last_updated': datetime.utcnow().isoformat() + 'Z',
|
||||
'source': 'racknerd_api',
|
||||
'vserver_id': self.vserver_id,
|
||||
'raw_response_length': len(response.text)
|
||||
}
|
||||
|
||||
logger.info("Successfully converted XML to JSON")
|
||||
return json_data
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"API request failed: {e}")
|
||||
return {
|
||||
'error': 'API Request Failed',
|
||||
'message': str(e),
|
||||
'timestamp': datetime.utcnow().isoformat() + 'Z'
|
||||
}
|
||||
except ET.ParseError as e:
|
||||
logger.error(f"XML parsing failed: {e}")
|
||||
logger.error(f"Raw response that failed to parse: {response.text}")
|
||||
return {
|
||||
'error': 'XML Parse Error',
|
||||
'message': str(e),
|
||||
'raw_response': response.text[:1000],
|
||||
'timestamp': datetime.utcnow().isoformat() + 'Z'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {e}")
|
||||
return {
|
||||
'error': 'Unexpected Error',
|
||||
'message': str(e),
|
||||
'timestamp': datetime.utcnow().isoformat() + 'Z'
|
||||
}
|
||||
|
||||
def xml_to_dict(self, element) -> Dict[str, Any]:
|
||||
"""Recursively convert XML element to dictionary"""
|
||||
result = {}
|
||||
|
||||
# Handle element text
|
||||
if element.text and element.text.strip():
|
||||
# Try to convert to appropriate type
|
||||
text = element.text.strip()
|
||||
if text.lower() in ['true', 'false']:
|
||||
result['_text'] = text.lower() == 'true'
|
||||
elif text.isdigit():
|
||||
result['_text'] = int(text)
|
||||
elif self.is_float(text):
|
||||
result['_text'] = float(text)
|
||||
else:
|
||||
result['_text'] = text
|
||||
|
||||
# Handle attributes
|
||||
if element.attrib:
|
||||
result['_attributes'] = element.attrib
|
||||
|
||||
# Handle child elements
|
||||
children = {}
|
||||
for child in element:
|
||||
child_data = self.xml_to_dict(child)
|
||||
|
||||
if child.tag in children:
|
||||
# Handle multiple children with same tag
|
||||
if not isinstance(children[child.tag], list):
|
||||
children[child.tag] = [children[child.tag]]
|
||||
children[child.tag].append(child_data)
|
||||
else:
|
||||
children[child.tag] = child_data
|
||||
|
||||
if children:
|
||||
result.update(children)
|
||||
|
||||
# If only text content, return it directly
|
||||
if len(result) == 1 and '_text' in result:
|
||||
return result['_text']
|
||||
|
||||
return result
|
||||
|
||||
def format_bytes(self, bytes_value: int) -> str:
|
||||
"""Convert bytes to human readable format"""
|
||||
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
||||
if bytes_value < 1024.0:
|
||||
return f"{bytes_value:.1f} {unit}"
|
||||
bytes_value /= 1024.0
|
||||
return f"{bytes_value:.1f} PB"
|
||||
|
||||
def is_float(self, value: str) -> bool:
|
||||
"""Check if string can be converted to float"""
|
||||
try:
|
||||
float(value)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def update_cache(self):
|
||||
"""Update cached data"""
|
||||
with self.lock:
|
||||
data = self.fetch_kvm_data()
|
||||
if data:
|
||||
self.cached_data = data
|
||||
self.last_update = datetime.utcnow()
|
||||
logger.info("Cache updated successfully")
|
||||
else:
|
||||
logger.error("Failed to update cache")
|
||||
|
||||
def get_cached_data(self) -> Dict[str, Any]:
|
||||
"""Get cached data with thread safety"""
|
||||
with self.lock:
|
||||
if not self.cached_data:
|
||||
return {
|
||||
'error': 'No data available',
|
||||
'message': 'Initial data fetch in progress'
|
||||
}
|
||||
return self.cached_data.copy()
|
||||
|
||||
def start_background_updates(self):
|
||||
"""Start background thread for periodic updates"""
|
||||
def update_loop():
|
||||
while True:
|
||||
self.update_cache()
|
||||
time.sleep(self.update_interval)
|
||||
|
||||
thread = threading.Thread(target=update_loop, daemon=True)
|
||||
thread.start()
|
||||
logger.info(f"Background updates started (interval: {self.update_interval} seconds)")
|
||||
|
||||
# Initialize converter
|
||||
converter = RackNerdAPIConverter()
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health_check():
|
||||
"""Health check endpoint"""
|
||||
return jsonify({
|
||||
'status': 'healthy',
|
||||
'timestamp': datetime.utcnow().isoformat() + 'Z',
|
||||
'last_update': converter.last_update.isoformat() + 'Z' if converter.last_update else None
|
||||
})
|
||||
|
||||
@app.route('/api/kvm', methods=['GET'])
|
||||
def get_kvm_data():
|
||||
"""Main endpoint to get KVM data in JSON format"""
|
||||
return jsonify(converter.get_cached_data())
|
||||
|
||||
@app.route('/api/kvm/raw', methods=['GET'])
|
||||
def get_raw_kvm_data():
|
||||
"""Endpoint to get fresh data (bypass cache)"""
|
||||
data = converter.fetch_kvm_data()
|
||||
if data:
|
||||
return jsonify(data)
|
||||
else:
|
||||
return jsonify({
|
||||
'error': 'Failed to fetch data',
|
||||
'message': 'Unable to retrieve data from RackNerd API'
|
||||
}), 500
|
||||
|
||||
@app.route('/api/kvm/debug', methods=['GET'])
|
||||
def debug_api():
|
||||
"""Debug endpoint to see raw API response"""
|
||||
try:
|
||||
params = {
|
||||
'key': converter.api_key,
|
||||
'hash': converter.api_hash,
|
||||
'action': 'info',
|
||||
'vserverid': converter.vserver_id,
|
||||
'bw': 'true'
|
||||
}
|
||||
|
||||
response = requests.get(converter.base_url, params=params, timeout=30)
|
||||
|
||||
return jsonify({
|
||||
'status_code': response.status_code,
|
||||
'headers': dict(response.headers),
|
||||
'raw_content': response.text,
|
||||
'content_length': len(response.text),
|
||||
'url': response.url
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'error': 'Debug request failed',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
@app.route('/api/kvm/status', methods=['GET'])
|
||||
def get_status():
|
||||
"""Get service status and metadata"""
|
||||
with converter.lock:
|
||||
return jsonify({
|
||||
'service': 'racknerd-api-converter',
|
||||
'status': 'running',
|
||||
'vserver_id': converter.vserver_id,
|
||||
'update_interval': converter.update_interval,
|
||||
'last_update': converter.last_update.isoformat() + 'Z' if converter.last_update else None,
|
||||
'has_cached_data': bool(converter.cached_data),
|
||||
'timestamp': datetime.utcnow().isoformat() + 'Z'
|
||||
})
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
return jsonify({
|
||||
'error': 'Not found',
|
||||
'message': 'The requested endpoint does not exist'
|
||||
}), 404
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(error):
|
||||
return jsonify({
|
||||
'error': 'Internal server error',
|
||||
'message': 'An unexpected error occurred'
|
||||
}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Initial data fetch
|
||||
logger.info("Starting RackNerd API Converter...")
|
||||
converter.update_cache()
|
||||
|
||||
# Start background updates
|
||||
converter.start_background_updates()
|
||||
|
||||
# Start Flask app
|
||||
port = int(os.getenv('PORT', '5000'))
|
||||
host = os.getenv('HOST', '0.0.0.0')
|
||||
|
||||
logger.info(f"Starting server on {host}:{port}")
|
||||
app.run(host=host, port=port, debug=False)
|
3
racknerd-converter/requirements.txt
Executable file
3
racknerd-converter/requirements.txt
Executable file
@@ -0,0 +1,3 @@
|
||||
Flask==2.3.3
|
||||
requests==2.31.0
|
||||
Werkzeug==2.3.7
|
1
secrets/authelia_jwt_secret
Executable file
1
secrets/authelia_jwt_secret
Executable file
@@ -0,0 +1 @@
|
||||
i5Dssz20RgmarmAl9Mo1ugp6HxxStmcJtWWkrbuS06ap2pOlmROCfScIEsHuyBXA
|
1
secrets/authelia_session_secret
Executable file
1
secrets/authelia_session_secret
Executable file
@@ -0,0 +1 @@
|
||||
B6vqqwdsRmdYOhUzo5RUgioAYaKdwZpVSmi5PCFKxYIko1LtazXjaERnKCzG3zl8
|
1
secrets/authelia_storage_encryption_key
Executable file
1
secrets/authelia_storage_encryption_key
Executable file
@@ -0,0 +1 @@
|
||||
nMtLQfZ4v8SH63aL54mM8EOtg2eLrobnElYtjsyKjBRzBa8CyDc4EwYfJZpgUmal
|
205
setup.sh
Executable file
205
setup.sh
Executable file
@@ -0,0 +1,205 @@
|
||||
#!/bin/bash
|
||||
# ===========================================
|
||||
# Docker Media Stack Setup Script
|
||||
# ===========================================
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
echo "🚀 Setting up Docker Media & Utility Stack..."
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if Docker and Docker Compose are installed
|
||||
check_dependencies() {
|
||||
print_status "Checking dependencies..."
|
||||
|
||||
if ! command -v docker &> /dev/null; then
|
||||
print_error "Docker is not installed. Please install Docker first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v docker compose &> /dev/null; then
|
||||
print_error "Docker Compose is not installed. Please install Docker Compose first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_status "Dependencies check passed ✓"
|
||||
}
|
||||
|
||||
# Create necessary directories
|
||||
create_directories() {
|
||||
print_status "Creating directory structure..."
|
||||
|
||||
# Main data directories
|
||||
sudo mkdir -p /data/{movies,tv,music,torrents}
|
||||
|
||||
# Docker config directories
|
||||
mkdir -p ../docker-local/{Arrs/{Sonarr,Radarr,Lidarr,Bazarr,Prowlarr,Jellyfin,Jellyseerr}/config,speedtest-tracker/{data,db},rustdesk/data,gramps,syncthing,obsidian/{vaults,config}}
|
||||
|
||||
# Application specific directories
|
||||
mkdir -p {qBittorrent,Homepage,filebrowser,maloja,scrobble,stirling/latest,paperless,gramps,racknerd-converter}/config
|
||||
mkdir -p {Homepage/config/{images,icons},filebrowser/data,maloja/{data,logs},stirling/latest/{data,config,logs},paperless/{data,media,export,consume},racknerd-converter/{data,logs}}
|
||||
|
||||
print_status "Directories created ✓"
|
||||
}
|
||||
|
||||
# Set proper permissions
|
||||
set_permissions() {
|
||||
print_status "Setting permissions..."
|
||||
|
||||
# Get current user ID and group ID
|
||||
CURRENT_UID=$(id -u)
|
||||
CURRENT_GID=$(id -g)
|
||||
|
||||
# Set ownership for data directories
|
||||
if [ -d "/data" ]; then
|
||||
sudo chown -R $CURRENT_UID:$CURRENT_GID /data
|
||||
fi
|
||||
|
||||
# Set ownership for config directories
|
||||
if [ -d "../docker-local" ]; then
|
||||
chown -R $CURRENT_UID:$CURRENT_GID ../docker-local
|
||||
fi
|
||||
|
||||
# Set ownership for local directories
|
||||
chown -R $CURRENT_UID:$CURRENT_GID .
|
||||
|
||||
print_status "Permissions set ✓"
|
||||
}
|
||||
|
||||
# Validate environment file
|
||||
validate_environment() {
|
||||
print_status "Validating environment configuration..."
|
||||
|
||||
if [ ! -f ".env" ]; then
|
||||
print_error ".env file not found. Please ensure it exists."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for required variables
|
||||
required_vars=("TZ" "PUID" "PGID" "DOMAIN" "DATA_ROOT")
|
||||
|
||||
for var in "${required_vars[@]}"; do
|
||||
if ! grep -q "^${var}=" .env; then
|
||||
print_error "Required environment variable $var not found in .env"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
print_status "Environment validation passed ✓"
|
||||
}
|
||||
|
||||
# Pull latest images
|
||||
pull_images() {
|
||||
print_status "Pulling latest Docker images..."
|
||||
|
||||
docker compose pull
|
||||
|
||||
print_status "Images pulled ✓"
|
||||
}
|
||||
|
||||
# Start services
|
||||
start_services() {
|
||||
print_status "Starting services..."
|
||||
|
||||
# Create networks first
|
||||
docker network create arr_network --subnet=172.20.0.0/16 2>/dev/null || true
|
||||
docker network create database_network --subnet=172.21.0.0/16 2>/dev/null || true
|
||||
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
|
||||
print_status "Services started ✓"
|
||||
}
|
||||
|
||||
# Display service URLs
|
||||
show_services() {
|
||||
echo
|
||||
print_status "🎉 Setup complete! Your services are available at:"
|
||||
echo
|
||||
echo -e "${BLUE}📊 Dashboard & Management:${NC}"
|
||||
echo " • Homepage (Dashboard): http://localhost:7575"
|
||||
echo " • FileBrowser: http://localhost:6633"
|
||||
echo
|
||||
echo -e "${BLUE}🎬 Media Services:${NC}"
|
||||
echo " • Jellyfin (Media Server): http://localhost:8096"
|
||||
echo " • Jellyseerr (Requests): http://localhost:5055"
|
||||
echo " • Sonarr (TV Shows): http://localhost:8989"
|
||||
echo " • Radarr (Movies): http://localhost:7878"
|
||||
echo " • Lidarr (Music): http://localhost:8686"
|
||||
echo " • Bazarr (Subtitles): http://localhost:6767"
|
||||
echo " • Prowlarr (Indexers): http://localhost:9696"
|
||||
echo " • qBittorrent: http://localhost:7070"
|
||||
echo
|
||||
echo -e "${BLUE}📄 Document Management:${NC}"
|
||||
echo " • Paperless NGX: http://localhost:8100"
|
||||
echo " • Paperless AI: http://localhost:3040"
|
||||
echo " • Stirling PDF: http://localhost:8090"
|
||||
echo
|
||||
echo -e "${BLUE}🛠️ Utilities:${NC}"
|
||||
echo " • Speedtest Tracker: http://localhost:8180"
|
||||
echo " • Syncthing: http://localhost:8384"
|
||||
echo " • Obsidian Remote: http://localhost:8181"
|
||||
echo " • Gramps (Genealogy): http://localhost:5511"
|
||||
echo
|
||||
echo -e "${BLUE}🎵 Music Services:${NC}"
|
||||
echo " • Multi-Scrobbler: http://localhost:9078"
|
||||
echo " • Maloja: http://localhost:42010"
|
||||
echo
|
||||
echo -e "${BLUE}🖥️ Server Monitoring:${NC}"
|
||||
echo " • RackNerd API: http://localhost:5000"
|
||||
echo
|
||||
print_warning "Some services may take a few minutes to fully initialize."
|
||||
echo
|
||||
echo -e "${GREEN}💡 Useful commands:${NC}"
|
||||
echo " • View status: docker compose ps"
|
||||
echo " • View logs: docker compose logs [service]"
|
||||
echo " • Stop all: docker compose down"
|
||||
echo " • Update services: docker compose pull && docker compose up -d"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
echo -e "${BLUE}"
|
||||
echo "╔══════════════════════════════════════════════════╗"
|
||||
echo "║ Docker Media Stack Setup ║"
|
||||
echo "║ ║"
|
||||
echo "║ This script will set up your complete ║"
|
||||
echo "║ media and utility stack with Docker Compose ║"
|
||||
echo "╚══════════════════════════════════════════════════╝"
|
||||
echo -e "${NC}"
|
||||
|
||||
check_dependencies
|
||||
validate_environment
|
||||
create_directories
|
||||
set_permissions
|
||||
pull_images
|
||||
start_services
|
||||
show_services
|
||||
|
||||
echo
|
||||
print_status "🎯 Setup completed successfully!"
|
||||
print_warning "Remember to configure your services through their web interfaces."
|
||||
echo
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
Reference in New Issue
Block a user