feat(cicd): switch CI/CD from GitHub Actions to Gitea Actions
Some checks failed
Build & Deploy to K8s / build-and-deploy (push) Failing after 15s

- Add .gitea/workflows/deploy.yaml (detect changes → docker build → Harbor push → kubectl deploy)
- Add gitea-sync-cronjob.yaml (GitHub → Gitea mirror sync every 5 min)
- Add act-runner-rbac.yaml (RBAC for act_runner to deploy to staging namespace)
- Add setup-secrets.sh (one-time cluster secret setup script)
- Disable GitHub Actions deploy-staging.yml (CI/CD now via Gitea)

Flow: GitHub push → Gitea sync (5min) → Gitea Actions → Docker build → Harbor → K8s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-04-10 20:03:19 +07:00
parent 966f5412bd
commit 48bb30b009
5 changed files with 436 additions and 6 deletions

View File

@@ -0,0 +1,223 @@
# EN: Build & Deploy GoodGo Platform to K8s staging via Gitea Actions
# VI: Build & Deploy GoodGo Platform len K8s staging qua Gitea Actions
#
# Flow: Push → detect changed services → docker build → push Harbor → kubectl deploy
# Runner: act_runner (host mode, in-cluster kubectl)
name: Build & Deploy to K8s
on:
push:
branches: [master, main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Clone source from GitHub
run: |
git clone --depth 2 --branch ${{ github.ref_name }} \
https://admin:Velik%402026@gitea.techbi.org/admin/pos-system.git .
- name: Detect changed services
run: |
IMAGE_TAG=$(echo "${{ github.sha }}" | cut -c1-7)
echo "IMAGE_TAG=${IMAGE_TAG}" >> $GITHUB_ENV
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "ALL")
SERVICES=""
check_change() {
local path="$1"
local name="$2"
if echo "$CHANGED" | grep -q "^${path}/" || echo "$CHANGED" | grep -q "ALL"; then
SERVICES="${SERVICES} ${name}"
fi
}
# Core services
check_change "services/iam-service-net" "iam-service"
check_change "services/merchant-service-net" "merchant-service"
check_change "services/order-service-net" "order-service"
check_change "services/fnb-engine-net" "fnb-engine"
check_change "services/catalog-service-net" "catalog-service"
check_change "services/inventory-service-net" "inventory-service"
check_change "services/wallet-service-net" "wallet-service"
check_change "services/storage-service-net" "storage-service"
check_change "services/booking-service-net" "booking-service"
check_change "services/chat-service-net" "chat-service"
check_change "services/social-service-net" "social-service"
check_change "services/promotion-service-net" "promotion-service"
check_change "services/membership-service-net" "membership-service"
check_change "services/mining-service-net" "mining-service"
check_change "services/mission-service-net" "mission-service"
# Ads services
check_change "services/ads-manager-service-net" "ads-manager-service"
check_change "services/ads-serving-service-net" "ads-serving-service"
check_change "services/ads-billing-service-net" "ads-billing-service"
check_change "services/ads-tracking-service-net" "ads-tracking-service"
check_change "services/ads-analytics-service-net" "ads-analytics-service"
# Marketing services
check_change "services/mkt-facebook-service-net" "mkt-facebook-service"
check_change "services/mkt-whatsapp-service-net" "mkt-whatsapp-service"
check_change "services/mkt-x-service-net" "mkt-x-service"
check_change "services/mkt-zalo-service-net" "mkt-zalo-service"
# Frontend
check_change "apps/web-client-tpos-net" "pos-web"
# If K8s configs changed, flag for config deploy
if echo "$CHANGED" | grep -q "^deployments/staging/"; then
SERVICES="${SERVICES} __k8s_config__"
fi
echo "CHANGED_SERVICES=${SERVICES}" >> $GITHUB_ENV
echo "Changed services: ${SERVICES:-none}"
- name: Build and push changed services
run: |
if [ -z "$CHANGED_SERVICES" ]; then
echo "No services changed, skipping build"
exit 0
fi
HARBOR="harbor.techbi.org"
PROJECT="goodgo"
echo "${{ secrets.HARBOR_PASSWORD }}" | docker login "${HARBOR}" -u "${{ secrets.HARBOR_USERNAME }}" --password-stdin
build_service() {
local name="$1"
local context="$2"
local image="${HARBOR}/${PROJECT}/${name}-net"
echo "=== Building ${name} ==="
docker build -t "${image}:${IMAGE_TAG}" -t "${image}:latest" "${context}"
docker push "${image}:${IMAGE_TAG}"
docker push "${image}:latest"
echo "=== ${name} pushed ==="
}
for svc in $CHANGED_SERVICES; do
case "$svc" in
iam-service) build_service "iam-service" "./services/iam-service-net" ;;
merchant-service) build_service "merchant-service" "./services/merchant-service-net" ;;
order-service) build_service "order-service" "./services/order-service-net" ;;
fnb-engine) build_service "fnb-engine" "./services/fnb-engine-net" ;;
catalog-service) build_service "catalog-service" "./services/catalog-service-net" ;;
inventory-service) build_service "inventory-service" "./services/inventory-service-net" ;;
wallet-service) build_service "wallet-service" "./services/wallet-service-net" ;;
storage-service) build_service "storage-service" "./services/storage-service-net" ;;
booking-service) build_service "booking-service" "./services/booking-service-net" ;;
chat-service) build_service "chat-service" "./services/chat-service-net" ;;
social-service) build_service "social-service" "./services/social-service-net" ;;
promotion-service) build_service "promotion-service" "./services/promotion-service-net" ;;
membership-service) build_service "membership-service" "./services/membership-service-net" ;;
mining-service) build_service "mining-service" "./services/mining-service-net" ;;
mission-service) build_service "mission-service" "./services/mission-service-net" ;;
ads-manager-service) build_service "ads-manager-service" "./services/ads-manager-service-net" ;;
ads-serving-service) build_service "ads-serving-service" "./services/ads-serving-service-net" ;;
ads-billing-service) build_service "ads-billing-service" "./services/ads-billing-service-net" ;;
ads-tracking-service) build_service "ads-tracking-service" "./services/ads-tracking-service-net" ;;
ads-analytics-service) build_service "ads-analytics-service" "./services/ads-analytics-service-net" ;;
mkt-facebook-service) build_service "mkt-facebook-service" "./services/mkt-facebook-service-net" ;;
mkt-whatsapp-service) build_service "mkt-whatsapp-service" "./services/mkt-whatsapp-service-net" ;;
mkt-x-service) build_service "mkt-x-service" "./services/mkt-x-service-net" ;;
mkt-zalo-service) build_service "mkt-zalo-service" "./services/mkt-zalo-service-net" ;;
pos-web) build_service "web-client-tpos" "./apps/web-client-tpos-net" ;;
__k8s_config__) echo "K8s config changed, will apply manifests" ;;
esac
done
- name: Deploy to K8s
run: |
if [ -z "$CHANGED_SERVICES" ]; then
echo "No services changed, skipping deploy"
exit 0
fi
HARBOR="harbor.techbi.org"
PROJECT="goodgo"
NS="staging"
# Apply K8s configs if changed
if echo "$CHANGED_SERVICES" | grep -q "__k8s_config__"; then
echo "=== Applying K8s configs ==="
kubectl apply -f deployments/staging/kubernetes/namespace.yaml
kubectl apply -f deployments/staging/kubernetes/configmap.yaml
kubectl apply -f deployments/staging/kubernetes/redis.yaml
kubectl apply -f deployments/staging/kubernetes/rabbitmq.yaml
kubectl apply -f deployments/staging/kubernetes/minio.yaml
kubectl apply -f deployments/staging/kubernetes/ingress.yaml
kubectl apply -f deployments/staging/kubernetes/network-policy.yaml
fi
deploy_service() {
local name="$1"
local image_name="$2"
local manifest="$3"
local image="${HARBOR}/${PROJECT}/${image_name}:${IMAGE_TAG}"
echo "=== Deploying ${name} ==="
kubectl apply -f "deployments/staging/kubernetes/${manifest}"
kubectl set image "deployment/${name}" "${name}=${image}" -n "${NS}"
kubectl patch deployment "${name}" -n "${NS}" \
-p '{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"harbor-pull-secret"}],"containers":[{"name":"'"${name}"'","imagePullPolicy":"Always"}]}}}}'
fi
for svc in $CHANGED_SERVICES; do
case "$svc" in
iam-service) deploy_service "iam-service" "iam-service-net" "iam-service.yaml" ;;
merchant-service) deploy_service "merchant-service" "merchant-service-net" "merchant-service.yaml" ;;
order-service) deploy_service "order-service" "order-service-net" "order-service.yaml" ;;
fnb-engine) deploy_service "fnb-engine" "fnb-engine-net" "fnb-engine.yaml" ;;
catalog-service) deploy_service "catalog-service" "catalog-service-net" "catalog-service.yaml" ;;
inventory-service) deploy_service "inventory-service" "inventory-service-net" "inventory-service.yaml" ;;
wallet-service) deploy_service "wallet-service" "wallet-service-net" "wallet-service.yaml" ;;
storage-service) deploy_service "storage-service" "storage-service-net" "storage-service.yaml" ;;
booking-service) deploy_service "booking-service" "booking-service-net" "booking-service.yaml" ;;
chat-service) deploy_service "chat-service" "chat-service-net" "chat-service.yaml" ;;
social-service) deploy_service "social-service" "social-service-net" "social-service.yaml" ;;
promotion-service) deploy_service "promotion-service" "promotion-service-net" "promotion-service.yaml" ;;
membership-service) deploy_service "membership-service" "membership-service-net" "membership-service.yaml" ;;
mining-service) deploy_service "mining-service" "mining-service-net" "mining-service.yaml" ;;
mission-service) deploy_service "mission-service" "mission-service-net" "mission-service.yaml" ;;
ads-manager-service) deploy_service "ads-manager-service" "ads-manager-service-net" "ads-manager-service.yaml" ;;
ads-serving-service) deploy_service "ads-serving-service" "ads-serving-service-net" "ads-serving-service.yaml" ;;
ads-billing-service) deploy_service "ads-billing-service" "ads-billing-service-net" "ads-billing-service.yaml" ;;
ads-tracking-service) deploy_service "ads-tracking-service" "ads-tracking-service-net" "ads-tracking-service.yaml" ;;
ads-analytics-service) deploy_service "ads-analytics-service" "ads-analytics-service-net" "ads-analytics-service.yaml" ;;
mkt-facebook-service) deploy_service "mkt-facebook-service" "mkt-facebook-service-net" "mkt-facebook-service.yaml" ;;
mkt-whatsapp-service) deploy_service "mkt-whatsapp-service" "mkt-whatsapp-service-net" "mkt-whatsapp-service.yaml" ;;
mkt-x-service) deploy_service "mkt-x-service" "mkt-x-service-net" "mkt-x-service.yaml" ;;
mkt-zalo-service) deploy_service "mkt-zalo-service" "mkt-zalo-service-net" "mkt-zalo-service.yaml" ;;
pos-web) deploy_service "pos-web" "web-client-tpos-net" "pos-web.yaml" ;;
esac
done
# Wait for rollouts
FAILED=0
for svc in $CHANGED_SERVICES; do
if [ "$svc" = "__k8s_config__" ]; then continue; fi
SVC_NAME="$svc"
if [ "$svc" = "pos-web" ]; then SVC_NAME="pos-web"; fi
echo "Waiting for ${SVC_NAME}..."
if ! kubectl rollout status "deployment/${SVC_NAME}" -n "${NS}" --timeout=180s 2>/dev/null; then
echo "WARNING: ${SVC_NAME} rollout did not complete"
FAILED=$((FAILED + 1))
fi
done
echo ""
echo "=== Deployment Summary ==="
kubectl get pods -n "${NS}" -o wide
echo ""
kubectl get svc -n "${NS}"
if [ $FAILED -gt 0 ]; then
echo "WARNING: ${FAILED} service(s) did not complete rollout"
exit 1
fi

