migration
This commit is contained in:
@@ -334,6 +334,7 @@ kubectl patch application myapp -n argocd \
|
||||
| **Loki** | Logs | `monitoring` | 1 |
|
||||
| **Tempo** | Distributed tracing | `monitoring` | 1 |
|
||||
| **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet |
|
||||
| **OpenCost** | Cost monitoring | `monitoring` | 1 |
|
||||
| **Trivy** | Vulnerability scanning | `trivy-system` | 1 |
|
||||
|
||||
**Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components)
|
||||
|
||||
@@ -16,14 +16,14 @@ metadata:
|
||||
spec:
|
||||
project: default
|
||||
sources:
|
||||
- repoURL: git@github.com:fortedigital/forte-helm
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
|
||||
path: forteapp
|
||||
targetRevision: HEAD
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/argocd-mcp/values.yaml
|
||||
|
||||
- repoURL: git@github.com:fortedigital/helm-values.git
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
|
||||
targetRevision: HEAD
|
||||
ref: values
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@ metadata:
|
||||
spec:
|
||||
project: default
|
||||
sources:
|
||||
- repoURL: git@github.com:fortedigital/forte-helm
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
|
||||
path: forteapp
|
||||
targetRevision: HEAD
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/mcp10x/values.yaml
|
||||
|
||||
- repoURL: git@github.com:fortedigital/helm-values.git
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
|
||||
targetRevision: HEAD
|
||||
ref: values
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@ metadata:
|
||||
spec:
|
||||
project: default
|
||||
sources:
|
||||
- repoURL: git@github.com:fortedigital/forte-helm
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
|
||||
path: forteapp
|
||||
targetRevision: HEAD
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/musicman/values.yaml
|
||||
|
||||
- repoURL: git@github.com:fortedigital/helm-values.git
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
|
||||
targetRevision: HEAD
|
||||
ref: values
|
||||
|
||||
|
||||
88
cluster-resources/gitea-backup-cronjob.yaml
Normal file
88
cluster-resources/gitea-backup-cronjob.yaml
Normal file
@@ -0,0 +1,88 @@
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: gitea-backup
|
||||
namespace: gitea
|
||||
spec:
|
||||
schedule: "0 3 * * *" # daily at 03:00 UTC
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
activeDeadlineSeconds: 1800
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
# Must run on the same node as Gitea to share the RWO volume
|
||||
affinity:
|
||||
podAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: gitea
|
||||
topologyKey: kubernetes.io/hostname
|
||||
initContainers:
|
||||
- name: gitea-dump
|
||||
image: gitea/gitea:1.25.4
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
gitea dump \
|
||||
-c /data/gitea/conf/app.ini \
|
||||
-f /backup/gitea-dump.zip \
|
||||
-t /tmp/gitea-dump && \
|
||||
echo "Dump completed: $(ls -lh /backup/gitea-dump.zip)"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
readOnly: true
|
||||
- name: backup
|
||||
mountPath: /backup
|
||||
- name: tmp
|
||||
mountPath: /tmp/gitea-dump
|
||||
containers:
|
||||
- name: upload
|
||||
image: minio/mc:latest
|
||||
env:
|
||||
- name: HOME
|
||||
value: /tmp
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
mc alias set upcloud "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
KEY="gitea-dump-${TIMESTAMP}.zip"
|
||||
echo "Uploading ${KEY}..."
|
||||
mc cp /backup/gitea-dump.zip "upcloud/${S3_BUCKET}/${KEY}" && \
|
||||
echo "Upload complete."
|
||||
|
||||
# Prune backups older than 7 days
|
||||
echo "Pruning backups older than 7 days..."
|
||||
mc rm --older-than 7d --force "upcloud/${S3_BUCKET}/" 2>&1 || true
|
||||
echo "Pruning complete."
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: gitea-backup-s3
|
||||
volumeMounts:
|
||||
- name: backup
|
||||
mountPath: /backup
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: gitea-shared-storage
|
||||
- name: backup
|
||||
emptyDir:
|
||||
sizeLimit: 5Gi
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
sizeLimit: 5Gi
|
||||
13
cluster-resources/gitea-ssh-ingressroute.yaml
Normal file
13
cluster-resources/gitea-ssh-ingressroute.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRouteTCP
|
||||
metadata:
|
||||
name: gitea-ssh
|
||||
namespace: gitea
|
||||
spec:
|
||||
entryPoints:
|
||||
- giteassh
|
||||
routes:
|
||||
- match: HostSNI(`*`)
|
||||
services:
|
||||
- name: gitea-ssh
|
||||
port: 22
|
||||
@@ -127,7 +127,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: authn
|
||||
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
|
||||
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
|
||||
ports:
|
||||
- containerPort: "{{ sidecarPort }}"
|
||||
name: auth
|
||||
@@ -208,7 +208,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: authn
|
||||
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
|
||||
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: "{{ sidecarPort }}"
|
||||
@@ -301,7 +301,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: authn
|
||||
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
|
||||
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: "{{ sidecarPort }}"
|
||||
@@ -380,7 +380,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: authn
|
||||
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
|
||||
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: "{{ sidecarPort }}"
|
||||
|
||||
@@ -180,7 +180,7 @@ Save the following file in private/ (gitignored) folder as secret.yaml
|
||||
argocd.argoproj.io/secret-type: repository
|
||||
stringData:
|
||||
type: git
|
||||
url: git@github.com:fortedigital/forte-helm.git
|
||||
url: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
|
||||
sshPrivateKey: |
|
||||
<paste your private key here>
|
||||
project: default
|
||||
|
||||
@@ -40,3 +40,9 @@ spec:
|
||||
- CreateNamespace=true
|
||||
- Validate=true
|
||||
- ServerSideApply=true
|
||||
|
||||
ignoreDifferences:
|
||||
- group: apps
|
||||
kind: StatefulSet
|
||||
jsonPointers:
|
||||
- /spec/volumeClaimTemplates
|
||||
|
||||
@@ -40,3 +40,9 @@ spec:
|
||||
- CreateNamespace=true
|
||||
- Validate=true
|
||||
- ServerSideApply=true
|
||||
|
||||
ignoreDifferences:
|
||||
- group: apps
|
||||
kind: StatefulSet
|
||||
jsonPointers:
|
||||
- /spec/volumeClaimTemplates
|
||||
|
||||
@@ -76,6 +76,10 @@ spec:
|
||||
{
|
||||
"name": "websecure",
|
||||
"mode": "tcp"
|
||||
},
|
||||
{
|
||||
"name": "giteassh",
|
||||
"mode": "tcp"
|
||||
}
|
||||
],
|
||||
"backends": [
|
||||
@@ -90,6 +94,9 @@ spec:
|
||||
"properties": {
|
||||
"outbound_proxy_protocol": "v2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "giteassh"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -129,6 +136,13 @@ spec:
|
||||
metrics: true
|
||||
tracing: true
|
||||
|
||||
giteassh:
|
||||
port: 2222
|
||||
expose:
|
||||
default: true
|
||||
exposedPort: 2222
|
||||
protocol: TCP
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: traefik-system
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
enabled: true
|
||||
|
||||
giteaRootURL: http://gitea-http.gitea.svc.cluster.local:3000
|
||||
giteaRootURL: https://git.forteapps.net
|
||||
|
||||
existingSecret: gitea-runner-token
|
||||
existingSecretKey: token
|
||||
@@ -30,8 +30,7 @@ statefulset:
|
||||
docker_timeout: 300s
|
||||
runner:
|
||||
labels:
|
||||
- "ubuntu-latest:docker://node:20-bookworm"
|
||||
- "ubuntu-22.04:docker://node:20-bookworm"
|
||||
|
||||
- "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04"
|
||||
- "ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
|
||||
dind:
|
||||
rootless: false
|
||||
|
||||
@@ -17,13 +17,17 @@ gitea:
|
||||
DOMAIN: git.forteapps.net
|
||||
ROOT_URL: https://git.forteapps.net
|
||||
SSH_DOMAIN: git.forteapps.net
|
||||
SSH_PORT: 22
|
||||
SSH_PORT: 2222
|
||||
LFS_START_SERVER: true
|
||||
ENABLE_GITEA_PAGES: true
|
||||
|
||||
service:
|
||||
DISABLE_REGISTRATION: false
|
||||
DEFAULT_ALLOW_CREATE_ORGANIZATION: false
|
||||
REQUIRE_SIGNIN_VIEW: false
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION: true
|
||||
ENABLE_BASIC_AUTHENTICATION: true
|
||||
ENABLE_PASSWORD_SIGNIN_FORM: false
|
||||
|
||||
openid:
|
||||
ENABLE_OPENID_SIGNIN: false
|
||||
@@ -67,8 +71,8 @@ gitea:
|
||||
existingSecret: gitea-credentials
|
||||
key: gitea
|
||||
autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration"
|
||||
scopes: "openid email profile"
|
||||
groupClaimName: ""
|
||||
scopes: "openid email profile organization"
|
||||
groupClaimName: "groups"
|
||||
adminGroup: ""
|
||||
restrictedGroup: ""
|
||||
# -- Prometheus metrics (scraped via annotations, no ServiceMonitor CRD needed)
|
||||
@@ -146,7 +150,7 @@ redis-cluster:
|
||||
test:
|
||||
enabled: false
|
||||
|
||||
# -- SSH service (ClusterIP for now; enable NodePort if SSH access needed)
|
||||
# -- SSH service (ClusterIP, exposed externally via Traefik TCP IngressRoute on port 2222)
|
||||
service:
|
||||
ssh:
|
||||
type: ClusterIP
|
||||
|
||||
@@ -15,10 +15,10 @@ opencost:
|
||||
provider: custom
|
||||
costModel:
|
||||
description: "UpCloud 4-node cluster pricing"
|
||||
CPU: "6.07"
|
||||
RAM: "1.52"
|
||||
CPU: "5.86"
|
||||
RAM: "1.46"
|
||||
GPU: "0"
|
||||
storage: "0.03"
|
||||
storage: "0.34"
|
||||
zoneNetworkEgress: "0"
|
||||
regionNetworkEgress: "0"
|
||||
internetNetworkEgress: "0"
|
||||
|
||||
91
scripts/gitea-backup.sh
Normal file
91
scripts/gitea-backup.sh
Normal file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Gitea backup helper — interacts with the S3 bucket via a temporary pod
|
||||
# Uses the gitea-backup-s3 secret in the gitea namespace
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/gitea-backup.sh list # list all backups
|
||||
# ./scripts/gitea-backup.sh download <filename> # download a backup to current dir
|
||||
# ./scripts/gitea-backup.sh download latest # download the most recent backup
|
||||
|
||||
NAMESPACE="gitea"
|
||||
SECRET="gitea-backup-s3"
|
||||
IMAGE="minio/mc:latest"
|
||||
POD_NAME="gitea-backup-helper"
|
||||
ALIAS_CMD='mc alias set upcloud ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null'
|
||||
|
||||
cleanup() {
|
||||
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
mc_run() {
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"${ALIAS_CMD}; $1\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1
|
||||
kubectl -n "$NAMESPACE" logs "$POD_NAME"
|
||||
cleanup
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
list)
|
||||
echo "Listing backups..."
|
||||
mc_run 'mc ls upcloud/${S3_BUCKET}/'
|
||||
;;
|
||||
|
||||
download)
|
||||
FILE="${2:?Usage: $0 download <filename|latest>}"
|
||||
|
||||
if [ "$FILE" = "latest" ]; then
|
||||
echo "Finding latest backup..."
|
||||
FILE=$(mc_run 'mc ls upcloud/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]')
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "No backups found."
|
||||
exit 1
|
||||
fi
|
||||
echo "Latest: $FILE"
|
||||
fi
|
||||
|
||||
echo "Downloading $FILE..."
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"sleep 300\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
|
||||
|
||||
echo "Saving to ./$FILE ..."
|
||||
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat upcloud/\${S3_BUCKET}/$FILE" > "./$FILE"
|
||||
cleanup
|
||||
|
||||
echo "Downloaded: ./$FILE"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Gitea backup helper"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 list List all backups in S3"
|
||||
echo " $0 download <filename> Download a specific backup"
|
||||
echo " $0 download latest Download the most recent backup"
|
||||
;;
|
||||
esac
|
||||
165
scripts/gitea-restore.sh
Normal file
165
scripts/gitea-restore.sh
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Gitea restore helper — restores a gitea-dump zip into a running Gitea deployment
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Gitea deployed on the target cluster (Helm chart via ArgoCD)
|
||||
# - kubectl context pointing to the target cluster
|
||||
# - A gitea-dump zip file (from gitea-backup.sh download or CronJob)
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/gitea-restore.sh <gitea-dump-file.zip>
|
||||
#
|
||||
# What it does:
|
||||
# 1. Scales Gitea down to 0
|
||||
# 2. Restores the PostgreSQL database from gitea-db.sql
|
||||
# 3. Restores Git repositories to the data PVC
|
||||
# 4. Scales Gitea back up
|
||||
|
||||
NAMESPACE="gitea"
|
||||
DEPLOYMENT="gitea"
|
||||
PG_STATEFULSET="gitea-postgresql"
|
||||
PVC="gitea-shared-storage"
|
||||
HELPER_POD="gitea-restore-helper"
|
||||
HELPER_IMAGE="alpine:3.20"
|
||||
PG_USER="gitea"
|
||||
PG_DB="gitea"
|
||||
|
||||
DUMP_FILE="${1:?Usage: $0 <gitea-dump-file.zip>}"
|
||||
|
||||
if [ ! -f "$DUMP_FILE" ]; then
|
||||
echo "Error: file not found: $DUMP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Gitea Restore ==="
|
||||
echo "Dump file: $DUMP_FILE"
|
||||
echo ""
|
||||
|
||||
# --- Safety prompt ---
|
||||
read -r -p "This will OVERWRITE the Gitea database and repositories on the current cluster. Continue? [y/N] " confirm
|
||||
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
kubectl -n "$NAMESPACE" delete pod "$HELPER_POD" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# --- Step 1: Extract dump locally ---
|
||||
RESTORE_DIR=$(mktemp -d)
|
||||
echo ""
|
||||
echo "[1/5] Extracting dump..."
|
||||
unzip -q "$DUMP_FILE" -d "$RESTORE_DIR"
|
||||
|
||||
# Detect SQL dump file (gitea-db.sql or gitea-dump-*.sql)
|
||||
SQL_FILE=$(find "$RESTORE_DIR" -maxdepth 1 -name '*.sql' | head -1)
|
||||
if [ -z "$SQL_FILE" ]; then
|
||||
echo "Error: no .sql file found in dump."
|
||||
rm -rf "$RESTORE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect repos — either a "repos" folder or gitea-repo.zip
|
||||
if [ -d "$RESTORE_DIR/repos" ]; then
|
||||
REPOS_SOURCE="dir"
|
||||
REPOS_PATH="$RESTORE_DIR/repos"
|
||||
elif [ -f "$RESTORE_DIR/gitea-repo.zip" ]; then
|
||||
REPOS_SOURCE="zip"
|
||||
REPOS_PATH="$RESTORE_DIR/gitea-repo.zip"
|
||||
else
|
||||
echo "Error: no repos/ directory or gitea-repo.zip found in dump."
|
||||
rm -rf "$RESTORE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Found: $(basename "$SQL_FILE") ($(du -h "$SQL_FILE" | cut -f1))"
|
||||
echo " Found: repos ($REPOS_SOURCE)"
|
||||
[ -d "$RESTORE_DIR/data" ] && echo " Found: data/"
|
||||
[ -f "$RESTORE_DIR/app.ini" ] && echo " Found: app.ini (managed by Helm, skipping)"
|
||||
|
||||
# --- Step 2: Scale down Gitea ---
|
||||
echo ""
|
||||
echo "[2/5] Scaling down Gitea..."
|
||||
kubectl -n "$NAMESPACE" scale deployment "$DEPLOYMENT" --replicas=0
|
||||
kubectl -n "$NAMESPACE" rollout status deployment "$DEPLOYMENT" --timeout=60s 2>/dev/null || true
|
||||
echo " Waiting for pods to terminate..."
|
||||
kubectl -n "$NAMESPACE" wait --for=delete pod -l app.kubernetes.io/name=gitea --timeout=120s 2>/dev/null || true
|
||||
echo " Gitea is down."
|
||||
|
||||
# --- Step 3: Restore database ---
|
||||
echo ""
|
||||
echo "[3/5] Restoring database..."
|
||||
# Drop and recreate to ensure clean state
|
||||
kubectl -n "$NAMESPACE" exec "${PG_STATEFULSET}-0" -- \
|
||||
psql -U "$PG_USER" -d postgres -c "DROP DATABASE IF EXISTS ${PG_DB};" 2>/dev/null
|
||||
kubectl -n "$NAMESPACE" exec "${PG_STATEFULSET}-0" -- \
|
||||
psql -U "$PG_USER" -d postgres -c "CREATE DATABASE ${PG_DB} OWNER ${PG_USER};" 2>/dev/null
|
||||
kubectl -n "$NAMESPACE" exec -i "${PG_STATEFULSET}-0" -- \
|
||||
psql -U "$PG_USER" -d "$PG_DB" < "$SQL_FILE" > /dev/null
|
||||
echo " Database restored."
|
||||
|
||||
# --- Step 4: Restore repositories ---
|
||||
echo ""
|
||||
echo "[4/5] Restoring repositories..."
|
||||
|
||||
# Need a helper pod on the same node as the PVC (RWO)
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$HELPER_POD" --restart=Never \
|
||||
--image="$HELPER_IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{
|
||||
\"containers\":[{
|
||||
\"name\":\"$HELPER_POD\",
|
||||
\"image\":\"$HELPER_IMAGE\",
|
||||
\"command\":[\"sleep\",\"3600\"],
|
||||
\"volumeMounts\":[{\"name\":\"data\",\"mountPath\":\"/data\"}]
|
||||
}],
|
||||
\"volumes\":[{
|
||||
\"name\":\"data\",
|
||||
\"persistentVolumeClaim\":{\"claimName\":\"$PVC\"}
|
||||
}]
|
||||
}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$HELPER_POD" --timeout=120s > /dev/null 2>&1
|
||||
|
||||
# Clear existing repos
|
||||
kubectl -n "$NAMESPACE" exec "$HELPER_POD" -- sh -c "rm -rf /data/gitea/repositories/*" 2>/dev/null || true
|
||||
|
||||
# Upload repos — tar via stdin since kubectl cp needs tar in the container
|
||||
echo " Uploading repositories..."
|
||||
if [ "$REPOS_SOURCE" = "dir" ]; then
|
||||
# repos/ directory — tar and stream into the PVC
|
||||
tar -cf - -C "$REPOS_PATH" . | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "tar -xf - -C /data/gitea/repositories/"
|
||||
else
|
||||
# gitea-repo.zip — stream and extract
|
||||
cat "$REPOS_PATH" | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "cat > /tmp/gitea-repo.zip && unzip -q -o /tmp/gitea-repo.zip -d /data/gitea/repositories/ && rm /tmp/gitea-repo.zip"
|
||||
fi
|
||||
|
||||
# Restore data/ directory (avatars, attachments, LFS, etc.) if present
|
||||
if [ -d "$RESTORE_DIR/data" ]; then
|
||||
echo " Uploading data (avatars, attachments, LFS)..."
|
||||
tar -cf - -C "$RESTORE_DIR/data" . | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "tar -xf - -C /data/gitea/"
|
||||
fi
|
||||
|
||||
# Fix ownership
|
||||
kubectl -n "$NAMESPACE" exec "$HELPER_POD" -- chown -R 1000:1000 /data/gitea/
|
||||
echo " Repositories restored."
|
||||
|
||||
# --- Step 5: Scale back up ---
|
||||
echo ""
|
||||
echo "[5/5] Scaling Gitea back up..."
|
||||
kubectl -n "$NAMESPACE" scale deployment "$DEPLOYMENT" --replicas=1
|
||||
kubectl -n "$NAMESPACE" rollout status deployment "$DEPLOYMENT" --timeout=120s
|
||||
|
||||
# Cleanup temp dir
|
||||
rm -rf "$RESTORE_DIR"
|
||||
|
||||
echo ""
|
||||
echo "=== Restore complete ==="
|
||||
echo "Gitea should be back online with restored data."
|
||||
echo "Verify at your Gitea URL that repos and users are present."
|
||||
19
secrets/gitea-backup-s3-sealed.yaml
Normal file
19
secrets/gitea-backup-s3-sealed.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
apiVersion: bitnami.com/v1alpha1
|
||||
kind: SealedSecret
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: gitea-backup-s3
|
||||
namespace: gitea
|
||||
spec:
|
||||
encryptedData:
|
||||
AWS_ACCESS_KEY_ID: AgByl2/ljFIHzTD0Vy7srQoXRfdJRLG+WukgeLMJeiJm9MOFJBNEkr5ju2DemDNdRcViXQLN3yxqT/L0fG0rz+kaPQtLeVFToqr58vokxDasHw4WVIUOosi6wE+yaI16H6vxvdV8dck09nHE3fdBcwctlvjqsY7mKvyx4tYKdGRDoeJ/C7shYoDTl4E/ZtsSRkfOQ4Ojm6M6FU10zn03OOKrzaOUczxnqbAyGNFrZvCUGG38QQVnyYm/2HLofQgheSSQx8p0w5IgPRRhBM3IAyCLGkyEA8qHXSO3J5Y2m1izAoU9RVsHAVUMVTfYEdtUMADkDcywKzi0bi0ehBxDs2PuC89Uz4s6rKQ/v8xU+jUf3KogrZxsbuCh1iFVO/NCOLvQhLY5wU/946wl1WmS76/HxkM/D9Iq+KF0VsP0pJAA+SIyJQ3Bh0a9GnKRsJjCfO8qX0M0WSXhOpjw+4DvBoe653mV7n+LOEjQy7LJURFaz1HzQColelhhlQ5tHCN8J1jtjscFNiHZqyzBBm226X3oxr0cAC6e/l53ohNkKS3NA5aM/wouBrscs1/CfmDYKxujLyGqontFRQc3rtCZ29829u/RmuwietIVGeu+ooCacSM73zDqGYKM7HRr/Y7QYxuW0TiSaJhYMZQqsC4uo7ebZhRa2bWbCTHiVCs3jDdpSRyPgEECOvnOJbkTsh0e02HtrUEx7HBjLZua9FD2sskr8C9XJQ==
|
||||
AWS_SECRET_ACCESS_KEY: AgCYxgmto8ytLz8QMm25/nIqqezlWWennhbPSPMB/aDYR+zW45LvAZbjwVp6wwSR/U5iXwfdZg5/k8+8CzGAKDjxc3Nwygih3cUpqVBOl+uOzD3W3oNDsyQckhmNA4jidwIbJF6ecV8O+GVuU19+E4QrkHTIP9lN5pnhkfIR7nMRVj4jdcNahH2O75huadGQII4GG+rmnGX1012IAhknq20CiOCbby3a2yHaU3om0srO1TkW/67jioQX2IvgUh7jVl6c4r1Y6b+glwV4bHc9GecDqNEF6uj6uy8ChNh1khRfUYVysIQRM9m1pV/qlKiUW/wjDZOjoW88IAg4wl2MMOFQby27jVwQWSe+kUPwRMf7HSNWoq/DaE/z71cMsdeEnAXtQMGwNzOr4EGM1n/faPGDWkj306l1xjoXNO2hLCNX8BspSBxQDWWADBGClC6C1AQX0HlsZLV0G18VCEkjTwvPRmPqigxzHganxWiWM0q3DfGrc+JvnGFW0r7waoKI5vIzxwzCbb3I042+3z2vsvo7ZW2mez+eKgeD0MvhRW2SlBMiE62MGJQL2BvTew1iU0Xean+19WGZO7PPysnOH6kU098kTJ5GjQpxlI2C+w6QC18q8eQeIvVyd/7wH+k7RMCDC+No6MCcYhDlcQNbIir6JJ7vIOd3n5NKXdg7Sy3SnjkyDPTOjXTwyn2hHkATMzUxgn/0frNZYSsEMTuoNlOfcZLr7UbFv+Qlr49rkAEMo3deohfGiQSD
|
||||
S3_BUCKET: AgA6ulIpP/DrYOQ7iqo7CSeaSj0L/+PXDPZ7SxPmdu/wrqbXw7nlxAyp+7QHqkUub5XgVhwrk3KPmqUPcECPDbHdt93+nlM3PVD0yPNkVaijncEPRVccGu/VhE6Nae5lXI9U3pnAVAXn+7z8iwRpF/vr1qIGyQwiizsKyfBQhvRSupzOvY8sypbItDjyjttxlwMRorGI94GObeUS89kSx/MB7BWZJMtUuRRSG6YwgH/XIkIWbo2p2LD90EhNtaT9rYa1PcGP9BVcgHf/9zVCI4+1LWbfmZSgobwAEKLhzZfhzCM2DhN31CsVWhpp0x0gYmspNbtQQvoosKmeBPBT+BLkTabAhjx00rvVX3J48Er3PaVAjw6JxT1KSdaUuCmcIzX3O8ys/8PNacaEgEiqmeuIPgID8YHSXSfs9RIUkjKBWGydjE90lMQPgnqOBkPWTd1BNRqHj60D2pFp0/h7+j8/OfBj7doDp9ECwcdQqjwzX4pNi7WQiGd+Ri0/7DK1xSAOL0lwgg8VSrqCOIdasAZRVQuHhuWwKMyhdQyQCr+zCOQ/bLQaPeF1m7tKxFfU4lNz5tRiC8AOQI5aHX2gRrkpfugD3G9qFFQMl9EPCdNeBh/ezVWxSxekWvQTuGJ2WnLD33BhsZVLKjXa+tHjD/BsQjQgdCiqv8J9gPgtngx/pAFf0NaQezVU4tBfaYD4tetcrZDz5UtW5tTHaF4=
|
||||
S3_ENDPOINT: AgBfKbxdU9hZrxIpbM6b+hDthXQ+uYrjWHGmxSdjGvxgHB2P7E+HqblPAHjIpiAsGiPESV75ZKs5/BdEBOoZpbvneAuRgVLZ5mxkWiQ35q7sJpWaUg47icnlEPFoFj8oxYbi1NRAYB5hbc3AU0s11mw7wre0pRZu0pgSidHk/lyuSXOHKQzuhXxKYmV61LjMxCQGwDNwbiDNuSZyU7AZ2r+vr2W7Tzu6G+tJctwQd3HOnYbOLMV4tBv93nc7EkU4tbdvdIvkGHEmKf4r4F+nGvKZ3fZie1QKyQvG/4+i8OKqby9XJtviEEfBqfrk5qb1dNQlqCfA4ThQ11MmRiP8VoaUp/yoUHHYACNY9HLBp+N5Cgbbcxo044U1c8b97I6ZOZJ2waZ9XkrBpYPPXWJRKxLeNgYoJqn3yMZV/U561DO1jLZ2cwQXXaFrm1WT7VjcB0czdJHW3FcOg9lzYKMCCTTX+cD4M1oK992931eECQxBecrtlQYD+NlJng8ARm7myTACOZGYMQo2gjdM4ZBh9KqoCT2jrFC6E29YwfRAIXrhiWdZZxOW6Bu9Txt8FgxnIlSz9iZ1hvbfdvrSZTilJbAAULKFqLUgNpQbdgYHtGXQkzFHqYmbdZ0vJ6taIli7y+/Rz6xKcql8uJLxnuncLvLvXHxXl+rWeKrAMn+jPvnuCcdq6yVPsI0Nz/B4EQRL7Nzl9XYQxSybAJACrrCjgEuHsquoPpuznlGuk2scuakXdWOzMg6i/MEk
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: gitea-backup-s3
|
||||
namespace: gitea
|
||||
type: Opaque
|
||||
Reference in New Issue
Block a user