initial commit

This commit is contained in:
bain 2022-12-31 14:19:03 +01:00
commit 5f455cd8d4
19 changed files with 513 additions and 0 deletions

23
README.md Normal file
View file

@ -0,0 +1,23 @@
# Homelab setup scripts
Everything is homebaked, because it's at my house!
## Dependencies
- python 3, bash, and coreutils
## Creating & running scripts
Every script has a directory inside `scripts/`. They must be ran through the `run.sh` script.
Usually scripts are checkpointed. Each checkpoint is a function beginning with `_ch_`. It is then
collected and run by calling `_run_checkpoints`. They will be run in alphabetical order and if
one fails, the script will stop execution. You can then make changes, or fix the issue externally,
and then run the script again. It will skip all successfully completed checkpoints.
`_lib.sh` should always be imported at the start of the script. It contains all the helper functions.
See `scripts/template` for an example.
## Unattended?
Not really, but most steps should be automatic

84
_lib.sh Normal file
View file

@ -0,0 +1,84 @@
#!/bin/bash
# Useful functions for all scripts
set -euo pipefail
_err() {
echo "ERR: $@";
exit 1;
}
# Shortcut for mktemp
_mktmp() {
mktemp -p "$HL_TMP_DIR" $@;
}
# Substitute env variables in a file using python template strings
# all env vars must be prefixed with "HL_", and are case insensitive
# Ex:
# -- file.txt
# hello $person
#
# -- script.sh
# export HL_PERSON="world";
# cat $(_fill file.txt)
#
# Output: "hello world"
_fill() {
OUT="$(_mktmp)"
python3 - $1 > "$OUT" <<EOF
import os
import sys
from string import Template
from pathlib import Path
file = Path(sys.argv[1]).read_text()
mapping = {k[3:].lower(): v for k, v in os.environ.items() if k.startswith("HL_")}
sys.stdout.write(Template(file).substitute(mapping))
sys.stdout.flush()
EOF
echo "$OUT"
}
# Decrypt a file
_decrypt() {
OUT="$(_mktmp)"
age --decrypt --identity "$HL_AGE_IDENTITY" --output "$OUT" "$1"
echo "$OUT"
}
# Encrypt a file
_encrypt() {
OUT="$(_mktmp)"
PUB=`ssh-keygen -y -f "$HL_AGE_IDENTITY"`
age --encrypt --recipient "$PUB" --armor --output "$OUT" "$1"
echo "$OUT"
}
# Checks if all env vars are filled
_assert_vars() {
for var in "$@"; do
if ! [ -v $var ]; then
_err "$var is not set";
fi;
done;
}
# Run a checkpointed script. In it should only be functions
# beginning with "_ch_". These will get run in alphabetical order.
_run_checkpoints() {
FUNCS="$(declare -F | awk '{ print $3 }' | grep -E '^_ch_' | sort)";
touch run.checkpoints;
for f in $FUNCS; do
pushd "$(pwd)" > /dev/null;
if grep -q -E "^$f$" run.checkpoints; then
echo "skipping $f, already run";
popd > /dev/null;
else
echo "running $f";
$f
popd > /dev/null;
echo "$f" >> run.checkpoints;
fi;
done;
}
_assert_vars HL_TMP_DIR

40
run.sh Executable file
View file

@ -0,0 +1,40 @@
#!/bin/bash
# Scripts runner
set -euo pipefail;
export HL_ROOT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd );
export HL_AGE_IDENTITY="${HL_AGE_IDENTITY:-$HL_ROOT_DIR/ident}";
export HL_LIB="$HL_ROOT_DIR/_lib.sh";
# create private temp dir
export HL_TMP_DIR="$(mktemp -d)";
chmod a-rwx,u+rwx "$HL_TMP_DIR";
pushd "$HL_ROOT_DIR" > /dev/null;
for var in "$@"; do
sudo whoami > /dev/null # pre-authorize sudo
pushd scripts/$var > /dev/null;
if [ "${DONT_HIDE:-0}" -eq "1" ]; then
echo "" | bash script.sh;
else
TEMP_OUT=$(mktemp);
chmod a-rwx,u+rw "$TEMP_OUT";
echo -n "scripts/base... ";
OK=0
echo "" | bash script.sh > "$TEMP_OUT" 2>&1 || OK=1;
if [ $OK -eq 0 ]; then
echo -e "\033[32mOK\033[0m";
else
echo -e "\033[1;31mFAILED\033[0m";
tail -n 10 "$TEMP_OUT" | sed 's/^/ /';
echo -e "\n\033[1mfull log:\033[0m $TEMP_OUT";
echo "temp dir not removed $HL_TMP_DIR";
fi;
fi;
popd > /dev/null;
done
popd > /dev/null
# remove temp dir
rm -r "$HL_TMP_DIR"

34
scripts/base/script.sh Normal file
View file

