feat: initial setup

This commit is contained in:
Tine 2025-03-05 22:43:20 +01:00
parent 165fdb0d8f
commit f8ba583709
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
19 changed files with 721 additions and 0 deletions

7
.envrc Normal file
View file

@ -0,0 +1,7 @@
# Automatically sets up your devbox environment whenever you cd into this
# directory via our direnv integration:
eval "$(devbox generate direnv --print-envrc)"
# check out https://www.jetpack.io/devbox/docs/ide_configuration/direnv/
# for more details

28
.gitignore vendored Normal file
View file

@ -0,0 +1,28 @@
# Encrypted environment variables
env
*.env
!root/**/*.env
# Encrypted ssh keys
ssh
*.ssh
# ---> Terraform
# Local .terraform directories
**/.terraform/*
**/.tofu/*
# .tfstate files
*.tfstate
*.tfstate.*
!*.tfstate.encrypted
# Crash log files
crash.log
crash.*.log
# Ignore CLI configuration files
.terraformrc
terraform.rc
.tofurc
tofu.rc

4
.sops.yaml Normal file
View file

@ -0,0 +1,4 @@
creation_rules:
- path_regex: (\.env)|(.*\.tfstate)
age: >-
age1cl3d4wtrrqrgldmrzpu53q2mk60r7hrhrymsrwss8s57z4mdv9fst4a55h

View file

@ -0,0 +1,12 @@
[Unit]
Description=An Authentik LDAP Server
[Container]
Image=ghcr.io/goauthentik/ldap:2025.2.1
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
After=authentik-server.service

View file

@ -0,0 +1,11 @@
[Unit]
Description=An Authentik Server
[Container]
Image=ghcr.io/goauthentik/authentik:2025.2.1
[Service]
Restart=always
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,12 @@
[Unit]
Description=An Authentik Worker
[Container]
Image=ghcr.io/goauthentik/authentik:2025.2.1
Exec=worker
[Service]
Restart=always
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,14 @@
[Unit]
Description=A Caddy Container
[Container]
Image=docker.io/caddy:2.9
PublishPort=443
Volume=/etc/caddy:/etc/caddy
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
WantedBy=authentik-server.service

View file

@ -0,0 +1,14 @@
[Unit]
Description=A Postgresql Container
[Container]
Image=docker.io/postgresql:17.4
Volime=/var/lib/postgresql/data:/srv/postgresql/data
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
RequiredBy=authentik-server.service
RequiredBy=authentik-worker.service

View file

@ -0,0 +1,14 @@
[Unit]
Description=A Valkey Container
[Container]
Image=docker.io/valkey/valkey:8
Memory=1g
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
RequiredBy=authentik-server.service
RequiredBy=authentik-worker.service

15
devbox.json Normal file
View file

@ -0,0 +1,15 @@
{
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.13.7/.schema/devbox.schema.json",
"packages": [
"tflint@latest",
"tenv@latest",
"just@latest"
],
"env": {
"TFENV_AUTO_INSTALL": "true"
},
"shell": {
"init_hook": [],
"scripts": {}
}
}

185
devbox.lock Normal file
View file

@ -0,0 +1,185 @@
{
"lockfile_version": "1",
"packages": {
"just@latest": {
"last_modified": "2025-02-07T11:26:36Z",
"resolved": "github:NixOS/nixpkgs/d98abf5cf5914e5e4e9d57205e3af55ca90ffc1d#just",
"source": "devbox-search",
"version": "1.39.0",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/7bn46fl2hqjim960bgf5mx00xrypnasb-just-1.39.0",
"default": true
},
{
"name": "man",
"path": "/nix/store/ydyky07sc2aq6al94ncmyrj9dzgylzpw-just-1.39.0-man",
"default": true
},
{
"name": "doc",
"path": "/nix/store/n2h7g1pvnl50l1c0mnwlw6ychvjr7d3f-just-1.39.0-doc"
}
],
"store_path": "/nix/store/7bn46fl2hqjim960bgf5mx00xrypnasb-just-1.39.0"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/c81p578r4qgcxpd1v8iy0g1zpp5zvala-just-1.39.0",
"default": true
},
{
"name": "man",
"path": "/nix/store/xyj23wacamprbqgfz7hbmn3aaghnkph3-just-1.39.0-man",
"default": true
},
{
"name": "doc",
"path": "/nix/store/9h788ccn90mzpd3ljf81l7zfn4rgf0sm-just-1.39.0-doc"
}
],
"store_path": "/nix/store/c81p578r4qgcxpd1v8iy0g1zpp5zvala-just-1.39.0"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/a9c2m24hznpx15z31d9sv9v4pwc3v5yw-just-1.39.0",
"default": true
},
{
"name": "man",
"path": "/nix/store/xzsnsx2wkc1si0yylkhlr827qypdag58-just-1.39.0-man",
"default": true
},
{
"name": "doc",
"path": "/nix/store/qvpzazymr1h2hp8ap0gamak6xj476vg5-just-1.39.0-doc"
}
],
"store_path": "/nix/store/a9c2m24hznpx15z31d9sv9v4pwc3v5yw-just-1.39.0"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/zl4v9j9ir2pv9j344ibn58cf1sgwiz3i-just-1.39.0",
"default": true
},
{
"name": "man",
"path": "/nix/store/4drvdrkg0wh881rzzfp4ad33nppcfcpd-just-1.39.0-man",
"default": true
},
{
"name": "doc",
"path": "/nix/store/7ny7c6g1yhavs0z5hlwmgss4r04wwkx8-just-1.39.0-doc"
}
],
"store_path": "/nix/store/zl4v9j9ir2pv9j344ibn58cf1sgwiz3i-just-1.39.0"
}
}
},
"tenv@latest": {
"last_modified": "2025-02-07T11:26:36Z",
"resolved": "github:NixOS/nixpkgs/d98abf5cf5914e5e4e9d57205e3af55ca90ffc1d#tenv",
"source": "devbox-search",
"version": "4.1.0",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/04f0sc3hcza1s3qlm8q6ijwwjxcl7dcj-tenv-4.1.0",
"default": true
}
],
"store_path": "/nix/store/04f0sc3hcza1s3qlm8q6ijwwjxcl7dcj-tenv-4.1.0"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/7a8qhipsrqpgsq7afyf602ppb60z2slj-tenv-4.1.0",
"default": true
}
],
"store_path": "/nix/store/7a8qhipsrqpgsq7afyf602ppb60z2slj-tenv-4.1.0"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/plp58mk98rzrv6v7qfbsb7ibsxaganc0-tenv-4.1.0",
"default": true
}
],
"store_path": "/nix/store/plp58mk98rzrv6v7qfbsb7ibsxaganc0-tenv-4.1.0"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/awzm82py4wd9nlx38s7i6xyzsyhawws4-tenv-4.1.0",
"default": true
}
],
"store_path": "/nix/store/awzm82py4wd9nlx38s7i6xyzsyhawws4-tenv-4.1.0"
}
}
},
"tflint@latest": {
"last_modified": "2025-02-07T11:26:36Z",
"resolved": "github:NixOS/nixpkgs/d98abf5cf5914e5e4e9d57205e3af55ca90ffc1d#tflint",
"source": "devbox-search",
"version": "0.55.1",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/68211bisbjwja8k9y2m25a1mpwzg8qkl-tflint-0.55.1",
"default": true
}
],
"store_path": "/nix/store/68211bisbjwja8k9y2m25a1mpwzg8qkl-tflint-0.55.1"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/0j1gmqwj26f77rv7v7fcq5f1l8fijjwg-tflint-0.55.1",
"default": true
}
],
"store_path": "/nix/store/0j1gmqwj26f77rv7v7fcq5f1l8fijjwg-tflint-0.55.1"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/9sxdfdzhd3v400xir7apm5lqc4yx6wk3-tflint-0.55.1",
"default": true
}
],
"store_path": "/nix/store/9sxdfdzhd3v400xir7apm5lqc4yx6wk3-tflint-0.55.1"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/l0w06x7r6c419mxc4xdm954j7rlm7xvp-tflint-0.55.1",
"default": true
}
],
"store_path": "/nix/store/l0w06x7r6c419mxc4xdm954j7rlm7xvp-tflint-0.55.1"
}
}
}
}
}

90
docker-compose.yml Normal file
View file

@ -0,0 +1,90 @@
---
services:
postgresql:
image: docker.io/library/postgres:16-alpine
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- database:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_DB: ${PG_DB:-authentik}
env_file:
- .env
redis:
image: docker.io/library/redis:alpine
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.1}
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./media:/media
- ./custom-templates:/templates
env_file:
- .env
ports:
- "${COMPOSE_PORT_HTTP:-9000}:9000"
- "${COMPOSE_PORT_HTTPS:-9443}:9443"
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.1}
restart: unless-stopped
command: worker
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
# `user: root` and the docker socket volume are optional.
# See more for the docker socket integration here:
# https://goauthentik.io/docs/outposts/integrations/docker
# Removing `user: root` also prevents the worker from fixing the permissions
# on the mounted folders, so when removing this make sure the folders have the correct UID/GID
# (1000:1000 by default)
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./media:/media
- ./certs:/certs
- ./custom-templates:/templates
env_file:
- .env
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
volumes:
database:
driver: local
redis:
driver: local

47
install.sh Executable file
View file

@ -0,0 +1,47 @@
#!/bin/bash
set -euo pipefail
if [[ $EUID -eq 0 ]]; then
echo "$0 is being run as root. Please run as a regular user." 1>&2
exit 2
fi
##
echo "== Fetch Source Code (from git)"
cd "$HOME/service"
# Clone if not yet cloned
if [ ! -d .git ]; then
git clone \
--depth 1 \
--no-checkout \
--filter=tree:0 \
https://github.com/tjo-space/tjo-cloud-infrastructure.git .
git sparse-checkout set --no-cone /id.tjo.space
git checkout
else
git fetch --depth=1
git reset --hard origin/main
fi
function provision() {
##
echo "=== Installing Dependencies"
apt update -y
apt install -y \
git \
podman
##
echo "=== Configure Firewall"
ufw allow 22/tcp # SSH
ufw allow 443/tcp # HTTPS
ufw allow 636/tcp # LDAPS
ufw enable
##
echo "=== Setting up the user"
loginctl enable-linger "ubuntu"
}
echo "=== Provision the System (as root)"
sudo -u root bash -c "$(declare -f provision); provision"

80
justfile Normal file
View file

@ -0,0 +1,80 @@
# Always use devbox environment to run commands.
set shell := ["devbox", "run"]
# Load dotenv
set dotenv-load
default:
@just --list
dot-env-encrypt:
sops \
--encrypt \
--input-type=dotenv \
--output-type=dotenv \
.env > .env.encrypted
dot-env-decrypt:
sops \
--decrypt \
--input-type=dotenv \
--output-type=dotenv \
.env.encrypted > .env
tofu-state-encrypt:
#!/bin/bash
for file in $(find -name tofu.tfstate -o -name terraform.tfstate)
do
echo "Encrypting $file"
sops \
--encrypt \
--input-type=json \
--output-type=json \
$file > ${file}.encrypted
done
tofu-state-decrypt:
#!/bin/bash
for file in $(find -name tofu.tfstate.encrypted -o -name terraform.tfstate.encrypted)
do
echo "Decrypting $file"
sops \
--decrypt \
--input-type=json \
--output-type=json \
$file > ${file%.encrypted}
done
lint:
@tofu fmt -check -recursive .
@tflint --recursive
format:
@tofu fmt -recursive .
@tflint --recursive
apply:
#!/usr/bin/env sh
cd {{source_directory()}}/terraform
tofu init
tofu apply
destroy:
#!/usr/bin/env sh
cd {{source_directory()}}/terraform
tofu destroy
outputs:
#!/usr/bin/env sh
cd {{source_directory()}}/terraform
tofu output
configure:
#!/usr/bin/env sh
set -eou pipefail
pushd {{source_directory()}}/terraform > /dev/null
IPV4=$(tofu output -json | jq -r '.ipv4')
popd > /dev/null
echo "= Provisioning id.tjo.space"
cat install.sh | ssh ubuntu@${IPV4} 'sudo bash -s'

View file

@ -0,0 +1,47 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/dnsimple/dnsimple" {
version = "1.8.0"
constraints = "1.8.0"
hashes = [
"h1:Nwu+3tVJnNmSJQoctRSWAamUX3AiTCZ5mOMtAUPtg7Q=",
"zh:0852fd9523268b30fb637a03a0cb6d6a5878cbbf7e0e4219615c9ba073fbdf17",
"zh:0ac43193082dd467abad4937b0abb97ea349205726fc450cb3a94dc0db6e9a49",
"zh:10e4aad54c2d6cbd9328a1661d72a978357743eda7099a3f120a497119be4ff1",
"zh:211d481935dec36903928c51f5f4f15d98313f6d50649ea064bc20a4d6541678",
"zh:2705b5ebac4219449f9126cc19fa982cf0644e5df60d3d5254131d2e2d676afd",
"zh:27f0df80af6652e96f85a0856daa571af495d2119ab126199d6d5ab53f6eb887",
"zh:27fbb2fb69291a660d8e99ba960f01051b7fc28658f7932772ce7e80a42bd6e9",
"zh:3ecf20ead1f044f08ae9e411c9341d47319eb6af5d6543b58f2f6932c6b288b0",
"zh:635055f0af3eb27d30801aeead51d8b960c386f369a378fad7146350ec6b4d68",
"zh:7ca26f64221a9c6634a02296e30a87e3fffed1144ac57e0ae9a86a448f42d4ca",
"zh:895e0732da00942b2eb13c78673a9c9268e87e92a225999cddf2d13b823f3295",
"zh:b3806e5b687faf97ad8cb2a23e105729059693ae07a229fecef52da5279d7bd1",
"zh:c3c284a54aab3ddea2dba140af4a707ce077c9c2d9d34556902afdb25fe6ca8e",
"zh:d2539f2cc5960a55a53eaaa90248abfb3167275e34af7e93735ec4571eb879eb",
"zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32",
]
}
provider "registry.opentofu.org/hetznercloud/hcloud" {
version = "1.50.0"
constraints = "1.50.0"
hashes = [
"h1:z5J9wgkt9xIKlr699hWCjHSS7K4bYKWWnGCg2T/YNmg=",
"zh:0bd650fb52e272f74eda5053a7bb62f0fd92182f57ad3ef742abe165cb8cac98",
"zh:1c36667aa89b672a96c0df3d3c613e80916a2d0944b1a1f9112065f40630b689",
"zh:21f90683890ea7a184b0ac55efd52911694ba86c58898bc8bbe87ee2507bb1eb",
"zh:24349d483a6ff97420d847433553fa031f68f99b9ead4ebb3592fc8955ef521f",
"zh:3fffd83c450bea2b382a986501ae51a4d3e6530eda48ed9ca74d518e4a909c37",
"zh:43d7de1dc4c50fae99d6c4ab4bb394608948091f5b53ddb29bc65deead9dc8a6",
"zh:47a37d5fec79dd8bc9cab2c892bc59e135b86cb51eebe2b01cdb40afac7ed777",
"zh:6efeb9530b8f57618c43f0b294b983d06cce43e9423bdd737eed81db913edb80",
"zh:7511ace4b33baddfc452ef95a634d83b92bfbfaa23cb30403899e95b64727075",
"zh:7bade77104ed8788c9b5171c7daae6ab6c011b3c40b152274fda803bf0bf2707",
"zh:83bce3ff9a1bd52a340a6ebdd2e2b731ec6fb86811ef0ed8a8264daf9d7beb61",
"zh:a09d5fce4c8d33e10b9a19318c965076db2d8ed5f62f5feb3e7502416f66d7bf",
"zh:c942832b80270eb982eeb9cc14f30a437db5fd28faf37d6aa32ec2cd345537d6",
"zh:e2c1812f2e1f9fac17c7551d4ab0efb713b6d751087c18b84b8acd542f587459",
]
}

96
terraform/main.tf Normal file
View file

@ -0,0 +1,96 @@
resource "hcloud_ssh_key" "main" {
for_each = var.ssh_keys
name = each.key
public_key = eeach.value
}
resource "hcloud_firewall" "main" {
name = "main"
# ICMP
rule {
direction = "in"
protocol = "icmp"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
# HTTPS
rule {
direction = "in"
protocol = "tcp"
port = "443"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
# SSH
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
}
resource "hcloud_server" "main" {
name = "id.tjo.space"
image = "ubuntu-24.04"
server_type = "cax11"
datacenter = "hel1-dc2"
public_net {
ipv4_enabled = true
ipv6_enabled = true
}
firewall_ids = [hcloud_firewall.main.id]
backups = true
ssh_keys = [for key in var.ssh_keys : hcloud_ssh_key.main[key].id]
user_data = <<-EOF
#cloud-config
hostname: id
fqdn: id.tjo.space
prefer_fqdn_over_hostname: true
packages:
- git
package_update: true
package_upgrade: true
power_state:
mode: reboot
swap:
filename: /swapfile
size: 512M
runcmd:
- su ubuntu -c "git clone --depth 1 git@github.com:tjo-space/infrastructure-ng.git /home/ubuntu/service"
- su ubuntu -c "/home/ubuntu/service/install.sh"
EOF
}
resource "dnsimple_zone_record" "a" {
zone_name = "tjo.space"
name = "id.tjo.space"
value = hcloud_server.main.ipv4_address
type = "A"
ttl = 300
}
resource "dnsimple_zone_record" "aaaa" {
zone_name = "tjo.space"
name = "id.tjo.space"
value = hcloud_server.main.ipv6_address
type = "AAAA"
ttl = 300
}

23
terraform/terraform.tf Normal file
View file

@ -0,0 +1,23 @@
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "1.50.0"
}
dnsimple = {
source = "dnsimple/dnsimple"
version = "1.8.0"
}
}
required_version = "~> 1.7.3"
}
provider "hcloud" {
token = var.hcloud_token
}
provider "dnsimple" {
token = var.dnsimple_token
account = var.dnsimple_aaccount_id
}

View file

@ -0,0 +1,5 @@
ssh_keys = {
"tine+pc" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICXAlzwziqfUUb2qmFwNF/nrBYc5MNT1MMOx81ohBmB+ tine+pc@tjo.space"
"tine+mobile" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAdPg/nG/Qzk110SBukHHEDqH6/3IJHsIKKHWTrqjaOh tine+mobile@tjo.space"
"tine+ipad" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHrX2u82zWpVhjWng1cR4Kj76SajLJQ/Nmwd2GPaJpt1 tine+ipad@tjo.cloud"
}

17
terraform/variables.tf Normal file
View file

@ -0,0 +1,17 @@
variable "hcloud_token" {
sensitive = true
type = string
}
variable "dnsimple_token" {
sensitive = true
type = string
}
variable "dnsimple_aaccount_id" {
type = string
}
variable "ssh_keys" {
type = map(string)
}