#!/usr/bin/env bash set -euo pipefail repo_url="https://github.com/pikasTech/unidesk" commit_id="" environment="dev" namespace="unidesk-ci" work_dir="/home/ubuntu/.unidesk/bootstrap/devops" image="unidesk-devops:dev" dry_run="0" usage() { cat <<'EOF' Usage: devops-install.sh --commit [--env dev] [--repo-url URL] [--namespace unidesk-ci] [--dry-run] This script is a one-shot D601 bootstrapper. It installs or repairs the UniDesk DevOps control service in native k3s, then normal CI/CD should use DevOps APIs. EOF } while [ "$#" -gt 0 ]; do case "$1" in --commit|--commit-id) commit_id="${2:-}" shift 2 ;; --env|--environment) environment="${2:-}" shift 2 ;; --repo-url) repo_url="${2:-}" shift 2 ;; --namespace) namespace="${2:-}" shift 2 ;; --work-dir) work_dir="${2:-}" shift 2 ;; --dry-run) dry_run="1" shift ;; -h|--help) usage exit 0 ;; *) echo "unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done if ! [[ "$commit_id" =~ ^[0-9a-fA-F]{7,40}$ ]]; then echo "--commit must be a 7-40 character git SHA" >&2 exit 2 fi if [ "$environment" != "dev" ]; then echo "only --env dev is supported by the first bootstrapper" >&2 exit 2 fi log() { printf '{"at":"%s","event":"%s"}\n' "$(date -Iseconds)" "$*" } need_cmd() { if ! command -v "$1" >/dev/null 2>&1; then echo "missing required command: $1" >&2 exit 1 fi } root_exec() { if [ "$(id -u)" = "0" ]; then "$@" elif sudo -n true >/dev/null 2>&1; then sudo -n "$@" else echo "root access is required for k3s containerd import" >&2 exit 1 fi } need_cmd git need_cmd docker need_cmd kubectl if [ ! -f /etc/rancher/k3s/k3s.yaml ]; then echo "native k3s kubeconfig not found: /etc/rancher/k3s/k3s.yaml" >&2 exit 1 fi export KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get nodes >/dev/null log "bootstrap_preflight_ok" if [ "$dry_run" = "1" ]; then exit 0 fi repo_dir="$work_dir/repo" mkdir -p "$work_dir" if [ ! -d "$repo_dir/.git" ]; then rm -rf "$repo_dir" git clone --no-checkout "$repo_url" "$repo_dir" fi git -C "$repo_dir" remote set-url origin "$repo_url" git -C "$repo_dir" fetch --no-tags origin "$commit_id" || git -C "$repo_dir" fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*' resolved="$(git -C "$repo_dir" rev-parse --verify "$commit_id^{commit}")" git -C "$repo_dir" checkout --detach "$resolved" log "source_ready commit=$resolved" docker buildx build --load \ --progress=plain \ --label "unidesk.ai/service-id=devops" \ --label "unidesk.ai/source-repo=$repo_url" \ --label "unidesk.ai/source-commit=$resolved" \ --label "unidesk.ai/dockerfile=src/components/microservices/devops/Dockerfile" \ -t "$image" \ -f "$repo_dir/src/components/microservices/devops/Dockerfile" \ "$repo_dir" archive="$work_dir/devops-image.tar" rm -f "$archive" docker save "$image" -o "$archive" root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images import "$archive" manifest="$work_dir/devops.k8s.yaml" cp "$repo_dir/src/components/microservices/k3sctl-adapter/k3s/devops.k8s.yaml" "$manifest" python3 - "$manifest" "$image" "$repo_url" "$resolved" "$commit_id" <<'PY' import re import sys path, image, repo, commit, requested = sys.argv[1:] text = open(path, encoding="utf-8").read() text = re.sub(r"image: unidesk-devops:[^\n]+", f"image: {image}", text) text = text.replace("value: https://github.com/pikasTech/unidesk", f"value: {repo}") text = text.replace("unidesk.ai/deploy-commit: replace-with-deploy-env-commit", f"unidesk.ai/deploy-commit: {commit}") text = text.replace("unidesk.ai/deploy-requested-commit: replace-with-deploy-env-commit", f"unidesk.ai/deploy-requested-commit: {requested}") text = text.replace("value: replace-with-deploy-env-commit", f"value: {commit}") open(path, "w", encoding="utf-8").write(text) PY kubectl create namespace "$namespace" --dry-run=client -o yaml | kubectl apply -f - kubectl apply -f "$manifest" kubectl -n "$namespace" rollout status deployment/devops --timeout=180s kubectl -n "$namespace" get --raw "/api/v1/namespaces/$namespace/services/http:devops:4286/proxy/health" >/tmp/unidesk-devops-health.json receipt="$work_dir/receipt.json" python3 - "$receipt" "$repo_url" "$resolved" "$commit_id" "$namespace" "$image"