commit 8176e71b8144aa7810db1f1d96778af349389464 Author: Lukas Stancik Date: Thu Feb 27 13:59:58 2025 +0000 Initial commit Basic working code that is capable of establishing the Wireguard tunnel given that there is properly configured `.psk` file for at least one known wifi network (see README.md for more details) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c59aaca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab + +[*.mk] +indent_style = tab + +[*.start] +indent_style = tab diff --git a/.env b/.env new file mode 100644 index 0000000..48c6e11 --- /dev/null +++ b/.env @@ -0,0 +1,26 @@ +BUILDER_UID=<%= $(id -u) %> +BUILDER_GID=<%= $(id -g) %> +BUILDER_USER=<%= $(whoami) %> +BUILDER_DOCKER_IMAGE=repository-builder +BUILDER_KEY_BASENAME=packager +CACHE_DIR=<%= $(pwd) %>/cache +ALPINE_MIRROR=https://dl-cdn.alpinelinux.org/alpine +ALPINE_ARCH=aarch64 +ALPINE_VER_MAJOR=3 +ALPINE_VER_MINOR=20 +ALPINE_VER_PATCH=3 +REQUIREMENTS_FILE=required-apks.txt +eth0=eth0 +wlan0=wlan0 +wlan1=wlan1 +wg0=wg0 +wg_port=51820 +wg_server=31.31.79.229 +hostname=entropi +ssid=entropi_nomap +wlan0_base=192.168.66 +wg0_base=10.0.22 +ROUTER_TIMEZONE=Europe/Prague +ROUTER_DNS_PRIMARY=1.1.1.1 +ROUTER_NTP_PRIMARY=195.113.144.201 +ROUTER_NTP_SECONDARY=195.113.144.238 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b102aa0 --- /dev/null +++ b/Makefile @@ -0,0 +1,105 @@ +.DEFAULT_GOAL:=build + +TEMP_DIR=$(shell ./preflight) +DIST_DIR=${TEMP_DIR}/dist +DOT_ENV_FILE=${TEMP_DIR}/.env +DOT_ENV_PAIRS=$(shell cat ${DOT_ENV_FILE}) +BUILD_ARGS=$(shell for ARG in ${DOT_ENV_PAIRS}; do echo "--build-arg $$ARG"; done;) + +-include ${DOT_ENV_FILE} + +vault/encrypt: + @echo "### Encrypting secrets..." + @./vault encrypt + @echo "### Encrypting done" + +vault/decrypt: + @echo "### Decrypting secrets..." + @./vault decrypt + @echo "### Decrypting done" + +init: + @echo "### Init..." + @./require ${DOT_ENV_FILE} alpine_source + @./require ${DOT_ENV_FILE} tailspin_bin + @./require ${DOT_ENV_FILE} age_bin + @echo "### Init done" + -@make vault/decrypt + +build: repository + @echo "### Assembling..." + @rsync -chaz ${CACHE_DIR}/${RELEASE_BASENAME}/ ${DIST_DIR} + @mkdir -p ${TEMP_DIR}/overlay_headless/etc/local.d + @mkdir -p ${TEMP_DIR}/overlay_headless/etc/runlevels/default + @touch ${TEMP_DIR}/overlay_headless/etc/.default_boot_services + -@ln -s /etc/init.d/local ${TEMP_DIR}/overlay_headless/etc/runlevels/default/local + @./src/esh -o ${TEMP_DIR}/overlay_headless/etc/local.d/headless.start src/headless.start ${DOT_ENV_PAIRS} + @chmod +x ${TEMP_DIR}/overlay_headless/etc/local.d/headless.start + @tar czf ${DIST_DIR}/headless.apkovl.tar.gz -C ${TEMP_DIR}/overlay_headless/ . + @rsync -chaz ${CACHE_DIR}/aport/${RELEASE_VERSION}/main/ ${DIST_DIR}/extras + @cp ${CACHE_DIR}/tailspin/tspin ${DIST_DIR} + @./src/esh -o ${DIST_DIR}/hotspot.conf src/hotspot.conf ${DOT_ENV_PAIRS} + @./src/esh -o ${DIST_DIR}/wg0.conf src/wg0.conf ${DOT_ENV_PAIRS} + @cp secrets/*.psk ${DIST_DIR} + @cp src/authorized_keys ${DIST_DIR} + @cp src/config.toml ${DIST_DIR} + @cp src/logpattern.conf ${DIST_DIR} + @cp src/main.conf ${DIST_DIR} + @cp src/rsyslog.conf ${DIST_DIR} + @cp src/usercfg.txt ${DIST_DIR} + @cp src/watchdog.sh ${DIST_DIR} + @cp src/repository-builder/packager.rsa.pub ${DIST_DIR} + @cp src/repository-builder/${REQUIREMENTS_FILE} ${DIST_DIR} + @echo "### Assembling done" + @make vault/encrypt + +card: clean build + @echo "### Copying to sdcard..." + @rsync -chavz --delete ${DIST_DIR}/ /media/$(shell whoami)/entropi + @echo "### Copying to sdcard done" + -@make clean + +clean: + @echo "### Cleaning..." + -@rm -rf ${DIST_DIR} + -@rm -rf tmp* + -@rm -f ${CACHE_DIR}/aport/${RELEASE_VERSION}/main/${ALPINE_ARCH}/APKINDEX.tar.gz + -@docker rmi ${BUILDER_DOCKER_IMAGE} 1> /dev/null + @echo "### Cleaning done" + +mrproper: clean + @echo "### Purging cache..." + -@rm -rf ${CACHE_DIR} + @echo "### Done" + +repository-builder: init + @mkdir -p ${CACHE_DIR}/aport/${RELEASE_VERSION}/main/ + @rsync -chaz ${CACHE_DIR}/${RELEASE_BASENAME}/apks/ ${CACHE_DIR}/aport/${RELEASE_VERSION}/main/ + + @tar \ + --exclude='.git' \ + -czh ./src/repository-builder | docker build -f ./src/repository-builder/repository.Dockerfile \ + --build-arg ALPINE_DOCKER_IMAGE=${ALPINE_DOCKER_IMAGE} \ + --build-arg REQUIREMENTS_FILE=${REQUIREMENTS_FILE} \ + --build-arg BUILDER_USER=${BUILDER_USER} \ + --build-arg BUILDER_UID=${BUILDER_UID} \ + --build-arg BUILDER_GID=${BUILDER_GID} \ + -t ${BUILDER_DOCKER_IMAGE} - + +repository: repository-builder + @echo "### Repository..." + @wget --quiet --spider https://alpinelinux.org/ && \ + docker run --rm -ti \ + -e ALPINE_ARCH=${ALPINE_ARCH} \ + -e RELEASE_VERSION=${RELEASE_VERSION} \ + -e REQUIREMENTS_FILE=${REQUIREMENTS_FILE} \ + -e BUILDER_KEY_BASENAME=${BUILDER_KEY_BASENAME} \ + -e BUILDER_UID=${BUILDER_UID} \ + -e BUILDER_GID=${BUILDER_GID} \ + -v $(PWD)/src/repository-builder/packager.rsa:/root/.abuild/packager.rsa \ + -v $(PWD)/src/repository-builder/packager.rsa.pub:/root/.abuild/packager.rsa.pub \ + -v $(PWD)/src/repository-builder/abuild.conf:/root/.abuild/abuild.conf \ + -v ${CACHE_DIR}/aport:/mirror \ + ${BUILDER_DOCKER_IMAGE} build-repository \ + || echo "No Internet!!! Skipping repository..." + @echo "### Repository done" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5f19e2 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Wireguard Road Warrior setup for RaspberryPi 4 + +Code in this repository produces files for bootable Micro-SD card for RaspberryPi 4. During the boot it creates `entropi_nomap` WIFI access point (hotspot) that clients can connect to. It then encrypts all client traffic through Wireguard tunnel via any known WIFI network in reach. This is supposed to be a headless Alpine Linux router essentially. There is no web admin interface - only SSH access from the client side using public keys (or, you know, physical access with keyboard and monitor attached to the RaspberryPi). + +## Requirements + + - RaspberryPi + - WIFI over USB dongle + - USB-C cable for powering RaspberryPi (could be plugged directly into a laptop) + - Micro-SD card (I use 2GB and that's plenty big) - Formatted with single FAT32 partition named `entropi` + - Properly set up machine running Wireguard peer instance reachable from the Internet + +## Usage + + 1. Clone this repository + 1. Edit `.env` file + 1. Create `.vault_password_file` and put a plain text password inside (used for decrypting/encrypting wireguard keys and other stuff) + 1. Replace all files listed as `FILES_IN_VAULT` inside the `vault` script with appropriate contents + 1. Create known networks files (see below) + 1. Insert MicroSD card into your computer and make sure it is mounted under /media/{user}/entropi (edit the Makefile if necessary) + 1. Run `make card` + 1. Eject the card and use it to boot RaspberryPi (after a minute or two you should see it provides your wifi) + +### Known WIFI networks setup + +Create `secrets` folder and put plain text files inside named `{KNOWN_WIFI_SSID}.psk` where `{KNOWN_WIFI_SSID}` is the name of each wifi you want to be used to tunnel through (coffee shops, your ISP etc.). Contents of each file needs to be following: + +``` +[Security] +Passphrase={KNOWN_WIFI_PASSWORD} +AlwaysRandomizeAddress=true + +[Settings] +AutoConnect=true +``` + +All the files will be automatically picked up by the wifi daemon after the RaspberryPi finishes boot process diff --git a/preflight b/preflight new file mode 100755 index 0000000..5a37e66 --- /dev/null +++ b/preflight @@ -0,0 +1,36 @@ +#! /bin/sh + +set -eu + +# Let's find out if a temporary build directory exists already +BUILD_DIR=$(find . -type d -name "tmp*" -print -quit 2>/dev/null) + +# Create it if it doesn't +if ! echo "$BUILD_DIR" | grep -q .; then + BUILD_DIR=$(mktemp --tmpdir=. --directory tmp.build.XXXXXXXXXX) +fi + +# Preprocess the dotenv file +./src/esh -o "$BUILD_DIR"/.env ./.env + +# Load the contents of the preprocessed dotenv file in order to +# use the values for init stage below +# https://www.shellcheck.net/wiki/SC1090 +# shellcheck source=/dev/null +. "$BUILD_DIR"/.env + +echo " +RELEASE_BASENAME=alpine-rpi-$ALPINE_VER_MAJOR.$ALPINE_VER_MINOR.$ALPINE_VER_PATCH-$ALPINE_ARCH +RELEASE_VERSION=v$ALPINE_VER_MAJOR.$ALPINE_VER_MINOR +ALPINE_DOCKER_IMAGE=alpine:$ALPINE_VER_MAJOR.$ALPINE_VER_MINOR.$ALPINE_VER_PATCH + +wlan0_cidr=$wlan0_base.0/24 +wlan0_address=$wlan0_base.1 +wlan0_range_start=$wlan0_base.2 +wlan0_range_end=$wlan0_base.254 + +wg0_cidr=$wg0_base.0/24 +wg0_address=$wg0_base.12 +wg0_gateway=$wg0_base.1" >> "$BUILD_DIR"/.env + +echo "$BUILD_DIR" diff --git a/require b/require new file mode 100755 index 0000000..b86e100 --- /dev/null +++ b/require @@ -0,0 +1,41 @@ +#!/bin/sh +set -e + +# First argument is preprocessed .env file supposed to be in tmp.build.XXXXXXXXXX directory +# Let's load it up +. $1 + +alpine_source() { + find ${CACHE_DIR} -type f -name ${RELEASE_BASENAME}.tar.gz | grep -q ${RELEASE_BASENAME} || \ + curl -sSL ${ALPINE_MIRROR}/${RELEASE_VERSION}/releases/${ALPINE_ARCH}/${RELEASE_BASENAME}.tar.gz -o ${CACHE_DIR}/${RELEASE_BASENAME}.tar.gz + + mkdir -p ${CACHE_DIR}/${RELEASE_BASENAME} + + find ${CACHE_DIR}/${RELEASE_BASENAME} -type d -empty -exec tar xfz ${CACHE_DIR}/${RELEASE_BASENAME}.tar.gz -C ${CACHE_DIR}/${RELEASE_BASENAME} \; +} + +tailspin_bin() { + find ${CACHE_DIR} -type f -name tailspin.tar.gz | grep -q tailspin || \ + curl -sSL https://github.com/bensadeh/tailspin/releases/download/4.0.0/tailspin-x86_64-unknown-linux-musl.tar.gz -o ${CACHE_DIR}/tailspin.tar.gz + + mkdir -p ${CACHE_DIR}/tailspin + + find ${CACHE_DIR}/tailspin -type d -empty -exec tar xfz ${CACHE_DIR}/tailspin.tar.gz -C ${CACHE_DIR}/tailspin \; +} + +age_bin() { + find ${CACHE_DIR} -type d -name age-src | grep -q age-src || \ + git clone https://github.com/wj/age.git ${CACHE_DIR}/age-src + + mkdir -p ${CACHE_DIR}/age + + find ${CACHE_DIR}/age -type d -empty -exec docker run --rm -v ${CACHE_DIR}/age:/usr/local/bin -e GOBIN=/usr/local/bin/ -v ${CACHE_DIR}/age-src:/usr/src/age -w /usr/src/age golang:1.22-alpine go install ./cmd/age \; +} + +mkdir -p secrets +mkdir -p ${CACHE_DIR}/aport +mkdir -p ${CACHE_DIR}/extras + +# Second argument is supposed to be name of the functions decalred above +# Let's call it +$2 diff --git a/src/authorized_keys b/src/authorized_keys new file mode 100644 index 0000000..c5b349d --- /dev/null +++ b/src/authorized_keys @@ -0,0 +1,14 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCBWcXR2SHJWTkIydlB2SHg4 +eXYwSHRRIDE4CkNvMG1MTkMxeEZvbkRDd1dSMzFaM3FtOEk1WVhKMUNVQm11T1Vk +YUpoY2cKLS0tIExHOW1ldWh0ZEY0MTdqZFNrR2c4OHpDcUpLNldaNTF3Y3B1L1dy +REt4SXcKzHFocRvKS4/XoF9XqntZJ537DswygYkETQ64qBcBZR45MF5WP/qGmz6p +YZ6S4KOOTJpgibcWht9zzAgaBI1pvGx61lM7StxOoUPpKJGbpx2vxm5T3hljf5GP +1veT6YHwFbVMHBRWNcgFji+ZVloOWC9rhHlF6tjzpY2XlWA+7YBH5OzMnPj8Wysu +JDp7Zx9XJ6Y3vZsLXOWlb36/22hhNRFCvnNA6Mc1RUosbc4PhjMTnh9jeO6sbhGp +kTM9Rq023c8Q8BuKhyJwNEVkOF+l/CvUmm5GGr6+P59/bjvrGseqJeCTESQ0Nlzh +dmm10NRzMKnqHSHUdvtCa5GEoD1NgnNh6cMTCyJr4yIJx6iwcr1G0vuX7iE7THtD +EH/MYowZ1oRPVE60ghWKTiY2bHQCT7VxZX81QVBdIw9LDey4b2MQdsni/2i2sO53 +cUqL3CzO559wiCwPm2gULjuS/QmG9rwfMks6wGZX/intyel2o1yqofyx+tdyxQtB +9qBZ1bEsREB4jUeK8uZJYjiJJgSemKs4sxJV0KdpAffryb8= +-----END AGE ENCRYPTED FILE----- diff --git a/src/config.toml b/src/config.toml new file mode 100644 index 0000000..4189fe1 --- /dev/null +++ b/src/config.toml @@ -0,0 +1,82 @@ +# style = { fg = "color", bg = "color", italic = false, bold = false, underline = false } + +[date] +disabled = true + +[date_word] # e.g. "Jan 01", "Mon Feb 28" +disabled = true + +[time] +disabled = true + +[[keywords]] +words = ['INBOUND'] +style = { fg = "cyan", faint = true } + +[[keywords]] +words = ['OUTBOUND'] +style = { fg = "cyan", faint = false } + +[[keywords]] +words = ['DROP'] +style = { fg = "red" } + +[[keywords]] +words = ['REJECT'] +style = { fg = "yellow" } + +[[keywords]] +words = ['ACCEPT'] +style = { fg = "green" } + +[[keywords]] +words = ['TCP'] +style = { fg = "blue" } + +[[keywords]] +words = ['UDP'] +style = { fg = "magenta" } + +[[regexps]] +regular_expression = 'MAC=(.*?) ' +style = { faint = true } + +# IP v4 regexp +[[regexps]] +regular_expression = '(\b\d{1,3})(\.)(\d{1,3})(\.)(\d{1,3})(\.)(\d{1,3}\b)' +style = { fg = "yellow", bold = true } + +[[regexps]] +regular_expression = 'SPT=(.*?) ' +style = { faint = true } + +[[regexps]] +regular_expression = 'DPT=(.*?)$' +style = { bold = true } + +[url] +disabled = true + +[number] +disabled = true + +[ip] +disabled = true + +[quotes] +disabled = true + +[path] +disabled = true + +[uuid] +disabled = true + +[pointer] +disabled = true + +[key_value] +disabled = true + +[process] +disabled = true diff --git a/src/esh b/src/esh new file mode 100755 index 0000000..ed0e34f --- /dev/null +++ b/src/esh @@ -0,0 +1,366 @@ +#!/bin/sh +# vim: set ts=4: +#---help--- +# USAGE: +# esh [options] [--] [...] +# esh <-h | -V> +# +# Process and evaluate an ESH template. +# +# ARGUMENTS: +# Path of the template file or "-" to read from STDIN. +# Variable(s) specified as = to pass into the +# template (the have higher priority than environment +# variables). +# +# OPTIONS: +# -d Don't evaluate template, just dump a shell script. +# -o Output file or "-" for STDOUT. Defaults to "-". +# -s Command name or path of the shell to use for template +# evaluation. It must not contain spaces. +# Defaults to "/bin/sh". +# -h Show this help message and exit. +# -V Print version and exit. +# +# ENVIRONMENT: +# ESH_AWK Command name of path of the awk program to use. +# It must not contain spaces. Defaults to "awk". +# ESH_MAX_DEPTH Maximum include depth. Defaults to 3. +# ESH_SHELL Same as -s. +# +# EXIT STATUS: +# 0 Clean exit, no error has encountered. +# 1 Generic error. +# 10 Invalid usage. +# 11 ESH syntax error. +# 12 Include error: file not found. +# 13 Include error: exceeded max include depth (ESH_MAX_DEPTH). +# +# Please report bugs at . +#---help--- +set -eu + +readonly PROGNAME='esh' +readonly VERSION='0.3.2' +readonly SCRIPTPATH="$0" + +AWK_CONVERTER=$(cat <<'AWK' +function fail(code, msg) { + state = "ERROR" + # FIXME: /dev/stderr is not portable + printf("%s: %s\n", line_info(), msg) > "/dev/stderr" + exit code +} +function line_info() { + return FILENAME ? (filenames[depth] ":" linenos[depth]) : "(init)" # (init) if inside BEGIN +} +# IMPORTANT: This is the only function that should print a newline. +function puts(str) { + print(line_info()) > MAP_FILE + print(str) +} +function fputs(str) { + printf("%s", str) +} +function trim(str) { + gsub(/^[ \t\r\n]+|[ \t\r\n]+$/, "", str) + return str +} +function read(len, _str) { + if (len == "") { + _str = buff + buff = "" + } else if (len > 0) { + _str = substr(buff, 1, len) + buff = substr(buff, len + 1, length(buff)) + } + return _str +} +function skip(len) { + buff = substr(buff, len + 1, length(buff)) +} +function flush(len, _str) { + _str = read(len) + + if (state == "TEXT") { + gsub("'", "'\\''", _str) + } + if (state != "COMMENT") { + fputs(_str) + } +} +function file_exists(filename, _junk) { + if ((getline _junk < filename) >= 0) { + close(filename) + return 1 + } + return 0 +} +function dirname(path) { + return sub(/\/[^\/]+\/*$/, "/", path) ? path : "" +} +function include(filename) { + if (index(filename, "/") != 1) { # if does not start with "/" + filename = dirname(filenames[depth]) filename + } + if (!file_exists(filename)) { + fail(12, "cannot include " filename ": not a file or not readable") + } + if (depth > MAX_DEPTH) { + fail(13, "cannot include " filename ": exceeded maximum depth of " MAX_DEPTH) + } + buffs[depth] = buff + states[depth] = state + filenames[depth + 1] = filename + depth++ + + init() + while ((getline buff < filename) > 0) { + if (print_nl && state != "COMMENT") { + puts("") + } + process_line() + } + end_text() + close(filename) + + depth-- + buff = buffs[depth] + state = states[depth] +} +function init() { + buff = "" + linenos[depth] = 0 + print_nl = 0 + start_text() +} +function start_text() { + puts("") + fputs("printf '%s' '") + state = "TEXT" +} +function end_text() { + if (state != "TEXT") { return } + puts("' #< " line_info()) + state = "UNDEF" +} +function process_line() { + print_nl = 1 + linenos[depth]++ + + while (buff != "") { + print_nl = 1 + + if (state == "TEXT" && match(buff, /<%/)) { + flush(RSTART - 1) # print buff before "<%" + skip(2) # skip "<%" + + flag = substr(buff, 1, 1) + if (flag != "%") { + end_text() + } + if (flag == "%") { # <%% + skip(1) + fputs("<%") + } else if (flag == "=") { # <%= + skip(1) + fputs("__print ") + state = "TAG" + } else if (flag == "+") { # <%+ + if (!match(buff, /[^%]%>/)) { + fail(11, "syntax error: <%+ must be closed on the same line") + } + filename = trim(substr(buff, 2, match(buff, /.-?%>/) - 1)) + skip(RSTART) + include(filename) + state = "TAG" + } else if (flag == "#") { # <%# + state = "COMMENT" + } else { + state = "TAG" + } + } else if (state != "TEXT" && match(buff, /%>/)) { + flag = RSTART > 1 ? substr(buff, RSTART - 1, 1) : "" + + if (flag == "%") { # %%> + flush(RSTART - 2) + skip(1) + flush(2) + } else if (flag == "-") { # -%> + flush(RSTART - 2) + skip(3) + print_nl = 0 + } else { # %> + flush(RSTART - 1) + skip(2) + } + if (flag != "%") { + start_text() + } + } else { + flush() + } + } +} +BEGIN { + FS = "" + depth = 0 + + puts("#!" (SHELL ~ /\// ? SHELL : "/usr/bin/env " SHELL)) + puts("set -eu") + puts("if ( set -o pipefail 2>/dev/null ); then set -o pipefail; fi") + puts("__print() { printf '%s' \"$*\"; }") + + split(VARS, _lines, /\n/) + for (_i in _lines) { + puts(_lines[_i]) + } + init() +} +{ + if (NR == 1) { + filenames[0] = FILENAME # this var is not defined in BEGIN so we must do it here + } + buff = $0 + process_line() + + if (print_nl && state != "COMMENT") { + puts("") + } +} +END { + end_text() +} +AWK +) +AWK_ERR_FILTER=$(cat <<'AWK' +function line_info(lno, _line, _i) { + while ((getline _line < MAPFILE) > 0 && _i++ < lno) { } + close(MAPFILE) + return _line +} +{ + if (match($0, "^" SRCFILE ":( line)? ?[0-9]+:") && match(substr($0, 1, RLENGTH), /[0-9]+:$/)) { + lno = substr($0, RSTART, RLENGTH - 1) + 0 + msg = substr($0, RSTART + RLENGTH + 1) # v-- some shells duplicate filename + msg = index(msg, SRCFILE ":") == 1 ? substr(msg, length(SRCFILE) + 3) : msg + print(line_info(lno) ": " msg) + } else if ($0 != "") { + print($0) + } +} +AWK +) +readonly AWK_CONVERTER AWK_ERR_FILTER + +print_help() { + sed -En '/^#---help---/,/^#---help---/p' "$SCRIPTPATH" | sed -E 's/^# ?//; 1d;$d;' +} + +filter_shell_stderr() { + $ESH_AWK \ + -v SRCFILE="$1" \ + -v MAPFILE="$2" \ + -- "$AWK_ERR_FILTER" +} + +evaluate() { + local srcfile="$1" + local mapfile="$2" + + # This FD redirection magic is for swapping stdout/stderr back and forth. + exec 3>&1 + { set +e; $ESH_SHELL "$srcfile" 2>&1 1>&3; echo $? >>"$mapfile"; } \ + | filter_shell_stderr "$srcfile" "$mapfile" >&2 + exec 3>&- + + return $(tail -n 1 "$mapfile") +} + +convert() { + local input="$1" + local vars="$2" + local map_file="${3:-"/dev/null"}" + + $ESH_AWK \ + -v MAX_DEPTH="$ESH_MAX_DEPTH" \ + -v SHELL="$ESH_SHELL" \ + -v MAP_FILE="$map_file" \ + -v VARS="$vars" \ + -- "$AWK_CONVERTER" "$input" +} + +process() { + local input="$1" + local vars="$2" + local evaluate="${3:-yes}" + local ret=0 tmpfile mapfile + + if [ "$evaluate" = yes ]; then + tmpfile=$(mktemp) + mapfile=$(mktemp) + + convert "$input" "$vars" "$mapfile" > "$tmpfile" || ret=$? + test $ret -ne 0 || evaluate "$tmpfile" "$mapfile" || ret=$? + + rm -f "$tmpfile" "$mapfile" + else + convert "$input" "$vars" || ret=$? + fi + return $ret +} + +: ${ESH_AWK:="awk"} +: ${ESH_MAX_DEPTH:=3} +: ${ESH_SHELL:="/bin/sh"} +EVALUATE='yes' +OUTPUT='' + +while getopts ':dho:s:V' OPT; do + case "$OPT" in + d) EVALUATE=no;; + h) print_help; exit 0;; + o) OUTPUT="$OPTARG";; + s) ESH_SHELL="$OPTARG";; + V) echo "$PROGNAME $VERSION"; exit 0;; + '?') echo "$PROGNAME: unknown option: -$OPTARG" >&2; exit 10;; + esac +done +shift $(( OPTIND - 1 )) + +if [ $# -eq 0 ]; then + printf "$PROGNAME: %s\n\n" 'missing argument ' >&2 + print_help >&2 + exit 10 +fi + +INPUT="$1"; shift +if [ "$INPUT" != '-' ] && ! [ -f "$INPUT" -a -r "$INPUT" ]; then + echo "$PROGNAME: can't read $INPUT: not a file or not readable" >&2; exit 10 +fi + +# Validate arguments. +for arg in "$@"; do + case "$arg" in + *=*) ;; + *) echo "$PROGNAME: illegal argument: $arg" >&2; exit 10;; + esac +done + +# Format variables into shell variable assignments. +vars=''; for item in "$@"; do + vars="$vars\n${item%%=*}='$( + printf %s "${item#*=}" | $ESH_AWK "{ gsub(/'/, \"'\\\\\\\''\"); print }" + )'" +done + +export ESH="$0" + +if [ "${OUTPUT#-}" ]; then + tmpfile="$(mktemp)" + trap 'rm -f -- "$tmpfile"' EXIT HUP INT TERM + process "$INPUT" "$vars" "$EVALUATE" > "$tmpfile" + cat "$tmpfile" > "$OUTPUT" +else + process "$INPUT" "$vars" "$EVALUATE" +fi diff --git a/src/headless.start b/src/headless.start new file mode 100755 index 0000000..f44583a --- /dev/null +++ b/src/headless.start @@ -0,0 +1,207 @@ +#!/bin/sh + +set -ex + +SDCARD_MEDIA=$(find /media -name *.apkovl.tar.gz -exec dirname {} \; -quit) + +copyfile() { + OWNER="$1" + PERMS="$2" + SOURCE="$3" + DEST="$4" + cp -f "$SOURCE" "$DEST" + chown "$OWNER" "$DEST" + chmod "$PERMS" "$DEST" +} + +__setup_loopback_interface() +{ + cat <<-EOF > /etc/network/interfaces + auto lo + iface lo inet loopback + + EOF +} + +__setup_eth_interface() +{ + cat <<-EOF >> /etc/network/interfaces + auto <%= $eth0 %> + iface <%= $eth0 %> inet dhcp + hostname <%= $hostname %> + + EOF +} + +__setup_integrated_wireless_interface() +{ + cat <<-EOF >> /etc/network/interfaces + auto <%= $wlan0 %> + iface <%= $wlan0 %> inet static + hostname <%= $hostname %> + pre-up sysctl -w net.ipv4.ip_forward=1 + pre-up iwctl device <%= $wlan0 %> set-property Mode ap + pre-up iwctl ap <%= $wlan0 %> start-profile <%= $ssid %> + post-up iptables -t nat -A POSTROUTING -s <%= $wlan0_cidr %> -j MASQUERADE + pre-down iptables -t nat -D POSTROUTING -s <%= $wlan0_cidr %> -j MASQUERADE + + EOF +} + +__setup_external_wireless_interface() +{ + cat <<-EOF >> /etc/network/interfaces + auto <%= $wlan1 %> + iface <%= $wlan1 %> inet dhcp + hostname <%= $hostname %> + + EOF +} + +# Uncomment for alternative: +# +# ALPINE_MIRROR=https://mirror.fit.cvut.cz/alpine +__setup_network_repositories() +{ + cat <<-EOF > /etc/apk/repositories + ${SDCARD_MEDIA}/apks + ${SDCARD_MEDIA}/extras + <%= $ALPINE_MIRROR %>/<%= $RELEASE_VERSION %>/main + <%= $ALPINE_MIRROR %>/<%= $RELEASE_VERSION %>/community + EOF +} + +__setup_local_repositories() +{ + cp ${SDCARD_MEDIA}/<%= $BUILDER_KEY_BASENAME %>.rsa.pub /etc/apk/keys + chmod 644 /etc/apk/keys/<%= $BUILDER_KEY_BASENAME %>.rsa.pub + + cat <<-EOF > /etc/apk/repositories + ${SDCARD_MEDIA}/apks + ${SDCARD_MEDIA}/extras + EOF + + apk update +} + +__setup_user_account() +{ + mkdir -p /root/.ssh + cp ${SDCARD_MEDIA}/authorized_keys /root/.ssh/authorized_keys + chown -R root:root /root/.ssh + + cat <<-EOF >> /root/.profile + tail -f -n 60 /var/log/messages + EOF +} + +__setup_time() +{ + rc-service chronyd stop + + cat <<-EOF > /etc/chrony/chrony.conf + initstepslew 60 <%= $ROUTER_NTP_PRIMARY %> <%= $ROUTER_NTP_SECONDARY %> + driftfile /var/lib/chrony/chrony.drift + rtcsync + cmdport 0 + server <%= $ROUTER_NTP_PRIMARY %> iburst + server <%= $ROUTER_NTP_SECONDARY %> iburst + makestep 60 10 + EOF + + rc-service chronyd start +} + +__setup_profiles() +{ + cat <<-EOF >> /etc/profile + + alias l="tail -f -n 60 /var/log/messages" + alias k="tail -f -n 60 /var/log/kern.log | tspin" + alias ll="ls -la" + alias ..="cd .." + + EOF +} + +__setup_randomness() +{ + rc-service haveged start +} + +__setup_logging() +{ + copyfile root:root 0700 ${SDCARD_MEDIA}/tspin /usr/bin/tspin + mkdir -p /root/.config/tailspin + copyfile root:root 0700 ${SDCARD_MEDIA}/config.toml /root/.config/tailspin/config.toml + + mkdir -p /etc/rsyslog.d + copyfile root:root 0700 ${SDCARD_MEDIA}/logpattern.conf /etc/rsyslog.d/logpattern.conf + copyfile root:root 0700 ${SDCARD_MEDIA}/rsyslog.conf /etc/rsyslog.conf + + rc-service --list | grep rsyslog && \ + rc-service syslog stop && \ + rc-service rsyslog start +} + +__setup_firewall() { + modprobe -v ip_tables + modprobe -v iptable_nat + + sysctl -w net.ipv4.ip_forward=1 + sysctl -w net.ipv4.conf.all.forwarding=1 + + iptables -A INPUT -j LOG --log-prefix 'fw: INBOUND ' + iptables -A OUTPUT -j LOG --log-prefix 'fw: OUTBOUND ' + + /etc/init.d/iptables save + rc-service iptables start +} + +__setup_wireless_client() { + mkdir -p /var/lib/iwd/ap/ + + copyfile root:root 0644 ${SDCARD_MEDIA}/main.conf /etc/iwd/main.conf + copyfile root:root 0644 ${SDCARD_MEDIA}/NUMERI_AC11.psk /var/lib/iwd/NUMERI_AC11.psk + copyfile root:root 0644 ${SDCARD_MEDIA}/robusta_nomap.psk /var/lib/iwd/robusta_nomap.psk + + copyfile root:root 0644 ${SDCARD_MEDIA}/hotspot.conf /var/lib/iwd/ap/<%= $ssid %>.ap + copyfile root:root 0700 ${SDCARD_MEDIA}/watchdog.sh /etc/wireguard/watchdog.sh + + rc-service dbus start + rc-service dbus start + rc-service iwd start +} + +/sbin/setup-hostname <%= $hostname %> +/sbin/setup-timezone -z <%= $ROUTER_TIMEZONE %> + +__setup_local_repositories + +apk add `cat ${SDCARD_MEDIA}/<%= $REQUIREMENTS_FILE %>` + +__setup_logging +__setup_randomness +__setup_profiles +__setup_user_account + +__setup_loopback_interface +__setup_eth_interface +__setup_integrated_wireless_interface +__setup_external_wireless_interface +__setup_wireless_client +__setup_firewall + +rc-service networking start + +mkdir -p /etc/wireguard +copyfile root:root 0600 ${SDCARD_MEDIA}/wg0.conf /etc/wireguard/wg0.conf + +ln -s /etc/init.d/wg-quick /etc/init.d/wg-quick.wg0 +# rc-service wg-quick.wg0 start + +__setup_time + +/etc/wireguard/watchdog.sh + +__setup_network_repositories diff --git a/src/hotspot.conf b/src/hotspot.conf new file mode 100644 index 0000000..205230a --- /dev/null +++ b/src/hotspot.conf @@ -0,0 +1,12 @@ +[General] +DisableHT=true + +[Security] +Passphrase=<% cat src/wifi-password.txt %> + +[IPv4] +Address=<%= $wlan0_address %> +Gateway=<%= $wlan0_address %> +Netmask=255.255.255.0 +DNSList=<%= $wg0_gateway %> +IPRange=<%= $wlan0_range_start %>,<%= $wlan0_range_end %> diff --git a/src/logpattern.conf b/src/logpattern.conf new file mode 100644 index 0000000..033a530 --- /dev/null +++ b/src/logpattern.conf @@ -0,0 +1 @@ +$template logpattern,"%timegenerated:1:19:date-rfc3339% %msg:F,32:3% %msg:R,ERE,1,BLANK:(IN=[a-zA-Z0-9]*)--end% %msg:R,ERE,1,BLANK:(OUT=[a-zA-Z0-9]*)--end% %msg:R,ERE,1,BLANK:(SRC=[a-zA-Z0-9\.]*)--end% %msg:R,ERE,1,BLANK:(DST=[a-zA-Z0-9\.]*)--end% %msg:R,ERE,1,BLANK:(PROTO=[a-zA-Z0-9]*)--end% %msg:R,ERE,1,BLANK:(SPT=[0-9]*)--end% %msg:R,ERE,1,BLANK:(DPT=[0-9]*)--end%\n" \ No newline at end of file diff --git a/src/main.conf b/src/main.conf new file mode 100644 index 0000000..093cfdf --- /dev/null +++ b/src/main.conf @@ -0,0 +1,41 @@ +# Note: The lines starting with # are ignored. To enable any of the +# configuration options below, remove # from the beginning of a respective line. + +# this file is not distributed upstream as of iwd 1.0 version +# It is created as transitional config from latest version +# Please read iwd.config(5), iwd.network(5), iwctl(1), iwmon(1) before setting +# these parameters below + +# main.conf format is changed with iwd 1.3 release + +#[EAP] +#mtu=1400 + +#[General] + +#EnableNetworkConfiguration=True +#ControlPortOverNL80211=True +#RoamThreshold=-70 + +#[Network] + +#NameResolvingService=resolvconf + +#[Scan] + +# DisablePeriodicScan=false +# DisableRoamingScan=false + +#[Blacklist] + +#[Rank] + +# BandModifier5Ghz=1.0 + +[General] +EnableNetworkConfiguration=True +Country=CZ + +[Network] +EnableIPv6=false +NameResolvingService=resolvconf diff --git a/src/repository-builder/abuild.conf b/src/repository-builder/abuild.conf new file mode 100644 index 0000000..432018b --- /dev/null +++ b/src/repository-builder/abuild.conf @@ -0,0 +1 @@ +PACKAGER_PRIVKEY="/root/.abuild/packager.rsa" diff --git a/src/repository-builder/packager.rsa b/src/repository-builder/packager.rsa new file mode 100644 index 0000000..74108d8 --- /dev/null +++ b/src/repository-builder/packager.rsa @@ -0,0 +1,74 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCBrbk9nejJwcDF6WmdHaEN2 +N0JHT1BBIDE4CndHZnVkUU5MUmhxT09kZXZ0V1Vwei9LRVFER1E1aktqT0VSaXUx +c043U3MKLS0tIGdLL3B4a0lhNXVGVXZyU01CQ09Rc2RyUlpFVVVoZXBSVlRBWlRW +QXRUb1kKULpkyDn3NpjI2IO/iehEDS1TxAPh9leKHNSNTeHvWnPMHeeUZbXZd9+O +DzInQbNT7x+g+eFBc2hAvMBm04OuP0Z0f2vuk6oUIjFSwbaRAkh9QsYKkglkUCEA +cUHu2xdl58IL51LROyHjAztxtQUTekWWh7l/PC60xWTXAWLeGZFBFAfcH25isvIF +EUfW77+HoUoYLZrorN60xF0QUFsQKqaCpT7KBxPw1Mh3NRdmC9MJbUd5A+v48ADh +/QFmg5MCofL3K7EIaKJiptAl2AQVbGKAEBbsqEMM0IwqBBphZm4Kp4o5xMBEFQeQ +fKmp+1Mm1jlX3388M8lkl3ugwOQ5Le9J6arZPP6VCbUdPP4vsYjykUjLQly6ihDe +O/u+MV3bKZErygZ8FNFQr3wQmSim60ys8nKpz6NH393F+lY69Y1TAFfx39GBI6li +7ZrzwgteYa/HTW8vcIfVxwKYHkgocdVOtuWjgG579ntPzieIYxTaj4win0BzkGaH ++5Z/z7tMTyUd7ISkXnQwwUomrkHp9FLj3AGgbjC7hp9WqZ/cgTysiRGGmrI6H4iG +xRsXAsB7VsHEuGQe+qvF2OYI7LUKcBBrmEDJXJ1cTJxf7qwUf/+dmiHeVuwOaozW +lxGDbZ8KdBuxfW28WMoH3gHq1jonOSwiPYhWfb/wtM3NIZPpZXAXjujUACIoaF/e +ZFmPn5N1nrefon3LaE53GusKuXpobKL+CY8Zkw2cPcCJW6E0IqanFD1waphYK1f2 +hkihZR4c8Jo5aWLlSkljFojo4n7+Xmtwcep/LKYDeXXgu1+amT9R/5+S081q2QzF ++jJotlojm2hu+O5Q7LgjuMgIuZzMa8rNYVAaOTZuxG1bu6JwHUjoUTdzrzQq3KeI +oQc16lWdxI5g+2tisyRTyPOVN6y5kAfG4ZkUqMj2tIhCwW615G1ywXvvLQUoLshl +FoGX/ITldWS+oQU/OKUPpGa8VZysM3C0XRWnwat+ChVQnIuVxkuAFXAC0oODpLnL +MI424fjM6/5YOgVmkpSd8E/+8oXgeDpXXAx7+Fo5Ngpd69EUNs4w4nK5huAWsg7m +QCWXL02xHP6iiPW7wKyegCLgMAFeVc2zcC6n+77Qlz9G7onww5+0Lnz687qz65FI +4SkqYfM7/1unmBDHOJlsG71tYMr2nbCUSisDWJdjNdmWiSH7RCNN42phLdQss9Kp +whIfCUpCBtBw6vPy4Bc318L/H0Bq4TpQWFdB/OVCyvUDNGbK3KAKLRyvsDif/Z6A +CffZdbxn4phtueK1vZQQvodGF4SgcNdd2AdVDyYxL6tnUs+EWAoxnH0MixlCxnKO +PNlyiYsiW/YdjeFZygUM+qdLYiMpxonhsjBbKbqCdddP2ccfArnxH7BnnILhbjCD +tKuihrblOWpiH0S3UkAx3HuCh/5q0fCa5MkL6o+I5bN/V/V//iGtr9FRPDEz6To4 +p/bTbq1CGLHci9mUJkdkU+Y8RW+tbDTpgcNHRc0LwiVdsc+bjVFDIs2kMAH6C/lz +7toauF7uGCmdBapZHJMwAZKeBYlXEQiR6NxcWEbUSPr7xVtRrwxQBATKFn6N+C/A +1A4Oiq87409Q9w/q8PFAQhBeK3tNzOb+6Sg61kzeZv5IMYu2cuKCTTMfSGxZyzCJ +ReGNHoye4kQ0w0TwpYnmYH898rnuKGLYgTm9sqg/7DBFG4Dez1YrOO6Rnr3XgwHb +S49HN1QGt//6Trv3orR6bjqn+ehEkFtWQk827cOsUtPAzw9Y2gmKnNOfUUo6pyuX +PoDTxg8L1Bv1zZAQJlwzH0FJrVjEYb5Eq4KOVRgclXQ1Qlm75YO5v7IJpiN/tkVL +olqAHnk7iXWcjMW9Zazv8qPg0S5NkKtYdPQg756Y54K/uhTvtpI0N9JHNRysLWL7 +diGvtJDkidA0frQ1P2Em2ABHOKc5ObKlt1sgWofjE+zGKgt46oAC/0erTrpyH8jt +lf4gFL8nUHMWxV3fl8rfNaS7a2Ti3qP6g/BYyLbvxf6xY+Ey/oqc+BqSvU1kcUdc +hpYmQ+eRwDKXHJvFzHNedG/I9tDIso/achpuxEot3V5+bT0TnTL54cR5y+AE0diz +frysWuMKxSGDzdMPsrAECK52J03J512wCEKAyjl4jZImIgfAb7dGVwPyRf9jbpXD +u3bOJ5j6hoMwtEPKS5Pi0WebUVoJfWd1GuZB0tMhsVQOvggnh+zuN5X1FwEzxfDq +mXufznwj+Qoi5sqQErD/uDIYISPryR81EvprkLKbq+pPuEMea2s5F1wjz3iBgqZT +vjeevrWJhR/hDgJsNt1Sq5lQj6xqAi6Tdfl1fG663ocxmMSPXQiH1Wi/R9A383Jk +vilvpAfvuAqdeLTwNi1Eo43qVwenmK4kZ2dbvJ5yJ0jq+pB4KgA5o3AAhCSVHhK8 +UYyZprNSE61N4rggxJsc6PBp83v/ZGqCe0owLrSIkK8BKJ/ANXuj6X4aLWdZkRFC +vzhGQxCCOpgGI5gWR7rdQE3UimnvvydoT4xnTLylHrhR0/51TLyz+FgHlhUSutnu +h6miERLXt52oDYKxvFgMPButZMe1plafKx3xsHgYcEyA7/xLDLRGCK82E7F+8wvo +Wcng71kfww2LnSOKyd8FhulBYcFLWZs9XgEdg5rWLg32q1GyHzl+unxFNh7FB27E +1V7+ZGEbEEJSBrNCFKNqCqcm8K8HkGDmNAgcMQtt2Ho1kS+4PhdcBKfWI7iLfLQX +9Tf7gsjLYLZhfsVlEnUVv/sZLxkJTbyNoOEEiQdKqcqifoqf/dKop3DP3SaFbAB7 +hthiQ+99M6ML20nIL3/xGeLfNIDZcgqOCEtBDQ2y3l4aF5UQLIj+hntMKBBOZ9wI +VIROnx48U/hEIyuFEuGwPdaC0Te15OuMq6o6QWWf2mfXOa2OmZuoD1FHFG4BCFNt +GTZRq13x75C4mP+vEbwPg5XaG7NtJMwMWFd5BsjFFezRa3V6YJ+YYG7mB5UvWiiL +oziBxrUhP8qcsA1uVWYvKPh42Lu3ClV0iPst6b+7rC1ztWu6npmvaAiuP1ELrRKM +JnrFGh26/n8Tqs4cXfOu5+5E1a1f2EuPSPlu/8uQUDYbLETmF+dUOMbggX3W4YP7 +rv/cqi9FwO7eq9RDWnwXkatN8uM57rxOPqZ3DRtVojdsacvuNS5lmX0V+8fBESNw +mSlwA/bvfiHXUZEo29waFaZxTb0Qbwje4UfFVW2YuYXN2TWY/3a1nMBUwTo8bOW2 +3abvIjmrVKYXnmTq4VBeYuAV/9++mMqzJcD+OTYFAxzP+QKbO6B4w9606vKBpZNz +JnZsL9rQF4EXUNTDZ/BRkFreP0l2rBsCb74PkFvF04iUzrQlb+K7+ZvaX1GpiOYO +pDArKFAw8v1pwwjjEwlak+JCNg/46iwKZnEID5iAZFtL3hTsSAiXTa+BXd7BYFZ1 +RsmcLu6EhCddBMq6wOTJ+pyRgLmGU9+id6nQTZ5VyhhV8uNXCjt1Q6uKuEQbp5QJ +UZOHjFQDPyXQ2KrHEmiwYql+zbGuYEQZfgT9x36+mWHLocGUMSPtJdvh1G/X5Nhq +rQKLg12adc1o2xqerQUrV6JgWOv5s2jDKQakyRXtNunLFVtzhbKfGsB1uxEtkL+L +/utFv0Tq/QE1D+ab3E7Skw6mjONn2iZKxkghU1xdiO0Vc7xVdPB+Ev9HW446a5+M +z6rdgTbnvgVmHov3pQuQ6j7ceuu9evvlJ+ewxBxIoLqWDl06kKFmEOMUFSgLKKKk ++tuS924mD3CShklHlc+fm2yLGSU4H4CMKkL0ZcgilB0DCTP0xE10r2/elzDR1YiS +xGto/ztrdEmGY50znWtx6TLPoNGqdfTU223k7dH4uKzxDFMjYFApUuAsFiNuIJ2X +EIVPD8hv6kXIncmonh4Rb5UJIpPwVrl42iY+IRcejIf+/UWN+SAy280lvLhMErsM +ClDCeHq4mDIFK0f6+Q6LKw7vU/vHK7wbDJFX2youaJVFZUE9CuPRkBWztj+LgAPA +c0A/YFSZ0j3IZraO3nsjXYlfDN5kvN2Y5e2APCXQafQRWGudfBN1XuHbPSNgsX3F +ZnAncBvKFFqh3KYwTLaWScpxkfB5xo/s+lU8GPXhLYDGXfbXgdUtSSxPbea28j4K +tIVB5jHAwSjZF0hw1717XlndYowFnxHmLlHh83EHXKbAraV6iNdpEmF1reaHJiRs +dscH1q6mIsV1tAWbD62BDA1jZW608Y3U4O2UK7L/dxb958UCcuZlpmPpJ3WhF+1s +qs50HrGTYxC2dMUHWCqXZMAxNnIYxwCJrK0hl67/bnEancPctmvW2rJ9AEmDno63 +ThPHxjeMrP3F9WQLmZlc0IgCd0leBWBoRNieNPOAWib0IHgHBNQ7bs9y +-----END AGE ENCRYPTED FILE----- diff --git a/src/repository-builder/packager.rsa.pub b/src/repository-builder/packager.rsa.pub new file mode 100644 index 0000000..6d83c20 --- /dev/null +++ b/src/repository-builder/packager.rsa.pub @@ -0,0 +1,23 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCAvS2hSWUIyMGRWb1JiSWZk +cGtDeWNnIDE4CkhLNlhkV1NNZTNKRGFEUGQ1MnRWMkpobzh0dnZuMXRlVWZhOCtF +cklmR1kKLS0tIFdmMUlRMWo0YVF6WWFLNFR6NEttMFhmQVh6c1V5Slh6M1dOZllY +WGs3WWcKsYHS7ureliLncEfsmDd+XOOYNjZybEDgy2FrfhzPLLGmIhItQP9xVuRp +ur7XwemHDZ6WY53DB27gSZI0zsCKivgLfrajOAQZoQtkR4A4TKxd9nQBhtCnoS+I +jrIJXh60D/5WxCHRH2MkvgoHWPEK2KfXcNorUXuuU3v6uYigEZik0H2QSubWFQLP +l349pTFvOZFXaSmj4Z2GBH8pZ206/7FhMZVdEuTx63T+HKNku4OS4e7/6M+kImn2 +0wvOQsKQWvBTFYeez8rtEeUMZWYUD4iaPIhZBw3jFICL0WC5ujiPJ61tyN67aa+d +igdT2VZ3PU0G6VOYRU+9Q4fU1A0mB2ZeOfhzQygCqe84tac4rCLmXpON03r5mAPm +khcWaqrMv4MZTApqyrvfSY/Nh1adr3hZjefTWbWuNOxCC3P3SL54MvxlMwJOtlhD +najYWdKIHeBbrMmZH3zw9g6Kh18ZnbMYVdq3zLxbpcKw71NVP8W1OfA6OcHVqWZy +ydyVPSRWRLzjxwDPmt+CYL3cM6H1jNoD+0xTk+QgFrmUMySvkjrnXodHTuyKKcYo +zud7DhtCRk6hueaXRauMdEkCoXDn2nOUxekgSLjlt1l1fYPmQliRijXxTnv9lNTg +3kovw+vUsl2RIUqMDT4dgs11oYe4gndkCMqYuM7tXNPTjRkWBF3UOp1LxJ9QH/A0 +TbPTHtBPCj0Jc/0+TN0nDkllf2RSMqu8AISyNkxSptdgsMfnMxxfb4Ec7rsdbKu9 +HTAaudA0TgdDa65PU2MFAHJbZGjZBtDwchNxtegaXjrNKjjarSkxSqBBpYoiRf5u +IE236DyyqF9QviD51dEgZzY333FFmOuIidDIYbYFSkLUQAVfrrF8HCqsu5/VFj2q +ZruHPOa6oFBuDeXGEOppvw1yarWHroygj7Ab01CxFCwMKmsNTM+AMi5K69ZatEB1 +a7L8MLk7cQiO1DgI7ysJZ9uPTbnd7Q/+osSO2tFLjstFWvGXRRutfJLDOet2+HhF +IO9ZrjpbY+lDvU4105LVDTOq6fwSuDQ2or/uRZiDPBzLwvcf1LuzTeukKV+saIt2 +77oRYixJFeHjfSHHz9Ue0F7YIyYSJg== +-----END AGE ENCRYPTED FILE----- diff --git a/src/repository-builder/repository.Dockerfile b/src/repository-builder/repository.Dockerfile new file mode 100644 index 0000000..c4c7744 --- /dev/null +++ b/src/repository-builder/repository.Dockerfile @@ -0,0 +1,17 @@ +# syntax = docker/dockerfile:1 +ARG ALPINE_DOCKER_IMAGE +FROM $ALPINE_DOCKER_IMAGE + +ARG REQUIREMENTS_FILE +ARG BUILDER_USER +ARG BUILDER_UID +ARG BUILDER_GID + +RUN /usr/sbin/addgroup -g $BUILDER_GID $BUILDER_USER && \ + /usr/sbin/adduser -D -g "" $BUILDER_USER -u $BUILDER_UID -G $BUILDER_USER + +RUN mkdir /root/.abuild +RUN apk add --no-cache abuild + +ADD src/repository-builder/$REQUIREMENTS_FILE /tmp/$REQUIREMENTS_FILE +ADD src/repository-builder/repository.build.sh /bin/build-repository diff --git a/src/repository-builder/repository.build.sh b/src/repository-builder/repository.build.sh new file mode 100755 index 0000000..5ab885c --- /dev/null +++ b/src/repository-builder/repository.build.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -e +# 1>/dev/null + +APKS_TO_INSTALL=`cat /tmp/$REQUIREMENTS_FILE` + +apk fix --arch $ALPINE_ARCH +apk update --arch $ALPINE_ARCH --allow-untrusted +apk fetch --allow-untrusted --recursive --link --arch $ALPINE_ARCH --output /mirror/$RELEASE_VERSION/main/$ALPINE_ARCH $APKS_TO_INSTALL + +if ! ls /mirror/$RELEASE_VERSION/main/$ALPINE_ARCH/*.apk >& /dev/null; then + return 1 +fi + +chown -R $BUILDER_UID:$BUILDER_GID /mirror + +echo /mirror/$RELEASE_VERSION/main > /etc/apk/repositories + +# abuild-keygen -a +cp /root/.abuild/$BUILDER_KEY_BASENAME.rsa.pub /etc/apk/keys + +apk index -vU -d $RELEASE_VERSION --allow-untrusted --rewrite-arch $ALPINE_ARCH -o /mirror/$RELEASE_VERSION/main/$ALPINE_ARCH/APKINDEX.tar.gz `ls /mirror/$RELEASE_VERSION/main/$ALPINE_ARCH/*.apk` +abuild-sign -k /root/.abuild/$BUILDER_KEY_BASENAME.rsa /mirror/$RELEASE_VERSION/main/$ALPINE_ARCH/APKINDEX.tar.gz +chown -R $BUILDER_UID:$BUILDER_GID /mirror + +apk update --arch $ALPINE_ARCH --allow-untrusted diff --git a/src/repository-builder/required-apks.txt b/src/repository-builder/required-apks.txt new file mode 100644 index 0000000..eeaab2c --- /dev/null +++ b/src/repository-builder/required-apks.txt @@ -0,0 +1,12 @@ +busybox-extras +busybox-extras-openrc +chrony +haveged +rsyslog-openrc +rsyslog +less +iptables +wireguard-tools +iwd +mc +nano diff --git a/src/rsyslog.conf b/src/rsyslog.conf new file mode 100644 index 0000000..00f12b6 --- /dev/null +++ b/src/rsyslog.conf @@ -0,0 +1,96 @@ +# rsyslog configuration file +# +# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html +# or latest version online at http://www.rsyslog.com/doc/rsyslog_conf.html +# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html + + +#### Global directives #### + +# Sets the directory that rsyslog uses for work files. +$WorkDirectory /var/lib/rsyslog + +# Sets default permissions for all log files. +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# Check config syntax on startup and abort if unclean (default off). +#$AbortOnUncleanConfig on + +# Reduce repeating messages (default off). +#$RepeatedMsgReduction on + + +#### Modules #### + +# Provides --MARK-- message capability. +module(load="immark") + +# Provides support for local system logging (e.g. via logger command). +module(load="imuxsock") + +# Reads kernel messages. +module(load="imklog") + +#### Config files #### + +# Include all config files in /etc/rsyslog.d/. +include(file="/etc/rsyslog.d/*.conf" mode="optional") + +#### Rules #### + +# Log all kernel messages to kern.log. + +# Log anything (except mail) of level info or higher. +# Don't log private authentication messages! +# NOTE: The minus sign in front of filename disables buffer flush. +*.info;authpriv.none;cron.none;kern.none;mail.none -/var/log/messages + +# The authpriv file has restricted access. +authpriv.* /var/log/auth.log + +# Log all the mail messages in one place. +mail.* -/var/log/mail.log + +# Log cron stuff. +cron.* -/var/log/cron.log + +# Everybody gets emergency messages. +*.emerg :omusrmsg:* + +# Log all kernel messages to the console. +# Logging much else clutters up the screen. +#kern.* /dev/console + + +### Examples #### + +# Send all logs to remote syslog via UDP. +# An on-disk queue is created for this action. If the remote host is +# down, messages are spooled to disk and sent when it is up again. +#*.* action( +# type="omfwd" +# target="192.168.0.1" +# port="514" +# protocol="udp" +# queue.filename="fwdRule1" # unique name prefix for spool files +# queue.type="LinkedList" +# queue.maxDiskSpace="256m" +# queue.saveOnShutdown="on" +# action.resumeRetryCount="-1" +# action.resumeInterval="30" +#) + +# Receive messages from remote host via UDP +# for parameters see http://www.rsyslog.com/doc/imudp.html +#module(load="imudp") # needs to be done just once +#input( +# type="imudp" +# port="514" +#) +:msg, contains, "fw:" /var/log/kern.log;logpattern +& stop +kern.* /var/log/kern.log diff --git a/src/usercfg.txt b/src/usercfg.txt new file mode 100644 index 0000000..e93114c --- /dev/null +++ b/src/usercfg.txt @@ -0,0 +1,10 @@ +disable_splash=1 + +# Disable Wi-Fi and Bluetooth to save power +# dtoverlay=disable-wifi +dtoverlay=disable-bt + +# This will use more power but lower +# overall system latency, which is fairly desirable for a +# system acting as a router. +force_turbo=1 diff --git a/src/watchdog.sh b/src/watchdog.sh new file mode 100755 index 0000000..80ce972 --- /dev/null +++ b/src/watchdog.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +current_process=$$ +tries=3 +some_time=5 + +kill_child_processes() { + for dir in /proc/[0-9]*; do + process_id=$(cat ${dir}/stat | awk '{print $4}') + child_id=$(cat ${dir}/stat | awk '{print $1}') + if [ ${process_id} -eq $1 ]; then + kill -9 ${child_id} + fi + done +} + +trap "kill_child_processes ${current_process} ; kill -9 ${current_process}" SIGTERM SIGINT SIGHUP + +while [ $tries -ge 1 ] +do + if ! ping -q -c 1 10.0.22.1 > /dev/null ; then + until ntpd -n -q -p $(nslookup tik.cesnet.cz 1.1.1.1 | grep 'Address' | tail -1 | cut -d ' ' -f 2) + do + rc-service wg-quick.wg0 stop + logger -t ntp_sync "Trying to sync time..." + sleep $some_time & + wait $! + done + rc-service wg-quick.wg0 status | grep "started" || rc-service wg-quick.wg0 start + sleep $some_time & + wait $! + fi + sleep $some_time & + wait $! + tries=$(( $tries - 1 )) +done +exit 0 diff --git a/src/wg0.conf b/src/wg0.conf new file mode 100644 index 0000000..d4c3651 --- /dev/null +++ b/src/wg0.conf @@ -0,0 +1,12 @@ +[Interface] +ListenPort = <%= $wg_port %> +PrivateKey = <%= $(cat src/wireguard.private.key) %> +Address = <%= $wg0_address %> +DNS = <%= $wg0_gateway %> +MTU = 1280 + +[Peer] +PublicKey = <%= $(cat src/wireguard.public.key) %> +Endpoint = <%= $wg_server %>:<%= $wg_port %> +AllowedIPs = 0.0.0.0/0 +PersistentKeepalive = 25 diff --git a/src/wifi-password.txt b/src/wifi-password.txt new file mode 100644 index 0000000..d7b0419 --- /dev/null +++ b/src/wifi-password.txt @@ -0,0 +1,6 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCAweWZSMk14YkN6NjZibmpN +ZjRXSkxnIDE4CitRZVR4aDZJZ21ZYVY0RWxvdTE4OVVhVXE3RVRmRUtYRlBRRm9Q +ZUFoTWcKLS0tIFg1S0pOOEt3NGlUYTRTeXVwTS9LaDhmdDdsNFpQRnpycGhXeCtD +S0FYN0kK7VkM5XZAPBGpnZy0imULXgM7M0ZG0CcxPoX/IjqBdpx99WJOVc3HReY= +-----END AGE ENCRYPTED FILE----- diff --git a/src/wireguard.private.key b/src/wireguard.private.key new file mode 100644 index 0000000..7aeef1c --- /dev/null +++ b/src/wireguard.private.key @@ -0,0 +1,7 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCBSMXRlOCtyRmlzZ3pRc2lT +eTBKWVJRIDE4Ckc2dmZVdTZjSjg3L1NzY2UzTCtDcFdFU3Z2NmF2WWhVd3IySy9J +MmlINVUKLS0tIC94NnpqOG5McXkvU0NEVDZHcDExMCswU0pOTDIyY0hIWnZIaCs5 +akpFWm8K6ny4xFB41oG/h8JToFnfgHBB3zWoPsLtuTCKpQE6EV+n9C/sWdB5yNJK +FoR5NdSUfxhIMK2W6HK4QAVB3oxtqqeQCY2vg3MNJ1iMkBE= +-----END AGE ENCRYPTED FILE----- diff --git a/src/wireguard.public.key b/src/wireguard.public.key new file mode 100644 index 0000000..c79e211 --- /dev/null +++ b/src/wireguard.public.key @@ -0,0 +1,7 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCAyanV2aU1wVWx3Q0J3ZVhu +ZGdQalNBIDE4Cm9LQzlZNk80M1VhcXNVREs0S0ZqdkpkclE2ZXhDNURSaG81aEV2 +Zk0zdkkKLS0tIGtOdHAwZDhYVWQ3N3Z2cmVOUktDU3NnV0ozNldSTGxrNngwMWd5 +aGZET0kKSHjNMXCJvE3aYWVH5CuJ6KTBVYrtKd8EP+lua36ECKK5ARb/zKnsqd8q +C3817wLCoyeW54XNtH34QU63noK6DBtTHD1mzCp+ytPiNUY= +-----END AGE ENCRYPTED FILE----- diff --git a/vault b/vault new file mode 100755 index 0000000..abb071d --- /dev/null +++ b/vault @@ -0,0 +1,39 @@ +#!/bin/sh +set -e + +FILES_IN_VAULT='./src/wifi-password.txt +./src/repository-builder/packager.rsa +./src/repository-builder/packager.rsa.pub +./src/wireguard.private.key +./src/wireguard.public.key +./src/authorized_keys' + +age_encrypt() { + local input="$1" + local output="$1.tmp" + local AGE_PASSPHRASE=`cat ./.vault_password_file` + + ./cache/age/age --encrypt --passphrase --armor --output $output $input && rm $input && mv $output $input +} + +age_decrypt() { + local input="$1" + local output="$1.tmp" + local AGE_PASSPHRASE=`cat ./.vault_password_file` + + ./cache/age/age --decrypt --output $output $input && rm $input && mv $output $input +} + +encrypt() { + for FILE in $FILES_IN_VAULT; do + age_encrypt $FILE + done +} + +decrypt() { + for FILE in $FILES_IN_VAULT; do + age_decrypt $FILE + done +} + +$1