feat(ingress.tjo.cloud): add dyndns support
Some checks failed
/ lint (push) Failing after 2m2s

This commit is contained in:
Tine 2025-01-02 16:27:42 +01:00
parent 339a72682a
commit 4a0b589859
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
10 changed files with 240 additions and 41 deletions

View file

@ -1,3 +1,18 @@
# ingress
Handling all Ingress traffic
## Rolling out changes
```sh
# Apply code changes to single node.
# Make sure to commit and push the changes first.
just provision-only nevaroo
# Apply infrastructure changes to single node.
just apply-only nevaroo
# Apply to all nodes
just provision
just apply
```

View file

@ -11,7 +11,7 @@ if [ ! -d .git ]; then
--depth 1 \
--no-checkout \
--filter=tree:0 \
https://code.tjo.space/tjo-cloud/infrastructure.git .
https://github.com/tjo-space/tjo-cloud-infrastructure.git .
git sparse-checkout set --no-cone /ingress.tjo.cloud
git checkout
else
@ -31,6 +31,8 @@ SERVICE_ACCOUNT_PASSWORD=$(jq -r ".service_account.password" /etc/tjo.cloud/meta
TAILSCALE_AUTH_KEY=$(jq -r ".tailscale.auth_key" /etc/tjo.cloud/meta.json)
DIGITALOCEAN_TOKEN=$(jq -r ".digitalocean.token" /etc/tjo.cloud/meta.json)
##
echo "== Install Dependencies"
apt update -y
@ -59,7 +61,7 @@ apt install -y tailscale
##
echo "== Ensure services are enabled"
systemctl enable --now nginx alloy tailscaled
systemctl enable --now nginx alloy tailscaled dydns
##
echo "== Configure Grafana Alloy"
@ -71,13 +73,22 @@ ATTRIBUTES+="service.name=${SERVICE_NAME},"
ATTRIBUTES+="service.version=${SERVICE_VERSION},"
ATTRIBUTES+="cloud.region=${CLOUD_REGION}"
echo "OTEL_RESOURCE_ATTRIBUTES=${ATTRIBUTES}" >>/etc/default/alloy
# Set Credentials
# set credentials
{
echo "ALLOY_USERNAME=${SERVICE_ACCOUNT_USERNAME}"
echo "ALLOY_PASSWORD=${SERVICE_ACCOUNT_PASSWORD}"
echo "alloy_username=${SERVICE_ACCOUNT_USERNAME}"
echo "alloy_password=${SERVICE_ACCOUNT_PASSWORD}"
} >>/etc/default/alloy
systemctl restart alloy
##
echo "== Configure Dydns"
cp -r root/etc/default/dydns /etc/default/dydns
{
echo "DIGITALOCEAN_TOKEN=${DIGITALOCEAN_TOKEN}"
echo "NAME=${CLOUD_REGION}"
} >>/etc/default/dydns
systemctl restart dydns
##
echo "== Configure Tailscale"
if tailscale status --json | jq -e -r '.BackendState != "Running"' >/dev/null; then

View file

@ -1,8 +1,3 @@
# Always use devbox environment to run commands.
set shell := ["devbox", "run"]
# Load dotenv
set dotenv-load
default:
@just --list
@ -14,17 +9,54 @@ format:
@tofu fmt -recursive .
@tflint --recursive
deploy:
apply:
#!/usr/bin/env sh
cd {{justfile_directory()}}/terraform
cd {{source_directory()}}/terraform
tofu init
tofu apply
apply-only node:
#!/usr/bin/env sh
cd {{source_directory()}}/terraform
tofu init
tofu apply --target 'proxmox_virtual_environment_vm.nodes["{{node}}"]'
destroy:
#!/usr/bin/env sh
cd {{justfile_directory()}}/terraform
cd {{source_directory()}}/terraform
tofu destroy
provision:
#!/usr/bin/env sh
set -eou pipefail
pushd {{source_directory()}}/terraform > /dev/null
NODES=$(tofu output -json | jq -r '.nodes.value[]')
popd > /dev/null
for NODE in $NODES
do
echo "= Provisioning node ${NODE}"
cat install.sh | tailscale ssh ubuntu@${NODE} 'sudo bash -s'
done
provision-only node:
#!/usr/bin/env sh
set -eou pipefail
pushd {{source_directory()}}/terraform > /dev/null
NODES=$(tofu output -json | jq -r '.nodes.value[]')
popd > /dev/null
for NODE in $NODES
do
if [ "$NODE" -eq "{{node}}" ]
echo "= Provisioning node ${NODE}"
cat install.sh | tailscale ssh ubuntu@${NODE} 'sudo bash -s'
fi
done
# Create a list of blocked IP ranges. Traffic we don't want.
update-blocked-list:
#!/usr/bin/env bash
@ -49,20 +81,3 @@ update-blocked-list:
for ip in $IP_RANGES; do
echo "deny $ip;" >> root/etc/nginx/partials/blocked.conf
done
provision:
#!/usr/bin/env sh
set -eou pipefail
pushd {{justfile_directory()}}/terraform > /dev/null
NODES=$(tofu output -json | jq -r '.nodes.value[]')
popd > /dev/null
for NODE in $NODES
do
echo "= Provisioning node ${NODE}"
cat install.sh | tailscale ssh ubuntu@${NODE} 'sudo bash -s'
done
list-servers:
@cd terraform && tofu output -json | jq -r '.nodes.value[]'

View file

@ -0,0 +1,17 @@
## Path:
## Description: DynDNS settings
## Type: string
## Default: ""
## ServiceRestart: dydns
# If set to "true", removes extra DNS records if more than one A record is found on a subdomain.
# Note that if this is not enabled, the script will NOT update subdomains with more than one A record
# (default: false)
REMOVE_DUPLICATES=true
# Polling time in seconds.
# (default: 300)
SLEEP_INTERVAL=600
# The domain your subdomain is registered at. (i.e. foo.com for home.foo.com)
DOMAIN=ingress.tjo.cloud

View file

@ -0,0 +1,8 @@
[Unit]
Description=Dynamic DNS Updater
[Service]
ExecStart=/usr/local/bin/dyndns
[Install]
WantedBy=multi-user.target

View file

@ -1,8 +0,0 @@
[Unit]
Description=Webhooks
[Service]
ExecStart=/usr/bin/webhook -hooks /etc/webhook/hooks.json -port 7777 -verbose
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,132 @@
#!/usr/bin/env bash
# vim: set filetype=sh
set -euo pipefail
api_host="https://api.digitalocean.com/v2"
sleep_interval=${SLEEP_INTERVAL:-300}
remove_duplicates=${REMOVE_DUPLICATES:-"false"}
# Only services with ipv6 supported are listed here.
# And are not using cloudflare or similar services
# that may block requests from this script.
services=(
"ifconfig.io"
)
info() {
echo "$1" >/dev/stdout
}
warn() {
echo "$1" >/dev/stderr
}
error() {
warn "$1"
exit 1
}
test -f "${DIGITALOCEAN_TOKEN_FILE:-}" && DIGITALOCEAN_TOKEN="$(cat "$DIGITALOCEAN_TOKEN_FILE")"
test -z "$DIGITALOCEAN_TOKEN" && error "DIGITALOCEAN_TOKEN not set!"
test -z "${DOMAIN}" && error "DOMAIN not set!"
test -z "${NAME}" && error "NAME not set!"
dns_list="$api_host/domains/$DOMAIN/records"
configure_record() {
# disable glob expansion
set -f
ip=$1
type=$2
for sub in ${NAME//;/ }; do
record_id=$(echo "$domain_records" | jq ".domain_records[] | select(.type == \"$type\" and .name == \"$sub\") | .id")
record_data=$(echo "$domain_records" | jq -r ".domain_records[] | select(.type == \"$type\" and .name == \"$sub\") | .data")
if [ "$(echo "$record_id" | wc -l)" -ge 2 ]; then
if [[ "${remove_duplicates}" == "true" ]]; then
echo "'$sub' domain name has duplicate DNS records, removing duplicates"
record_id_to_delete=$(echo "$record_id" | tail -n +2)
record_id=$(echo "$record_id" | head -1)
record_data=$(echo "$record_data" | head -1)
while IFS= read -r line; do
curl -s -X DELETE \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
"$dns_list/$line" &>/dev/null
done <<<"$record_id_to_delete"
else
warn "Unable to update '$sub' domain name as it has duplicate DNS records. Set REMOVE_DUPLICATES='true' to remove them."
continue
fi
fi
# re-enable glob expansion
set +f
data="{\"type\": \"$type\", \"name\": \"$sub\", \"data\": \"$ip\"}"
url="$dns_list/$record_id"
if [[ -z $record_id ]]; then
info "No record found with '$sub' domain name. Creating record, sending data=$data to url=$url"
new_record=$(curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
-d "$data" \
"$url")
record_data=$(echo "$new_record" | jq -r ".data")
fi
if [[ "$ip" != "$record_data" ]]; then
info "existing DNS record address ($record_data) doesn't match current IP ($ip), sending data=$data to url=$url"
curl -s -X PUT \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
-d "$data" \
"$url" &>/dev/null
else
info "existing DNS record address ($record_data) did not need updating"
fi
done
}
while (true); do
domain_records=$(curl -s -X GET \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
"$dns_list?per_page=200")
for service in "${services[@]}"; do
info "Trying with $service..."
ipv4="$(curl -4 -s -f --connect-timeout 2 "$service" || echo "")"
ipv6="$(curl -6 -s -f --connect-timeout 2 "$service" || echo "")"
if [[ -n "$ipv4$ipv6" ]]; then
break
else
warn "Failed to retrieve IP from $service"
fi
done
if [[ -z $ipv4 ]]; then
warn "IPv4 wasn't retrieved within allowed interval. Will try $sleep_interval seconds later.."
else
info "Found IPv4 address $ipv4"
configure_record "$ipv4" "A"
fi
if [[ -z $ipv6 ]]; then
warn "IPv6 wasn't retrieved within allowed interval. Will try $sleep_interval seconds later.."
else
info "Found IPv6 address $ipv6"
configure_record "$ipv6" "AAAA"
fi
sleep "$sleep_interval"
done

View file

@ -14,6 +14,9 @@ locals {
tailscale = {
auth_key = tailscale_tailnet_key.key.key
}
digitalocean = {
token = var.digitalocean_token
}
}
})
}
@ -60,8 +63,9 @@ resource "proxmox_virtual_environment_file" "userdata" {
power_state:
mode: reboot
runcmd:
- git clone https://code.tjo.space/tjo-cloud/ingress.git /srv
- /srv/install.sh
- git clone --depth 1 --no-checkout --filter=tree:0 https://github.com/tjo-space/tjo-cloud-infrastructure.git /srv
- cd /srv && git sparse-checkout set --no-cone /ingress.tjo.cloud && git checkout
- /srv/ingress.tjo.cloud/install.sh
EOF
file_name = "${each.value.host}.ingress.tjo.cloud.userconfig.yaml"
}
@ -87,7 +91,7 @@ Repo: https://code.tjo.space/tjo-cloud/ingress
timeout_stop_vm = 60
timeout_shutdown_vm = 60
timeout_reboot = 60
timeout_create = 600
timeout_create = 60
cpu {
cores = each.value.cores

View file

@ -37,3 +37,8 @@ variable "tailscale_apikey" {
type = string
sensitive = true
}
variable "digitalocean_token" {
type = string
sensitive = true
}