diff --git a/Dockerfile.k8s b/Dockerfile.k8s new file mode 100644 index 0000000..4913ad8 --- /dev/null +++ b/Dockerfile.k8s @@ -0,0 +1,12 @@ +FROM node:20-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY index.html vite.config.js links.json ./ +COPY public ./public +COPY src ./src +RUN npm run build + +FROM nginx:alpine +COPY --from=build /app/dist /usr/share/nginx/html +RUN printf 'server {\n listen 80;\n root /usr/share/nginx/html;\n location /assets/ { expires max; add_header Cache-Control "public, immutable"; }\n location / { try_files $uri /index.html; }\n}\n' > /etc/nginx/conf.d/default.conf diff --git a/k8s/backup.yaml b/k8s/backup.yaml new file mode 100644 index 0000000..42aa4e7 --- /dev/null +++ b/k8s/backup.yaml @@ -0,0 +1,29 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: backup + namespace: rain-hub +spec: + schedule: "0 3 * * *" + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: backup + image: rain-hub-backup:latest + imagePullPolicy: Never + envFrom: + - secretRef: + name: rain-hub-secrets + volumeMounts: + - name: ssh-key + mountPath: /ssh + readOnly: true + volumes: + - name: ssh-key + secret: + secretName: backup-ssh-key + defaultMode: 0400 diff --git a/k8s/db.yaml b/k8s/db.yaml new file mode 100644 index 0000000..0851341 --- /dev/null +++ b/k8s/db.yaml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: Service +metadata: + name: db + namespace: rain-hub +spec: + clusterIP: None + selector: + app: db + ports: + - port: 5432 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: db + namespace: rain-hub +spec: + serviceName: db + replicas: 1 + selector: + matchLabels: + app: db + template: + metadata: + labels: + app: db + spec: + containers: + - name: postgres + image: postgres:16-alpine + ports: + - containerPort: 5432 + envFrom: + - secretRef: + name: rain-hub-secrets + volumeMounts: + - name: pgdata + mountPath: /var/lib/postgresql/data + - name: initdb + mountPath: /docker-entrypoint-initdb.d + readOnly: true + readinessProbe: + exec: + command: ["pg_isready", "-U", "rainhub", "-d", "rainhub"] + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: initdb + configMap: + name: db-init + volumeClaimTemplates: + - metadata: + name: pgdata + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: local-path + resources: + requests: + storage: 5Gi diff --git a/k8s/linkcheck.yaml b/k8s/linkcheck.yaml new file mode 100644 index 0000000..4c58c3d --- /dev/null +++ b/k8s/linkcheck.yaml @@ -0,0 +1,21 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: linkcheck + namespace: rain-hub +spec: + schedule: "0 4 * * *" + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: linkcheck + image: rain-hub-tracker:latest + imagePullPolicy: Never + command: ["python", "check_links.py"] + envFrom: + - secretRef: + name: rain-hub-secrets diff --git a/k8s/namespace.yaml b/k8s/namespace.yaml new file mode 100644 index 0000000..4c3d5ab --- /dev/null +++ b/k8s/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: rain-hub diff --git a/k8s/secrets.yaml b/k8s/secrets.yaml new file mode 100644 index 0000000..b3777a2 --- /dev/null +++ b/k8s/secrets.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Secret +metadata: + name: rain-hub-secrets + namespace: rain-hub +type: Opaque +stringData: + POSTGRES_USER: rainhub + POSTGRES_PASSWORD: "CHANGE-ME" + POSTGRES_DB: rainhub + TRACKER_PASSWORD: "CHANGE-ME" + STATS_RO_PASSWORD: "CHANGE-ME" + TRACKER_SECRET: "CHANGE-ME" + DATABASE_URL: "postgresql://tracker_rw:CHANGE-ME@db:5432/rainhub" + SITE_HOST: "links.yourdomain.com" + BACKUP_TARGET: "user@backup-host:/srv/backups/rain-hub" + BACKUP_PORT: "22" diff --git a/k8s/tracker.yaml b/k8s/tracker.yaml new file mode 100644 index 0000000..3dbdfbb --- /dev/null +++ b/k8s/tracker.yaml @@ -0,0 +1,47 @@ +apiVersion: v1 +kind: Service +metadata: + name: tracker + namespace: rain-hub +spec: + type: NodePort + selector: + app: tracker + ports: + - port: 8000 + targetPort: 8000 + nodePort: 30800 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tracker + namespace: rain-hub +spec: + replicas: 1 + selector: + matchLabels: + app: tracker + template: + metadata: + labels: + app: tracker + spec: + containers: + - name: tracker + image: rain-hub-tracker:latest + imagePullPolicy: Never + ports: + - containerPort: 8000 + envFrom: + - secretRef: + name: rain-hub-secrets + volumeMounts: + - name: geoip + mountPath: /data + readOnly: true + volumes: + - name: geoip + configMap: + name: geoip-data + optional: true diff --git a/k8s/web.yaml b/k8s/web.yaml new file mode 100644 index 0000000..4873e80 --- /dev/null +++ b/k8s/web.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: Service +metadata: + name: web + namespace: rain-hub +spec: + type: NodePort + selector: + app: web + ports: + - port: 80 + targetPort: 80 + nodePort: 30080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: web + namespace: rain-hub +spec: + replicas: 2 + selector: + matchLabels: + app: web + template: + metadata: + labels: + app: web + spec: + containers: + - name: web + image: rain-hub-web:latest + imagePullPolicy: Never + ports: + - containerPort: 80 diff --git a/npm/docker-compose.yml b/npm/docker-compose.yml new file mode 100644 index 0000000..2139945 --- /dev/null +++ b/npm/docker-compose.yml @@ -0,0 +1,15 @@ +services: + npm: + image: jc21/nginx-proxy-manager:latest + restart: unless-stopped + ports: + - "80:80" + - "443:443" + - "81:81" + volumes: + - npm_data:/data + - npm_certs:/etc/letsencrypt + +volumes: + npm_data: {} + npm_certs: {}