This commit is contained in:
parent
339a72682a
commit
4a0b589859
10 changed files with 240 additions and 41 deletions
|
@ -1,3 +1,18 @@
|
||||||
# ingress
|
# ingress
|
||||||
|
|
||||||
Handling all Ingress traffic
|
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
|
||||||
|
```
|
|
@ -11,7 +11,7 @@ if [ ! -d .git ]; then
|
||||||
--depth 1 \
|
--depth 1 \
|
||||||
--no-checkout \
|
--no-checkout \
|
||||||
--filter=tree:0 \
|
--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 sparse-checkout set --no-cone /ingress.tjo.cloud
|
||||||
git checkout
|
git checkout
|
||||||
else
|
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)
|
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"
|
echo "== Install Dependencies"
|
||||||
apt update -y
|
apt update -y
|
||||||
|
@ -59,7 +61,7 @@ apt install -y tailscale
|
||||||
|
|
||||||
##
|
##
|
||||||
echo "== Ensure services are enabled"
|
echo "== Ensure services are enabled"
|
||||||
systemctl enable --now nginx alloy tailscaled
|
systemctl enable --now nginx alloy tailscaled dydns
|
||||||
|
|
||||||
##
|
##
|
||||||
echo "== Configure Grafana Alloy"
|
echo "== Configure Grafana Alloy"
|
||||||
|
@ -71,13 +73,22 @@ ATTRIBUTES+="service.name=${SERVICE_NAME},"
|
||||||
ATTRIBUTES+="service.version=${SERVICE_VERSION},"
|
ATTRIBUTES+="service.version=${SERVICE_VERSION},"
|
||||||
ATTRIBUTES+="cloud.region=${CLOUD_REGION}"
|
ATTRIBUTES+="cloud.region=${CLOUD_REGION}"
|
||||||
echo "OTEL_RESOURCE_ATTRIBUTES=${ATTRIBUTES}" >>/etc/default/alloy
|
echo "OTEL_RESOURCE_ATTRIBUTES=${ATTRIBUTES}" >>/etc/default/alloy
|
||||||
# Set Credentials
|
# set credentials
|
||||||
{
|
{
|
||||||
echo "ALLOY_USERNAME=${SERVICE_ACCOUNT_USERNAME}"
|
echo "alloy_username=${SERVICE_ACCOUNT_USERNAME}"
|
||||||
echo "ALLOY_PASSWORD=${SERVICE_ACCOUNT_PASSWORD}"
|
echo "alloy_password=${SERVICE_ACCOUNT_PASSWORD}"
|
||||||
} >>/etc/default/alloy
|
} >>/etc/default/alloy
|
||||||
systemctl restart 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"
|
echo "== Configure Tailscale"
|
||||||
if tailscale status --json | jq -e -r '.BackendState != "Running"' >/dev/null; then
|
if tailscale status --json | jq -e -r '.BackendState != "Running"' >/dev/null; then
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
# Always use devbox environment to run commands.
|
|
||||||
set shell := ["devbox", "run"]
|
|
||||||
# Load dotenv
|
|
||||||
set dotenv-load
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@just --list
|
@just --list
|
||||||
|
|
||||||
|
@ -14,17 +9,54 @@ format:
|
||||||
@tofu fmt -recursive .
|
@tofu fmt -recursive .
|
||||||
@tflint --recursive
|
@tflint --recursive
|
||||||
|
|
||||||
deploy:
|
apply:
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
cd {{justfile_directory()}}/terraform
|
cd {{source_directory()}}/terraform
|
||||||
tofu init
|
tofu init
|
||||||
tofu apply
|
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:
|
destroy:
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
cd {{justfile_directory()}}/terraform
|
cd {{source_directory()}}/terraform
|
||||||
tofu destroy
|
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.
|
# Create a list of blocked IP ranges. Traffic we don't want.
|
||||||
update-blocked-list:
|
update-blocked-list:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
@ -49,20 +81,3 @@ update-blocked-list:
|
||||||
for ip in $IP_RANGES; do
|
for ip in $IP_RANGES; do
|
||||||
echo "deny $ip;" >> root/etc/nginx/partials/blocked.conf
|
echo "deny $ip;" >> root/etc/nginx/partials/blocked.conf
|
||||||
done
|
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[]'
|
|
||||||
|
|
17
ingress.tjo.cloud/root/etc/default/dyndns
Normal file
17
ingress.tjo.cloud/root/etc/default/dyndns
Normal 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
|
8
ingress.tjo.cloud/root/systemd/system/dyndns.service
Normal file
8
ingress.tjo.cloud/root/systemd/system/dyndns.service
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Dynamic DNS Updater
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/dyndns
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -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
|
|
132
ingress.tjo.cloud/root/usr/local/bin/dyndns
Executable file
132
ingress.tjo.cloud/root/usr/local/bin/dyndns
Executable 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
|
|
@ -14,6 +14,9 @@ locals {
|
||||||
tailscale = {
|
tailscale = {
|
||||||
auth_key = tailscale_tailnet_key.key.key
|
auth_key = tailscale_tailnet_key.key.key
|
||||||
}
|
}
|
||||||
|
digitalocean = {
|
||||||
|
token = var.digitalocean_token
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -60,8 +63,9 @@ resource "proxmox_virtual_environment_file" "userdata" {
|
||||||
power_state:
|
power_state:
|
||||||
mode: reboot
|
mode: reboot
|
||||||
runcmd:
|
runcmd:
|
||||||
- git clone https://code.tjo.space/tjo-cloud/ingress.git /srv
|
- git clone --depth 1 --no-checkout --filter=tree:0 https://github.com/tjo-space/tjo-cloud-infrastructure.git /srv
|
||||||
- /srv/install.sh
|
- cd /srv && git sparse-checkout set --no-cone /ingress.tjo.cloud && git checkout
|
||||||
|
- /srv/ingress.tjo.cloud/install.sh
|
||||||
EOF
|
EOF
|
||||||
file_name = "${each.value.host}.ingress.tjo.cloud.userconfig.yaml"
|
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_stop_vm = 60
|
||||||
timeout_shutdown_vm = 60
|
timeout_shutdown_vm = 60
|
||||||
timeout_reboot = 60
|
timeout_reboot = 60
|
||||||
timeout_create = 600
|
timeout_create = 60
|
||||||
|
|
||||||
cpu {
|
cpu {
|
||||||
cores = each.value.cores
|
cores = each.value.cores
|
||||||
|
|
|
@ -37,3 +37,8 @@ variable "tailscale_apikey" {
|
||||||
type = string
|
type = string
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "digitalocean_token" {
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
Loading…
Reference in a new issue