Add portfolio to homelab

This commit is contained in:
Nik Afiq 2026-03-18 00:06:47 +09:00
parent 26c4234dc6
commit 01e7a48403
4 changed files with 161 additions and 58 deletions

View File

@ -14,4 +14,14 @@ GITEA_RUNNER_TOKEN=your_token_here
# Grafana admin password
GRAFANA_ADMIN_PASSWORD=your_password_here
# Authentik secrets
AUTHENTIK_PROXY_TOKEN=your_token_here
AUTHENTIK_GITEA_CLIENT_ID=your_client_id_here
AUTHENTIK_GITEA_CLIENT_SECRET=your_client_secret_here
AUTHENTIK_GRAFANA_CLIENT_ID=your_client_id_here
AUTHENTIK_GRAFANA_CLIENT_SECRET=your_client_secret_here
# Gitea container registry credentials
REGISTRY_SERVER=your_registry_server_here
REGISTRY_USER=your_username_here
REGISTRY_PASSWORD=your_token_here

View File

@ -1,6 +1,6 @@
# Apply: kubectl apply -f manifests/network/ddns-cronjob.yaml
# Delete: kubectl delete -f manifests/network/ddns-cronjob.yaml
# Description: CronJob that updates home.nik4nao.com DNS on Porkbun every 5 minutes.
# Description: CronJob that updates nik4nao.com and home.nik4nao.com DNS on Porkbun every 5 minutes.
apiVersion: v1
kind: Namespace
metadata:
@ -42,8 +42,6 @@ spec:
apk add --no-cache curl jq -q
DOMAIN="nik4nao.com"
RECORD_NAME="home"
RECORD_FQDN="${RECORD_NAME}.${DOMAIN}"
TTL=300
timestamp() { date +"%Y-%m-%d %H:%M:%S%z"; }
@ -56,68 +54,74 @@ spec:
fi
echo "[$(timestamp)] WAN IP: $WAN_IP"
# Get current DNS records from Porkbun API
# Get all DNS records once (reused for all iterations)
RECORDS="$(curl -sf -X POST \
"https://api.porkbun.com/api/json/v3/dns/retrieve/${DOMAIN}" \
-H 'Content-Type: application/json' \
-d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\"}")"
# Get all A record IDs for this subdomain
RECORD_IDS="$(echo "$RECORDS" | jq -r \
--arg fqdn "$RECORD_FQDN" \
'.records[] | select(.type=="A" and .name==$fqdn) | .id')"
for RECORD_NAME in "" "home"; do
RECORD_FQDN="${DOMAIN}"
[ -n "$RECORD_NAME" ] && RECORD_FQDN="${RECORD_NAME}.${DOMAIN}"
echo "[$(timestamp)] Processing: ${RECORD_FQDN}"
# Get the current DNS IP from the first record
DNS_IP="$(echo "$RECORDS" | jq -r \
--arg fqdn "$RECORD_FQDN" \
'.records[] | select(.type=="A" and .name==$fqdn) | .content' | head -1)"
# Get all A record IDs for this record
RECORD_IDS="$(echo "$RECORDS" | jq -r \
--arg fqdn "$RECORD_FQDN" \
'.records[] | select(.type=="A" and .name==$fqdn) | .id')"
echo "[$(timestamp)] DNS IP: ${DNS_IP:-none}"
# Get the current DNS IP from the first record
DNS_IP="$(echo "$RECORDS" | jq -r \
--arg fqdn "$RECORD_FQDN" \
'.records[] | select(.type=="A" and .name==$fqdn) | .content' | head -1)"
# Delete all stale duplicate records (keep none — we'll create/update cleanly)
RECORD_COUNT="$(echo "$RECORD_IDS" | grep -c . || true)"
if [ "$RECORD_COUNT" -gt 1 ]; then
echo "[$(timestamp)] Found $RECORD_COUNT duplicate records, deleting all..."
for ID in $RECORD_IDS; do
curl -sf -X POST \
"https://api.porkbun.com/api/json/v3/dns/delete/${DOMAIN}/${ID}" \
echo "[$(timestamp)] DNS IP: ${DNS_IP:-none}"
# Delete all stale duplicate records
RECORD_COUNT="$(echo "$RECORD_IDS" | grep -c . || true)"
if [ "$RECORD_COUNT" -gt 1 ]; then
echo "[$(timestamp)] Found $RECORD_COUNT duplicate records, deleting all..."
for ID in $RECORD_IDS; do
curl -sf -X POST \
"https://api.porkbun.com/api/json/v3/dns/delete/${DOMAIN}/${ID}" \
-H 'Content-Type: application/json' \
-d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\"}" > /dev/null
echo "[$(timestamp)] Deleted record ID $ID"
done
DNS_IP=""
fi
# Get the single remaining record ID (if any)
RECORD_ID="$(echo "$RECORDS" | jq -r \
--arg fqdn "$RECORD_FQDN" \
'.records[] | select(.type=="A" and .name==$fqdn) | .id' | head -1)"
# Skip if already correct
if [ "$RECORD_COUNT" -le 1 ] && [ "$WAN_IP" = "$DNS_IP" ]; then
echo "[$(timestamp)] No change, skipping."
continue
fi
echo "[$(timestamp)] IP changed ${DNS_IP:-none} -> $WAN_IP, updating..."
if [ -z "$RECORD_ID" ] || [ "$RECORD_COUNT" -gt 1 ]; then
# Create fresh record
echo "[$(timestamp)] Creating new record..."
RESP="$(curl -sf -X POST \
"https://api.porkbun.com/api/json/v3/dns/create/${DOMAIN}" \
-H 'Content-Type: application/json' \
-d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\"}" > /dev/null
echo "[$(timestamp)] Deleted record ID $ID"
done
DNS_IP=""
fi
-d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\",\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$WAN_IP\",\"ttl\":$TTL}")"
else
# Update existing single record
echo "[$(timestamp)] Updating record ID $RECORD_ID..."
RESP="$(curl -sf -X POST \
"https://api.porkbun.com/api/json/v3/dns/edit/${DOMAIN}/${RECORD_ID}" \
-H 'Content-Type: application/json' \
-d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\",\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$WAN_IP\",\"ttl\":$TTL}")"
fi
# Get the single remaining record ID (if any)
RECORD_ID="$(echo "$RECORDS" | jq -r \
--arg fqdn "$RECORD_FQDN" \
'.records[] | select(.type=="A" and .name==$fqdn) | .id' | head -1)"
# Skip if already correct (single record, correct IP)
if [ "$RECORD_COUNT" -le 1 ] && [ "$WAN_IP" = "$DNS_IP" ]; then
echo "[$(timestamp)] No change, skipping."
exit 0
fi
echo "[$(timestamp)] IP changed ${DNS_IP:-none} -> $WAN_IP, updating..."
if [ -z "$RECORD_ID" ] || [ "$RECORD_COUNT" -gt 1 ]; then
# Create fresh record
echo "[$(timestamp)] Creating new record..."
RESP="$(curl -sf -X POST \
"https://api.porkbun.com/api/json/v3/dns/create/${DOMAIN}" \
-H 'Content-Type: application/json' \
-d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\",\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$WAN_IP\",\"ttl\":$TTL}")"
else
# Update existing single record
echo "[$(timestamp)] Updating record ID $RECORD_ID..."
RESP="$(curl -sf -X POST \
"https://api.porkbun.com/api/json/v3/dns/edit/${DOMAIN}/${RECORD_ID}" \
-H 'Content-Type: application/json' \
-d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\",\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$WAN_IP\",\"ttl\":$TTL}")"
fi
echo "[$(timestamp)] Response: $(echo "$RESP" | jq -c .)"
echo "$RESP" | grep -q '"status":"SUCCESS"' && \
echo "[$(timestamp)] Update successful" || \
echo "[$(timestamp)] Update failed"
echo "[$(timestamp)] Response: $(echo "$RESP" | jq -c .)"
echo "$RESP" | grep -q '"status":"SUCCESS"' && \
echo "[$(timestamp)] Update successful" || \
echo "[$(timestamp)] Update failed"
done

View File

@ -0,0 +1,77 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: portfolio
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: portfolio
namespace: portfolio
spec:
replicas: 1
selector:
matchLabels:
app: portfolio
template:
metadata:
labels:
app: portfolio
spec:
imagePullSecrets:
- name: gitea-registry
containers:
- name: portfolio
image: gitea.nik4nao.com/nik/portfolio:latest
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: portfolio
namespace: portfolio
spec:
selector:
app: portfolio
ports:
- port: 80
targetPort: 80
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: portfolio-tls
namespace: portfolio
spec:
secretName: portfolio-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- nik4nao.com
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: portfolio
namespace: portfolio
spec:
entryPoints:
- websecure
routes:
- match: Host(`nik4nao.com`)
kind: Rule
services:
- name: portfolio
port: 80
tls:
secretName: portfolio-tls

View File

@ -0,0 +1,12 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../../.env"
kubectl create secret docker-registry gitea-registry \
--namespace portfolio \
--docker-server="$REGISTRY_SERVER" \
--docker-username="$REGISTRY_USER" \
--docker-password="$REGISTRY_PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -