Compare commits

..
16 Commits
Author SHA1 Message Date
lucas 7b0b438740 add nextcloud app update to upgrade script 2026-10-13 20:15:03 +02:00
lucas 2eda7cd33e align convert heic with mediahost and new picture sync
asd
2026-10-13 20:15:03 +02:00
lucas efc150d9ce ssh config and hosts
asd
2026-10-13 20:15:03 +02:00
lucas 044fbd81ad working smartmon node exporter script for sd/nvme 2026-10-12 21:54:40 +02:00
lucas 01ab319eea node exporter: add hostname dependent docker count and default value for smartd script 2026-10-12 19:19:15 +00:00
lucas d5e627325d add quiethost to smartd 2026-10-12 20:49:53 +02:00
lucas deaff35bc3 remove raspberry from bash_aliases
add mediahost to wol.sh
2026-10-12 20:40:34 +02:00
lucas 27d570fc5c node exporter script for smartd 2026-10-12 18:39:53 +00:00
lucas d5fb01adb2 docker backup serverhost
add cadvisor to serverhost
change node-exporter to network host to see propper netw traffic
refine torrentscomplete script
2026-10-12 20:10:54 +02:00
lucas 52c02beb3d mediahost docker backup
add gite to shared proxy
add usb dev and NET privilige to HA
add immich image server
remove sonarr-en
add jackett indexer for ABB
add gluetun-proxy for general proxy
add blackbox exporter to check https sites
set node-exporter to network host to see interface traffic
decrease resolution of cadvisor to save RAM
add status and code SANs for nginx
add upstream block so sites are not redirecte to cloud...
add homeassistant config
add nginx exporter for prometheus
2026-10-12 18:10:41 +00:00
lucas 3f14f53184 add serverhost to smartd.conf 2026-10-12 17:59:00 +00:00
lucas c41bf36afb add smartd conf 2026-10-12 17:50:54 +00:00
lucas 07bab58ae0 add smartctl service 2026-10-12 15:01:20 +00:00
Lucas Vivo d06cff4cbd compare playlists m3u und navidrome 2026-10-12 16:33:53 +02:00
Lucas Vivo 09343a7523 first version of general audiobook library.py 2026-10-12 16:33:53 +02:00
Lucas Vivo dc0cd02e58 combine audiobooks: remove unecessary function 2026-10-12 16:33:53 +02:00
32 changed files with 1147 additions and 274 deletions

No files matched your search

