Compare commits
5 Commits
ea8fff629c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 192972b624 | |||
| 9c0a3c19f6 | |||
| 9f36a48864 | |||
| 9eb3333931 | |||
| ea00ad6d25 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/data
|
/data
|
||||||
/config/media
|
/config/media
|
||||||
|
scripts/restic-backup.env
|
||||||
167
README.md
Normal file
167
README.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Homelab
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Docker** and **Docker Compose**
|
||||||
|
- Host paths used by the stacks:
|
||||||
|
- `/srv/homelab`
|
||||||
|
- `/srv/media`
|
||||||
|
- `/backup`
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Media dirs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /srv/media/{movies,shows,downloads,immich}
|
||||||
|
sudo chown -R 1000:1000 /srv/media
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Monitoring permissions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /srv/homelab/data/monitoring/{prometheus,loki,grafana,alloy}
|
||||||
|
sudo chown -R 65534:65534 /srv/homelab/data/monitoring/prometheus
|
||||||
|
sudo chown -R 10001:10001 /srv/homelab/data/monitoring/loki
|
||||||
|
sudo chown -R 472:472 /srv/homelab/data/monitoring/grafana
|
||||||
|
sudo chown -R 10001:10001 /srv/homelab/data/monitoring/alloy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Env
|
||||||
|
|
||||||
|
- **Glance:** `stacks/monitoring/glance.env`
|
||||||
|
- **Immich:** `stacks/media/immich.env`
|
||||||
|
|
||||||
|
## Deploying
|
||||||
|
|
||||||
|
From the repo root (`/srv/homelab`):
|
||||||
|
|
||||||
|
| Stack | Command |
|
||||||
|
|------------|---------|
|
||||||
|
| **Media** | `docker compose -f stacks/media/compose.yaml up -d` |
|
||||||
|
| **Monitoring** | `docker compose -f stacks/monitoring/compose.yaml up -d` |
|
||||||
|
| **Storage** | `docker compose -f stacks/storage/compose.yaml up -d` |
|
||||||
|
|
||||||
|
To update images:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f stacks/media/compose.yaml pull
|
||||||
|
docker compose -f stacks/media/compose.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
To stop a stack:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f stacks/media/compose.yaml down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
| Service | Port(s) | Stack |
|
||||||
|
|---------------|----------------------|-----------|
|
||||||
|
| **Media** | | |
|
||||||
|
| Sonarr | 8989 | media |
|
||||||
|
| Radarr | 7878 | media |
|
||||||
|
| Lidarr | 8686 | media |
|
||||||
|
| Prowlarr | 9696 | media |
|
||||||
|
| qBittorrent | 8081, 6881 (tcp/udp) | media |
|
||||||
|
| Jellyfin | 8096 | media |
|
||||||
|
| Immich | 2283 | media |
|
||||||
|
| Navidrome | 4533 | media |
|
||||||
|
| Seer | 5055 | media |
|
||||||
|
| **Monitoring**| | |
|
||||||
|
| Grafana | 3034 | monitoring|
|
||||||
|
| Prometheus | 9094 | monitoring|
|
||||||
|
| Loki | 3100 | monitoring|
|
||||||
|
| Node Exporter | 9100 | monitoring|
|
||||||
|
| Alloy | 12345 | monitoring|
|
||||||
|
| cAdvisor | 8088 | monitoring|
|
||||||
|
| Glance | 9090 | monitoring|
|
||||||
|
| Portainer | 9443, 8000 | monitoring|
|
||||||
|
| **Storage** | | |
|
||||||
|
| Gitea | 3000, 222 | storage |
|
||||||
|
| Copyparty | 3923 | storage |
|
||||||
|
|
||||||
|
## Backup
|
||||||
|
|
||||||
|
Backups use [Restic](https://restic.net/). The script backs up `/srv/homelab` (including `data/`), `/etc/caddy/Caddyfile`, and `/etc/unbound/unbound.conf`. It **stops all Docker stacks** before backup for consistent data, then starts them again—run it manually when you can afford a few minutes of downtime.
|
||||||
|
|
||||||
|
### One-time setup
|
||||||
|
|
||||||
|
1. Install restic (e.g. `pacman -S restic` or from [restic.net](https://restic.net/)).
|
||||||
|
|
||||||
|
2. Copy the env example and set repository and password:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp scripts/restic-backup.env.example scripts/restic-backup.env
|
||||||
|
# Edit scripts/restic-backup.env: set RESTIC_REPOSITORY and RESTIC_PASSWORD
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples for `RESTIC_REPOSITORY`:
|
||||||
|
- Local: `RESTIC_REPOSITORY=/backup/restic`
|
||||||
|
- SFTP: `RESTIC_REPOSITORY=sftp:user@backup-host:/restic`
|
||||||
|
- S3: `RESTIC_REPOSITORY=s3:s3.amazonaws.com/bucket-name`
|
||||||
|
|
||||||
|
3. Initialize the repo (once):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export RESTIC_PASSWORD='your-password'
|
||||||
|
restic -r /backup/restic init # use your actual repo path
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a backup
|
||||||
|
|
||||||
|
From the repo root, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./scripts/restic-backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Stacks are stopped, then the backup runs, then they are started again. A failed backup still triggers the start so the homelab comes back up.
|
||||||
|
|
||||||
|
### Inspect backups
|
||||||
|
|
||||||
|
List snapshots (IDs and timestamps):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source scripts/restic-backup.env
|
||||||
|
sudo restic snapshots -r $RESTIC_REPOSITORY
|
||||||
|
```
|
||||||
|
|
||||||
|
List files in a snapshot (e.g. latest or by ID):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
restic ls latest -r $RESTIC_REPOSITORY
|
||||||
|
restic ls -r $RESTIC_REPOSITORY <snapshot-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Browse a path inside a snapshot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
restic ls latest -r $RESTIC_REPOSITORY /srv/homelab/config
|
||||||
|
```
|
||||||
|
|
||||||
|
### See diffs between backups
|
||||||
|
|
||||||
|
Compare two snapshots (added, changed, removed files and content diff):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
restic diff <older-snapshot-id> <newer-snapshot-id> -r $RESTIC_REPOSITORY
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restore
|
||||||
|
|
||||||
|
Restore the latest snapshot into a directory (does not overwrite the repo; use a separate target dir):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
restic restore latest --target /tmp/restore -r $RESTIC_REPOSITORY
|
||||||
|
```
|
||||||
|
|
||||||
|
Restore a specific snapshot or path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
restic restore <snapshot-id> --target /tmp/restore -r $RESTIC_REPOSITORY
|
||||||
|
restic restore latest --path /etc/caddy --target /tmp/restore -r $RESTIC_REPOSITORY
|
||||||
|
```
|
||||||
|
|
||||||
|
After restore, fix ownership on `data/` if needed (see [Monitoring permissions](#2-monitoring-permissions)).
|
||||||
72
config/monitoring/alloy.alloy
Normal file
72
config/monitoring/alloy.alloy
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// Log pipeline: files, Docker, journald → Loki (replaces Promtail).
|
||||||
|
// See: https://grafana.com/docs/alloy/latest/set-up/migrate/from-promtail/
|
||||||
|
|
||||||
|
loki.write "default" {
|
||||||
|
endpoint {
|
||||||
|
url = "http://loki:3100/loki/api/v1/push"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// varlogs: /var/log/**/*.log
|
||||||
|
loki.source.file "varlogs" {
|
||||||
|
targets = [
|
||||||
|
{ __path__ = "/var/log/**/*.log", job = "varlogs", host = "cyberdeck" },
|
||||||
|
]
|
||||||
|
forward_to = [loki.write.default.receiver]
|
||||||
|
file_match {
|
||||||
|
enabled = true
|
||||||
|
sync_period = "10s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// docker: container logs with docker parsing and container label from path
|
||||||
|
loki.source.file "docker" {
|
||||||
|
targets = [
|
||||||
|
{ __path__ = "/var/lib/docker/containers/*/*-json.log", job = "docker", host = "cyberdeck" },
|
||||||
|
]
|
||||||
|
forward_to = [loki.process.docker.receiver]
|
||||||
|
file_match {
|
||||||
|
enabled = true
|
||||||
|
sync_period = "10s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loki.process "docker" {
|
||||||
|
forward_to = [loki.relabel.docker.receiver]
|
||||||
|
stage.docker {}
|
||||||
|
}
|
||||||
|
|
||||||
|
loki.relabel "docker" {
|
||||||
|
forward_to = [loki.write.default.receiver]
|
||||||
|
rule {
|
||||||
|
source_labels = ["filename"]
|
||||||
|
regex = "/var/lib/docker/containers/([^/]+)/.*"
|
||||||
|
target_label = "container"
|
||||||
|
replacement = "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// journald: systemd journal with unit, host, level labels
|
||||||
|
loki.relabel "journald" {
|
||||||
|
forward_to = []
|
||||||
|
rule {
|
||||||
|
source_labels = ["__journal__systemd_unit"]
|
||||||
|
target_label = "unit"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__journal__hostname"]
|
||||||
|
target_label = "host"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__journal__priority_keyword"]
|
||||||
|
target_label = "level"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loki.source.journal "journald" {
|
||||||
|
forward_to = [loki.write.default.receiver]
|
||||||
|
relabel_rules = loki.relabel.journald.rules
|
||||||
|
labels = { job = "journald", host = "cyberdeck" }
|
||||||
|
path = "/var/log/journal"
|
||||||
|
max_age = "12h"
|
||||||
|
}
|
||||||
@@ -42,43 +42,43 @@ pages:
|
|||||||
- same-tab: true
|
- same-tab: true
|
||||||
links:
|
links:
|
||||||
- title: Jellyfin
|
- title: Jellyfin
|
||||||
url: https://jellyfin.home
|
url: https://jellyfin.internal
|
||||||
icon: mdi:television-play
|
icon: mdi:television-play
|
||||||
- title: Seer
|
- title: Seer
|
||||||
url: https://jellyseerr.home
|
url: https://seerr.internal
|
||||||
icon: mdi:movie-search
|
icon: mdi:movie-search
|
||||||
- title: Radarr
|
- title: Radarr
|
||||||
url: https://radarr.home
|
url: https://radarr.internal
|
||||||
icon: mdi:film
|
icon: mdi:film
|
||||||
- title: Sonarr
|
- title: Sonarr
|
||||||
url: https://sonarr.home
|
url: https://sonarr.internal
|
||||||
icon: mdi:television
|
icon: mdi:television
|
||||||
- title: Lidarr
|
- title: Lidarr
|
||||||
url: https://lidarr.home
|
url: https://lidarr.internal
|
||||||
icon: mdi:music
|
icon: mdi:music
|
||||||
- title: Prowlarr
|
- title: Prowlarr
|
||||||
url: https://prowlarr.home
|
url: https://prowlarr.internal
|
||||||
icon: mdi:radar
|
icon: mdi:radar
|
||||||
- title: qBittorrent
|
- title: qBittorrent
|
||||||
url: https://qbit.home
|
url: https://qbit.internal
|
||||||
icon: mdi:download
|
icon: mdi:download
|
||||||
- title: Portainer
|
- title: Portainer
|
||||||
url: https://portainer.home
|
url: https://portainer.internal
|
||||||
icon: mdi:docker
|
icon: mdi:docker
|
||||||
- title: Immich
|
- title: Immich
|
||||||
url: https://photo.home
|
url: https://photo.internal
|
||||||
icon: mdi:image-multiple
|
icon: mdi:image-multiple
|
||||||
- title: Navidrome
|
- title: Navidrome
|
||||||
url: https://music.home
|
url: https://music.internal
|
||||||
icon: mdi:music-box-multiple
|
icon: mdi:music-box-multiple
|
||||||
- title: CopyParty
|
- title: CopyParty
|
||||||
url: https://copyparty.home
|
url: https://copyparty.internal
|
||||||
icon: mdi:folder-multiple
|
icon: mdi:folder-multiple
|
||||||
- title: Gitea
|
- title: Gitea
|
||||||
url: https://git.home
|
url: https://git.internal
|
||||||
icon: mdi:git
|
icon: mdi:git
|
||||||
- title: Grafana
|
- title: Grafana
|
||||||
url: https://grafana.home
|
url: https://grafana.internal
|
||||||
icon: mdi:chart-line
|
icon: mdi:chart-line
|
||||||
- name: Media
|
- name: Media
|
||||||
columns:
|
columns:
|
||||||
|
|||||||
@@ -19,50 +19,50 @@
|
|||||||
title: Service status
|
title: Service status
|
||||||
sites:
|
sites:
|
||||||
- title: Jellyfin
|
- title: Jellyfin
|
||||||
url: https://jellyfin.home
|
url: https://jellyfin.internal
|
||||||
icon: mdi:television-play
|
icon: mdi:television-play
|
||||||
- title: Seer
|
- title: Seer
|
||||||
url: https://jellyseerr.home
|
url: https://seerr.internal
|
||||||
icon: mdi:movie-search
|
icon: mdi:movie-search
|
||||||
- title: Radarr
|
- title: Radarr
|
||||||
url: https://radarr.home
|
url: https://radarr.internal
|
||||||
icon: mdi:film
|
icon: mdi:film
|
||||||
- title: Sonarr
|
- title: Sonarr
|
||||||
url: https://sonarr.home
|
url: https://sonarr.internal
|
||||||
icon: mdi:television
|
icon: mdi:television
|
||||||
- title: Lidarr
|
- title: Lidarr
|
||||||
url: https://lidarr.home
|
url: https://lidarr.internal
|
||||||
icon: mdi:music
|
icon: mdi:music
|
||||||
- title: Prowlarr
|
- title: Prowlarr
|
||||||
url: https://prowlarr.home
|
url: https://prowlarr.internal
|
||||||
icon: mdi:radar
|
icon: mdi:radar
|
||||||
- title: qBittorrent
|
- title: qBittorrent
|
||||||
url: https://qbit.home
|
url: https://qbit.internal
|
||||||
icon: mdi:download
|
icon: mdi:download
|
||||||
- title: Portainer
|
- title: Portainer
|
||||||
url: https://portainer.home
|
url: https://portainer.internal
|
||||||
icon: mdi:docker
|
icon: mdi:docker
|
||||||
- title: Immich
|
- title: Immich
|
||||||
url: https://photo.home
|
url: https://photo.internal
|
||||||
icon: mdi:image-multiple
|
icon: mdi:image-multiple
|
||||||
- title: Navidrome
|
- title: Navidrome
|
||||||
url: https://music.home
|
url: https://music.internal
|
||||||
icon: mdi:music-box-multiple
|
icon: mdi:music-box-multiple
|
||||||
- title: CopyParty
|
- title: CopyParty
|
||||||
url: https://copyparty.home
|
url: https://copyparty.internal
|
||||||
icon: mdi:folder-multiple
|
icon: mdi:folder-multiple
|
||||||
- title: Gitea
|
- title: Gitea
|
||||||
url: https://git.home
|
url: https://git.internal
|
||||||
icon: mdi:git
|
icon: mdi:git
|
||||||
- title: Grafana
|
- title: Grafana
|
||||||
url: https://grafana.home
|
url: https://grafana.internal
|
||||||
icon: mdi:chart-line
|
icon: mdi:chart-line
|
||||||
- type: docker-containers
|
- type: docker-containers
|
||||||
title: Docker
|
title: Docker
|
||||||
show-stats: true
|
show-stats: true
|
||||||
- type: custom-api
|
- type: custom-api
|
||||||
title: Portainer
|
title: Portainer
|
||||||
title-url: https://portainer.home
|
title-url: https://portainer.internal
|
||||||
cache: 1h
|
cache: 1h
|
||||||
url: ${PORTAINER_URL}/api/endpoints/${PORTAINER_ENDPOINT_ID}
|
url: ${PORTAINER_URL}/api/endpoints/${PORTAINER_ENDPOINT_ID}
|
||||||
headers:
|
headers:
|
||||||
|
|||||||
@@ -2,26 +2,26 @@
|
|||||||
title: Media
|
title: Media
|
||||||
sites:
|
sites:
|
||||||
- title: Jellyfin
|
- title: Jellyfin
|
||||||
url: https://jellyfin.home
|
url: https://jellyfin.internal
|
||||||
icon: mdi:television-play
|
icon: mdi:television-play
|
||||||
- title: Jellyseerr
|
- title: seerr
|
||||||
url: https://jellyseerr.home
|
url: https://seerr.internal
|
||||||
icon: mdi:movie-search
|
icon: mdi:movie-search
|
||||||
- title: Radarr
|
- title: Radarr
|
||||||
url: https://radarr.home
|
url: https://radarr.internal
|
||||||
icon: mdi:film
|
icon: mdi:film
|
||||||
- title: Sonarr
|
- title: Sonarr
|
||||||
url: https://sonarr.home
|
url: https://sonarr.internal
|
||||||
icon: mdi:television
|
icon: mdi:television
|
||||||
- title: Lidarr
|
- title: Lidarr
|
||||||
url: https://lidarr.home
|
url: https://lidarr.internal
|
||||||
icon: mdi:music
|
icon: mdi:music
|
||||||
- title: Prowlarr
|
- title: Prowlarr
|
||||||
url: https://prowlarr.home
|
url: https://prowlarr.internal
|
||||||
icon: mdi:radar
|
icon: mdi:radar
|
||||||
- title: qBittorrent
|
- title: qBittorrent
|
||||||
url: https://qbit.home
|
url: https://qbit.internal
|
||||||
icon: mdi:download
|
icon: mdi:download
|
||||||
- title: TorrServe
|
- title: TorrServe
|
||||||
url: https://torrserve.home
|
url: https://torrserve.internal
|
||||||
icon: mdi:server-network
|
icon: mdi:server-network
|
||||||
|
|||||||
@@ -1,49 +1,52 @@
|
|||||||
|
# Loki 3.6 single-binary config (from official loki-local-config.yaml).
|
||||||
|
# See: https://grafana.com/docs/loki/latest/configure/
|
||||||
|
|
||||||
auth_enabled: false
|
auth_enabled: false
|
||||||
|
|
||||||
server:
|
server:
|
||||||
http_listen_port: 3100
|
http_listen_port: 3100
|
||||||
|
http_listen_address: 0.0.0.0
|
||||||
|
grpc_listen_port: 9096
|
||||||
log_level: info
|
log_level: info
|
||||||
|
grpc_server_max_concurrent_streams: 1000
|
||||||
|
|
||||||
ingester:
|
common:
|
||||||
wal:
|
instance_addr: 127.0.0.1
|
||||||
dir: /loki/wal
|
path_prefix: /loki
|
||||||
lifecycler:
|
storage:
|
||||||
ring:
|
filesystem:
|
||||||
kvstore:
|
chunks_directory: /loki/chunks
|
||||||
store: inmemory
|
rules_directory: /loki/rules
|
||||||
replication_factor: 1
|
replication_factor: 1
|
||||||
chunk_idle_period: 5m
|
ring:
|
||||||
chunk_target_size: 1048576
|
kvstore:
|
||||||
max_transfer_retries: 0
|
store: inmemory
|
||||||
|
|
||||||
|
query_range:
|
||||||
|
results_cache:
|
||||||
|
cache:
|
||||||
|
embedded_cache:
|
||||||
|
enabled: true
|
||||||
|
max_size_mb: 100
|
||||||
|
|
||||||
|
limits_config:
|
||||||
|
metric_aggregation_enabled: true
|
||||||
|
enable_multi_variant_queries: true
|
||||||
|
|
||||||
schema_config:
|
schema_config:
|
||||||
configs:
|
configs:
|
||||||
- from: 2020-10-24
|
- from: 2020-10-24
|
||||||
store: boltdb
|
store: tsdb
|
||||||
object_store: filesystem
|
object_store: filesystem
|
||||||
schema: v11
|
schema: v13
|
||||||
index:
|
index:
|
||||||
prefix: index_
|
prefix: index_
|
||||||
period: 168h
|
period: 24h
|
||||||
|
|
||||||
storage_config:
|
pattern_ingester:
|
||||||
boltdb:
|
enabled: true
|
||||||
directory: /loki/index
|
metric_aggregation:
|
||||||
filesystem:
|
loki_address: localhost:3100
|
||||||
directory: /loki/chunks
|
|
||||||
|
|
||||||
limits_config:
|
frontend:
|
||||||
enforce_metric_name: false
|
encoding: protobuf
|
||||||
reject_old_samples: true
|
|
||||||
reject_old_samples_max_age: 168h
|
|
||||||
split_queries_by_interval: 15m
|
|
||||||
|
|
||||||
querier:
|
|
||||||
max_outstanding_per_tenant: 4096
|
|
||||||
|
|
||||||
chunk_store_config:
|
|
||||||
max_look_back_period: 0s
|
|
||||||
|
|
||||||
table_manager:
|
|
||||||
retention_deletes_enabled: true
|
|
||||||
retention_period: 168h
|
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ scrape_configs:
|
|||||||
tls_config:
|
tls_config:
|
||||||
insecure_skip_verify: true
|
insecure_skip_verify: true
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ["metrics.home:443"]
|
- targets: ["metrics.internal:443"]
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
server:
|
|
||||||
http_listen_port: 9080
|
|
||||||
grpc_listen_port: 0
|
|
||||||
|
|
||||||
positions:
|
|
||||||
filename: /tmp/positions.yaml
|
|
||||||
|
|
||||||
clients:
|
|
||||||
- url: http://loki:3100/loki/api/v1/push
|
|
||||||
|
|
||||||
scrape_configs:
|
|
||||||
- job_name: varlogs
|
|
||||||
static_configs:
|
|
||||||
- targets: [localhost]
|
|
||||||
labels:
|
|
||||||
job: varlogs
|
|
||||||
host: cyberdeck
|
|
||||||
__path__: /var/log/**/*.log
|
|
||||||
|
|
||||||
- job_name: docker
|
|
||||||
static_configs:
|
|
||||||
- targets: [localhost]
|
|
||||||
labels:
|
|
||||||
job: docker
|
|
||||||
host: cyberdeck
|
|
||||||
__path__: /var/lib/docker/containers/*/*-json.log
|
|
||||||
pipeline_stages:
|
|
||||||
- docker: {}
|
|
||||||
relabel_configs:
|
|
||||||
- source_labels: [__path__]
|
|
||||||
regex: "/var/lib/docker/containers/([^/]+)/.*"
|
|
||||||
target_label: container
|
|
||||||
replacement: "$1"
|
|
||||||
|
|
||||||
- job_name: journald
|
|
||||||
journal:
|
|
||||||
path: /var/log/journal
|
|
||||||
max_age: 12h
|
|
||||||
labels:
|
|
||||||
job: journald
|
|
||||||
host: cyberdeck
|
|
||||||
relabel_configs:
|
|
||||||
- source_labels: [__journal__systemd_unit]
|
|
||||||
target_label: unit
|
|
||||||
- source_labels: [__journal__hostname]
|
|
||||||
target_label: host
|
|
||||||
- source_labels: [__journal__priority_keyword]
|
|
||||||
target_label: level
|
|
||||||
12
scripts/restic-backup.env.example
Normal file
12
scripts/restic-backup.env.example
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Copy to restic-backup.env and set values.
|
||||||
|
# restic-backup.env is not committed (add to .gitignore if needed).
|
||||||
|
|
||||||
|
# Repository: local path, sftp, rclone, or S3-compatible.
|
||||||
|
# Examples:
|
||||||
|
# RESTIC_REPOSITORY=/backup/restic
|
||||||
|
# RESTIC_REPOSITORY=sftp:user@backup-host:/restic
|
||||||
|
# RESTIC_REPOSITORY=s3:s3.amazonaws.com/bucket-name
|
||||||
|
export RESTIC_REPOSITORY=""
|
||||||
|
|
||||||
|
# Repository password (used for encryption).
|
||||||
|
export RESTIC_PASSWORD=""
|
||||||
57
scripts/restic-backup.sh
Executable file
57
scripts/restic-backup.sh
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ENV_FILE="${SCRIPT_DIR}/restic-backup.env"
|
||||||
|
HOMELAB_ROOT="/srv/homelab"
|
||||||
|
STACKS_STOPPED=false
|
||||||
|
|
||||||
|
start_stacks() {
|
||||||
|
if [[ "$STACKS_STOPPED" != true ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "Starting Docker stacks..."
|
||||||
|
cd "$HOMELAB_ROOT"
|
||||||
|
docker compose -f stacks/media/compose.yaml up -d
|
||||||
|
docker compose -f stacks/monitoring/compose.yaml up -d
|
||||||
|
docker compose -f stacks/storage/compose.yaml up -d
|
||||||
|
echo "Stacks started."
|
||||||
|
}
|
||||||
|
trap start_stacks EXIT
|
||||||
|
|
||||||
|
if [[ -f "$ENV_FILE" ]]; then
|
||||||
|
set -a
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${RESTIC_REPOSITORY:-}" ]]; then
|
||||||
|
echo "Error: RESTIC_REPOSITORY is not set. Create ${ENV_FILE} or export it." >&2
|
||||||
|
echo "Example: RESTIC_REPOSITORY=sftp:user@host:/backups/restic" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${RESTIC_PASSWORD:-}" ]]; then
|
||||||
|
echo "Error: RESTIC_PASSWORD is not set. Create ${ENV_FILE} or export it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CADDYFILE="/etc/caddy/Caddyfile"
|
||||||
|
UNBOUND_CONF="/etc/unbound/unbound.conf"
|
||||||
|
|
||||||
|
echo "Stopping Docker stacks..."
|
||||||
|
cd "$HOMELAB_ROOT"
|
||||||
|
docker compose -f stacks/media/compose.yaml down
|
||||||
|
docker compose -f stacks/monitoring/compose.yaml down
|
||||||
|
docker compose -f stacks/storage/compose.yaml down
|
||||||
|
STACKS_STOPPED=true
|
||||||
|
|
||||||
|
echo "Starting restic backup..."
|
||||||
|
restic backup \
|
||||||
|
"$HOMELAB_ROOT" \
|
||||||
|
"$CADDYFILE" \
|
||||||
|
"$UNBOUND_CONF"
|
||||||
|
|
||||||
|
echo "Backup finished. Snapshot list:"
|
||||||
|
restic snapshots --latest 5
|
||||||
@@ -7,3 +7,4 @@ include:
|
|||||||
- ./seer.yaml
|
- ./seer.yaml
|
||||||
- ./navidrome.yaml
|
- ./navidrome.yaml
|
||||||
- ./immich.yaml
|
- ./immich.yaml
|
||||||
|
- ./plex.yaml
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ services:
|
|||||||
|
|
||||||
immich-redis:
|
immich-redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63
|
image: docker.io/valkey/valkey:9
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -51,7 +51,7 @@ services:
|
|||||||
|
|
||||||
immich-postgres:
|
immich-postgres:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||||
env_file:
|
env_file:
|
||||||
- ./immich.env
|
- ./immich.env
|
||||||
shm_size: 128mb
|
shm_size: 128mb
|
||||||
|
|||||||
13
stacks/media/plex.yaml
Normal file
13
stacks/media/plex.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
plex:
|
||||||
|
container_name: plex
|
||||||
|
image: plexinc/pms-docker
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Moscow
|
||||||
|
network_mode: host
|
||||||
|
volumes:
|
||||||
|
- /srv/homelab/config/media/plex:/config
|
||||||
|
- /tmp/plex:/transcode
|
||||||
|
- /srv/media:/data
|
||||||
@@ -4,19 +4,19 @@
|
|||||||
MY_SECRET_TOKEN=123456
|
MY_SECRET_TOKEN=123456
|
||||||
|
|
||||||
# *arr stack – API base URL and key (http from container)
|
# *arr stack – API base URL and key (http from container)
|
||||||
RADARR_URL=http://radarr.home
|
RADARR_URL=http://radarr.internal
|
||||||
RADARR_API_URL=http://radarr.home
|
RADARR_API_URL=http://radarr.internal
|
||||||
RADARR_KEY=512cab3acfe84420957a9c8585560f8f
|
RADARR_KEY=512cab3acfe84420957a9c8585560f8f
|
||||||
|
|
||||||
SONARR_URL=http://sonarr.home
|
SONARR_URL=http://sonarr.internal
|
||||||
SONARR_API_URL=http://sonarr.home
|
SONARR_API_URL=http://sonarr.internal
|
||||||
SONARR_KEY=b3a84ef407ca42d99f5bc22fb8afc401
|
SONARR_KEY=b3a84ef407ca42d99f5bc22fb8afc401
|
||||||
|
|
||||||
LIDARR_URL=http://lidarr.home
|
LIDARR_URL=http://lidarr.internal
|
||||||
LIDARR_API_URL=http://lidarr.home
|
LIDARR_API_URL=http://lidarr.internal
|
||||||
LIDARR_KEY=e0862cb9aa8c4c0f8115ec794c5bebc1
|
LIDARR_KEY=e0862cb9aa8c4c0f8115ec794c5bebc1
|
||||||
|
|
||||||
# Portainer (Settings → API → Enable API; endpoint ID from URL e.g. !#/2/docker → 2)
|
# Portainer (Settings → API → Enable API; endpoint ID from URL e.g. !#/2/docker → 2)
|
||||||
PORTAINER_URL=http://portainer.home
|
PORTAINER_URL=http://portainer.internal
|
||||||
PORTAINER_ENDPOINT_ID=3
|
PORTAINER_ENDPOINT_ID=3
|
||||||
PORTAINER_API_KEY=ptr_zmLlSaAbw8nQRoJlDRb006IDVtJLojneToSVNWJxo3c=
|
PORTAINER_API_KEY=ptr_zmLlSaAbw8nQRoJlDRb006IDVtJLojneToSVNWJxo3c=
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
MY_SECRET_TOKEN=change_me
|
|
||||||
RADARR_URL=http://radarr.home
|
|
||||||
RADARR_API_URL=http://radarr.home
|
|
||||||
RADARR_KEY=change_me
|
|
||||||
SONARR_URL=http://sonarr.home
|
|
||||||
SONARR_API_URL=http://sonarr.home
|
|
||||||
SONARR_KEY=change_me
|
|
||||||
LIDARR_URL=http://lidarr.home
|
|
||||||
LIDARR_API_URL=http://lidarr.home
|
|
||||||
LIDARR_KEY=change_me
|
|
||||||
PORTAINER_URL=http://portainer.home
|
|
||||||
PORTAINER_ENDPOINT_ID=1
|
|
||||||
PORTAINER_API_KEY=change_me
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
glance:
|
glance:
|
||||||
container_name: glance
|
container_name: glance
|
||||||
image: glanceapp/glance
|
image: glanceapp/glance:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- /srv/homelab/config/monitoring/glance:/app/config
|
- /srv/homelab/config/monitoring/glance:/app/config
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
# sudo chown -R 65534:65534 /srv/homelab/data/monitoring/prometheus
|
# sudo chown -R 65534:65534 /srv/homelab/data/monitoring/prometheus
|
||||||
# sudo chown -R 10001:10001 /srv/homelab/data/monitoring/loki
|
# sudo chown -R 10001:10001 /srv/homelab/data/monitoring/loki
|
||||||
# sudo chown -R 472:472 /srv/homelab/data/monitoring/grafana
|
# sudo chown -R 472:472 /srv/homelab/data/monitoring/grafana
|
||||||
|
# sudo chown -R 10001:10001 /srv/homelab/data/monitoring/alloy
|
||||||
#
|
#
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: monitoring
|
||||||
|
|
||||||
services:
|
services:
|
||||||
cadvisor:
|
cadvisor:
|
||||||
image: gcr.io/cadvisor/cadvisor:latest
|
image: gcr.io/cadvisor/cadvisor:latest
|
||||||
@@ -25,7 +30,7 @@ services:
|
|||||||
image: prom/prometheus:latest
|
image: prom/prometheus:latest
|
||||||
container_name: prometheus
|
container_name: prometheus
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "metrics.home:192.168.1.70"
|
- "metrics.internal:192.168.1.70"
|
||||||
ports:
|
ports:
|
||||||
- "9094:9090"
|
- "9094:9090"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -38,6 +43,22 @@ services:
|
|||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "5"
|
max-file: "5"
|
||||||
|
|
||||||
|
node_exporter:
|
||||||
|
image: quay.io/prometheus/node-exporter:latest
|
||||||
|
container_name: node_exporter
|
||||||
|
command:
|
||||||
|
- '--path.rootfs=/host'
|
||||||
|
network_mode: host
|
||||||
|
pid: host
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- '/:/host:ro,rslave'
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "5"
|
||||||
|
|
||||||
grafana:
|
grafana:
|
||||||
image: grafana/grafana:latest
|
image: grafana/grafana:latest
|
||||||
container_name: grafana
|
container_name: grafana
|
||||||
@@ -62,7 +83,7 @@ services:
|
|||||||
max-file: "5"
|
max-file: "5"
|
||||||
|
|
||||||
loki:
|
loki:
|
||||||
image: grafana/loki:2.9.0
|
image: grafana/loki:3.6.0
|
||||||
container_name: loki
|
container_name: loki
|
||||||
ports:
|
ports:
|
||||||
- "3100:3100"
|
- "3100:3100"
|
||||||
@@ -77,14 +98,17 @@ services:
|
|||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "5"
|
max-file: "5"
|
||||||
|
|
||||||
promtail:
|
alloy:
|
||||||
image: grafana/promtail:2.9.0
|
image: grafana/alloy:latest
|
||||||
container_name: promtail
|
container_name: alloy
|
||||||
command: -config.file=/etc/promtail/config.yaml
|
command: run --storage.path=/var/lib/alloy /etc/alloy/config.alloy
|
||||||
depends_on:
|
depends_on:
|
||||||
- loki
|
- loki
|
||||||
|
ports:
|
||||||
|
- "12345:12345"
|
||||||
volumes:
|
volumes:
|
||||||
- /srv/homelab/config/monitoring/promtail-config.yaml:/etc/promtail/config.yaml:ro
|
- /srv/homelab/config/monitoring/alloy.alloy:/etc/alloy/config.alloy:ro
|
||||||
|
- /srv/homelab/data/monitoring/alloy:/var/lib/alloy
|
||||||
- /var/log:/var/log:ro
|
- /var/log:/var/log:ro
|
||||||
- /etc/machine-id:/etc/machine-id:ro
|
- /etc/machine-id:/etc/machine-id:ro
|
||||||
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
portainer:
|
portainer:
|
||||||
container_name: portainer
|
container_name: portainer
|
||||||
image: portainer/portainer-ce:lts
|
image: portainer/portainer-ce:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
@@ -10,7 +10,7 @@ services:
|
|||||||
- "9443:9443"
|
- "9443:9443"
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
environment:
|
environment:
|
||||||
- TRUSTED_ORIGINS=portainer.home
|
- TRUSTED_ORIGINS=portainer.internal
|
||||||
logging:
|
logging:
|
||||||
driver: json-file
|
driver: json-file
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ networks:
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
server:
|
server:
|
||||||
image: docker.gitea.com/gitea:1.25.4
|
image: docker.gitea.com/gitea:latest
|
||||||
container_name: gitea
|
container_name: gitea
|
||||||
environment:
|
environment:
|
||||||
- USER_UID=1000
|
- USER_UID=1000
|
||||||
|
|||||||
Reference in New Issue
Block a user