migration

This commit is contained in:
2026-04-13 15:54:14 +02:00
parent cb548ee09a
commit 827213c883
17 changed files with 428 additions and 22 deletions

View File

@@ -334,6 +334,7 @@ kubectl patch application myapp -n argocd \
| **Loki** | Logs | `monitoring` | 1 | | **Loki** | Logs | `monitoring` | 1 |
| **Tempo** | Distributed tracing | `monitoring` | 1 | | **Tempo** | Distributed tracing | `monitoring` | 1 |
| **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet | | **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet |
| **OpenCost** | Cost monitoring | `monitoring` | 1 |
| **Trivy** | Vulnerability scanning | `trivy-system` | 1 | | **Trivy** | Vulnerability scanning | `trivy-system` | 1 |
**Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components) **Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components)

View File

@@ -16,14 +16,14 @@ metadata:
spec: spec:
project: default project: default
sources: sources:
- repoURL: git@github.com:fortedigital/forte-helm - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/argocd-mcp/values.yaml - $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 targetRevision: HEAD
ref: values ref: values

View File

@@ -17,14 +17,14 @@ metadata:
spec: spec:
project: default project: default
sources: sources:
- repoURL: git@github.com:fortedigital/forte-helm - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/mcp10x/values.yaml - $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 targetRevision: HEAD
ref: values ref: values

View File

@@ -17,14 +17,14 @@ metadata:
spec: spec:
project: default project: default
sources: sources:
- repoURL: git@github.com:fortedigital/forte-helm - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/musicman/values.yaml - $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 targetRevision: HEAD
ref: values ref: values

View 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

View 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

View File

@@ -127,7 +127,7 @@ spec:
spec: spec:
containers: containers:
- name: authn - 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: ports:
- containerPort: "{{ sidecarPort }}" - containerPort: "{{ sidecarPort }}"
name: auth name: auth
@@ -208,7 +208,7 @@ spec:
spec: spec:
containers: containers:
- name: authn - 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 imagePullPolicy: Always
ports: ports:
- containerPort: "{{ sidecarPort }}" - containerPort: "{{ sidecarPort }}"
@@ -301,7 +301,7 @@ spec:
spec: spec:
containers: containers:
- name: authn - 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 imagePullPolicy: Always
ports: ports:
- containerPort: "{{ sidecarPort }}" - containerPort: "{{ sidecarPort }}"
@@ -380,7 +380,7 @@ spec:
spec: spec:
containers: containers:
- name: authn - 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 imagePullPolicy: Always
ports: ports:
- containerPort: "{{ sidecarPort }}" - containerPort: "{{ sidecarPort }}"

View File

@@ -180,7 +180,7 @@ Save the following file in private/ (gitignored) folder as secret.yaml
argocd.argoproj.io/secret-type: repository argocd.argoproj.io/secret-type: repository
stringData: stringData:
type: git type: git
url: git@github.com:fortedigital/forte-helm.git url: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
sshPrivateKey: | sshPrivateKey: |
<paste your private key here> <paste your private key here>
project: default project: default

View File

@@ -40,3 +40,9 @@ spec:
- CreateNamespace=true - CreateNamespace=true
- Validate=true - Validate=true
- ServerSideApply=true - ServerSideApply=true
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates

View File

@@ -40,3 +40,9 @@ spec:
- CreateNamespace=true - CreateNamespace=true
- Validate=true - Validate=true
- ServerSideApply=true - ServerSideApply=true
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates

View File

@@ -76,6 +76,10 @@ spec:
{ {
"name": "websecure", "name": "websecure",
"mode": "tcp" "mode": "tcp"
},
{
"name": "giteassh",
"mode": "tcp"
} }
], ],
"backends": [ "backends": [
@@ -90,6 +94,9 @@ spec:
"properties": { "properties": {
"outbound_proxy_protocol": "v2" "outbound_proxy_protocol": "v2"
} }
},
{
"name": "giteassh"
} }
] ]
} }
@@ -129,6 +136,13 @@ spec:
metrics: true metrics: true
tracing: true tracing: true
giteassh:
port: 2222
expose:
default: true
exposedPort: 2222
protocol: TCP
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: traefik-system namespace: traefik-system

View File

@@ -3,7 +3,7 @@
enabled: true enabled: true
giteaRootURL: http://gitea-http.gitea.svc.cluster.local:3000 giteaRootURL: https://git.forteapps.net
existingSecret: gitea-runner-token existingSecret: gitea-runner-token
existingSecretKey: token existingSecretKey: token
@@ -30,8 +30,7 @@ statefulset:
docker_timeout: 300s docker_timeout: 300s
runner: runner:
labels: labels:
- "ubuntu-latest:docker://node:20-bookworm" - "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04"
- "ubuntu-22.04:docker://node:20-bookworm" - "ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
dind: dind:
rootless: false rootless: false

View File

@@ -17,13 +17,17 @@ gitea:
DOMAIN: git.forteapps.net DOMAIN: git.forteapps.net
ROOT_URL: https://git.forteapps.net ROOT_URL: https://git.forteapps.net
SSH_DOMAIN: git.forteapps.net SSH_DOMAIN: git.forteapps.net
SSH_PORT: 22 SSH_PORT: 2222
LFS_START_SERVER: true LFS_START_SERVER: true
ENABLE_GITEA_PAGES: true
service: service:
DISABLE_REGISTRATION: false DISABLE_REGISTRATION: false
DEFAULT_ALLOW_CREATE_ORGANIZATION: false
REQUIRE_SIGNIN_VIEW: false REQUIRE_SIGNIN_VIEW: false
ALLOW_ONLY_EXTERNAL_REGISTRATION: true ALLOW_ONLY_EXTERNAL_REGISTRATION: true
ENABLE_BASIC_AUTHENTICATION: true
ENABLE_PASSWORD_SIGNIN_FORM: false
openid: openid:
ENABLE_OPENID_SIGNIN: false ENABLE_OPENID_SIGNIN: false
@@ -67,8 +71,8 @@ gitea:
existingSecret: gitea-credentials existingSecret: gitea-credentials
key: gitea key: gitea
autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration" autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration"
scopes: "openid email profile" scopes: "openid email profile organization"
groupClaimName: "" groupClaimName: "groups"
adminGroup: "" adminGroup: ""
restrictedGroup: "" restrictedGroup: ""
# -- Prometheus metrics (scraped via annotations, no ServiceMonitor CRD needed) # -- Prometheus metrics (scraped via annotations, no ServiceMonitor CRD needed)
@@ -146,7 +150,7 @@ redis-cluster:
test: test:
enabled: false 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: service:
ssh: ssh:
type: ClusterIP type: ClusterIP

View File

@@ -15,10 +15,10 @@ opencost:
provider: custom provider: custom
costModel: costModel:
description: "UpCloud 4-node cluster pricing" description: "UpCloud 4-node cluster pricing"
CPU: "6.07" CPU: "5.86"
RAM: "1.52" RAM: "1.46"
GPU: "0" GPU: "0"
storage: "0.03" storage: "0.34"
zoneNetworkEgress: "0" zoneNetworkEgress: "0"
regionNetworkEgress: "0" regionNetworkEgress: "0"
internetNetworkEgress: "0" internetNetworkEgress: "0"

91
scripts/gitea-backup.sh Normal file
View 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
View 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."

View 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