View File

@@ -1,11 +1,21 @@
# EN: Deploy GoodGo Platform MVP services to Kubernetes staging
# VI: Trien khai cac service MVP cua GoodGo Platform len K8s staging
name: Deploy to Staging
# EN: DISABLED — CI/CD moved to Gitea Actions (gitea.techbi.org)
# VI: DA TAT — CI/CD chuyen sang Gitea Actions (gitea.techbi.org)
# See: .gitea/workflows/deploy.yaml
name: Deploy to Staging (DISABLED)
on:
push:
branches:
- master
workflow_dispatch:
inputs:
confirm:
description: 'This workflow is disabled. CI/CD runs via Gitea Actions.'
required: true
default: 'I understand'
# Original trigger (disabled):
# on:
# push:
# branches:
# - master
paths:
- 'services/iam-service-net/**'
- 'services/merchant-service-net/**'

View File

@@ -0,0 +1,46 @@
# EN: RBAC for Gitea act_runner to deploy to staging namespace
# VI: RBAC cho Gitea act_runner deploy vao namespace staging
#
# The act_runner ServiceAccount (in gitea namespace) needs permissions to:
# - Apply manifests (deployments, services, configmaps, secrets, ingress, hpa, networkpolicies, pvc)
# - Patch deployments (set image, rollout restart)
# - Read pods/logs (rollout status)
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: act-runner-staging
namespace: staging
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "configmaps", "secrets", "persistentvolumeclaims"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["autoscaling"]
resources: ["horizontalpodautoscalers"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: act-runner-staging-binding
namespace: staging
subjects:
- kind: ServiceAccount
name: act-runner
namespace: gitea
roleRef:
kind: Role
name: act-runner-staging
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,69 @@
# EN: CronJob to sync GitHub → Gitea every 5 minutes
# VI: CronJob dong bo GitHub → Gitea moi 5 phut
#
# Flow: GitHub (hongochai10/Microservices-Development)
# → git clone --bare via SSH
# → git push --mirror to Gitea (admin/pos-system)
#
# Prerequisites:
# 1. Create Gitea repo: admin/pos-system on gitea.techbi.org
# 2. Create secrets:
# kubectl create secret generic pos-gitea-push-creds -n gitea \
# --from-literal=url=https://admin:Velik%402026@gitea.techbi.org/admin/pos-system.git
# (git-ssh-key secret already exists in gitea namespace from neon-ui setup)
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: github-gitea-sync-pos
namespace: gitea
labels:
app: pos-system
platform: goodgo
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 2
jobTemplate:
spec:
backoffLimit: 1
ttlSecondsAfterFinished: 120
template:
spec:
containers:
- name: sync
image: alpine/git:latest
command: ["sh", "-c"]
args:
- |
mkdir -p /root/.ssh
cp /ssh/ssh-privatekey /root/.ssh/id_ed25519
cp /ssh/known_hosts /root/.ssh/known_hosts
chmod 600 /root/.ssh/id_ed25519
git clone --bare git@github.com:hongochai10/Microservices-Development.git /tmp/repo
cd /tmp/repo
git push --mirror "${GITEA_PUSH_URL}"
env:
- name: GITEA_PUSH_URL
valueFrom:
secretKeyRef:
name: pos-gitea-push-creds
key: url
volumeMounts:
- name: git-ssh
mountPath: /ssh
readOnly: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
restartPolicy: Never
volumes:
- name: git-ssh
secret:
secretName: git-ssh-key
defaultMode: 0600

View File

@@ -0,0 +1,82 @@
#!/bin/bash
# EN: Setup K8s secrets for GoodGo staging deployment
# VI: Thiet lap K8s secrets cho GoodGo staging deployment
#
# Prerequisites:
# - kubectl configured with cluster access
# - Gitea repo admin/pos-system created on gitea.techbi.org
# - Harbor project "goodgo" created on harbor.techbi.org
#
# Usage: ./setup-secrets.sh
set -e
NS="staging"
echo "=== Creating namespace ==="
kubectl create namespace "$NS" --dry-run=client -o yaml | kubectl apply -f -
echo "=== Creating Gitea push credentials (for CronJob sync) ==="
kubectl create secret generic pos-gitea-push-creds -n gitea \
--from-literal=url='https://admin:Velik%402026@gitea.techbi.org/admin/pos-system.git' \
--dry-run=client -o yaml | kubectl apply -f -
echo "=== Creating Harbor pull secret (for K8s to pull images) ==="
kubectl create secret docker-registry harbor-pull-secret -n "$NS" \
--docker-server=harbor.techbi.org \
--docker-username=admin \
--docker-password='Velik@2026' \
--docker-email=admin@techbi.org \
--dry-run=client -o yaml | kubectl apply -f -
echo "=== Applying RBAC for act_runner ==="
kubectl apply -f act-runner-rbac.yaml
echo "=== Applying K8s secrets (DB, JWT, Redis, etc.) ==="
# Create goodgo-secrets with real values
kubectl create secret generic goodgo-secrets -n "$NS" \
--from-literal=Jwt__Secret='GoodGo-Local-Dev-JWT-Secret-2024-Min32Chars!!' \
--from-literal=Jwt__RefreshSecret='GoodGo-Local-Dev-Refresh-Secret-2024-32Ch!!' \
--from-literal=IdentityServer__IssuerUri='https://api.staging.goodgo.vn' \
--from-literal=Redis__Password='goodgo-redis-local' \
--from-literal=ConnectionStrings__Redis='redis:6379,password=goodgo-redis-local' \
--from-literal=Storage__MinIO__AccessKey='minioadmin' \
--from-literal=Storage__MinIO__SecretKey='minioadmin123' \
--from-literal=Storage__MinIO__Endpoint='minio.staging.svc.cluster.local:9000' \
--from-literal=RabbitMQ__Host='rabbitmq' \
--from-literal=RabbitMQ__Username='goodgo' \
--from-literal=RabbitMQ__Password='goodgo-rabbitmq-local' \
--from-literal=IAM_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=iam_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=MERCHANT_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=merchant_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=ORDER_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=order_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=FNB_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=fnb_engine;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=CATALOG_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=catalog_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=INVENTORY_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=inventory_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=WALLET_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=wallet_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=STORAGE_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=storage_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=BOOKING_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=booking_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=CHAT_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=chat_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=SOCIAL_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=social_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=PROMOTION_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=promotion_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=MEMBERSHIP_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=membership_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=MINING_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=mining_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=MISSION_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=mission_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=ADS_MANAGER_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=ads_manager_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=ADS_SERVING_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=ads_serving_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=ADS_BILLING_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=ads_billing_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=ADS_TRACKING_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=ads_tracking_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=ADS_ANALYTICS_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=ads_analytics_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=MKT_FACEBOOK_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=mkt_facebook_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=MKT_WHATSAPP_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=mkt_whatsapp_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=MKT_X_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=mkt_x_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--from-literal=MKT_ZALO_DATABASE_URL='Host=212.28.186.239;Port=30992;Database=mkt_zalo_service;Username=cloud_admin;Password=XbnKQ2ONe6pMxxCh;SSL Mode=Prefer' \
--dry-run=client -o yaml | kubectl apply -f -
echo ""
echo "=== Setup complete ==="
echo "Next steps:"
echo " 1. Create Harbor project 'goodgo' at https://harbor.techbi.org"
echo " 2. Create Gitea repo 'admin/pos-system' at https://gitea.techbi.org"
echo " 3. Set Gitea repo secrets: HARBOR_USERNAME=admin, HARBOR_PASSWORD=Velik@2026"
echo " 4. Apply CronJob: kubectl apply -f gitea-sync-cronjob.yaml"
echo " 5. Apply manifests: kubectl apply -f deployments/staging/kubernetes/"