initial commit
This commit is contained in:
commit
5f455cd8d4
19 changed files with 513 additions and 0 deletions
23
README.md
Normal file
23
README.md
Normal 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
84
_lib.sh
Normal 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
40
run.sh
Executable 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
34
scripts/base/script.sh
Normal 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;
|
9
scripts/caddy/Caddyfile.templ
Normal file
9
scripts/caddy/Caddyfile.templ
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
acme_dns hetzner $hetzner_secret
|
||||
}
|
||||
|
||||
https://$domain {
|
||||
respond "Hello world"
|
||||
}
|
||||
|
||||
import /etc/caddy/conf.d/*
|
37
scripts/caddy/caddy.service
Normal file
37
scripts/caddy/caddy.service
Normal 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
49
scripts/caddy/script.sh
Normal 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;
|
6
scripts/jellyfin/Caddyfile.templ
Normal file
6
scripts/jellyfin/Caddyfile.templ
Normal 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
|
||||
}
|
||||
|
27
scripts/jellyfin/docker-compose.yml.templ
Normal file
27
scripts/jellyfin/docker-compose.yml.templ
Normal 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]
|
44
scripts/jellyfin/script.sh
Normal file
44
scripts/jellyfin/script.sh
Normal 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
|
7
scripts/nextcloud/Caddyfile.templ
Normal file
7
scripts/nextcloud/Caddyfile.templ
Normal file
|
@ -0,0 +1,7 @@
|
|||
https://cloud.$domain {
|
||||
reverse_proxy https://localhost:4987 {
|
||||
transport http {
|
||||
tls_insecure_skip_verify
|
||||
}
|
||||
}
|
||||
}
|
15
scripts/nextcloud/docker-compose.yml.templ
Normal file
15
scripts/nextcloud/docker-compose.yml.templ
Normal 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
|
28
scripts/nextcloud/script.sh
Normal file
28
scripts/nextcloud/script.sh
Normal 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
|
5
scripts/paperlessngx/Caddyfile.templ
Normal file
5
scripts/paperlessngx/Caddyfile.templ
Normal 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
|
||||
}
|
36
scripts/paperlessngx/docker-compose.env.templ
Normal file
36
scripts/paperlessngx/docker-compose.env.templ
Normal 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
|
26
scripts/paperlessngx/docker-compose.yml
Normal file
26
scripts/paperlessngx/docker-compose.yml
Normal 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
|
29
scripts/paperlessngx/script.sh
Normal file
29
scripts/paperlessngx/script.sh
Normal 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
|
1
scripts/template/greeting.templ
Normal file
1
scripts/template/greeting.templ
Normal file
|
@ -0,0 +1 @@
|
|||
hello $person
|
13
scripts/template/script.sh
Normal file
13
scripts/template/script.sh
Normal 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
|
Loading…
Reference in a new issue