Compare commits
4 Commits
39bbe94c23
...
55a12e0fb7
| Author | SHA1 | Date | |
|---|---|---|---|
| 55a12e0fb7 | |||
| f272c00ff3 | |||
| 9ef2ec345c | |||
| b5c0439b04 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
.DS_Store
|
||||
secrets/
|
||||
|
||||
48
homelab/applications/caddy/Caddyfile
Normal file
48
homelab/applications/caddy/Caddyfile
Normal file
@ -0,0 +1,48 @@
|
||||
# --- Home Assistant ---
|
||||
(logging) {
|
||||
log {
|
||||
output file /var/log/caddy/access.log {
|
||||
# Roll logs to save space
|
||||
roll_size 100mb
|
||||
roll_keep 10
|
||||
roll_keep_for 720h # 30 days
|
||||
}
|
||||
format json
|
||||
level INFO # This ensures all requests (INFO, WARN, ERROR) are logged
|
||||
}
|
||||
}
|
||||
|
||||
# --- top domain ---
|
||||
saljic.me {
|
||||
import logging
|
||||
respond "Welcome! In the making..."
|
||||
}
|
||||
|
||||
ha.saljic.me {
|
||||
import logging
|
||||
reverse_proxy 10.10.10.6:8123
|
||||
}
|
||||
|
||||
# --- FreshRSS ---
|
||||
feed.saljic.me {
|
||||
import logging
|
||||
reverse_proxy 10.10.10.6:8081
|
||||
}
|
||||
|
||||
# --- Immich ---
|
||||
tagebuch.saljic.me {
|
||||
import logging
|
||||
reverse_proxy 10.10.10.6:2283
|
||||
}
|
||||
|
||||
# --- Gitea ---
|
||||
git.saljic.me {
|
||||
import logging
|
||||
reverse_proxy 10.10.10.6:8030
|
||||
}
|
||||
|
||||
# --- ntfy ---
|
||||
ntfy.saljic.me {
|
||||
import logging
|
||||
reverse_proxy 10.10.10.6:8500
|
||||
}
|
||||
155
homelab/applications/caddy/caddy.json
Normal file
155
homelab/applications/caddy/caddy.json
Normal file
@ -0,0 +1,155 @@
|
||||
{
|
||||
"admin": {
|
||||
"listen": "127.0.0.1:2019"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"logs": {
|
||||
"default_logger_name": "default"
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "10.10.10.6:8123"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"ha.saljic.me"
|
||||
],
|
||||
"remote_ip": {
|
||||
"ranges": [
|
||||
"217.82.27.57"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"error": "Unauthorized",
|
||||
"handler": "error",
|
||||
"status_code": "401" }
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"ha.saljic.me"
|
||||
],
|
||||
"not": [
|
||||
{
|
||||
"remote_ip": {
|
||||
"ranges": [
|
||||
"217.82.27.57"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "10.10.10.6:8000"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"baby.saljic.me"
|
||||
],
|
||||
"remote_ip": {
|
||||
"ranges": [
|
||||
"217.82.27.57"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "handle": [
|
||||
{
|
||||
"error": "Unauthorized",
|
||||
"handler": "error",
|
||||
"status_code": "401"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"baby.saljic.me"
|
||||
],
|
||||
"not": [
|
||||
{
|
||||
"remote_ip": {
|
||||
"ranges": [
|
||||
"217.82.27.57"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "10.10.10.6:2283"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"tagebuch.saljic.me"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"encoder": {
|
||||
"format": "json"
|
||||
},
|
||||
"level": "INFO",
|
||||
"writer": {
|
||||
"filename": "/var/log/caddy/access.log",
|
||||
"output": "file",
|
||||
"roll_gzip": false,
|
||||
"roll_keep": 5,
|
||||
"roll_keep_days": 60,
|
||||
"roll_local_time": false,
|
||||
"roll_size_mb": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
homelab/applications/gitea/compose.yml
Normal file
14
homelab/applications/gitea/compose.yml
Normal file
@ -0,0 +1,14 @@
|
||||
services:
|
||||
server:
|
||||
image: docker.gitea.com/gitea:latest
|
||||
container_name: gitea
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- DISABLE_REGISTRATION=true
|
||||
restart: always
|
||||
volumes:
|
||||
- ./data:/data
|
||||
ports:
|
||||
- "8030:3000"
|
||||
- "222:22"
|
||||
19
homelab/applications/homeassistant/config/configuration.yaml
Normal file
19
homelab/applications/homeassistant/config/configuration.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
# Loads default set of integrations. Do not remove.
|
||||
default_config:
|
||||
|
||||
# Load frontend themes from the themes folder
|
||||
frontend:
|
||||
themes: !include_dir_merge_named themes
|
||||
|
||||
automation: !include automations.yaml
|
||||
script: !include scripts.yaml
|
||||
scene: !include scenes.yaml
|
||||
http:
|
||||
use_x_forwarded_for: true
|
||||
trusted_proxies:
|
||||
- 192.168.100.5
|
||||
homeassistant:
|
||||
external_url: "https://ha.saljic.me"
|
||||
internal_url: "http://10.10.10.6:8123"
|
||||
|
||||
sensor: !include sensor.yaml
|
||||
28
homelab/applications/postgres/README.md
Normal file
28
homelab/applications/postgres/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Postgres
|
||||
## Set up non-root user for container
|
||||
We are providing a non-root user to the container to limit the attack surface for privilege escalations. In order for this to work in our setup, please make sure to check if you have a user called `postgres` set up.
|
||||
|
||||
1. Check if user `postgres` exists and if the UID is 1002
|
||||
|
||||
```
|
||||
cat /etc/passwd | grep postgres
|
||||
```
|
||||
|
||||
In case the `postgres` user exists but the UID is not 1002, please adjust it via
|
||||
```
|
||||
sudo usermod -u 1002 postgres
|
||||
```
|
||||
|
||||
In case the `postgres` user doesn't exist at all, please create the user incl. the right UID by running
|
||||
```
|
||||
sudo useradd -u 1002 postgres
|
||||
```
|
||||
|
||||
## About secrets
|
||||
In order to manage secrets centrally in 1Password and due to the need for secrets in Postgres, using `docker compose` directly in the terminal does not work.
|
||||
|
||||
## Bring up/tear down container
|
||||
Please use the `start.sh` to spin up the container
|
||||
### Prerequisites start.sh
|
||||
- User executing the script is part of the `docker` group
|
||||
- Env variable `OP_SERVICE_ACCOUNT_TOKEN` is set up \[check out top-level README.md for more information on how to set this up\]
|
||||
18
homelab/applications/postgres/compose.yml
Normal file
18
homelab/applications/postgres/compose.yml
Normal file
@ -0,0 +1,18 @@
|
||||
secrets:
|
||||
postgres_password:
|
||||
environment: POSTGRES_PASSWORD
|
||||
postgres_user:
|
||||
environment: POSTGRES_USER
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
container_name: postgres
|
||||
user: "1002"
|
||||
restart: always
|
||||
shm_size: 1024mb
|
||||
environment:
|
||||
POSTGRES_USER_FILE: /run/secrets/postgres_user
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||
secrets: ['postgres_password', 'postgres_user']
|
||||
ports: ['5432:5432']
|
||||
volumes: ['./data:/var/lib/postgresql']
|
||||
13
homelab/applications/postgres/start.sh
Normal file
13
homelab/applications/postgres/start.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/bin/zsh
|
||||
# Exit immediately if a command exits with a non-zero status.
|
||||
set -e
|
||||
|
||||
echo "--- Starting Docker Secret Management ---"
|
||||
# Mount secrets
|
||||
export POSTGRES_USER="$(op read 'op://NAxS Homelab/Postgres Homelab/username')"
|
||||
export POSTGRES_PASSWORD="$(op read 'op://NAxS Homelab/Postgres Homelab/password')"
|
||||
|
||||
# Bring up container
|
||||
docker compose up -d
|
||||
|
||||
echo "--- Docker Secret Management Complete ---"
|
||||
1
homelab/mikrotik/README.md
Normal file
1
homelab/mikrotik/README.md
Normal file
@ -0,0 +1 @@
|
||||
Installation instructions for Bonjour reflection: https://github.com/nberlee/bonjour-reflector/blob/main/docs/RouterOS/README.md
|
||||
39
homelab/mikrotik/config.toml
Normal file
39
homelab/mikrotik/config.toml
Normal file
@ -0,0 +1,39 @@
|
||||
net_interface = "eth0"
|
||||
|
||||
[devices]
|
||||
|
||||
[devices."B0:22:7A:91:01:CB"]
|
||||
description = "HP Printer"
|
||||
origin_pool = 10
|
||||
shared_pools = [30]
|
||||
|
||||
[devices."48:A6:B8:A5:BC:7E"]
|
||||
description = "Sonos One #1"
|
||||
origin_pool = 10
|
||||
shared_pools = [30]
|
||||
|
||||
[devices."48:A6:B8:A5:BB:7C"]
|
||||
description = "Sonos One #2"
|
||||
origin_pool = 10
|
||||
shared_pools = [30]
|
||||
|
||||
[devices."48:A6:B8:B0:4C:80"]
|
||||
description = "Sonos Arc"
|
||||
origin_pool = 10
|
||||
shared_pools = [30]
|
||||
|
||||
[devices."F0:B3:EC:05:48:3D"]
|
||||
description = "Apple TV"
|
||||
origin_pool = 10
|
||||
shared_pools = [30]
|
||||
|
||||
[vlan]
|
||||
|
||||
[vlan.10]
|
||||
ip_source = "10.10.10.2"
|
||||
|
||||
[vlan.20]
|
||||
ip_source = "10.10.20.2"
|
||||
|
||||
[vlan.30]
|
||||
ip_source = "10.10.30.2"
|
||||
15
homelab/mikrotik/mqtt.rsc
Normal file
15
homelab/mikrotik/mqtt.rsc
Normal file
@ -0,0 +1,15 @@
|
||||
:local cpuLoad [/system/resource/get cpu-load]
|
||||
:local cpuTemperature [/system health get [find name="temperature"] value]
|
||||
|
||||
:local sfp1Info [/interface ethernet monitor sfp-sfpplus1 once as-value]
|
||||
:local sfpTemperature ($sfp1Info->"sfp-temperature")
|
||||
|
||||
:local pppoeClientInfo [/interface pppoe-client monitor [find name="pppoe-telekom"] once as-value]
|
||||
:local pppoeUptime ($pppoeClientInfo->"uptime")
|
||||
|
||||
:local totalRAM [/system/resource/get total-memory]
|
||||
:local freeRAM [/system/resource/get free-memory]
|
||||
:local usedRAM ($totalRAM - $freeRAM)
|
||||
:local ramUtilization (($usedRAM * 100) / $totalRAM)
|
||||
|
||||
/iot mqtt publish message="{\"cpu\": \"$cpuLoad\", \"ram\": \"$ramUtilization\", \"cpu_temp\": \"$cpuTemperature\", \"sfp_temp\": \"$sfpTemperature\", \"pppoe_telekom_uptime\": \"$pppoeUptime\"}" broker="mosquitto" topic="mikrotik-info"
|
||||
106
homelab/ubuntu-server-setup.sh
Normal file
106
homelab/ubuntu-server-setup.sh
Normal file
@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Simple installer for Ubuntu server:
|
||||
# - unattended-upgrades (security updates + automatic reboot)
|
||||
# - Docker (engine + compose plugin) per Docker docs steps 1-3
|
||||
# - zsh (set as default shell for original user)
|
||||
# - 1Password CLI for access to secrets
|
||||
# - secret-tools for storing tokens needed (i.e. for 1Password CLI)
|
||||
|
||||
# Must be run as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root: sudo bash $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect target user to set default shell for
|
||||
TARGET_USER="${SUDO_USER:-$(whoami)}"
|
||||
|
||||
apt-get update
|
||||
|
||||
# 1) Enable automatic security updates and automatic reboot
|
||||
apt-get install -y unattended-upgrades
|
||||
|
||||
# Enable periodic updates/unattended-upgrades
|
||||
cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
EOF
|
||||
|
||||
# Ensure automatic reboot after unattended-upgrades (time adjustable)
|
||||
cat > /etc/apt/apt.conf.d/99auto-reboot <<'EOF'
|
||||
Unattended-Upgrade::Automatic-Reboot "true";
|
||||
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
|
||||
EOF
|
||||
|
||||
# Start/enable unattended-upgrades (if system uses service/timer)
|
||||
if systemctl list-unit-files | grep -q unattended-upgrades; then
|
||||
systemctl enable --now unattended-upgrades || true
|
||||
fi
|
||||
|
||||
# 2) Install Docker (steps 1-3 from Docker docs)
|
||||
# Install prerequisites
|
||||
apt-get install -y ca-certificates curl gnupg lsb-release
|
||||
|
||||
# Create keyrings dir and add Docker GPG key
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
# Add Docker apt repository
|
||||
ARCH=$(dpkg --print-architecture)
|
||||
. /etc/os-release
|
||||
UBU_CODENAME="${UBUNTU_CODENAME:-$VERSION_CODENAME}"
|
||||
echo "deb [arch=${ARCH} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${UBU_CODENAME} stable" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
|
||||
apt-get update
|
||||
|
||||
# Install Docker Engine + plugins including compose plugin
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
# Verify Docker works by running hello-world (this will pull an image)
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
docker run --rm hello-world || true
|
||||
fi
|
||||
|
||||
# 3) Install zsh and make it the default shell for the target user
|
||||
apt-get install -y zsh
|
||||
|
||||
ZSH_PATH="$(which zsh)"
|
||||
if ! grep -q "^${ZSH_PATH}$" /etc/shells; then
|
||||
echo "${ZSH_PATH}" >> /etc/shells
|
||||
fi
|
||||
|
||||
# Change shell for target user (if possible)
|
||||
if id "${TARGET_USER}" >/dev/null 2>&1; then
|
||||
chsh -s "${ZSH_PATH}" "${TARGET_USER}" || echo "chsh failed for ${TARGET_USER}; you may need to run 'chsh -s ${ZSH_PATH} ${TARGET_USER}' manually"
|
||||
else
|
||||
echo "User ${TARGET_USER} not found; skipping chsh"
|
||||
fi
|
||||
|
||||
# 4) Install 1Password CLI for access to secrets
|
||||
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
|
||||
gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg && \
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/$(dpkg --print-architecture) stable main" | \
|
||||
tee /etc/apt/sources.list.d/1password.list && \
|
||||
mkdir -p /etc/debsig/policies/AC2D62742012EA22/ && \
|
||||
curl -sS https://downloads.1password.com/linux/debian/debsig/1password.pol | \
|
||||
tee /etc/debsig/policies/AC2D62742012EA22/1password.pol && \
|
||||
mkdir -p /usr/share/debsig/keyrings/AC2D62742012EA22 && \
|
||||
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
|
||||
gpg --dearmor --output /usr/share/debsig/keyrings/AC2D62742012EA22/debsig.gpg && \
|
||||
apt update && apt install 1password-cli
|
||||
|
||||
# Check successful install
|
||||
op --version
|
||||
|
||||
# 5) Install gnome-keyring secret-tool for securely storing tokens
|
||||
apt install pass gnupg2
|
||||
|
||||
|
||||
|
||||
|
||||
echo "Done. Recommended: log out and back in (or reboot) to start using zsh and ensure all services are active."
|
||||
86
homelab/vps/wg_monitor.sh
Normal file
86
homelab/vps/wg_monitor.sh
Normal file
@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
|
||||
# IP address to ping
|
||||
IP="10.10.10.6"
|
||||
|
||||
# Number of ping attempts
|
||||
COUNT=5
|
||||
|
||||
# Log file
|
||||
LOG_FILE="/var/log/wg_monitor.log"
|
||||
|
||||
# Function to log messages with timestamp
|
||||
log_message() {
|
||||
# Create log file if it doesn't exist and set proper permissions
|
||||
if [ ! -f "$LOG_FILE" ]; then
|
||||
touch "$LOG_FILE"
|
||||
chmod 644 "$LOG_FILE"
|
||||
fi
|
||||
local message="$(date '+%Y-%m-%d %H:%M:%S') - $1"
|
||||
echo "$message" >> "$LOG_FILE"
|
||||
echo "$message" # Also print to console for debugging
|
||||
}
|
||||
|
||||
# Always log script start
|
||||
log_message "Script started - pinging $IP"
|
||||
|
||||
# Ping the IP address
|
||||
echo "Executing ping command..."
|
||||
ping_result=$(ping -c $COUNT -W 5 $IP 2>&1)
|
||||
exit_code=$?
|
||||
|
||||
echo "Ping exit code: $exit_code"
|
||||
echo "Ping result:"
|
||||
echo "$ping_result"
|
||||
|
||||
# Check if ping failed completely
|
||||
if [ $exit_code -eq 1 ]; then
|
||||
log_message "Ping failed with exit code 1"
|
||||
|
||||
# Extract packet loss percentage
|
||||
packet_loss=$(echo "$ping_result" | grep -o '[0-9]*% packet loss' | grep -o '[0-9]*')
|
||||
|
||||
echo "Packet loss: $packet_loss%"
|
||||
log_message "Packet loss detected: $packet_loss%"
|
||||
|
||||
# If 100% packet loss, restart WireGuard
|
||||
if [ "$packet_loss" = "100" ]; then
|
||||
log_message "100% packet loss detected for $IP. Restarting WireGuard interface wg0..."
|
||||
|
||||
# Stop WireGuard interface
|
||||
echo "Stopping WireGuard..."
|
||||
/usr/bin/wg-quick down wg0
|
||||
down_result=$?
|
||||
|
||||
if [ $down_result -eq 0 ]; then
|
||||
log_message "WireGuard interface wg0 stopped successfully"
|
||||
else
|
||||
log_message "Failed to stop WireGuard interface wg0 (exit code: $down_result)"
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
|
||||
# Start WireGuard interface
|
||||
echo "Starting WireGuard..."
|
||||
/usr/bin/wg-quick up wg0
|
||||
up_result=$?
|
||||
|
||||
if [ $up_result -eq 0 ]; then
|
||||
log_message "WireGuard interface wg0 started successfully"
|
||||
else
|
||||
log_message "Failed to start WireGuard interface wg0 (exit code: $up_result)"
|
||||
fi
|
||||
else
|
||||
log_message "Ping failed but not 100% packet loss ($packet_loss%)"
|
||||
fi
|
||||
elif [ $exit_code -eq 0 ]; then
|
||||
# Ping successful
|
||||
log_message "Ping to $IP successful"
|
||||
echo "Ping successful"
|
||||
else
|
||||
log_message "Ping command failed with exit code $exit_code"
|
||||
echo "Ping failed with exit code $exit_code"
|
||||
fi
|
||||
|
||||
log_message "Script completed"
|
||||
echo "Script completed"
|
||||
Reference in New Issue
Block a user