diff --git a/ai/docker-compose.yml b/ai/docker-compose.yml
index 3ba07e9..051e3a9 100644
--- a/ai/docker-compose.yml
+++ b/ai/docker-compose.yml
@@ -1,6 +1,6 @@
services:
openwebui:
- image: ghcr.io/open-webui/open-webui:0.6.23
+ image: ghcr.io/open-webui/open-webui:0.6.40
container_name: open-webui
restart: always
environment:
diff --git a/authentik/.env b/authentik/.env
index fa4fb56..639c78c 100644
--- a/authentik/.env
+++ b/authentik/.env
@@ -13,4 +13,4 @@ AUTHENTIK_EMAIL__USE_SSL=false
AUTHENTIK_EMAIL__TIMEOUT=10
# Email address authentik will send from, should have a correct @domain
AUTHENTIK_EMAIL__FROM=home@thoster.net
-
+AUTHENTIK_TAG=2025.10.2
diff --git a/backup.sh b/backup.sh
index 405d7d8..fb0944f 100755
--- a/backup.sh
+++ b/backup.sh
@@ -3,70 +3,90 @@ set -Eeuo pipefail
shopt -s inherit_errexit nullglob
# -----------------------------------------------------------------------------
-# CONFIGURATION — adjust to taste
+# CONFIGURATION
# -----------------------------------------------------------------------------
-readonly RETENTION_DAYS=7
readonly BACKUP_BASE=/media/extension/backup/ubuntu
readonly MOUNT_POINT=/media/extension
-
-# timestamp format — no spaces
-#readonly NOW=$(date +'%Y-%m-%d_%H-%M-%S')
readonly NOW=$(date +'%Y-%m-%d')
-readonly THIS_RUN=${BACKUP_BASE}/${NOW}
-readonly LATEST=${BACKUP_BASE}/latest
+readonly THIS_RUN="${BACKUP_BASE}/${NOW}"
+readonly LATEST="${BACKUP_BASE}/latest"
-# directories (or files) to back up:
+# Directories (or files) to back up (without DB volumes)
declare -a SOURCE_ITEMS=(
"/home/oster/server"
- "/home/oster/photo-lib"
+# "/home/oster/photo-lib"
)
-# rsync exclusions — add your own
+# Rsync exclusions — exclude DB-related dirs that are dumped separately + common junk
declare -a EXCLUDES=(
--exclude='.cache'
--exclude='node_modules'
--exclude='*.tmp'
+ --exclude='**/postgresql'
+ --exclude='**/mysql'
+ --exclude='**/db'
+ --exclude='**/database'
)
-# rsync niceties
+# Rsync options
RSYNC_OPTS=(
- -aAXHv # archive, preserve ACLs/xattrs, human-readable, verbose
+ -aAXHv # preserve everything, human-friendly, verbose
--delete # mirror deletions
--partial # keep partials if interrupted
- --stats # give stats at end
+ --stats # summary
"${EXCLUDES[@]}"
)
-# Dry-run? set to "--dry-run" to test without writing
-DRY_RUN=""
+# -----------------------------------------------------------------------------
+# LOGGING
+# -----------------------------------------------------------------------------
+log() { echo "[$(date +'%F %T')] $*"; }
+die() { log "FATAL: $*"; exit 1; }
-# -----------------------------------------------------------------------------
-# LOGGING & LOCKING / CLEANUP
-# -----------------------------------------------------------------------------
-log() { echo "[$(date +'%F %T')] $*"; }
-die() { log "FATAL: $*"; exit 1; }
+# Locking
lockfile="${BACKUP_BASE}/.backup.lock"
-
-#trap 'rm -rf "$lockfile"' EXIT
-#if ! touch "$lockfile" 2>/dev/null; then
-# die "Another backup is running (lockfile exists)."
-#fi
+cleanup() { rm -f "$lockfile"; }
+trap cleanup EXIT
+if ! ( set -o noclobber; echo "$$" > "$lockfile" ) 2>/dev/null; then
+ die "Another backup is already running (lockfile exists at $lockfile)."
+fi
# -----------------------------------------------------------------------------
# FUNCTIONS
# -----------------------------------------------------------------------------
check_mount() {
if ! mountpoint -q "$MOUNT_POINT"; then
- die "Backup mountpoint $MOUNT_POINT is not mounted."
+ die "Backup mountpoint $MOUNT_POINT is NOT mounted."
fi
}
+# Rotation: keep 7 daily and 4 weekly
rotate_old() {
- log "Rotating backups older than ${RETENTION_DAYS} days."
- find "$BACKUP_BASE" -maxdepth 1 -mindepth 1 -type d \
- ! -name 'latest' ! -name "$(basename "$THIS_RUN")" \
- -mtime +${RETENTION_DAYS} \
- -print -exec rm -rf {} \;
+ log "Applying retention policy (7 daily, 4 weekly)."
+ cd "$BACKUP_BASE" || die "Cannot cd into $BACKUP_BASE"
+
+ # find snapshot dirs YYYY-MM-DD only
+ for dir in $(ls -1 | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'); do
+ keep=0
+
+ # Keep last 7 daily
+ for i in $(seq 0 6); do
+ [ "$dir" = "$(date -d "-$i day" +%F)" ] && keep=1
+ done
+
+ # Keep last 4 Sundays (weeklies)
+ for i in $(seq 0 27); do
+ d=$(date -d "last sunday -$i week" +%F)
+ [ "$dir" = "$d" ] && keep=1
+ done
+
+ if [ $keep -eq 0 ]; then
+ log " Deleting expired snapshot $dir"
+ rm -rf "$dir"
+ else
+ log " Keeping snapshot $dir"
+ fi
+ done
}
# -----------------------------------------------------------------------------
@@ -75,33 +95,33 @@ rotate_old() {
log "=== Starting backup run: $NOW ==="
check_mount
-mkdir -p "$BACKUP_BASE"
-mkdir -p "$THIS_RUN"
+mkdir -p "$BACKUP_BASE" "$THIS_RUN"
-# Determine if we can do a hardlink-incremental snapshot
+# Incremental snapshot?
if [ -d "$LATEST" ]; then
- LINK_DEST="--link-dest=$(readlink -f $LATEST)"
- log "Using link‐dest for incremental snapshot → $LATEST"
+ LINK_DEST="--link-dest=$(readlink -f "$LATEST")"
+ log "Using link-dest for incremental snapshot → $LATEST"
else
LINK_DEST=""
- log "No previous backup; doing full snapshot."
+ log "No previous backup; creating full snapshot."
fi
+# Run backups
for src in "${SOURCE_ITEMS[@]}"; do
base=$(basename "$src")
dst="${THIS_RUN}/${base}"
+ mkdir -p "$dst"
log "Backing up $src → $dst"
-
nice -n 10 ionice -c2 -n7 \
- rsync "${RSYNC_OPTS[@]}" $LINK_DEST $DRY_RUN \
+ rsync "${RSYNC_OPTS[@]}" $LINK_DEST \
"$src"/ "$dst"/
done
-# update the "latest" symlink atomically
-tmp_latest="${BACKUP_BASE}/.latest.tmp"
-rm -f "$tmp_latest"
-ln -sfn "$(basename "$THIS_RUN")" "$tmp_latest"
-mv -T "$tmp_latest" "$LATEST"
+# Update latest symlink atomically
+ln -sfn "$(basename "$THIS_RUN")" "$BACKUP_BASE/.latest.tmp"
+mv -T "$BACKUP_BASE/.latest.tmp" "$LATEST"
+log "Snapshots done. Running rotation policy..."
rotate_old
+
log "=== Backup completed successfully ==="
diff --git a/hass/homeassistant/automations.yaml b/hass/homeassistant/automations.yaml
index 744faf0..1c0dc1d 100644
--- a/hass/homeassistant/automations.yaml
+++ b/hass/homeassistant/automations.yaml
@@ -141,7 +141,7 @@
- at: 01:00:00
trigger: time
- trigger: time
- at: 08:00:00
+ at: 08:30:00
conditions: []
actions:
- action: light.turn_off
@@ -194,37 +194,7 @@
kelvin: 2627
target:
device_id: 4a993938cbf7289987eb684f71b00b92
-- id: '1732736495026'
- alias: Lichter aus bei Sonnenaufgang
- description: ''
- trigger:
- - platform: time
- at: 01:00:00
- condition: []
- action:
- - action: light.turn_off
- metadata: {}
- data: {}
- target:
- device_id: 03477708398bb2e1ceb3f4a776770a29
- mode: single
-- id: '1732736623945'
- alias: Licht an bei Sonnenuntergang
- description: ''
- trigger:
- - platform: sun
- event: sunset
- offset: -00:30:00
- condition: []
- action:
- - action: light.turn_on
- metadata: {}
- data:
- kelvin: 2490
- brightness_pct: 85
- target:
- device_id: 03477708398bb2e1ceb3f4a776770a29
- mode: single
+ controller_device: 180e52c2b4e6bfd02de1a022cd77eda0
- id: '1733523441756'
alias: Küche Theke Schalter
description: ''
@@ -297,11 +267,6 @@
device_id: a09f77bde7ba5098df0c3ec599ca7377
entity_id: 2a315e13dac7a4d0e3a5331216e79a5b
domain: switch
- - action: light.turn_off
- metadata: {}
- data: {}
- target:
- device_id: aa992894922dfd99d4de351691fddbeb
mode: single
- id: '1733558486213'
alias: Küche Theke und Tisch aus
@@ -569,3 +534,54 @@
target:
entity_id: counter.bewegung_kueche
mode: single
+- id: '1761475605456'
+ alias: Bewegungsmelder Licht Treppe oben
+ description: ''
+ triggers:
+ - type: occupied
+ device_id: 4629e222cda722b7ad07de427acb56bd
+ entity_id: aae8135070bc723603a2477818b484a9
+ domain: binary_sensor
+ trigger: device
+ - type: occupied
+ device_id: d0addbba1dd8a92697554ff8a015345e
+ entity_id: 42c91c337a8f76b1559776a7daec6e84
+ domain: binary_sensor
+ trigger: device
+ conditions: []
+ actions:
+ - type: turn_on
+ device_id: 68bb158ff829c34611860fedff328e32
+ entity_id: 073c2fbfb9a0796461063e922049587e
+ domain: switch
+ - delay:
+ hours: 0
+ minutes: 10
+ seconds: 0
+ milliseconds: 0
+ - type: turn_off
+ device_id: 68bb158ff829c34611860fedff328e32
+ entity_id: 073c2fbfb9a0796461063e922049587e
+ domain: switch
+ mode: single
+- id: '1761484676480'
+ alias: Treppenlicht aus verzögert
+ description: ''
+ triggers:
+ - type: turned_on
+ device_id: 68bb158ff829c34611860fedff328e32
+ entity_id: 073c2fbfb9a0796461063e922049587e
+ domain: switch
+ trigger: device
+ conditions: []
+ actions:
+ - delay:
+ hours: 0
+ minutes: 10
+ seconds: 0
+ milliseconds: 0
+ - type: turn_off
+ device_id: 68bb158ff829c34611860fedff328e32
+ entity_id: 073c2fbfb9a0796461063e922049587e
+ domain: switch
+ mode: single
diff --git a/hass/homeassistant/configuration.yaml b/hass/homeassistant/configuration.yaml
index 4a15e72..f64c730 100644
--- a/hass/homeassistant/configuration.yaml
+++ b/hass/homeassistant/configuration.yaml
@@ -14,8 +14,8 @@ shell_command: !include shell_commands.yaml
switch:
- platform: wake_on_lan
name: "mini PC"
- host: "192.168.178.87"
- mac: "58:47:CA:78:4E:59"
+ host: "192.168.178.138"
+ mac: "9c:6b:00:8b:3c:20"
turn_off:
service: shell_command.turn_off_remote_pc
- platform: template
diff --git a/hass/zb2mqttdata/configuration.yaml b/hass/zb2mqttdata/configuration.yaml
index 394367b..d7dee26 100644
--- a/hass/zb2mqttdata/configuration.yaml
+++ b/hass/zb2mqttdata/configuration.yaml
@@ -51,8 +51,6 @@ devices:
friendly_name: Flurlicht
'0x7cb03eaa0a0176bd':
friendly_name: Küchentisch
- '0x000d6f001417c142':
- friendly_name: Fernbedienung 2
'0xa4c138e68023f4c6':
friendly_name: Wassersensor Keller
'0xa4c1389d42e94844':
@@ -65,6 +63,12 @@ devices:
friendly_name: Wasser Sensor Bad
'0x000d6f00109eb214':
friendly_name: Fernbedienung
+ '0xa4c138f8d17c3daa':
+ friendly_name: Bewegungsmelder Treppe oben
+ '0xa4c138b00278ea51':
+ friendly_name: Bewegungsmelder Treppe oben 2
+ '0x000d6f001417c142':
+ friendly_name: Fernbedienung Kühlschrank
homeassistant:
enabled: true
version: 4
diff --git a/hass/zb2mqttdata/state.json b/hass/zb2mqttdata/state.json
index 6bb8ecb..e434089 100644
--- a/hass/zb2mqttdata/state.json
+++ b/hass/zb2mqttdata/state.json
@@ -6,40 +6,40 @@
"installed_version": 51999504,
"latest_version": 51999504
},
- "brightness": 105,
- "state": "OFF",
+ "brightness": 254,
+ "state": "ON",
"color": {
"x": 0.4599,
"y": 0.4106
},
"color_temp": 370,
- "linkquality": 90,
+ "linkquality": 72,
"update_available": false
},
"0x00212effff0cb8b6": {
"color_mode": "xy",
"state": "OFF",
"state_rgb": "OFF",
- "brightness_white": 76,
- "state_white": "OFF",
+ "brightness_white": 254,
+ "state_white": "ON",
"brightness_rgb": 26,
"color_mode_rgb": "color_temp",
"color_temp_rgb": 199,
"color_mode_white": "color_temp",
- "color_temp_white": 284,
+ "color_temp_white": 365,
"color": {
"x": 0.6942,
"y": 0.2963
},
"color_white": {
- "x": 0.4042,
- "y": 0.3902
+ "x": 0.4566,
+ "y": 0.4098
},
"color_rgb": {
"x": 0.3445,
"y": 0.3512
},
- "linkquality": 75,
+ "linkquality": 129,
"state_10": "OFF"
},
"0xa4c1384187f0960d": {
@@ -51,20 +51,20 @@
"linkquality": 78
},
"0xa4c13864e6c8c400": {
- "battery": 93,
+ "battery": 89,
"voltage": 2900,
"occupancy": false,
"tamper": false,
"battery_low": false,
- "linkquality": 111
+ "linkquality": 60
},
"0xa4c138ef60e2fdbe": {
"contact": true,
"tamper": false,
"battery_low": false,
- "voltage": 2800,
- "battery": 100,
- "linkquality": 150
+ "voltage": 2700,
+ "battery": 98,
+ "linkquality": 156
},
"0xf0d1b800001388f0": {
"update": {
@@ -72,9 +72,9 @@
"installed_version": 17130496,
"latest_version": 17130496
},
- "state": "OFF",
+ "state": "ON",
"brightness": 229,
- "linkquality": 12,
+ "linkquality": 0,
"update_available": false
},
"0x7cb03eaa0a0176bd": {
@@ -85,14 +85,9 @@
},
"brightness": 152,
"state": "ON",
- "linkquality": 147,
+ "linkquality": 117,
"update_available": false
},
- "0x000d6f001417c142": {
- "voltage": 3000,
- "battery": 100,
- "linkquality": 66
- },
"0xa4c138e68023f4c6": {
"battery": 59,
"water_leak": false,
@@ -101,23 +96,23 @@
},
"0xa4c13838ba59a1b2": {
"battery": 100,
- "temperature": 30.3,
- "humidity": 39,
+ "temperature": 28.9,
+ "humidity": 42,
"temperature_unit": "celsius",
"temperature_calibration": 0,
"humidity_calibration": 0,
- "linkquality": 144
+ "linkquality": 120
},
"0x000d6f001417c16e": {
"voltage": 3000,
"battery": 100,
- "linkquality": 138
+ "linkquality": 117
},
"0xa4c1383ec7aa7d95": {
- "temperature": 24.58,
- "humidity": 52.71,
- "battery": 59,
- "linkquality": 150
+ "temperature": 23.45,
+ "humidity": 60.7,
+ "battery": 14,
+ "linkquality": 117
},
"0xa4c138066bf1f4c7": {
"battery": 100,
@@ -134,12 +129,12 @@
"installed_version": 33574183,
"latest_version": 33574183
},
- "linkquality": 90
+ "linkquality": 12
},
"0x001788010d1c884e": {
"brightness": 254,
"state": "ON",
- "linkquality": 0,
+ "linkquality": 15,
"update": {
"state": "available",
"installed_version": 16786434,
@@ -149,10 +144,10 @@
"color_mode": "color_temp"
},
"0xa4c1383db5531833": {
- "temperature": 23.8,
- "humidity": 43.3,
- "battery": 29,
- "linkquality": 147,
+ "temperature": 21.5,
+ "humidity": 84.2,
+ "battery": 25,
+ "linkquality": 15,
"update": {
"state": "idle",
"installed_version": 268513281,
@@ -160,23 +155,45 @@
}
},
"0xa4c1389d42e94844": {
- "temperature": 22.4,
- "linkquality": 0,
+ "temperature": 21,
+ "linkquality": 126,
"battery": 100,
- "humidity": 61,
+ "humidity": 53,
"temperature_unit": "fahrenheit",
"temperature_calibration": 0,
"humidity_calibration": 0
},
"0xa4c138570b1956d9": {
- "temperature": 24.95,
- "humidity": 51.97,
- "battery": 46,
- "linkquality": 72
+ "temperature": 18.63,
+ "humidity": 66.52,
+ "battery": 0,
+ "linkquality": 24
},
"0x000d6f00109eb214": {
+ "voltage": 3000,
+ "battery": 100,
+ "battery_low": false,
+ "linkquality": 138
+ },
+ "0xa4c138f8d17c3daa": {
+ "battery": 96,
+ "voltage": 2900,
+ "occupancy": false,
+ "tamper": false,
+ "battery_low": false,
+ "linkquality": 54
+ },
+ "0xa4c138b00278ea51": {
+ "battery": 86,
+ "occupancy": false,
+ "tamper": false,
+ "battery_low": false,
+ "voltage": 2900,
+ "linkquality": 24
+ },
+ "0x000d6f001417c142": {
"voltage": 3100,
"battery": 100,
- "battery_low": false
+ "linkquality": 123
}
}
\ No newline at end of file
diff --git a/jellyfin/config/config/migrations.xml b/jellyfin/config/config/migrations.xml
index 1ad6c1c..9521dfe 100644
--- a/jellyfin/config/config/migrations.xml
+++ b/jellyfin/config/config/migrations.xml
@@ -149,5 +149,133 @@
96c156a2-7a13-4b3b-a8b8-fb80c94d20c0
RemoveDuplicatePlaylistChildren
+
+ 9b354818-94d5-4b68-ac49-e35cb85f9d84
+ CreateNetworkConfiguration
+
+
+ a6dcacf4-c057-4ef9-80d3-61cef9ddb4f0
+ MigrateMusicBrainzTimeout
+
+
+ 4fb5c950-1991-11ee-9b4b-0800200c9a66
+ MigrateNetworkConfiguration
+
+
+ a8e61960-7726-4450-8f3d-82c12daabbcb
+ MigrateEncodingOptions
+
+
+ 4124c2cd-e939-4ffb-9be9-9b311c413638
+ DisableTranscodingThrottling
+
+
+ ef103419-8451-40d8-9f34-d1a8e93a1679
+ CreateLoggingConfigHeirarchy
+
+
+ 3793eb59-bc8c-456c-8b9f-bd5a62a42978
+ MigrateActivityLogDatabase
+
+
+ acbe17b7-8435-4a83-8b64-6fcf162cb9bd
+ RemoveDuplicateExtras
+
+
+ 5c4b82a2-f053-4009-bd05-b6fcad82f14c
+ MigrateUserDatabase
+
+
+ 06387815-c3cc-421f-a888-fb5f9992bea8
+ MigrateDisplayPreferencesDatabase
+
+
+ a81f75e0-8f43-416f-a5e8-516ccab4d8cc
+ RemoveDownloadImagesInAdvance
+
+
+ 5bd72f41-e6f3-4f60-90aa-09869abe0e22
+ MigrateAuthenticationDatabase
+
+
+ 615dfa9e-2497-4dbb-a472-61938b752c5b
+ FixPlaylistOwner
+
+
+ d34bfc33-5d2e-4790-8085-069ef6eecb4e
+ MigrateRatingLevels
+
+
+ cf6fabc2-9fbe-4933-84a5-ffe52ef22a58
+ FixAudioData
+
+
+ 96c156a2-7a13-4b3b-a8b8-fb80c94d20c0
+ RemoveDuplicatePlaylistChildren
+
+
+ 9b354818-94d5-4b68-ac49-e35cb85f9d84
+ CreateNetworkConfiguration
+
+
+ a6dcacf4-c057-4ef9-80d3-61cef9ddb4f0
+ MigrateMusicBrainzTimeout
+
+
+ 4fb5c950-1991-11ee-9b4b-0800200c9a66
+ MigrateNetworkConfiguration
+
+
+ a8e61960-7726-4450-8f3d-82c12daabbcb
+ MigrateEncodingOptions
+
+
+ 4124c2cd-e939-4ffb-9be9-9b311c413638
+ DisableTranscodingThrottling
+
+
+ ef103419-8451-40d8-9f34-d1a8e93a1679
+ CreateLoggingConfigHeirarchy
+
+
+ 3793eb59-bc8c-456c-8b9f-bd5a62a42978
+ MigrateActivityLogDatabase
+
+
+ acbe17b7-8435-4a83-8b64-6fcf162cb9bd
+ RemoveDuplicateExtras
+
+
+ 5c4b82a2-f053-4009-bd05-b6fcad82f14c
+ MigrateUserDatabase
+
+
+ 06387815-c3cc-421f-a888-fb5f9992bea8
+ MigrateDisplayPreferencesDatabase
+
+
+ a81f75e0-8f43-416f-a5e8-516ccab4d8cc
+ RemoveDownloadImagesInAdvance
+
+
+ 5bd72f41-e6f3-4f60-90aa-09869abe0e22
+ MigrateAuthenticationDatabase
+
+
+ 615dfa9e-2497-4dbb-a472-61938b752c5b
+ FixPlaylistOwner
+
+
+ d34bfc33-5d2e-4790-8085-069ef6eecb4e
+ MigrateRatingLevels
+
+
+ cf6fabc2-9fbe-4933-84a5-ffe52ef22a58
+ FixAudioData
+
+
+ 96c156a2-7a13-4b3b-a8b8-fb80c94d20c0
+ RemoveDuplicatePlaylistChildren
+
\ No newline at end of file
diff --git a/jellyfin/docker-compose.yml b/jellyfin/docker-compose.yml
index d96f941..dac7660 100644
--- a/jellyfin/docker-compose.yml
+++ b/jellyfin/docker-compose.yml
@@ -1,6 +1,6 @@
services:
jellyfin:
- image: jellyfin/jellyfin:10.10.6
+ image: jellyfin/jellyfin:10.10.7
container_name: jellyfin
user: 1000:1000
# network_mode: 'host'
@@ -30,7 +30,7 @@ services:
restart: 'unless-stopped'
# Optional - alternative address used for autodiscovery
environment:
- - JELLYFIN_PublishedServerUrl=http://example.com
+ - JELLYFIN_PublishedServerUrl=https://media.home.thoster.net
# Optional - may be necessary for docker healthcheck to pass if running in host network mode
extra_hosts:
- 'host.docker.internal:host-gateway'
diff --git a/wikimd/docker-compose.yml b/wikimd/docker-compose.yml
index f6c950a..9baa7fa 100644
--- a/wikimd/docker-compose.yml
+++ b/wikimd/docker-compose.yml
@@ -1,4 +1,3 @@
-version: "2.1"
services:
wikmd:
image: linbreux/wikmd:latest