* fix(k8s): expose / without auth so homepage is publicly reachable
Adds a second Ingress (api-public) for the exact path / with no
forward-auth middleware. Traefik prefers the Exact match for the root,
while the Prefix ingress (with auth) still protects all other routes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: homepage renders correctly at / for unauthenticated visitors
Two fixes:
1. Added parseStandalone() helper — parseTmpl() roots on "" but ParseFS()
stores standalone (no {{define}}) files under their base filename, so
Execute() ran the empty root and returned Content-Length: 0.
2. Added router.priority: 100 annotation to api-public ingress so Traefik
picks the Exact / rule over the Prefix / rule (Traefik ranks by rule
string length by default, which made PathPrefix beat Path).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(k8s): remove forward-auth middleware from finance ingress
The app now handles its own auth at /auth/login — Traefik no longer
needs to forward-auth requests, which was causing redirects to
auth.homelab.local instead of finance.homelab.local.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(auth): harden authentication for cloud deployment
1. Secure cookie flag — set when BASE_URL starts with https://
2. SameSite=Strict on session cookie (was Lax)
3. Rate limiter — per-IP, 10 failures → 15-min lockout, auto-cleanup goroutine
4. Session rotation on login — old session deleted before issuing new one
(prevents session fixation attacks)
5. bcrypt cost 12 (was DefaultCost/10, OWASP minimum for cloud)
6. Security headers middleware on all responses:
X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
Permissions-Policy, Content-Security-Policy, HSTS (when HTTPS)
7. Structured audit logging — login success/failure/lockout with IP + email
8. Google OAuth state cookie gets Secure flag too
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(infra): Gitea self-hosted CI/CD + MongoDB PVC + registry pipeline
- Add Gitea Helm deployment (git hosting, container registry, Gitea Actions)
- Add act runner with DinD sidecar for Docker builds in-cluster
- Add RBAC so act runner can kubectl-deploy to finance namespace
- Fix MongoDB StatefulSet: add volumeClaimTemplates (data was lost on restart)
- Configure k3d containerd to mirror git.homelab.local → Gitea NodePort 30002
- Add .gitea/workflows/finance-api.yml: test → build/push → rolling deploy
- Update finance-api deployment: Gitea registry image, imagePullPolicy Always
- Extract finance-api secrets (SESSION_SECRET, Google OAuth) into Terraform
- Add variables.tf for Gitea admin password and runner token
All changes testable on local k3d before the VPS exists.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
2.5 KiB
Makefile
64 lines
2.5 KiB
Makefile
# Usage: include infrastructure/Makefile/service.mk
|
|
# SERVICE_NAME is inferred from the directory name by default.
|
|
# Optional: IMAGE_TAG, K8S_DIR, CLUSTER_NAME
|
|
# Auto-detects Go project (main/ dir) vs Node project (package.json)
|
|
|
|
SERVICE_NAME ?= $(notdir $(CURDIR))
|
|
SERVICE_DIR ?= .
|
|
K8S_DIR ?= $(SERVICE_DIR)/k8s
|
|
PROJECT_ROOT ?= $(shell git rev-parse --show-toplevel 2>/dev/null || \
|
|
dir=$(CURDIR); until [ -f "$$dir/go.mod" ] || [ "$$dir" = / ]; do dir=$$(dirname "$$dir"); done; \
|
|
[ -f "$$dir/go.mod" ] && echo "$$dir" || echo "")
|
|
|
|
_abs_root := $(abspath $(PROJECT_ROOT))
|
|
_rel_path := $(patsubst $(_abs_root)/%,%,$(CURDIR))
|
|
NAMESPACE ?= $(word 2,$(subst /, ,$(_rel_path)))
|
|
|
|
IMAGE_TAG ?= latest
|
|
REGISTRY ?= git.homelab.local/admin
|
|
IMAGE ?= $(REGISTRY)/$(SERVICE_NAME):$(IMAGE_TAG)
|
|
CLUSTER_NAME ?= homelab
|
|
|
|
_is_node := $(shell [ -f $(SERVICE_DIR)/package.json ] && echo yes)
|
|
|
|
.PHONY: help
|
|
help: ## Show this help
|
|
@grep -hE '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
|
|
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
|
|
|
.PHONY: build
|
|
build: ## Build the Docker image
|
|
docker build -t $(IMAGE) -f $(SERVICE_DIR)/Dockerfile $(PROJECT_ROOT)
|
|
|
|
.PHONY: load
|
|
load: ## Load the image into the k3d cluster
|
|
k3d image import $(IMAGE) -c $(CLUSTER_NAME)
|
|
|
|
.PHONY: deploy
|
|
deploy: ## Apply k8s manifests to the cluster
|
|
@kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f -
|
|
kubectl apply -f $(K8S_DIR)/ -n $(NAMESPACE)
|
|
|
|
.PHONY: build-deploy
|
|
build-deploy: build load deploy ## Build, load, and deploy in one step
|
|
|
|
.PHONY: skaffold-gen
|
|
skaffold-gen: ## Generate skaffold.yaml for the service
|
|
@echo "Generating $(SERVICE_DIR)/skaffold.yaml ..."
|
|
@ROOT="$$(cd "$(PROJECT_ROOT)" && pwd)"; \
|
|
SVC="$$(pwd)"; \
|
|
REL="$${SVC#$$ROOT/}"; \
|
|
printf 'apiVersion: skaffold/v4beta13\nkind: Config\nmetadata:\n name: %s\nbuild:\n artifacts:\n - image: homelab/%s\n context: %s\n docker:\n dockerfile: %s/Dockerfile\n local:\n push: false\nmanifests:\n rawYaml:\n - k8s/*.yaml\ndeploy:\n kubectl: {}\n' "$(SERVICE_NAME)" "$(SERVICE_NAME)" "$(PROJECT_ROOT)" "$$REL" > $(SERVICE_DIR)/skaffold.yaml
|
|
|
|
.PHONY: skaffold-dev
|
|
skaffold-dev: ## Run skaffold in dev mode (auto-sync, port-forward)
|
|
skaffold dev -f $(SERVICE_DIR)/skaffold.yaml
|
|
|
|
.PHONY: skaffold-run
|
|
skaffold-run: ## Run skaffold once (build + deploy)
|
|
skaffold run -f $(SERVICE_DIR)/skaffold.yaml
|
|
|
|
.PHONY: skaffold-delete
|
|
skaffold-delete: ## Delete resources deployed by skaffold
|
|
skaffold delete -f $(SERVICE_DIR)/skaffold.yaml
|