@@ -8,7 +8,6 @@ import subprocess
import tempfile
from pathlib import Path
import time
from typing import Any
RED = "\033[91m"
GREEN = "\033[92m"
@@ -145,36 +144,6 @@ def create_m4b(root_path: Path, book_dir: Path) -> None:
else:
log_message(f"{RED}[ERROR]{RESET} - {book_dir.name} - Code: {result.returncode}, Stderr: {result.stderr}")
def find_book_dirs(root: Path) -> tuple[list[Any], int]:
"""
A book directory is any directory containing more than one
audiobook file (.mp3, .m4a, .m4b)
"""
media_files = (
list(root.rglob("*.mp3"))
+ list(root.rglob("*.m4a"))
+ list(root.rglob("*.m4b"))
+ list(root.rglob("*.opus"))
)
counts: dict[Path, int] = {}
for file in media_files:
if counts.get(file.parent, 0) > 1:
continue
counts[file.parent] = counts.get(file.parent, 0) + 1
directories = []
size = 0
for directory, count in counts.items():
if count > 1:
directories.append(directory)
size += sum(file.stat().st_size for pattern in ("*.mp3", "*.m4a", "*.m4b", "*.opus") for file in directory.glob(pattern))
return sorted(directories), size
def process_book(args):
target, book_dir = args
try:
-60
View File
@@ -1,60 +0,0 @@
#!/usr/bin/env python3
import sys
import subprocess
from pathlib import Path
ROOT = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
audio_extensions = {
".m4b",
".m4a",
".mp3",
".flac",
".ogg",
}
def get_metadata(field, file):
try:
result = subprocess.run(
["kid3-cli", "-c", f"get {field}", str(file)],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
return result.stdout.strip()
except (OSError, subprocess.SubprocessError):
return ""
# Include ROOT itself, matching find "$ROOT" -type d
for directory in [ROOT] + [p for p in ROOT.rglob("*") if p.is_dir()]:
audio_files = sorted(
(
file
for file in directory.iterdir()
if file.is_file()
and file.suffix.lower() in audio_extensions
),
key=lambda p: str(p),
)
if not audio_files:
continue
first_file = audio_files[0]
artist = get_metadata("artist", first_file)
album = get_metadata("album", first_file)
composer = get_metadata("composer", first_file)
comment = get_metadata("comment", first_file)
print("====≠===============================")
print(f"Folder: {directory.name}")
print(f"Album: {album}")
print(f"Artist: {artist}")
print(f"Narrator: {composer}")
print(f"Comment: {comment}")
+440
View File
@@ -0,0 +1,440 @@
#!/usr/bin/env python3
import subprocess
import tempfile
import argparse
import logging
import re
from pathlib import Path
from typing import Any
from collections import defaultdict
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
RESET = "\033[0m"
logger = logging.getLogger(__name__)
audio_extensions = {
"*.m4b",
"*.m4a",
"*.mp3",
"*.flac",
"*.ogg",
"*.opus"
}
class Audiobook():
def __init__(self, bookPath: Path) -> None:
self.bookPath = bookPath
self.files = [file for pattern in audio_extensions for file in
bookPath.glob(pattern)]
self.firstFile = self.get_first_file()
self.size = sum(file.stat().st_size for pattern in audio_extensions for file in
bookPath.glob(pattern))
self.fileConunt = len(self.files)
self.singleFile = self.fileConunt > 1
# Metadata
self.loadMetadata()
logger.info(f"Found {self.bookPath.name}, {self.fileConunt} files")
def __repr__(self):
return (
f"Audiobook("
f"name={self.name!r}, "
f"artist={self.artist!r}, "
f"composer={self.composer!r}, "
f"path={str(self.bookPath)!r}),"
f"size={self.size!r}, "
f"fileConunt={self.fileConunt!r}, "
)
def get_first_file(self):
return self.files[0]
def loadMetadata(self):
fields = ["artist", "album", "composer", "comment", "genre"]
metadata = self.get_metadata(self.get_first_file(), fields )
self.artist = metadata["artist"]
self.name = metadata["album"]
self.composer = metadata["composer"]
self.comment = metadata["comment"]
self.genre = metadata["genre"]
def get_metadata(self, file, fields):
metadata=dict.fromkeys(fields, "")
for field in fields:
cmd= (["kid3-cli", "-c", f"get {field}", str(file)])
try:
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=True,
)
metadata[field] = result.stdout.strip()
except (OSError, subprocess.SubprocessError) as error:
print(f"Error getting metadata for {file}: {error}")
return metadata
return metadata
def write_metadata(self, file, fields, values):
metadata=dict.fromkeys(fields, "")
for field,value in fields,values:
cmd= (["kid3-cli", "-c" , "tag 2","-c", f"set {field} \"{value}\"", "-c" "save", str(file)])
try:
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=True,
)
except (OSError, subprocess.SubprocessError) as error:
print(f"Error writing metadata for {file}: {error}")
return
return
def fixComposer(self):
if not self.composer:
m = re.search(
r"(?:read\s+by|spoken\s+by|narrated\s+by|gesprochen\s+von|gelesen\s+von)\s+([^\n.,;]+)",
self.comment,
re.IGNORECASE,
)
if not m:
logger.info(f"No composer found for {self.bookPath}")
return
logger.info(f"[Updated] composer found for {self.bookPath}")
self.composer = m.group(1).strip()
self.comment = (self.comment[:m.start()] + self.comment[m.end():] ).strip(" .,;\n")
self.comment = re.sub(r"\s+"," ",self.comment)
def fixName(self):
if not self.name:
self.name = self.bookPath.name
logger.info(f"[Updated] name found for {self.bookPath}")
def clean_genre(self,genre):
if not self.genre:
return
genre = self.genre.lower()
genre = re.sub(r'(?i)^audio\s*books?$', '', genre)
genre = re.sub(r'(?i)^audiobooks?;?$', '', genre)
genre = re.sub(r'(?i)\b(hörb(uch|ücher)|hoerb(uch|uecher))\b', '', genre)
genre = re.sub(r'(?i)^(hörspiele?|hoerspiele?)$', '', genre)
genre = re.sub(r'(?i)^spoken\s*word$', '', genre)
genre = re.sub(r'(?i)gesprochene\s*inhalte$', '', genre)
genre = re.sub(r'(?i)^speech$', '', genre)
genre = re.sub(r'Fanasie', 'Fantasie', genre)
genre = re.sub(r'Mit Videospiel-?', 'Videospielbezug', genre)
genre = re.sub(r'(?i)nonfiction', 'NonFiction', genre)
genre = re.sub(r'(?i)^speech$', '', genre)
genre = re.sub(r'(?i)^vocal$', '', genre)
genre = re.sub(r'(?i)^retail$', '', genre)
genre = re.sub(r'(?i)^other$', '', genre)
genre = re.sub(r'(?i)^misc$', '', genre)
genre = re.sub(r'(?i)^(sci[- ]?fi|sf)$', 'Science Fiction', genre)
genre = re.sub(r'(?i)^\(?science fiction\)?$', 'Science Fiction', genre)
genre = re.sub(r'Thrillers', 'Thriller', genre)
genre = re.sub(r"(?i)\bchildren'?s audiobooks?\b", "", genre)
genre = re.sub(r'^-\s*|\s*-$', '', genre)
genre = re.sub(r'(?i)Engelske\s*\sLydbånd', '', genre)
genre = re.sub(r'^FIC028000.*$', '', genre)
genre = re.sub(r'^800.*$', '', genre)
genre = re.sub(r'^Fantastic Beasts.*$', '', genre)
genre = re.sub(r'^and thei.*$', '', genre)
genre = re.sub(r'^those you.*$', '', genre)
# Normalize separators
genre = re.sub(r'\s*[,&:/]\s*',';',genre)
genre = re.sub(r'\s*;\s*','; ',genre)
# Trim punctuation / whitespace
genre = re.sub(r'^[\s,;:\-]+','',genre)
genre = re.sub(r'[\s,;\-]+$','',genre)
genre = genre.strip()
self.genre = genre.lstrip()
return
def create_m4b(self, root_path) -> None:
output_file_final = self.bookPath / f"{self.bookPath.name}.m4b"
if output_file_final.exists():
logger.info(f"{RED}[SKIP]{RESET} {output_file_final}")
logger.info("Book already present")
return
logger.info(f"[CREATE] {output_file_final}")
with (tempfile.TemporaryDirectory(dir=root_path.parent) as tmp):
tmpdir = Path(tmp)
output_file_tmp = tmpdir / f"{self.bookPath.name}.m4b"
concat_file = tmpdir / "concat.txt"
metadata_file = tmpdir / "chapters.txt"
current_ms = 0
with concat_file.open("w", encoding="utf-8") as cf, \
metadata_file.open("w", encoding="utf-8") as mf:
mf.write(";FFMETADATA1\n")
for media in self.files:
cf.write(f"file '{ffmpeg_escape(media)}'\n")
duration_ms = self.get_duration_ms(media)
mf.write("[CHAPTER]\n")
mf.write("TIMEBASE=1/1000\n")
mf.write(f"START={current_ms}\n")
mf.write(f"END={current_ms + duration_ms}\n")
mf.write(f"title={media.stem}\n")
current_ms += duration_ms
cmd_backup = ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
"-i", str(concat_file), "-i", str(metadata_file),
# cover from first media
"-i", str(self.firstFile),
# audio from concat
"-map", "0:a",
# cover image from first media
"-map", "2:v?",
# copy artist/album/composer/etc from first media file
"-map_metadata", "2",
# copy chapters from ffmetadata
"-map_chapters", "1",
"-c:a", "copy",
"-c:a", "aac",
"-b:a", "128k",
# copy cover image without re-encoding
"-c:v", "copy", "-disposition:v:0", "attached_pic",
str(output_file_tmp),
]
cmd_default = ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
"-i", str(concat_file), "-i", str(metadata_file),
# cover from first media
"-i", str(self.firstFile),
# audio from concat
"-map", "0:a",
# cover image from first media
"-map", "2:v?",
# copy artist/album/composer/etc from first media file
"-map_metadata", "2",
# copy chapters from ffmetadata
"-map_chapters", "1",
"-c:a", "copy",
# copy cover image without re-encoding
"-c:v", "copy", "-disposition:v:0", "attached_pic",
str(output_file_tmp),
]
result = subprocess.run(
cmd_default,
capture_output=True,
text=True,
)
logger.info(result.stdout)
if result.returncode == 234:
logger.warning(
f"{YELLOW}[WARNING]{RESET} - {self.bookPath.name} - {result.returncode} retrying with aac encoder")
result = subprocess.run(
cmd_backup,
capture_output=True,
text=True,
)
logger.info(result.stdout)
if result.returncode == 0:
output_file_tmp.rename(output_file_final)
for media in self.files:
media.unlink()
self.files = Path(output_file_final)
self.firstFile = Path(output_file_final)
self.fileConunt = 1
self.singleFile = True
self.size = Path(output_file_final).stat().st_size
logger.info(f"{GREEN}[SUCCESS]{RESET} - {self.bookPath.name} - delete media files")
else:
logger.error(
f"{RED}[ERROR]{RESET} - {self.bookPath.name} - Code: {result.returncode}, Stderr: {result.stderr}")
def get_duration_ms(self,media: Path) -> int:
duration = subprocess.check_output(
[
"ffprobe",
"-v",
"error",
"-show_entries",
"format=duration",
"-of",
"default=noprint_wrappers=1:nokey=1",
str(media),
],
text=True,
).strip()
return int(float(duration) * 1000)
class AudiobookLibrary:
def __init__(self, libraryPath,logfilename: str="audiobook_library.log"):
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
handlers=[
logging.FileHandler(logfilename),
logging.StreamHandler()
]
)
logger.info('Started')
self.libraryPath = Path(libraryPath)
self.audiobooks = []
self._by_path = {}
self._by_name = defaultdict(list)
self._by_artist = defaultdict(list)
self._by_composer = defaultdict(list)
self.loadAudiobooksFromDir()
self.size = sum( audiobook.size for audiobook in self.audiobooks ) # size in bytes
def add(self, book: Audiobook):
self.audiobooks.append(book)
self._by_path[book.bookPath] = book
self._by_name[book.name].append(book)
if book.artist:
self._by_artist[book.artist].append(book)
if book.composer:
self._by_composer[book.composer].append(book)
def printName(self, books):
if not isinstance(books, list):
books = [books]
for book in books:
if book.name:
logger.info(book.name)
else:
logger.info(f"No name in {book.bookPath.name}")
def get_by_path(self, path):
return self._by_path.get(Path(path))
def get_by_name(self, name):
return self._by_name.get(name, [])
def get_by_artist(self, artist):
return self._by_artist.get(artist, [])
def get_by_composer(self, composer):
return self._by_artist.get(composer, [])
def find_name_contains(self, text):
text = text.lower()
return [
book
for book in self.audiobooks
if text in book.name.lower()
]
def find_artist_contains(self, text):
text = text.lower()
return [
book
for book in self.audiobooks
if book.artist and text in book.artist.lower()
]
def loadAudiobooksFromDir(self) -> tuple[list[Any], int]:
"""
A book directory is any directory containing more than one
audiobook file (.mp3, .m4a, .m4b)
"""
media_files = []
for ext in audio_extensions:
media_files.extend(list(self.libraryPath.rglob( ext)) )
dirs = {file.parent for file in media_files}
for directory in dirs:
self.add(Audiobook(directory))
def loadMetadata(self):
for audiobook in self.audiobooks:
audiobook.loadMetadata()
def fixComposers(self):
for book in self.audiobooks:
book.fixComposer()
def fixNames(self):
for book in self.audiobooks:
book.fixName()
def fixComments(self):
for book in self.audiobooks:
if len(book.comment) < 40:
logger.info(f"Remove short comment: {book.comment} of {book.name}")
book.comment = ""
def ffmpeg_escape(path: Path) -> str:
return str(path.resolve()).replace("'", r"'\''")
def main():
parser = argparse.ArgumentParser(
description="Audiobook Directory Processing"
)
parser.add_argument(
"library",
type=Path,
help="Library path"
)
args = parser.parse_args()
library_dir = args.library
library = AudiobookLibrary(library_dir)
library.fixComposers()
library.fixNames()
library.fixComments()
library.printName(library.get_by_artist("Astrid Lindgren"))
library.printName(library.find_artist_contains("Astrid Lindgren"))
library.printName(library.get_by_name("Pippi Langstrumpf"))
library.printName(library.find_name_contains("Pippi Langstrumpf"))
library.printName(library.get_by_composer("Heike Maklatsch"))
# library.printName(library.get_by_path("/home/lucas/Nextcloud/Audiobooks/Neu/Astrid Lindgren/Junker Nils von Eka (Hoerfest)"))
for audiobook in library.audiobooks:
first_file = audio_files[0]
artist = get_metadata("artist", first_file)
album = get_metadata("album", first_file)
composer = get_metadata("composer", first_file)
comment = get_metadata("comment", first_file)
print("====≠===============================")
print(f"Folder: {library_dir.name}")
print(f"Album: {album}")
print(f"Artist: {artist}")
print(f"Narrator: {composer}")
print(f"Comment: {comment}")
if __name__ == "__main__":
main()
-27
View File
@@ -1,27 +0,0 @@
#!/usr/bin/env bash
ROOT="${1:-.}"
find "$ROOT" -type d | while read -r dir; do
first_file=$(
find "$dir" -maxdepth 1 -type f \
\( -iname "*.m4b" -o -iname "*.m4a" -o -iname "*.mp3" \
-o -iname "*.flac" -o -iname "*.ogg" \) |
sort |
head -n1
)
[[ -z "$first_file" ]] && continue
artist=$(kid3-cli -c "get artist" "$first_file" 2>/dev/null)
album=$(kid3-cli -c "get album" "$first_file" 2>/dev/null)
composer=$(kid3-cli -c "get composer" "$first_file" 2>/dev/null)
comment=$(kid3-cli -c "get comment" "$first_file" 2>/dev/null)
echo "====≠==============================="
echo "Folder: $(basename "$dir")"
echo "Album: $album"
echo "Artist: $artist"
echo "Narrator: $composer"
echo "Comment: $comment"
done
@@ -27,7 +27,7 @@ fi
list="$1"
while IFS= read -r file; do
book
[[ -z "$file" ]] && continue
if [[ ! -e "$file" ]]; then
@@ -16,4 +16,10 @@ services:
- /data/gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- default
- shared-proxy-services
networks:
default:
shared-proxy-services:
external: true
@@ -9,6 +9,11 @@ services:
TZ: Europe/Vienna
volumes:
- /srv/homeassistant:/config
devices:
- /dev/bus/usb:/dev/bus/usb
cap_add:
- NET_ADMIN
- NET_RAW
mosquitto:
container_name: mosquitto
@@ -0,0 +1,81 @@
#
# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose
#
# Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
# extends:
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
volumes:
# Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
- ${UPLOAD_LOCATION}:/data
- /data/immich/pictures:/libary
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- '2283:2283'
depends_on:
- database
restart: always
healthcheck:
disable: false
networks:
- immich
- shared-redis-services
immich-machine-learning:
container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends: # uncomment this section for hardware acceleration - see https://docs.immich.app/features/ml-hardware-acceleration
# file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
volumes:
- model-cache:/cache
env_file:
- .env
restart: always
healthcheck:
disable: false
networks:
- immich
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
# Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs
# DB_STORAGE_TYPE: 'HDD'
volumes:
# Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
shm_size: 128mb
restart: always
healthcheck:
disable: false
networks:
- immich
volumes:
model-cache:
networks:
immich:
shared-redis-services:
external: true
@@ -15,7 +15,6 @@ services:
networks:
- default
- shared-proxy-services
seerr:
image: ghcr.io/seerr-team/seerr:latest
container_name: seerr
@@ -29,6 +28,7 @@ services:
networks:
- default
- shared-proxy-services
networks:
default:
external: true
@@ -1,22 +1,5 @@
services:
sonarr-en:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr-en
restart: unless-stopped
ports:
- "8989:8989"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Berlin
volumes:
- ./sonarr_EN:/config
- /data/jellyfin/media/series-english:/JellyfinSeriesEnglish
- /data/jellyfin/media/anime-english:/JellyfinAnimeEnglish
- /data/jellyfin/media/documentaries-english:/JellyfinDocus
- /data/jellyfin/buffer:/buffer
- /torrent/qbittorrent/downloads:/downloads
sonarr-de:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr-de
@@ -31,10 +14,13 @@ services:
- ./sonarr_DE:/config
- /data/jellyfin/media/series-german:/JellyfinSeriesGerman
- /data/jellyfin/media/anime-german:/JellyfinAnimeGerman
- /data/jellyfin/media/series-english:/JellyfinSeriesEnglish
- /data/jellyfin/media/anime-english:/JellyfinAnimeEnglish
- /data/jellyfin/media/documentaries-english:/JellyfinDocus
- /data/jellyfin/media/series-kids:/JellyfinKinder
- /data/jellyfin/buffer:/buffer
- /torrent/qbittorrent/downloads:/downloads
- /torrent/rundfunkarr:/downloads-rundfunkarr
- /torrent/rundfunkarr-de:/downloads-rundfunkarr
radarr-en:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr-en
@@ -67,7 +53,7 @@ services:
- /data/jellyfin/media/movies-kids:/JellyfinKinder
- /data/jellyfin/buffer:/buffer
- /torrent/qbittorrent/downloads:/downloads
- /torrent/rundfunkarr:/downloads-rundfunkarr
- /torrent/rundfunkarr-de:/downloads-rundfunkarr
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
@@ -177,7 +163,7 @@ services:
- TZ=Europe/Berlin
volumes:
- ./lidarr:/config
- /data/navidrome/music:/music
- /data/navidrome/music/music:/music
- /torrent/qbittorrent/downloads:/downloads
ports:
@@ -261,14 +247,43 @@ services:
- /data/jellyfin/media/comedy-english:/data/comedy-english:ro
- /data/jellyfin/media/movies-german:/data/movies-german:ro
- /data/jellyfin/media/movies-kids:/data/movies-kids:ro
# - /data/audiobookshelf/podcasts:/podcasts
# - /data/audiobookshelf/audiobooks-englisch:/audiobooks
- /data/navidrome/music:/data/music:ro
- /data/audiobookshelf/data/audiobooks-englisch:/data/audiobooks-english:ro
- /data/audiobookshelf/data/audiobooks-deutsch:/data/audiobooks-german:ro
- /data/audiobookshelf/data/audiobooks-kinder:/data/audiobooks-kinder:ro
# Download location
- /torrent/qbittorrent/downloads:/downloads
jackett:
image: lscr.io/linuxserver/jackett:latest
container_name: jackett
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Vienna
volumes:
- ./jackett/config:/config
- ./jackett/downloads:/downloads
ports:
- "9117:9117"
restart: unless-stopped
gluetun-proxy:
image: qmcgaw/gluetun:latest
container_name: gluetun-proxy
cap_add:
- NET_ADMIN
ports:
- "8889:8888" # HTTP proxy
- "8388:8388" # SOCKS5 proxy
volumes:
- ./gluetun-proxy:/gluetun/wireguard
- /dev/net/tun:/dev/net/tun
environment:
- VPN_SERVICE_PROVIDER=custom
- VPN_TYPE=wireguard
- SOCKS5_ENABLED=on
- HTTPPROXY=on
- SOCKSPROXY=on
restart: unless-stopped
networks:
default:
@@ -0,0 +1,11 @@
#
# Use this configuration with WireGuard client
#
[Interface]
Address = 10.14.0.2/16
PrivateKey = mKeDQdxtM1iFJS7WLlb2jPGabZIt3NkoA/nGCcNOU1I=
DNS = 162.252.172.57, 149.154.159.92
[Peer]
PublicKey = dPZe8Jq3Hu0k07MDk+Y4+AS2XHSLYalyg91TSFXRYEA=
AllowedIPs = 0.0.0.0/0
Endpoint = 87.249.133.4:51820
@@ -0,0 +1,8 @@
modules:
http_2xx:
prober: http
timeout: 5s
http:
valid_status_codes: []
method: GET
preferred_ip_protocol: ip4
@@ -8,6 +8,9 @@ services:
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
networks:
- default
- shared-proxy-services
grafana:
image: grafana/grafana:latest
@@ -47,9 +50,9 @@ services:
volumes:
- '/:/host:ro,rslave'
- ./node_exporter/textfile_collector:/textfile:ro
ports:
- '9100:9100'
# ports:
# - '9100:9100'
network_mode: host
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
@@ -58,9 +61,24 @@ services:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /data:/data:ro
- /docker:/docker:ro
# - /data:/data:ro
# - /docker:/docker:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
command:
- "--disable_metrics=percpu,tcp,udp,process,sched"
- "--housekeeping_interval=30s"
- "--housekeeping_interval=1m"
- "--docker_only=true"
blackbox-exporter:
image: prom/blackbox-exporter:latest
container_name: blackbox-exporter
restart: unless-stopped
ports:
- "9115:9115"
volumes:
- ./blackbox.yml:/etc/blackbox_exporter/config.yml:ro
volumes:
prometheus-data:
grafana-data:
@@ -1,6 +1,6 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_interval: 30s
evaluation_interval: 30s
scrape_configs:
@@ -8,6 +8,35 @@ scrape_configs:
static_configs:
- targets:
- "prometheus:9090"
- job_name: "nginx"
static_configs:
- targets:
- nginx-exporter:9113
- job_name: blackbox_https
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://cloud.someoneslinux.selfhost.eu
- https://media.someoneslinux.selfhost.eu
- https://audiobooks.someoneslinux.selfhost.eu
# - https://login.someoneslinux.selfhost.eu
- https://auto.someoneslinux.selfhost.eu
- https://music.someoneslinux.selfhost.eu
- https://passwd.someoneslinux.selfhost.eu
- https://requests.someoneslinux.selfhost.eu
- https://torrent.someoneslinux.selfhost.eu
- https://status.someoneslinux.selfhost.eu
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
- job_name: "node-exporter"
static_configs:
@@ -16,20 +45,33 @@ scrape_configs:
labels:
host: serverhost
- targets:
- "node-exporter:9100"
- "192.168.0.4:9100"
labels:
host: "mediahost"
- targets:
- "192.168.0.39:9100"
labels:
host: "quiethost"
- job_name: "cadvisor"
static_configs:
- targets:
- "cadvisor:8080"
labels:
host: mediahost
- targets:
- "192.168.0.2:8081"
labels:
host: serverhost
- job_name: "smartctl"
static_configs:
- targets:
- "192.168.0.2:9633"
labels:
host: serverhost
- targets:
- "192.168.0.39:9633"
labels:
host: "quiethost"
- targets:
- "192.168.0.4:9633"
labels:
@@ -23,6 +23,7 @@ services:
- /data/navidrome/music/kamerun:/kamerun:ro
- /data/navidrome/music/weihnachten:/weihnachten:ro
- /data/navidrome/music/playlists:/playlists:ro
- /data/navidrome/music/playlists-export:/playlists-export
networks:
- shared-proxy-services
- default
@@ -17,7 +17,8 @@ server {
passwd.someoneslinux.selfhost.eu
requests.someoneslinux.selfhost.eu
torrent.someoneslinux.selfhost.eu
status.someoneslinux.selfhost.eu;
status.someoneslinux.selfhost.eu
code.someoneslinux.selfhost.eu;
#ssl_conf_command Groups X25519:secp256r1:secp384r1;
# ssl_conf_command Groups X25519:prime256v1:secp384r1;
location /.well-known/acme-challenge/ {
@@ -27,12 +28,28 @@ server {
location / {
return 301 https://$host$request_uri;
}
listen 8080;
location /nginx_status {
stub_status;
allow 172.16.0.0/12;
deny all;
}
}
# Common SSL settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
server {
listen 443 ssl default_server;
server_name _;
ssl_certificate /etc/nginx/certs/live/cloud.someoneslinux.selfhost.eu/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/cloud.someoneslinux.selfhost.eu/privkey.pem;
return 404;
}
###################
# NEXTCLOUD
@@ -51,6 +68,8 @@ server {
location / {
proxy_pass http://nextcloud:80;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -76,6 +95,8 @@ server {
location / {
proxy_pass http://jellyfin:8096;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_http_version 1.1;
proxy_set_header Host $host;
@@ -103,6 +124,8 @@ server {
location / {
proxy_pass http://audiobookshelf:80;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_http_version 1.1;
proxy_set_header Host $host;
@@ -112,40 +135,14 @@ server {
}
}
###################
# AUTHENTIK
###################
server {
listen 443 ssl;http2 on;
server_name login.someoneslinux.selfhost.eu;
add_header Content-Security-Policy "frame-ancestors 'self';" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_certificate /etc/nginx/certs/live/cloud.someoneslinux.selfhost.eu/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/cloud.someoneslinux.selfhost.eu/privkey.pem;
location / {
proxy_pass http://authentik-server-1:9000;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
####################
## HOME ASSISTANT
## AUTHENTIK
####################
#server {
# listen 443 ssl;http2 on;
# server_name auto.someoneslinux.selfhost.eu;
# server_name login.someoneslinux.selfhost.eu;
#
# add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'self';" always;
# add_header Content-Security-Policy "frame-ancestors 'self';" always;
# add_header X-Frame-Options "SAMEORIGIN" always;
# add_header X-Content-Type-Options "nosniff" always;
# add_header Referrer-Policy "strict-origin-when-cross-origin" always;
@@ -155,17 +152,45 @@ server {
# ssl_certificate_key /etc/nginx/certs/live/cloud.someoneslinux.selfhost.eu/privkey.pem;
#
# location / {
# proxy_pass http://192.168.0.4:8123;
# proxy_pass http://authentik-server-1:9000;
# proxy_http_version 1.1;
#
# proxy_set_header Host $host;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection $connection_upgrade;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#
# proxy_set_header X-Forwarded-Proto https;
# proxy_set_header X-Forwarded-Host $host;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# }
#}
###################
# HOME ASSISTANT
###################
server {
listen 443 ssl;http2 on;
server_name auto.someoneslinux.selfhost.eu;
# add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'self';" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_certificate /etc/nginx/certs/live/cloud.someoneslinux.selfhost.eu/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/cloud.someoneslinux.selfhost.eu/privkey.pem;
location / {
proxy_pass http://192.168.0.4:8123;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
###################
# NAVIDROME
###################
@@ -184,6 +209,8 @@ server {
location / {
proxy_pass http://navidrome:4533;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
@@ -208,6 +235,8 @@ server {
location / {
proxy_pass http://vaultwarden:80;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
@@ -216,6 +245,8 @@ server {
location /notifications/hub {
proxy_pass http://vaultwarden:3012;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
@@ -240,6 +271,8 @@ server {
location / {
proxy_pass http://seerr:5055;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_http_version 1.1;
proxy_set_header Host $host;
@@ -268,6 +301,8 @@ server {
location / {
proxy_pass http://192.168.0.2:8080;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_http_version 1.1;
proxy_set_header Host $host;
@@ -275,6 +310,7 @@ server {
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-Proto https;
}
}
###################
@@ -294,6 +330,8 @@ server {
location / {
proxy_pass http://grafana:3000;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_http_version 1.1;
proxy_set_header Host $host;
@@ -302,3 +340,33 @@ server {
proxy_set_header X-Forwarded-Proto https;
}
}
###################
# GITEA
###################
server {
listen 443 ssl;http2 on;
server_name code.someoneslinux.selfhost.eu;
add_header Content-Security-Policy "frame-ancestors 'self';" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_certificate /etc/nginx/certs/live/cloud.someoneslinux.selfhost.eu/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/cloud.someoneslinux.selfhost.eu/privkey.pem;
location / {
proxy_pass http://gitea:3000;
# Do not try another upstream on errors
proxy_next_upstream off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
@@ -3,11 +3,9 @@ services:
image: nginx:stable
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./conf.d:/etc/nginx/conf.d
- ./certs:/etc/nginx/certs
@@ -15,8 +13,23 @@ services:
# Access and error logs
- ./logs:/var/log/nginx
networks:
- default
- shared-proxy-services
nginx-exporter:
image: nginx/nginx-prometheus-exporter:latest
container_name: nginx-exporter
restart: unless-stopped
command:
- --nginx.scrape-uri=http://nginx:8080/nginx_status
ports:
- "9113:9113"
depends_on:
- nginx
networks:
- default
- shared-proxy-services
crowdsec:
image: crowdsecurity/crowdsec:latest
container_name: crowdsec
@@ -29,7 +42,8 @@ services:
- crowdsec-db:/var/lib/crowdsec/data
- ./logs:/var/log/nginx:ro
- /etc/localtime:/etc/localtime:ro
networks:
- default
certbot:
image: certbot/certbot:latest
@@ -41,7 +55,9 @@ services:
- shared-proxy-services
networks:
default:
shared-proxy-services:
external: true
volumes:
crowdsec-db:
@@ -3,7 +3,7 @@ services:
image: redis:7
container_name: redis
restart: unless-stopped
command: redis-server --appendonly yes --requirepass PerCent7heStorySp0tInOrd3r
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
healthcheck:
test:
@@ -10,6 +10,19 @@ services:
volumes:
- '/:/host:ro,rslave'
- ./node_exporter/textfile_collector:/textfile:ro
# ports:
# - '9100:9100'
network_mode: host
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
ports:
- '9100:9100'
- "8081:8080"
volumes:
- /:/rootfs:ro
- /data:/data:ro
- /docker:/docker:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
@@ -72,8 +72,8 @@ fi
TORRENT_NAME="$1"
ROOT_PATH="$2"
CATEGORY="$3"
if [[ "$CATEGORY" != *arr* && "$CATEGORY" != "mytorrents" ]]; then
if [[ "$CATEGORY" != *arr* ]]; then
###############################################################################
# Start
###############################################################################
@@ -155,7 +155,7 @@ if [[ "$CATEGORY" != *arr* ]]; then
lucas@mediahost "mkdir -p \"/torrent/qbittorrent/mediahost/$remote_dir\""
SSH_STATUS=$?
scp -i /config/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-r "$ROOT_PATH"/. lucas@mediahost:/torrent/qbittorrent/mediahost/
-r "$ROOT_PATH"/. lucas@mediahost:/torrent/qbittorrent/mediahost/$remote_dir/
SCP_STATUS=$?
echo "scp exit status: $SCP_STATUS"
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
import re
import sys
from pathlib import Path
from difflib import SequenceMatcher
def normalize(name):
name = Path(name).stem.lower()
# remove brackets
name = re.sub(r"\(.*?\)|\[.*?\]", "", name)
# replace punctuation with spaces
name = re.sub(r"[^a-z0-9]", " ", name)
# collapse whitespace
name = " ".join(name.split())
return name
def similarity(a, b):
words_a = set(normalize(a).split())
words_b = set(normalize(b).split())
if not words_a or not words_b:
return 0.0
# Jaccard similarity
intersection = words_a & words_b
union = words_a | words_b
return len(intersection) / len(union)
def load_playlist(path):
entries = []
with open(path, encoding="utf-8", errors="ignore") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
entries.append(Path(line).name)
return entries
def compare_playlists(orig_file, new_file, threshold=0.85):
orig_files = load_playlist(orig_file)
new_files = load_playlist(new_file)
print(f"\n=== {orig_file.parent} ===")
print(f"Comparing:")
print(f" {orig_file.name}")
print(f" {new_file.name}")
missing = []
for orig in orig_files:
best_match = None
best_score = 0
for candidate in new_files:
score = similarity(orig, candidate)
if score > best_score:
best_score = score
best_match = candidate
if best_score >= threshold:
pass
# print(
# f"MATCH {best_score:.1%} "
# f"{orig} -> {best_match}"
# )
else:
missing.append((orig, best_score, best_match))
if missing:
print("\nMISSING:")
for orig, score, best_match in sorted(
missing,
key=lambda x: x[1],
reverse=True,
):
print(
f" {orig}"
f" (best={score:.1%}"
f"{', candidate=' + best_match if best_match else ''})"
)
return len(missing)
def compare_directory(root, threshold=0.85):
total_missing = 0
playlist_pairs = 0
for orig_file in root.rglob("*_orig.m3u"):
new_file = orig_file.with_name(
orig_file.name.replace("_orig.m3u", ".m3u")
)
if not new_file.exists():
print(
f"\nWARNING: Missing playlist for "
f"{orig_file}"
)
continue
playlist_pairs += 1
total_missing += compare_playlists(
orig_file,
new_file,
threshold,
)
print("\n" + "=" * 80)
print(f"Playlists compared : {playlist_pairs}")
print(f"Total missing : {total_missing}")
def main():
args = sys.argv[1:]
threshold = 0.85
if len(args) == 3 and args[0] == "--threshold":
threshold = float(args[1])
args = [args[2]]
elif len(args) == 4 and args[0] == "--threshold":
threshold = float(args[1])
args = [args[2], args[3]]
if len(args) == 1:
path = Path(args[0])
if not path.is_dir():
print("Single argument must be a directory")
sys.exit(1)
compare_directory(path, threshold)
elif len(args) == 2:
compare_playlists(
Path(args[0]),
Path(args[1]),
threshold,
)
else:
print(
f"Usage:\n"
f" {Path(sys.argv[0]).name} "
f"<playlist_orig.m3u> <playlist.m3u>\n\n"
f" {Path(sys.argv[0]).name} "
f"<directory>\n\n"
f"Optional:\n"
f" --threshold 0.90"
)
sys.exit(1)
if __name__ == "__main__":
main()
+8 -5
View File
@@ -167,19 +167,22 @@ process_file() {
HOSTNAME_EXPECTED="mediahost"
SOURCE_DIR="/data/nextcloud/syncuser/alina/Dropfolder"
TARGET_DIR="/data/nextcloud/syncuser/camera"
SOURCE_DIR="/data/nextcloud/syncuser/pictures/Alina Share/Dropfolder"
TARGET_DIR="/data/nextcloud/syncuser/pictures/Camera"
# If running on serverhost, perform initial sync
if [[ "$(hostname)" == "$HOSTNAME_EXPECTED" ]]; then
syncNC -d -c
echo Sync Nextcloud
syncNC -c
cd "$SOURCE_DIR" || exit 1
fi
if [[ $# -gt 0 ]]; then
echo Process single file $1
process_file "$1"
else
echo Process dir .
shopt -s nullglob
files=( *.heic *.mov )
@@ -202,7 +205,7 @@ if [[ "$(hostname)" == "$HOSTNAME_EXPECTED" ]]; then
fi
done
shopt -u nullglob
syncNC -d -c
echo Sync Nextcloud
syncNC -c
fi
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
OUT="/docker/monitoring/node_exporter/textfile_collector/smart_selftest.prom"
> "$OUT"
for dev in $(smartctl --scan-open | awk '{print $1}'); do
name=$(basename "$dev")
LAST=0
if [[ "$dev" == *nvme* ]]; then
# POWER_ON=$(smartctl -a "$dev" | awk -F': *' '/Power On Hours:/ {print $2}')
LAST=$(smartctl -l selftest "$dev" 2>/dev/null \
| grep "Completed" \
| head -1 \
| awk '{print $(NF-5)}')
elif [[ "$dev" == *sd* ]]; then
# POWER_ON=$(smartctl -a "$dev" | awk '/Power_On_Hours/ {print $10}')
LAST=$(smartctl -l selftest "$dev" 2>/dev/null \
| grep "Completed" \
| head -1 \
| awk '{print $(NF-1)}')
else
POWER_ON=99999999999
echo $dev
fi
# Skip devices without self-test history
echo $name: ${LAST}
echo "smart_last_selftest_poweron_hours{device=\"$name\"} $LAST" >> "$OUT"
done
+7 -1
View File
@@ -2,7 +2,13 @@
OUT=/docker/monitoring/node_exporter/textfile_collector/custom.prom
TS=$(date +%s%3N)
DOCKER_CONTAINER_NUM=44
if [[ "$(hostname)" == "mediahost" ]]; then
DOCKER_CONTAINER_NUM=44
elif [[ "$(hostname)" == "serverhost" ]]; then
DOCKER_CONTAINER_NUM=4
elif [[ "$(hostname)" == "quiethost" ]]; then
DOCKER_CONTAINER_NUM=1
fi
{
# Docker container states
docker ps -a --format '{{.Names}}' | while read -r name; do
+2
View File
@@ -2,6 +2,8 @@
device=$1
if [ "$device" == "serverhost" ]; then
mac="64:51:06:51:cb:39"
elif [ "$device" == "mediahost" ]; then
mac="9c:6b:00:a3:36:c7"
elif [ "$device" == "quiethost" ]; then
mac="24:4B:FE:DE:93:3E"
elif [ "$(echo ${device} | grep -E -o "([0-9A-Fa-f]{2}[\:]){5}[0-9A-Fa-f]{2}")" != "" ]; then
+1 -18
View File
@@ -25,7 +25,6 @@ SYNC_MEMES=1
SYNC_AUDIOBOOKS=1
SYNC_RECIPIES=1
SYNC_ALINA=1
SYNC_ALINA_DROP=1
SYNC_WALLPAPER=1
SYNC_PICTURES=1
SYNC_SYNCFOLDER=1
@@ -39,13 +38,12 @@ Synchronize configured directories with Nextcloud.
Options:
-h, --help Show this help message and exit
-l Show synchronization output
-d Enable Alina Share synchronization
-c Enable Pictures synchronization
-w Enable Wallpaper synchronization
-m Enable Memes synchronization
-s Enable Syncfolder synchronization
-ab Enable Audiobook synchronization
-al Enable Alina synchronization
-al Enable Alina Documents synchronization
-all Enable all synchronization operations
-del Remove the Nextcloud sync marker
--version Show script version
@@ -109,11 +107,6 @@ parse_args() {
-w)
SYNC_WALLPAPER=0
;;
-d)
SYNC_ALINA_DROP=0
;;
-s)
SYNC_SYNCFOLDER=0
;;
@@ -139,7 +132,6 @@ parse_args() {
SYNC_RECIPIES=0
SYNC_ALINA=0
SYNC_MEMES=0
SYNC_ALINA_DROP=0
SYNC_WALLPAPER=0
SYNC_PICTURES=0
;;
@@ -172,7 +164,6 @@ validate_sync_options() {
SYNC_ALINA == 1 &&
SYNC_MEMES == 1 &&
SYNC_SYNCFOLDER == 1 &&
SYNC_ALINA_DROP == 1 &&
SYNC_WALLPAPER == 1 &&
SYNC_PICTURES == 1 )); then
@@ -202,14 +193,6 @@ sync_all() {
"/data/nextcloud/syncuser/Syncfolder"
(( ret += $? ))
fi
if (( SYNC_ALINA_DROP == 0 )); then
sync_directory \
"Alina Share" \
"Alina Share/" \
"/data/nextcloud/syncuser/alina"
(( ret += $? ))
fi
if (( SYNC_MEMES == 0 )); then
sync_directory \
"Memes" \
+5 -53
View File
@@ -1,24 +1,6 @@
HOST=$(hostname)
if [ "${HOST}" == "quiethost" ]; then
DOC_DIR="/home/lucas/Documents"
UNSYNCED="SW"
elif [ "${HOST}" == "vivohost" ];then
UNSYNCED="Music\nSW\nPictures\nDokumente/Studium\nDokumente/Auslandsdienst"
DOC_DIR="/home/lucas/Documents"
rasp_host="someoneslinux.ddns.net"
elif [ "${HOST}" != "raspberry" ]; then
UNSYNCED="Music\nSW\nPictures "
DOC_DIR="/home/lucas/Documents"
fi
if [ "${HOST}" == "raspberry" ]; then
LOCAL_SCRIPTS="/home/lucas/Raspberry/scripts"
DOC_DIR="/home/lucas/Documents"
else
LOCAL_SCRIPTS="/home/lucas/scripts"
rasp_host=raspberry
fi
NC_SCRIPTS="$DOC_DIR/Dokumente/Projekte/Server/scripts"
LOCAL_SCRIPTS="/home/lucas/scripts"
alias please="sudo"
alias NC_scripts="cd ${NC_SCRIPTS}"
alias regex_rename="${LOCAL_SCRIPTS}/rename.sh"
@@ -35,6 +17,10 @@ alias pubip="wget -T 5 -qO- ifconfig.co"
alias pubip_country="wget -T 5 -qO- ifconfig.co/country"
alias ipinfo='${LOCAL_SCRIPTS}/ip_info.sh'
alias pygen="/home/lucas/scripts/.venv/bin/python3"
alias mediawake="bash ${LOCAL_SCRIPTS}/network/wol.sh mediahost -c"
alias serverwake="bash ${LOCAL_SCRIPTS}/network/wol.sh serverhost -c"
alias quietwake="bash ${LOCAL_SCRIPTS}/network/wol.sh quiethost"
if [[ "${HOST}" == "quiethost" ]] || [[ "${HOST}" == "serverhost" ]]; then
### Veracrypt aliases
#Passwd Mount and Grep
@@ -52,40 +38,6 @@ if [[ "${HOST}" == "quiethost" ]] || [[ "${HOST}" == "serverhost" ]]; then
alias recipeAI="~/.venv_general/bin/python3 ~/scripts/quiethost/generateNutritionInfowithGemini.py /data/Nextcloud/Documents/Dokumente/Shared/Diverses/Kochbuch/RecepiesLucas/"
alias ffmepeg_handling="~/.venv_general/bin/python3 ~/scripts/ffmpeg_handling.py"
fi
if [ "${HOST}" != "raspberry" ]; then
# Shutdown
alias alldown='ssh ${rasp_host} hostdown all -c'
alias serverdown='ssh ${rasp_host} hostdown serverhost -c'
alias delldown='ssh ${rasp_host} hostdown quiethost -c'
# Wake
alias serverwake="ssh ${rasp_host} wake serverhost"
alias quietwake="bash ${LOCAL_SCRIPTS}/network/wol.sh quiethost"
# SSH
alias rasp="ssh ${rasp_host}"
alias raspcopy='bash ${LOCAL_SCRIPTS}/copy.sh -h ${rasp_host} -f '
#NW Stat
alias NWscan="ssh ${rasp_host} hoststatus netw -c"
alias hoststat="ssh ${rasp_host} hoststatus hosts -c"
elif [ "${HOST}" != "vivohost" ];then
# Raspberry Shutdown
alias serverdown='hostdown serverhost -c'
alias delldown='hostdown quiethost -c'
alias alldown='hostdown all -c'
alias pcdown='hostdown pc -c'
# Raspberry Wake
alias serverwake='wake serverhost'
alias quietwake='wake quiethost'
# Raspberry Hoststatus
alias hoststat="hoststatus hosts -c"
alias NWscan="hoststatus netw -c"
# Raspberry Network Settings
alias IFchange="sudo bash ${LOCAL_SCRIPTS}/IFchange.sh"
alias IPsettings="sudo bash /home/lucas/Raspberry/scripts/iptables.sh"
alias IPtables-save='sudo sh -c '\''iptables-save > /etc/network/iptables'\'''
# Raspberry Owncloud
alias syncNC="nextcloudcmd --path Dokumente/Dokumente/Projekte/Server /home/lucas/Documents/Dokumente/Projekte/Server https://lucas:S1mpleRe1e4sedP1ayingDesign@someoneslinux.ddns.net"
fi
if [ "${HOST}" != "serverhost" ]; then
alias serverhost="ssh -X serverhost"
alias lstorr="ssh serverhost du -a -d1 -h /torrent/complete/ | sed 's/\/torrent\/complete\///'g | head -n -1"
+5
View File
@@ -0,0 +1,5 @@
192.168.0.2 serverhost
192.168.0.39 quiethost
192.168.0.4 mediahost
192.168.0.81 vivohost
#192.168.0.122 homeassistant
+84
View File
@@ -0,0 +1,84 @@
## /etc/smartd.conf
# ============================================================================
# Common options:
#
# -a Enable all standard SMART monitoring:
# * SMART health status
# * Error logs
# * Self-test logs
# * Attribute monitoring
#
# -o on Enable automatic offline data collection
#
# -S on Enable SMART attribute autosave
#
# -s (...) Schedule SMART self-tests
#
# Test schedule syntax:
# S = Short self-test
# L = Long/Extended self-test
#
# Fields:
# (TYPE/MM/DD/DW/HH)
#
# MM = month (01-12 or .. for every month)
# DD = day of month (01-31 or .. for every day)
# DW = day of week (1-7, where 7=Sunday)
# HH = hour (00-23)
#
# Schedule used below:
# S/../../7/02
# → Run SHORT test every Sunday at 02:00
#
# L/../01/./03
# → Run LONG test on the 1st day of every month at 03:00
#
# ============================================================================
# SERVERHOST
# ============================================================================
# # WD Red 3 TB Backup Drive
#/dev/sda -a -o on -S on -s (S/../../7/02|L/../01/./03)
# Samsung/Seagate 2 TB Laptop Drive
# /dev/sdb -a -o on -S on -s (S/../../7/02|L/../01/./03)
# ============================================================================
# MEDIAHOST
# ============================================================================
#
## WD Gold 16 TB
#/dev/sda -a -o on -S on -s (S/../../7/02|L/../01/./03)
#
## WD Red Pro 16 TB
#/dev/sdb -a -o on -S on -s (S/../../7/02|L/../01/./03)
#
## WD Red Pro 16 TB
#/dev/sdc -a -o on -S on -s (S/../../7/02|L/../01/./03)
#
## Seagate Exos 8 TB
#/dev/sdd -a -o on -S on -s (S/../../7/02|L/../01/./03)
#
## WD Blue SN5100 NVMe
## NVMe drives support SMART differently, so -o and -S are not needed.
#/dev/nvme0n1 -a -s (S/../../7/02|L/../01/./03)
#
# ============================================================================
# QUIETHOST
# ============================================================================
#
## Seagate IronWolf 8TB
## /dev/sda
#/dev/sda -a -o on -S on -s (S/../../7/02|L/../01/./03)
#
## Intenso 250GB NVMe
## /dev/nvme0n1
## NVMe devices do not use ATA offline data collection,
## therefore -o on and -S on are omitted.
#/dev/nvme0n1 -a -s (S/../../7/02|L/../01/./03)
#
## Crucial P1 1TB NVMe
## /dev/nvme1n1
#/dev/nvme1n1 -a -s (S/../../7/02|L/../01/./03)#
#
+15
View File
@@ -0,0 +1,15 @@
Host serverhost
HostName someoneslinux.ddns.net
User lucas
Host raspberry
HostName raspberry
User lucas
Host quiethost
HostName quiethost
User lucas
Host mediahost
HostName mediahost
User lucas
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=Prometheus Smartctl Exporter
After=network.target
[Service]
User=root
ExecStart=/opt/smartctl_exporter/smartctl_exporter
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
+4
View File
@@ -268,6 +268,10 @@ if command -v docker >/dev/null 2>&1; then
fi
)
;;
nextcloud:*)
docker pull "$image"
docker exec -it nextcloud php occ app:update --all
;;
*"<none>"*)
;;
*)