@ -0,0 +1,34 @@
#!/bin/bash
set -euo pipefail;
source "$HL_LIB";
# update and install dependencies
_ch_001-install() {
sudo apt-get update;
sudo apt-get upgrade;
sudo apt-get install -y docker docker-compose screenfetch vim;
}
# give myself docker privileges
_ch_002-usermod() {
sudo usermod -aG docker $USER;
# a bit of a hack, mark this checkpoint as completed but exit execution
echo "_ch_002-usermod" >> run.checkpoints
_err "please relogin"
}
# create data folder with correct perms
_ch_003-datafolder() {
sudo mkdir -p /data;
sudo chgrp $USER /data;
sudo chmod g+w /data;
}
_ch_004-firewall() {
sudo apt-get install -y ufw;
sudo ufw allow 22;
}
_run_checkpoints;

View file

@ -0,0 +1,9 @@
{
acme_dns hetzner $hetzner_secret
}
https://$domain {
respond "Hello world"
}
import /etc/caddy/conf.d/*

View file

@ -0,0 +1,37 @@
# caddy.service
#
# For using Caddy with a config file.
#
# Make sure the ExecStart and ExecReload commands are correct
# for your installation.
#
# See https://caddyserver.com/docs/install for instructions.
#
# WARNING: This service does not use the --resume flag, so if you
# use the API to make changes, they will be overwritten by the
# Caddyfile next time the service is restarted. If you intend to
# use Caddy's API to configure it, add the --resume flag to the
# `caddy run` command or use the caddy-api.service file instead.
[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target
[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateDevices=yes
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target

49
scripts/caddy/script.sh Normal file
View file

@ -0,0 +1,49 @@
#!/bin/bash
set -euo pipefail;
source "$HL_LIB";
_assert_vars HL_HETZNER_SECRET HL_DOMAIN;
_ch_001-download_binary() {
sudo curl -L "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fcaddy-dns%2Fhetzner&idempotency=76765041209660" -o /usr/bin/caddy;
sudo chmod +x /usr/bin/caddy;
}
# fill and copy config
_ch_002-add_config() {
sudo mkdir -p /etc/caddy/conf.d;
sudo cp $(_fill Caddyfile.templ) /etc/caddy/Caddyfile;
sudo chmod a+rx /etc/caddy;
sudo chgrp $USER /etc/caddy/Caddyfile;
sudo chmod a+r,g+w /etc/caddy/Caddyfile;
sudo chgrp $USER /etc/caddy/conf.d;
sudo chmod a+rx,g+w /etc/caddy/conf.d;
}
# create data folder with correct perms
_ch_003-create_user() {
sudo groupadd --system caddy;
sudo useradd --system \
--gid caddy \
--create-home \
--home-dir /var/lib/caddy \
--shell /usr/sbin/nologin \
--comment "Caddy web server" \
caddy;
}
_ch_004-create_service() {
sudo cp caddy.service /etc/systemd/system/caddy.service;
sudo systemctl daemon-reload;
sudo systemctl enable --now caddy;
}
_ch_005-allow_firewall() {
if command -v ufw &> /dev/null; then
sudo ufw allow 80;
sudo ufw allow 443;
fi;
}
_run_checkpoints;

View file

@ -0,0 +1,6 @@
https://film.$domain {
@blocked not remote_ip 10.0.0.0/24 10.0.89.0/24
respond @blocked "Internal use only" 403
reverse_proxy http://localhost:8096
}

View file

@ -0,0 +1,27 @@
version: '3.5'
services:
jellyfin:
image: jellyfin/jellyfin
user: 1000:1000
container_name: jellyfin
ports:
- 127.0.0.1:8096:8096
- 7359:7359/udp
- 1900:1900/udp
volumes:
- /data/jellyfin/config:/config
- /data/jellyfin/cache:/cache
- /data/jellyfin/media/films:/media/films
- /data/jellyfin/media/shows:/media/shows
restart: 'unless-stopped'
# Optional - alternative address used for autodiscovery
environment:
- JELLYFIN_PublishedServerUrl=https://film.$domain
# Optional - may be necessary for docker healthcheck to pass if running in host network mode
extra_hosts:
- "host.docker.internal:host-gateway"
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]

View file

@ -0,0 +1,44 @@
#!/bin/bash
set -euo pipefail
source "$HL_LIB"
_assert_vars HL_DOMAIN;
_ch_001-install_nvidia_drivers() {
sudo apt-get install -y nvidia-driver-515-server
echo "_ch_001-install_nvidia_drivers" >> run.checkpoints
_err "installed nvidia drivers; please reboot"
}
_ch_002-install_nvidia_containers() {
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
sudo apt-get install -y nvidia-docker2
sudo systemctl restart docker
}
_ch_003-make_dirs() {
mkdir -p ~/jellyfin /data/jellyfin;
}
_ch_004-copy_compose() {
cp $(_fill docker-compose.yml.templ) ~/jellyfin/docker-compose.yml;
}
_ch_005-update_caddy() {
cp $(_fill Caddyfile.templ) /etc/caddy/conf.d/jellyfin.Caddyfile;
chmod a+r /etc/caddy/conf.d/jellyfin.Caddyfile;
sudo systemctl reload caddy.service;
}
_ch_006-run-service() {
cd ~/jellyfin;
docker-compose up -d;
}
_run_checkpoints

View file

@ -0,0 +1,7 @@
https://cloud.$domain {
reverse_proxy https://localhost:4987 {
transport http {
tls_insecure_skip_verify
}
}
}

View file

@ -0,0 +1,15 @@
version: "2.1"
services:
nextcloud:
image: lscr.io/linuxserver/nextcloud:latest
container_name: nextcloud
environment:
- PUID=1000
- PGID=1000
- TZ=$timezone
volumes:
- /data/nextcloud/appdata:/config
- /data/nextcloud/data:/data
ports:
- 4987:443
restart: unless-stopped

View file

@ -0,0 +1,28 @@
#!/bin/bash
set -euo pipefail
source "$HL_LIB"
_assert_vars HL_TIMEZONE HL_DOMAIN;
_ch_001-make_dirs() {
mkdir -p ~/nextcloud /data/nextcloud;
}
_ch_002-copy_compose() {
cp $(_fill docker-compose.yml.templ) ~/nextcloud/docker-compose.yml;
}
_ch_003-update_caddy() {
cp $(_fill Caddyfile.templ) /etc/caddy/conf.d/nextcloud.Caddyfile;
chmod a+r /etc/caddy/conf.d/nextcloud.Caddyfile;
sudo systemctl reload caddy.service;
}
_ch_004-run_service() {
cd ~/nextcloud;
docker-compose up -d;
}
_run_checkpoints

View file

@ -0,0 +1,5 @@
https://paperless.$domain {
@blocked not remote_ip 10.0.0.0/24 10.0.89.0/24
respond @blocked "Internal use only" 403
reverse_proxy http://localhost:8097
}

View file

@ -0,0 +1,36 @@
# The UID and GID of the user used to run paperless in the container. Set this
# to your UID and GID on the host so that you have write access to the
# consumption directory.
#USERMAP_UID=1000
#USERMAP_GID=1000
# Additional languages to install for text recognition, separated by a
# whitespace. Note that this is
# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the
# language used for OCR.
# The container installs English, German, Italian, Spanish and French by
# default.
# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
# for available languages.
PAPERLESS_OCR_LANGUAGES=ces
###############################################################################
# Paperless-specific settings #
###############################################################################
# All settings defined in the paperless.conf.example can be used here. The
# Docker setup does not use the configuration file.
# A few commonly adjusted settings are provided below.
# Adjust this key if you plan to make paperless available publicly. It should
# be a very long sequence of random characters. You don't need to remember it.
PAPERLESS_SECRET_KEY=$paperless_secret
# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC.
PAPERLESS_TIME_ZONE=$timezone
# The default language to use for OCR. Set this to the language most of your
# documents are written in.
PAPERLESS_OCR_LANGUAGE=ces
PAPERLESS_URL=https://paperless.$domain

View file

@ -0,0 +1,26 @@
version: "3.4"
services:
broker:
image: redis:6.0
restart: unless-stopped
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped
depends_on:
- broker
ports:
- 127.0.0.1:8097:8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- /data/paperlessngx/data:/usr/src/paperless/data
- /data/paperlessngx/media:/usr/src/paperless/media
- /data/paperlessngx/export:/usr/src/paperless/export
- /data/paperlessngx/consume:/usr/src/paperless/consume
env_file: docker-compose.env
environment:
PAPERLESS_REDIS: redis://broker:6379

View file

@ -0,0 +1,29 @@
#!/bin/bash
set -euo pipefail
source "$HL_LIB"
_assert_vars HL_TIMEZONE HL_DOMAIN;
_ch_001-make_dirs() {
mkdir -p ~/paperlessngx /data/paperlessngx;
}
_ch_002-copy_compose() {
cp docker-compose.yml ~/paperlessngx/docker-compose.yml;
export HL_PAPERLESS_SECRET="$(openssl rand -hex 24)";
cp $(_fill docker-compose.env.templ) ~/paperlessngx/docker-compose.env;
}
_ch_003-update_caddy() {
cp $(_fill Caddyfile.templ) /etc/caddy/conf.d/paperlessngx.Caddyfile;
chmod a+r /etc/caddy/conf.d/paperlessngx.Caddyfile;
sudo systemctl reload caddy.service;
}
_ch_004-run_service() {
cd ~/paperlessngx;
docker-compose up -d;
}
_run_checkpoints

View file

@ -0,0 +1 @@
hello $person

View file

@ -0,0 +1,13 @@
#!/bin/bash
# Template script not meant to be run
set -euo pipefail
source "$HL_LIB"
_assert_vars HL_PERSON
_ch_001-cat_filled_greeting() {
cat $(_fill greeting.templ);
}
_run_checkpoints