commit 5f455cd8d4e4d7d34917c340c136cb3c79ad55c6 Author: bain Date: Sat Dec 31 14:19:03 2022 +0100 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d4404e --- /dev/null +++ b/README.md @@ -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 diff --git a/_lib.sh b/_lib.sh new file mode 100644 index 0000000..9ea1c92 --- /dev/null +++ b/_lib.sh @@ -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" < /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 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..f9ece05 --- /dev/null +++ b/run.sh @@ -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" diff --git a/scripts/base/script.sh b/scripts/base/script.sh new file mode 100644 index 0000000..f4c49aa --- /dev/null +++ b/scripts/base/script.sh @@ -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; diff --git a/scripts/caddy/Caddyfile.templ b/scripts/caddy/Caddyfile.templ new file mode 100644 index 0000000..b80a752 --- /dev/null +++ b/scripts/caddy/Caddyfile.templ @@ -0,0 +1,9 @@ +{ + acme_dns hetzner $hetzner_secret +} + +https://$domain { + respond "Hello world" +} + +import /etc/caddy/conf.d/* diff --git a/scripts/caddy/caddy.service b/scripts/caddy/caddy.service new file mode 100644 index 0000000..618eaf7 --- /dev/null +++ b/scripts/caddy/caddy.service @@ -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 diff --git a/scripts/caddy/script.sh b/scripts/caddy/script.sh new file mode 100644 index 0000000..af9d562 --- /dev/null +++ b/scripts/caddy/script.sh @@ -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; diff --git a/scripts/jellyfin/Caddyfile.templ b/scripts/jellyfin/Caddyfile.templ new file mode 100644 index 0000000..b098834 --- /dev/null +++ b/scripts/jellyfin/Caddyfile.templ @@ -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 +} + diff --git a/scripts/jellyfin/docker-compose.yml.templ b/scripts/jellyfin/docker-compose.yml.templ new file mode 100644 index 0000000..1b6a84c --- /dev/null +++ b/scripts/jellyfin/docker-compose.yml.templ @@ -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] diff --git a/scripts/jellyfin/script.sh b/scripts/jellyfin/script.sh new file mode 100644 index 0000000..e0b75bd --- /dev/null +++ b/scripts/jellyfin/script.sh @@ -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 diff --git a/scripts/nextcloud/Caddyfile.templ b/scripts/nextcloud/Caddyfile.templ new file mode 100644 index 0000000..1e87c8b --- /dev/null +++ b/scripts/nextcloud/Caddyfile.templ @@ -0,0 +1,7 @@ +https://cloud.$domain { + reverse_proxy https://localhost:4987 { + transport http { + tls_insecure_skip_verify + } + } +} diff --git a/scripts/nextcloud/docker-compose.yml.templ b/scripts/nextcloud/docker-compose.yml.templ new file mode 100644 index 0000000..3ddab90 --- /dev/null +++ b/scripts/nextcloud/docker-compose.yml.templ @@ -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 diff --git a/scripts/nextcloud/script.sh b/scripts/nextcloud/script.sh new file mode 100644 index 0000000..639cf46 --- /dev/null +++ b/scripts/nextcloud/script.sh @@ -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 diff --git a/scripts/paperlessngx/Caddyfile.templ b/scripts/paperlessngx/Caddyfile.templ new file mode 100644 index 0000000..4a86c29 --- /dev/null +++ b/scripts/paperlessngx/Caddyfile.templ @@ -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 +} diff --git a/scripts/paperlessngx/docker-compose.env.templ b/scripts/paperlessngx/docker-compose.env.templ new file mode 100644 index 0000000..ca87d75 --- /dev/null +++ b/scripts/paperlessngx/docker-compose.env.templ @@ -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 diff --git a/scripts/paperlessngx/docker-compose.yml b/scripts/paperlessngx/docker-compose.yml new file mode 100644 index 0000000..0717998 --- /dev/null +++ b/scripts/paperlessngx/docker-compose.yml @@ -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 diff --git a/scripts/paperlessngx/script.sh b/scripts/paperlessngx/script.sh new file mode 100644 index 0000000..9821fff --- /dev/null +++ b/scripts/paperlessngx/script.sh @@ -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 diff --git a/scripts/template/greeting.templ b/scripts/template/greeting.templ new file mode 100644 index 0000000..9bf756a --- /dev/null +++ b/scripts/template/greeting.templ @@ -0,0 +1 @@ +hello $person diff --git a/scripts/template/script.sh b/scripts/template/script.sh new file mode 100644 index 0000000..9d22634 --- /dev/null +++ b/scripts/template/script.sh @@ -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