From 545c3e306917a0989e7dbd907a7daaf8cdd56d74 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Apr 2026 11:39:11 +1100 Subject: [PATCH 01/16] Add DNS validation to install script --- deployment/install.sh | 69 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/deployment/install.sh b/deployment/install.sh index b8115d7..c89cd4e 100755 --- a/deployment/install.sh +++ b/deployment/install.sh @@ -33,6 +33,56 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_header() { echo -e "\n${BOLD}${CYAN}=== $1 ===${NC}\n"; } +detect_public_ip() { + local ip + ip=$(curl -fsSL -4 --connect-timeout 5 https://ifconfig.me 2>/dev/null) || \ + ip=$(curl -fsSL -4 --connect-timeout 5 https://api.ipify.org 2>/dev/null) || \ + ip="" + echo "$ip" +} + +verify_dns() { + local domain="$1" + local expected_ip="$2" + local timeout=300 + local interval=10 + local elapsed=0 + local domains=("$domain" "registry.$domain" "logs.$domain") + + log_info "Verifying DNS records (timeout: ${timeout}s, checking every ${interval}s)..." + echo "" + + while (( elapsed < timeout )); do + local all_ok=true + + for d in "${domains[@]}"; do + local resolved + resolved=$(dig +short "$d" A 2>/dev/null | head -1) + + if [[ "$resolved" == "$expected_ip" ]]; then + echo -e " ${GREEN}✓${NC} ${d} → ${resolved}" + else + echo -e " ${RED}✗${NC} ${d} → ${resolved:-not found} (expected ${expected_ip})" + all_ok=false + fi + done + + if $all_ok; then + echo "" + log_success "All DNS records verified!" + return 0 + fi + + (( elapsed += interval )) + echo -e "\n Retrying in ${interval}s... (${elapsed}s/${timeout}s)\n" + sleep "$interval" + done + + echo "" + log_warn "DNS verification timed out. Some records may not have propagated yet." + log_warn "Continuing with installation — SSL certificate provisioning may fail until DNS propagates." +} + check_root() { if [[ $EUID -ne 0 ]]; then log_error "This script must be run as root (use sudo)" @@ -70,7 +120,7 @@ install_docker_debian() { log_info "Installing Docker via official apt repository..." apt-get update -qq - apt-get install -y -qq ca-certificates curl gnupg >/dev/null + apt-get install -y -qq ca-certificates curl gnupg dnsutils >/dev/null install -m 0755 -d /etc/apt/keyrings curl -fsSL "https://download.docker.com/linux/${ID}/gpg" | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg @@ -96,6 +146,7 @@ install_docker_rhel() { $PKG_MGR install -y -q yum-utils >/dev/null 2>&1 || true $PKG_MGR config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 2>/dev/null || \ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 2>/dev/null + $PKG_MGR install -y -q bind-utils >/dev/null 2>&1 || true $PKG_MGR install -y -q docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null } @@ -162,6 +213,22 @@ configure_interactive() { log_header "Configuration" prompt_value ROOT_DOMAIN "Enter your root domain (e.g. cloud.example.com)" + + local public_ip + public_ip=$(detect_public_ip) + + echo "" + log_info "Please add the following DNS A records pointing to this server:" + echo "" + echo -e " ${BOLD}${ROOT_DOMAIN}${NC} → A → ${GREEN}${public_ip}${NC}" + echo -e " ${BOLD}registry.${ROOT_DOMAIN}${NC} → A → ${GREEN}${public_ip}${NC}" + echo -e " ${BOLD}logs.${ROOT_DOMAIN}${NC} → A → ${GREEN}${public_ip}${NC}" + echo "" + read -rp "$(echo -e "${YELLOW}Press Enter once you have configured DNS records...${NC}")" + echo "" + + verify_dns "$ROOT_DOMAIN" "$public_ip" + prompt_value ACME_EMAIL "Enter email for Let's Encrypt certificates" echo "" From a9b0e4e073d415882ab2579ceee213d0ef230422 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Apr 2026 11:42:02 +1100 Subject: [PATCH 02/16] Fix sudo --- deployment/install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deployment/install.sh b/deployment/install.sh index c89cd4e..bc22cfc 100755 --- a/deployment/install.sh +++ b/deployment/install.sh @@ -120,6 +120,7 @@ install_docker_debian() { log_info "Installing Docker via official apt repository..." apt-get update -qq + apt-mark hold sudo sudo-rs 2>/dev/null || true apt-get install -y -qq ca-certificates curl gnupg dnsutils >/dev/null install -m 0755 -d /etc/apt/keyrings @@ -143,6 +144,7 @@ install_docker_rhel() { PKG_MGR="yum" fi + $PKG_MGR versionlock add sudo sudo-rs 2>/dev/null || true $PKG_MGR install -y -q yum-utils >/dev/null 2>&1 || true $PKG_MGR config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 2>/dev/null || \ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 2>/dev/null From 4a2a08c14ad93ec963c12732ce13f89166b7e727 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Apr 2026 11:44:44 +1100 Subject: [PATCH 03/16] Fix install timeout --- deployment/install.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/deployment/install.sh b/deployment/install.sh index bc22cfc..6e2abd3 100755 --- a/deployment/install.sh +++ b/deployment/install.sh @@ -50,9 +50,13 @@ verify_dns() { local domains=("$domain" "registry.$domain" "logs.$domain") log_info "Verifying DNS records (timeout: ${timeout}s, checking every ${interval}s)..." + echo -e " ${YELLOW}Press Ctrl+C to skip verification and continue${NC}" echo "" - while (( elapsed < timeout )); do + local skipped=false + trap 'skipped=true' INT + + while (( elapsed < timeout )) && ! $skipped; do local all_ok=true for d in "${domains[@]}"; do @@ -70,16 +74,23 @@ verify_dns() { if $all_ok; then echo "" log_success "All DNS records verified!" + trap - INT return 0 fi (( elapsed += interval )) echo -e "\n Retrying in ${interval}s... (${elapsed}s/${timeout}s)\n" - sleep "$interval" + sleep "$interval" || true done + trap - INT + echo "" - log_warn "DNS verification timed out. Some records may not have propagated yet." + if $skipped; then + log_warn "DNS verification skipped." + else + log_warn "DNS verification timed out. Some records may not have propagated yet." + fi log_warn "Continuing with installation — SSL certificate provisioning may fail until DNS propagates." } From ec95a4fe192c95c438330a9e7451065fdb0a10c2 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Apr 2026 11:49:37 +1100 Subject: [PATCH 04/16] Fix sudo hold --- deployment/install.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/deployment/install.sh b/deployment/install.sh index 6e2abd3..2a86d76 100755 --- a/deployment/install.sh +++ b/deployment/install.sh @@ -131,7 +131,6 @@ install_docker_debian() { log_info "Installing Docker via official apt repository..." apt-get update -qq - apt-mark hold sudo sudo-rs 2>/dev/null || true apt-get install -y -qq ca-certificates curl gnupg dnsutils >/dev/null install -m 0755 -d /etc/apt/keyrings @@ -155,7 +154,6 @@ install_docker_rhel() { PKG_MGR="yum" fi - $PKG_MGR versionlock add sudo sudo-rs 2>/dev/null || true $PKG_MGR install -y -q yum-utils >/dev/null 2>&1 || true $PKG_MGR config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 2>/dev/null || \ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 2>/dev/null @@ -394,6 +392,14 @@ main() { check_root detect_os + + # Prevent sudo/sudo-rs from being upgraded mid-session (breaks sudo on Ubuntu 25.10+) + if [[ "$OS_FAMILY" == "debian" ]]; then + apt-mark hold sudo sudo-rs 2>/dev/null || true + elif command -v dnf &>/dev/null; then + dnf versionlock add sudo sudo-rs 2>/dev/null || true + fi + install_docker download_compose_files From 34fa59dd83225eef76895b83d171cb55faa0de3d Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Apr 2026 11:51:11 +1100 Subject: [PATCH 05/16] Fix dig installation --- deployment/install.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/deployment/install.sh b/deployment/install.sh index 2a86d76..7d36d35 100755 --- a/deployment/install.sh +++ b/deployment/install.sh @@ -49,6 +49,15 @@ verify_dns() { local elapsed=0 local domains=("$domain" "registry.$domain" "logs.$domain") + if ! command -v dig &>/dev/null; then + log_info "Installing dnsutils..." + if [[ "$OS_FAMILY" == "debian" ]]; then + apt-get install -y -qq dnsutils >/dev/null 2>&1 + else + $PKG_MGR install -y -q bind-utils >/dev/null 2>&1 + fi + fi + log_info "Verifying DNS records (timeout: ${timeout}s, checking every ${interval}s)..." echo -e " ${YELLOW}Press Ctrl+C to skip verification and continue${NC}" echo "" @@ -61,7 +70,7 @@ verify_dns() { for d in "${domains[@]}"; do local resolved - resolved=$(dig +short "$d" A 2>/dev/null | head -1) + resolved=$(dig +short "$d" A 2>/dev/null | head -1 || true) if [[ "$resolved" == "$expected_ip" ]]; then echo -e " ${GREEN}✓${NC} ${d} → ${resolved}" From 5b0961d75ceb5a6cbcbd8c206cb6d3e0ce57c70f Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Apr 2026 11:54:11 +1100 Subject: [PATCH 06/16] Use tip image --- deployment/compose.postgres.yml | 4 ++-- deployment/compose.production.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deployment/compose.postgres.yml b/deployment/compose.postgres.yml index 8996e0e..ed4a0f1 100644 --- a/deployment/compose.postgres.yml +++ b/deployment/compose.postgres.yml @@ -35,7 +35,7 @@ services: restart: unless-stopped web: - image: ghcr.io/techulus/cloud/web:latest + image: ghcr.io/techulus/cloud/web:tip env_file: - ./.env environment: @@ -63,7 +63,7 @@ services: restart: unless-stopped registry: - image: ghcr.io/techulus/cloud/registry:latest + image: ghcr.io/techulus/cloud/registry:tip env_file: - ./.env volumes: diff --git a/deployment/compose.production.yml b/deployment/compose.production.yml index 4e0d294..9399fab 100644 --- a/deployment/compose.production.yml +++ b/deployment/compose.production.yml @@ -24,7 +24,7 @@ services: restart: unless-stopped web: - image: ghcr.io/techulus/cloud/web:latest + image: ghcr.io/techulus/cloud/web:tip env_file: - ./.env environment: @@ -51,7 +51,7 @@ services: restart: unless-stopped registry: - image: ghcr.io/techulus/cloud/registry:latest + image: ghcr.io/techulus/cloud/registry:tip env_file: - ./.env volumes: From 1cd2ad1083342233bb2c04284ddac8c24eb93dde Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Apr 2026 12:05:20 +1100 Subject: [PATCH 07/16] Fix inngest key --- deployment/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/install.sh b/deployment/install.sh index 7d36d35..720601f 100755 --- a/deployment/install.sh +++ b/deployment/install.sh @@ -289,7 +289,7 @@ configure_interactive() { REGISTRY_USERNAME="admin" REGISTRY_PASSWORD="$(openssl rand -hex 16)" REGISTRY_HTTP_SECRET="$(openssl rand -hex 32)" - INNGEST_SIGNING_KEY="signkey-prod-$(openssl rand -hex 32)" + INNGEST_SIGNING_KEY="$(openssl rand -hex 32)" INNGEST_EVENT_KEY="$(openssl rand -hex 16)" if [[ "$USE_BUNDLED_PG" == "true" ]]; then From 11b9b0ce309e67f0430a9a87c2226bf7156df6a9 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Apr 2026 13:46:25 +1100 Subject: [PATCH 08/16] Env config for disabling signup --- deployment/compose.postgres.yml | 1 + deployment/compose.production.yml | 1 + deployment/install.sh | 6 ++++++ web/lib/auth.ts | 2 +- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/deployment/compose.postgres.yml b/deployment/compose.postgres.yml index ed4a0f1..e4d02bb 100644 --- a/deployment/compose.postgres.yml +++ b/deployment/compose.postgres.yml @@ -49,6 +49,7 @@ services: - INNGEST_BASE_URL=http://inngest:8288 - INNGEST_SIGNING_KEY=${INNGEST_SIGNING_KEY} - INNGEST_EVENT_KEY=${INNGEST_EVENT_KEY} + - ALLOW_SIGNUP=${ALLOW_SIGNUP:-false} depends_on: - postgres - victoria-logs diff --git a/deployment/compose.production.yml b/deployment/compose.production.yml index 9399fab..21c8be0 100644 --- a/deployment/compose.production.yml +++ b/deployment/compose.production.yml @@ -38,6 +38,7 @@ services: - INNGEST_BASE_URL=http://inngest:8288 - INNGEST_SIGNING_KEY=${INNGEST_SIGNING_KEY} - INNGEST_EVENT_KEY=${INNGEST_EVENT_KEY} + - ALLOW_SIGNUP=${ALLOW_SIGNUP:-false} depends_on: - victoria-logs - registry diff --git a/deployment/install.sh b/deployment/install.sh index 720601f..dc8b993 100755 --- a/deployment/install.sh +++ b/deployment/install.sh @@ -343,6 +343,8 @@ REGISTRY_HTTP_SECRET=${REGISTRY_HTTP_SECRET} INNGEST_SIGNING_KEY=${INNGEST_SIGNING_KEY} INNGEST_EVENT_KEY=${INNGEST_EVENT_KEY} +ALLOW_SIGNUP=true + COMPOSE_FILE=${COMPOSE_FILE} EOF @@ -384,6 +386,10 @@ build_and_start() { echo "" echo -e "${YELLOW}It may take a few minutes for SSL certificates to be provisioned.${NC}" echo "" + echo -e "${YELLOW}${BOLD}IMPORTANT:${NC} Signup is enabled. After creating your account, disable it:${NC}" + echo -e " 1. Edit ${DEPLOY_DIR}/.env and set ${BOLD}ALLOW_SIGNUP=false${NC}" + echo -e " 2. Run: ${BOLD}cd ${DEPLOY_DIR} && docker compose -f ${COMPOSE_FILE} up -d${NC}" + echo "" docker compose -f "$COMPOSE_FILE" ps } diff --git a/web/lib/auth.ts b/web/lib/auth.ts index 3d76726..9a1ad2a 100644 --- a/web/lib/auth.ts +++ b/web/lib/auth.ts @@ -10,6 +10,6 @@ export const auth = betterAuth({ }), emailAndPassword: { enabled: true, - disableSignUp: true, + disableSignUp: process.env.ALLOW_SIGNUP !== "true", }, }); From 3a3ed0b0a5e0ecf96ca42880d3b0cbe9614e1c16 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 3 Apr 2026 23:02:22 +1100 Subject: [PATCH 09/16] CLI Initial draft --- cli/.gitignore | 2 + cli/.mise.toml | 2 + cli/bun.lock | 91 +++ cli/package.json | 24 + cli/src/config.ts | 64 ++ cli/src/main.ts | 444 ++++++++++++ cli/src/manifest.ts | 89 +++ cli/tsconfig.json | 14 + web/app/(auth)/register/page.tsx | 122 +--- web/app/api/v1/cli/auth/exchange/route.ts | 57 ++ web/app/api/v1/cli/auth/whoami/route.ts | 18 + web/app/api/v1/manifest/apply/route.ts | 32 + web/app/api/v1/manifest/deploy/route.ts | 32 + web/app/api/v1/manifest/status/route.ts | 45 ++ web/app/device/approve/page.tsx | 17 + web/app/device/page.tsx | 17 + web/app/page.tsx | 129 +--- web/components/auth/device-approval-page.tsx | 119 ++++ .../auth/device-authorization-page.tsx | 119 ++++ web/components/auth/register-page.tsx | 122 ++++ web/components/auth/sign-in-page.tsx | 133 ++++ web/db/schema.ts | 73 ++ web/lib/api-auth.ts | 23 + web/lib/auth-client.ts | 8 +- web/lib/auth.ts | 17 + web/lib/cli-manifest.ts | 99 +++ web/lib/cli-service.ts | 634 ++++++++++++++++++ 27 files changed, 2318 insertions(+), 228 deletions(-) create mode 100644 cli/.gitignore create mode 100644 cli/.mise.toml create mode 100644 cli/bun.lock create mode 100644 cli/package.json create mode 100644 cli/src/config.ts create mode 100644 cli/src/main.ts create mode 100644 cli/src/manifest.ts create mode 100644 cli/tsconfig.json create mode 100644 web/app/api/v1/cli/auth/exchange/route.ts create mode 100644 web/app/api/v1/cli/auth/whoami/route.ts create mode 100644 web/app/api/v1/manifest/apply/route.ts create mode 100644 web/app/api/v1/manifest/deploy/route.ts create mode 100644 web/app/api/v1/manifest/status/route.ts create mode 100644 web/app/device/approve/page.tsx create mode 100644 web/app/device/page.tsx create mode 100644 web/components/auth/device-approval-page.tsx create mode 100644 web/components/auth/device-authorization-page.tsx create mode 100644 web/components/auth/register-page.tsx create mode 100644 web/components/auth/sign-in-page.tsx create mode 100644 web/lib/api-auth.ts create mode 100644 web/lib/cli-manifest.ts create mode 100644 web/lib/cli-service.ts diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 0000000..44d646d --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist/ diff --git a/cli/.mise.toml b/cli/.mise.toml new file mode 100644 index 0000000..a94d1ed --- /dev/null +++ b/cli/.mise.toml @@ -0,0 +1,2 @@ +[tools] +bun = "latest" diff --git a/cli/bun.lock b/cli/bun.lock new file mode 100644 index 0000000..4b10c3f --- /dev/null +++ b/cli/bun.lock @@ -0,0 +1,91 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "techulus-cli", + "dependencies": { + "yaml": "^2.8.2", + "zod": "^4.3.5", + }, + "devDependencies": { + "@types/node": "^22.17.0", + "tsx": "^4.19.2", + "typescript": "^5.9.2", + }, + }, + }, + "packages": { + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "@types/node": ["@types/node@22.19.16", "", { "dependencies": { "undici-types": "6.21.0" } }, "sha512-K6csxIjY+9RoDxdP6/wzaJzXaCf4znBz0/y0rrQDsbqmzQ5QFsOjubbsYWZhj6ZCgz3mjlyDZS+EJkhA9jWl9Q=="], + + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "0.27.7", "get-tsconfig": "4.13.7" }, "optionalDependencies": { "fsevents": "2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], + + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + } +} diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 0000000..1b8a94f --- /dev/null +++ b/cli/package.json @@ -0,0 +1,24 @@ +{ + "name": "techulus-cli", + "version": "0.1.0", + "private": true, + "type": "module", +"scripts": { + "dev": "node --import tsx src/main.ts", + "build": "bun build src/main.ts --compile --outfile dist/tcloud", + "build:linux-x64": "bun build src/main.ts --compile --target=bun-linux-x64 --outfile dist/tcloud-linux-x64", + "build:linux-arm64": "bun build src/main.ts --compile --target=bun-linux-arm64 --outfile dist/tcloud-linux-arm64", + "build:darwin-x64": "bun build src/main.ts --compile --target=bun-darwin-x64 --outfile dist/tcloud-darwin-x64", + "build:darwin-arm64": "bun build src/main.ts --compile --target=bun-darwin-arm64 --outfile dist/tcloud-darwin-arm64", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "yaml": "^2.8.2", + "zod": "^4.3.5" + }, + "devDependencies": { + "@types/node": "^22.17.0", + "tsx": "^4.19.2", + "typescript": "^5.9.2" + } +} diff --git a/cli/src/config.ts b/cli/src/config.ts new file mode 100644 index 0000000..c4b5c15 --- /dev/null +++ b/cli/src/config.ts @@ -0,0 +1,64 @@ +import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; + +export type CliConfig = { + host: string; + apiKey: string; + keyId?: string; + keyName?: string | null; + user?: { + id: string; + email: string; + name: string; + }; +}; + +function getConfigRoot() { + if (process.env.XDG_CONFIG_HOME) { + return process.env.XDG_CONFIG_HOME; + } + + if (process.platform === "darwin") { + return path.join(os.homedir(), "Library", "Application Support"); + } + + if (process.platform === "win32" && process.env.APPDATA) { + return process.env.APPDATA; + } + + return path.join(os.homedir(), ".config"); +} + +export function getConfigDir() { + return path.join(getConfigRoot(), "techulus-cloud-cli"); +} + +export function getConfigPath() { + return path.join(getConfigDir(), "config.json"); +} + +export async function readConfig(): Promise { + try { + const contents = await readFile(getConfigPath(), "utf8"); + return JSON.parse(contents) as CliConfig; + } catch { + return null; + } +} + +export async function writeConfig(config: CliConfig) { + const dir = getConfigDir(); + const file = getConfigPath(); + + await mkdir(dir, { recursive: true, mode: 0o700 }); + await writeFile(file, JSON.stringify(config, null, 2), { + encoding: "utf8", + mode: 0o600, + }); + await chmod(file, 0o600); +} + +export async function deleteConfig() { + await rm(getConfigPath(), { force: true }); +} diff --git a/cli/src/main.ts b/cli/src/main.ts new file mode 100644 index 0000000..1b1796f --- /dev/null +++ b/cli/src/main.ts @@ -0,0 +1,444 @@ +import { access, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { constants as fsConstants } from "node:fs"; +import { spawn } from "node:child_process"; +import { deleteConfig, readConfig, writeConfig } from "./config.js"; +import { loadManifest, slugify, type TechulusManifest } from "./manifest.js"; + +const CLI_VERSION = "0.1.0"; +const CLI_CLIENT_ID = "techulus-cli"; + +type JsonRequestOptions = { + method?: string; + headers?: Record; + body?: unknown; +}; + +function normalizeHost(host: string) { + const trimmed = host.trim().replace(/\/$/, ""); + if (!trimmed.startsWith("http://") && !trimmed.startsWith("https://")) { + return `https://${trimmed}`; + } + + return trimmed; +} + +async function requestJson(url: string, options: JsonRequestOptions = {}) { + const response = await fetch(url, { + method: options.method ?? "GET", + headers: { + "content-type": "application/json", + ...(options.headers ?? {}), + }, + body: options.body === undefined ? undefined : JSON.stringify(options.body), + }); + + const text = await response.text(); + const data = text ? (JSON.parse(text) as T | { error?: string }) : null; + + if (!response.ok) { + const message = + data && typeof data === "object" && "error" in data && data.error + ? data.error + : `Request failed with ${response.status}`; + throw new Error(message); + } + + return data as T; +} + +async function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function parseOption(args: string[], name: string) { + const index = args.indexOf(name); + if (index === -1) { + return null; + } + + const value = args[index + 1]; + if (!value || value.startsWith("--")) { + throw new Error(`Missing value for ${name}`); + } + + return value; +} + +function printUsage() { + console.log(`Usage: + tcloud auth login --host + tcloud auth logout + tcloud auth whoami + tcloud init + tcloud apply + tcloud deploy + tcloud status`); +} + +function openBrowser(url: string) { + const command = + process.platform === "darwin" + ? "open" + : process.platform === "win32" + ? "cmd" + : "xdg-open"; + const args = + process.platform === "win32" ? ["/c", "start", "", url] : [url]; + + const child = spawn(command, args, { + detached: true, + stdio: "ignore", + }); + + child.unref(); +} + +async function ensureManifest(cwd: string) { + try { + return await loadManifest(cwd); + } catch (error) { + if (error instanceof Error && "code" in error && error.code === "ENOENT") { + throw new Error( + "No techulus.yml found in the current directory. Run `tcloud init` to create one.", + ); + } + throw new Error( + error instanceof Error + ? `Invalid techulus.yml: ${error.message}` + : "Failed to load techulus.yml", + ); + } +} + +function authHeaders(apiKey: string) { + return { + "x-api-key": apiKey, + }; +} + +async function requireConfig() { + const config = await readConfig(); + if (!config) { + throw new Error("Not logged in. Run `tcloud auth login --host ` first."); + } + + return config; +} + +async function commandAuthLogin(args: string[]) { + const existingConfig = await readConfig(); + const rawHost = parseOption(args, "--host") ?? existingConfig?.host; + + if (!rawHost) { + throw new Error("Missing --host"); + } + + const host = normalizeHost(rawHost); + + const deviceCode = await requestJson<{ + device_code: string; + user_code: string; + verification_uri: string; + verification_uri_complete: string; + expires_in: number; + interval: number; + }>(`${host}/api/auth/device/code`, { + method: "POST", + body: { + client_id: CLI_CLIENT_ID, + scope: "cli", + }, + }); + + console.log(`Visit ${deviceCode.verification_uri}`); + console.log(`Enter code: ${deviceCode.user_code}`); + + try { + openBrowser(deviceCode.verification_uri_complete || deviceCode.verification_uri); + console.log("Opened your browser for approval."); + } catch { + console.log("Could not open the browser automatically."); + } + + let accessToken = ""; + let intervalMs = deviceCode.interval * 1000; + + while (!accessToken) { + await sleep(intervalMs); + + const response = await fetch(`${host}/api/auth/device/token`, { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + grant_type: "urn:ietf:params:oauth:grant-type:device_code", + device_code: deviceCode.device_code, + client_id: CLI_CLIENT_ID, + }), + }); + + const data = (await response.json()) as + | { + access_token: string; + } + | { + error: string; + error_description?: string; + }; + + if (response.ok && "access_token" in data) { + accessToken = data.access_token; + break; + } + + if (!("error" in data)) { + throw new Error("Unexpected response from device token endpoint"); + } + + switch (data.error) { + case "authorization_pending": + process.stdout.write("."); + break; + case "slow_down": + intervalMs += 5000; + break; + case "access_denied": + throw new Error(data.error_description || "Device authorization was denied"); + case "expired_token": + throw new Error(data.error_description || "Device authorization expired"); + default: + throw new Error(data.error_description || data.error); + } + } + + console.log("\nDevice login approved. Creating a CLI API key..."); + + const machineName = os.hostname(); + const platform = `${process.platform}/${process.arch}`; + const exchange = await requestJson<{ + apiKey: string; + keyId: string; + name: string | null; + user: { id: string; email: string; name: string }; + }>(`${host}/api/v1/cli/auth/exchange`, { + method: "POST", + headers: { + authorization: `Bearer ${accessToken}`, + }, + body: { + machineName, + platform, + cliVersion: CLI_VERSION, + }, + }); + + await writeConfig({ + host, + apiKey: exchange.apiKey, + keyId: exchange.keyId, + keyName: exchange.name, + user: exchange.user, + }); + + console.log(`Signed in as ${exchange.user.email}`); +} + +async function commandAuthLogout() { + await deleteConfig(); + console.log("Signed out."); +} + +async function commandAuthWhoAmI() { + const config = await requireConfig(); + const whoami = await requestJson<{ + user: { id: string; email: string; name: string }; + }>(`${config.host}/api/v1/cli/auth/whoami`, { + headers: authHeaders(config.apiKey), + }); + + console.log(`Signed in as ${whoami.user.email}`); + console.log(`Name: ${whoami.user.name}`); + console.log(`Host: ${config.host}`); +} + +async function commandInit(cwd: string) { + const manifestPath = path.join(cwd, "techulus.yml"); + try { + await access(manifestPath, fsConstants.F_OK); + throw new Error("techulus.yml already exists"); + } catch (error) { + if (error instanceof Error && error.message === "techulus.yml already exists") { + throw error; + } + } + + const folderName = slugify(path.basename(cwd)) || "my-service"; + const manifest = `apiVersion: v1 +project: ${folderName} +environment: production +service: + name: ${folderName} + source: + type: image + image: nginx:latest + replicas: + count: 1 + ports: + - port: 80 + public: false +`; + + await writeFile(manifestPath, manifest, "utf8"); + console.log(`Created ${manifestPath}`); +} + +function printApplyResult(result: { + action: "created" | "updated" | "noop"; + serviceId: string; + changes: Array<{ field: string; from: string; to: string }>; +}) { + console.log(`Action: ${result.action}`); + console.log(`Service ID: ${result.serviceId}`); + + if (result.changes.length === 0) { + console.log("No changes."); + return; + } + + console.log("Changes:"); + for (const change of result.changes) { + console.log(`- ${change.field}: ${change.from} -> ${change.to}`); + } +} + +async function commandApply(cwd: string) { + const config = await requireConfig(); + const { manifest } = await ensureManifest(cwd); + const result = await requestJson<{ + action: "created" | "updated" | "noop"; + serviceId: string; + changes: Array<{ field: string; from: string; to: string }>; + }>(`${config.host}/api/v1/manifest/apply`, { + method: "POST", + headers: authHeaders(config.apiKey), + body: manifest, + }); + + printApplyResult(result); +} + +async function commandDeploy(cwd: string) { + const config = await requireConfig(); + const { manifest } = await ensureManifest(cwd); + const result = await requestJson<{ + serviceId: string; + rolloutId: string | null; + status: string; + }>(`${config.host}/api/v1/manifest/deploy`, { + method: "POST", + headers: authHeaders(config.apiKey), + body: manifest, + }); + + console.log(`Service ID: ${result.serviceId}`); + console.log(`Status: ${result.status}`); + if (result.rolloutId) { + console.log(`Rollout ID: ${result.rolloutId}`); + } +} + +async function commandStatus(cwd: string) { + const config = await requireConfig(); + const { manifest } = await ensureManifest(cwd); + const params = new URLSearchParams({ + project: manifest.project, + environment: manifest.environment, + service: manifest.service.name, + }); + const status = await requestJson<{ + service: { + id: string; + image: string; + hostname: string | null; + replicas: number; + }; + latestRollout: { + id: string; + status: string; + currentStage: string | null; + } | null; + deployments: Array<{ + id: string; + status: string; + serverId: string; + }>; + }>(`${config.host}/api/v1/manifest/status?${params.toString()}`, { + headers: authHeaders(config.apiKey), + }); + + console.log(`Service ID: ${status.service.id}`); + console.log(`Image: ${status.service.image}`); + console.log(`Hostname: ${status.service.hostname ?? "(none)"}`); + console.log(`Replicas: ${status.service.replicas}`); + if (status.latestRollout) { + console.log( + `Latest rollout: ${status.latestRollout.id} (${status.latestRollout.status}${status.latestRollout.currentStage ? `, ${status.latestRollout.currentStage}` : ""})`, + ); + } else { + console.log("Latest rollout: none"); + } + console.log(`Deployments: ${status.deployments.length}`); + for (const deployment of status.deployments) { + console.log(`- ${deployment.id}: ${deployment.status} on ${deployment.serverId}`); + } +} + +async function main() { + const [command, subcommand, ...rest] = process.argv.slice(2); + const cwd = process.cwd(); + + if (!command) { + printUsage(); + return; + } + + switch (command) { + case "auth": + switch (subcommand) { + case "login": + await commandAuthLogin(rest); + return; + case "logout": + await commandAuthLogout(); + return; + case "whoami": + await commandAuthWhoAmI(); + return; + default: + printUsage(); + return; + } + case "init": + await commandInit(cwd); + return; + case "apply": + await commandApply(cwd); + return; + case "deploy": + await commandDeploy(cwd); + return; + case "status": + await commandStatus(cwd); + return; + default: + printUsage(); + } +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : "Unknown error"); + process.exit(1); +}); diff --git a/cli/src/manifest.ts b/cli/src/manifest.ts new file mode 100644 index 0000000..2c91a93 --- /dev/null +++ b/cli/src/manifest.ts @@ -0,0 +1,89 @@ +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import YAML from "yaml"; +import { z } from "zod"; + +const manifestPortSchema = z + .object({ + port: z.number().int().min(1).max(65535), + public: z.boolean().default(false), + domain: z.string().trim().min(1).optional(), + }) + .strict(); + +const manifestHealthCheckSchema = z + .object({ + cmd: z.string().trim().min(1), + interval: z.number().int().min(1).default(10), + timeout: z.number().int().min(1).default(5), + retries: z.number().int().min(1).default(3), + startPeriod: z.number().int().min(0).default(30), + }) + .strict(); + +const manifestResourcesSchema = z + .object({ + cpuCores: z.number().min(0.1).max(64).nullable().optional(), + memoryMb: z.number().int().min(64).max(65536).nullable().optional(), + }) + .strict() + .superRefine((value, ctx) => { + const hasCpu = value.cpuCores !== undefined && value.cpuCores !== null; + const hasMemory = value.memoryMb !== undefined && value.memoryMb !== null; + + if (hasCpu !== hasMemory) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "resources must set both cpuCores and memoryMb together", + }); + } + }); + +export const techulusManifestSchema = z + .object({ + apiVersion: z.literal("v1"), + project: z.string().trim().min(1), + environment: z.string().trim().min(1), + service: z + .object({ + name: z.string().trim().min(1), + source: z + .object({ + type: z.literal("image"), + image: z.string().trim().min(1), + }) + .strict(), + hostname: z.string().trim().min(1).optional(), + ports: z.array(manifestPortSchema).default([]), + replicas: z + .object({ + count: z.number().int().min(1).max(10).default(1), + }) + .strict() + .default({ count: 1 }), + healthCheck: manifestHealthCheckSchema.optional(), + startCommand: z.string().trim().min(1).optional(), + resources: manifestResourcesSchema.optional(), + }) + .strict(), + }) + .strict(); + +export type TechulusManifest = z.infer; + +export async function loadManifest(cwd: string) { + const manifestPath = path.join(cwd, "techulus.yml"); + const raw = await readFile(manifestPath, "utf8"); + const parsed = YAML.parse(raw); + return { + path: manifestPath, + manifest: techulusManifestSchema.parse(parsed), + }; +} + +export function slugify(value: string) { + return value + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, ""); +} diff --git a/cli/tsconfig.json b/cli/tsconfig.json new file mode 100644 index 0000000..0005191 --- /dev/null +++ b/cli/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/web/app/(auth)/register/page.tsx b/web/app/(auth)/register/page.tsx index 097d904..583f9b9 100644 --- a/web/app/(auth)/register/page.tsx +++ b/web/app/(auth)/register/page.tsx @@ -1,113 +1,17 @@ -"use client"; - -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { signUp } from "@/lib/auth-client"; - -export default function RegisterPage() { - const router = useRouter(); - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); - const [loading, setLoading] = useState(false); - - async function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - setError(""); - setLoading(true); - - const { error } = await signUp.email({ - name, - email, - password, - }); - - setLoading(false); - - if (error) { - setError(error.message || "Failed to create account"); - return; - } - - router.push("/dashboard"); - } +import { Suspense } from "react"; +import { RegisterPage } from "@/components/auth/register-page"; +import { Spinner } from "@/components/ui/spinner"; +export default function Page() { return ( -
- - - Create Account - - Enter your details to create a new account - - -
- - {error && ( -
- {error} -
- )} -
- - setName(e.target.value)} - required - /> -
-
- - setEmail(e.target.value)} - required - /> -
-
- - setPassword(e.target.value)} - required - minLength={8} - /> -
-
- - -

- Already have an account?{" "} - - Sign in - -

-
-
-
-
+ + + + } + > + + ); } diff --git a/web/app/api/v1/cli/auth/exchange/route.ts b/web/app/api/v1/cli/auth/exchange/route.ts new file mode 100644 index 0000000..03778ee --- /dev/null +++ b/web/app/api/v1/cli/auth/exchange/route.ts @@ -0,0 +1,57 @@ +export const dynamic = "force-dynamic"; + +import { z } from "zod"; +import { auth } from "@/lib/auth"; +import { requireRequestSession } from "@/lib/api-auth"; + +const exchangeSchema = z + .object({ + machineName: z.string().trim().min(1).max(128).optional(), + platform: z.string().trim().min(1).max(128).optional(), + cliVersion: z.string().trim().min(1).max(64).optional(), + }) + .strict(); + +export async function POST(request: Request) { + const sessionResult = await requireRequestSession(request); + if (!sessionResult.ok) { + return sessionResult.response; + } + + const body = await request.json().catch(() => ({})); + const parsed = exchangeSchema.safeParse(body); + + if (!parsed.success) { + return Response.json( + { error: parsed.error.issues[0]?.message || "Invalid request" }, + { status: 400 }, + ); + } + + const metadata = { + creationSource: "techulus-cli", + machineName: parsed.data.machineName ?? null, + platform: parsed.data.platform ?? null, + cliVersion: parsed.data.cliVersion ?? null, + host: new URL(request.url).origin, + }; + + const name = parsed.data.machineName + ? `CLI - ${parsed.data.machineName}`.slice(0, 32) + : "CLI"; + + const apiKey = await auth.api.createApiKey({ + headers: request.headers, + body: { + name, + metadata, + }, + }); + + return Response.json({ + apiKey: apiKey.key, + keyId: apiKey.id, + name: apiKey.name, + user: sessionResult.session.user, + }); +} diff --git a/web/app/api/v1/cli/auth/whoami/route.ts b/web/app/api/v1/cli/auth/whoami/route.ts new file mode 100644 index 0000000..d7e00fe --- /dev/null +++ b/web/app/api/v1/cli/auth/whoami/route.ts @@ -0,0 +1,18 @@ +export const dynamic = "force-dynamic"; + +import { requireRequestSession } from "@/lib/api-auth"; + +export async function GET(request: Request) { + const sessionResult = await requireRequestSession(request); + if (!sessionResult.ok) { + return sessionResult.response; + } + + return Response.json({ + user: sessionResult.session.user, + session: { + id: sessionResult.session.session.id, + expiresAt: sessionResult.session.session.expiresAt, + }, + }); +} diff --git a/web/app/api/v1/manifest/apply/route.ts b/web/app/api/v1/manifest/apply/route.ts new file mode 100644 index 0000000..9d8b1b2 --- /dev/null +++ b/web/app/api/v1/manifest/apply/route.ts @@ -0,0 +1,32 @@ +export const dynamic = "force-dynamic"; + +import { techulusManifestSchema } from "@/lib/cli-manifest"; +import { applyManifest } from "@/lib/cli-service"; +import { requireRequestSession } from "@/lib/api-auth"; + +export async function POST(request: Request) { + const sessionResult = await requireRequestSession(request); + if (!sessionResult.ok) { + return sessionResult.response; + } + + const body = await request.json().catch(() => null); + const parsed = techulusManifestSchema.safeParse(body); + + if (!parsed.success) { + return Response.json( + { error: parsed.error.issues[0]?.message || "Invalid manifest" }, + { status: 400 }, + ); + } + + try { + const result = await applyManifest(parsed.data); + return Response.json(result); + } catch (error) { + return Response.json( + { error: error instanceof Error ? error.message : "Failed to apply manifest" }, + { status: 400 }, + ); + } +} diff --git a/web/app/api/v1/manifest/deploy/route.ts b/web/app/api/v1/manifest/deploy/route.ts new file mode 100644 index 0000000..bcf0696 --- /dev/null +++ b/web/app/api/v1/manifest/deploy/route.ts @@ -0,0 +1,32 @@ +export const dynamic = "force-dynamic"; + +import { techulusManifestSchema } from "@/lib/cli-manifest"; +import { deployManifest } from "@/lib/cli-service"; +import { requireRequestSession } from "@/lib/api-auth"; + +export async function POST(request: Request) { + const sessionResult = await requireRequestSession(request); + if (!sessionResult.ok) { + return sessionResult.response; + } + + const body = await request.json().catch(() => null); + const parsed = techulusManifestSchema.safeParse(body); + + if (!parsed.success) { + return Response.json( + { error: parsed.error.issues[0]?.message || "Invalid manifest" }, + { status: 400 }, + ); + } + + try { + const result = await deployManifest(parsed.data); + return Response.json(result); + } catch (error) { + return Response.json( + { error: error instanceof Error ? error.message : "Failed to deploy manifest" }, + { status: 400 }, + ); + } +} diff --git a/web/app/api/v1/manifest/status/route.ts b/web/app/api/v1/manifest/status/route.ts new file mode 100644 index 0000000..5cf919d --- /dev/null +++ b/web/app/api/v1/manifest/status/route.ts @@ -0,0 +1,45 @@ +export const dynamic = "force-dynamic"; + +import { z } from "zod"; +import { getManifestStatus } from "@/lib/cli-service"; +import { requireRequestSession } from "@/lib/api-auth"; +import { slugify } from "@/lib/utils"; + +const querySchema = z.object({ + project: z.string().trim().min(1), + environment: z.string().trim().min(1), + service: z.string().trim().min(1), +}); + +export async function GET(request: Request) { + const sessionResult = await requireRequestSession(request); + if (!sessionResult.ok) { + return sessionResult.response; + } + + const { searchParams } = new URL(request.url); + const parsed = querySchema.safeParse({ + project: searchParams.get("project"), + environment: searchParams.get("environment"), + service: searchParams.get("service"), + }); + + if (!parsed.success) { + return Response.json( + { error: parsed.error.issues[0]?.message || "Invalid request" }, + { status: 400 }, + ); + } + + const status = await getManifestStatus({ + project: slugify(parsed.data.project), + environment: parsed.data.environment, + service: parsed.data.service, + }); + + if (!status) { + return Response.json({ error: "Service not found" }, { status: 404 }); + } + + return Response.json(status); +} diff --git a/web/app/device/approve/page.tsx b/web/app/device/approve/page.tsx new file mode 100644 index 0000000..12eb557 --- /dev/null +++ b/web/app/device/approve/page.tsx @@ -0,0 +1,17 @@ +import { Suspense } from "react"; +import { DeviceApprovalPage } from "@/components/auth/device-approval-page"; +import { Spinner } from "@/components/ui/spinner"; + +export default function Page() { + return ( + + + + } + > + + + ); +} diff --git a/web/app/device/page.tsx b/web/app/device/page.tsx new file mode 100644 index 0000000..eb9592d --- /dev/null +++ b/web/app/device/page.tsx @@ -0,0 +1,17 @@ +import { Suspense } from "react"; +import { DeviceAuthorizationPage } from "@/components/auth/device-authorization-page"; +import { Spinner } from "@/components/ui/spinner"; + +export default function Page() { + return ( + + + + } + > + + + ); +} diff --git a/web/app/page.tsx b/web/app/page.tsx index f7a776a..bdc6fe0 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -1,124 +1,17 @@ -"use client"; - -import Image from "next/image"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { Suspense } from "react"; +import { SignInPage } from "@/components/auth/sign-in-page"; import { Spinner } from "@/components/ui/spinner"; -import { signIn, useSession } from "@/lib/auth-client"; export default function Page() { - const router = useRouter(); - const { data: session, isPending } = useSession(); - - useEffect(() => { - if (!isPending && session) { - router.push("/dashboard"); - } - }, [session, isPending, router]); - - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); - const [loading, setLoading] = useState(false); - - async function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - setError(""); - setLoading(true); - - const { error } = await signIn.email({ - email, - password, - }); - - setLoading(false); - - if (error) { - setError(error.message || "Failed to sign in"); - return; - } - - router.push("/dashboard"); - } - - if (isPending || session) { - return ( -
- -
- ); - } - return ( -
- Logo - - - Sign In - - Enter your credentials to access your account - - -
- - {error && ( -
- {error} -
- )} -
- - setEmail(e.target.value)} - required - /> -
-
- - setPassword(e.target.value)} - required - /> -
-
- - -

- Don't have an account?{" "} - - Sign up - -

-
-
-
-
+ + + + } + > + + ); } diff --git a/web/components/auth/device-approval-page.tsx b/web/components/auth/device-approval-page.tsx new file mode 100644 index 0000000..62d2659 --- /dev/null +++ b/web/components/auth/device-approval-page.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Spinner } from "@/components/ui/spinner"; +import { authClient, useSession } from "@/lib/auth-client"; + +export function DeviceApprovalPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { data: session, isPending } = useSession(); + const userCode = useMemo( + () => searchParams.get("user_code") || searchParams.get("userCode") || "", + [searchParams], + ); + const [isProcessing, setIsProcessing] = useState(false); + const [error, setError] = useState(""); + const [successMessage, setSuccessMessage] = useState(""); + + useEffect(() => { + if (isPending || session || !userCode) { + return; + } + + router.replace(`/?redirect=${encodeURIComponent(`/device/approve?user_code=${userCode}`)}`); + }, [isPending, router, session, userCode]); + + async function handleDecision(type: "approve" | "deny") { + if (!userCode) { + setError("Missing device code"); + return; + } + + setIsProcessing(true); + setError(""); + setSuccessMessage(""); + + try { + if (type === "approve") { + await authClient.device.approve({ + userCode, + }); + setSuccessMessage("Device approved. You can return to the terminal."); + } else { + await authClient.device.deny({ + userCode, + }); + setSuccessMessage("Device denied. You can close this page."); + } + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to update device request"); + } finally { + setIsProcessing(false); + } + } + + if (isPending) { + return ( +
+ +
+ ); + } + + return ( +
+ + + Device Authorization Request + + Review the pending terminal sign-in request for your account. + + + +
+

Code

+

{userCode || "Unavailable"}

+
+ {error ? ( +
+ {error} +
+ ) : null} + {successMessage ? ( +
+ {successMessage} +
+ ) : null} +
+ + + + +
+
+ ); +} diff --git a/web/components/auth/device-authorization-page.tsx b/web/components/auth/device-authorization-page.tsx new file mode 100644 index 0000000..21c4c88 --- /dev/null +++ b/web/components/auth/device-authorization-page.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { authClient } from "@/lib/auth-client"; + +function normalizeUserCode(value: string) { + return value.trim().replace(/-/g, "").toUpperCase(); +} + +export function DeviceAuthorizationPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const initialUserCode = useMemo( + () => searchParams.get("user_code") || "", + [searchParams], + ); + const [userCode, setUserCode] = useState(initialUserCode); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setUserCode(initialUserCode); + }, [initialUserCode]); + + async function verifyCode(code: string) { + const formatted = normalizeUserCode(code); + if (!formatted) { + setError("Enter the device code to continue"); + return; + } + + setLoading(true); + setError(""); + + try { + const response = await authClient.device({ + query: { user_code: formatted }, + }); + + if (response.error || !response.data) { + setError(response.error?.error_description || "Invalid or expired code"); + return; + } + + router.push(`/device/approve?user_code=${encodeURIComponent(formatted)}`); + } catch (err) { + setError(err instanceof Error ? err.message : "Invalid or expired code"); + } finally { + setLoading(false); + } + } + + useEffect(() => { + if (initialUserCode) { + void verifyCode(initialUserCode); + } + // initialUserCode is intentionally the only trigger here so a shared + // verification link can continue without another submit. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialUserCode]); + + return ( +
+ + + Authorize Device + + Enter the code shown in your terminal to continue signing in. + + +
{ + event.preventDefault(); + void verifyCode(userCode); + }} + > + +
+ + { + setUserCode(event.target.value); + setError(""); + }} + placeholder="ABCD1234" + autoFocus + autoComplete="one-time-code" + /> +
+ {error ? ( +
+ {error} +
+ ) : null} +
+ + + +
+
+
+ ); +} diff --git a/web/components/auth/register-page.tsx b/web/components/auth/register-page.tsx new file mode 100644 index 0000000..a2a8966 --- /dev/null +++ b/web/components/auth/register-page.tsx @@ -0,0 +1,122 @@ +"use client"; + +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { signUp } from "@/lib/auth-client"; + +export function RegisterPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const redirectTo = searchParams.get("redirect") || "/dashboard"; + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(""); + setLoading(true); + + const { error } = await signUp.email({ + name, + email, + password, + }); + + setLoading(false); + + if (error) { + setError(error.message || "Failed to create account"); + return; + } + + router.push(redirectTo); + } + + return ( +
+ + + Create Account + + Enter your details to create a new account + + +
+ + {error && ( +
+ {error} +
+ )} +
+ + setName(e.target.value)} + required + /> +
+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + minLength={8} + /> +
+
+ + +

+ Already have an account?{" "} + + Sign in + +

+
+
+
+
+ ); +} diff --git a/web/components/auth/sign-in-page.tsx b/web/components/auth/sign-in-page.tsx new file mode 100644 index 0000000..1301327 --- /dev/null +++ b/web/components/auth/sign-in-page.tsx @@ -0,0 +1,133 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Spinner } from "@/components/ui/spinner"; +import { signIn, useSession } from "@/lib/auth-client"; + +export function SignInPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { data: session, isPending } = useSession(); + const redirectTo = searchParams.get("redirect") || "/dashboard"; + + useEffect(() => { + if (!isPending && session) { + router.push(redirectTo); + } + }, [session, isPending, redirectTo, router]); + + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(""); + setLoading(true); + + const { error } = await signIn.email({ + email, + password, + }); + + setLoading(false); + + if (error) { + setError(error.message || "Failed to sign in"); + return; + } + + router.push(redirectTo); + } + + if (isPending || session) { + return ( +
+ +
+ ); + } + + return ( +
+ Logo + + + Sign In + + Enter your credentials to access your account + + +
+ + {error && ( +
+ {error} +
+ )} +
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+
+ + +

+ Don't have an account?{" "} + + Sign up + +

+
+
+
+
+ ); +} diff --git a/web/db/schema.ts b/web/db/schema.ts index c3e60b8..e8e83bb 100644 --- a/web/db/schema.ts +++ b/web/db/schema.ts @@ -84,9 +84,68 @@ export const verification = pgTable( (table) => [index("verification_identifier_idx").on(table.identifier)], ); +export const deviceCode = pgTable( + "deviceCode", + { + id: text("id").primaryKey(), + deviceCode: text("device_code").notNull(), + userCode: text("user_code").notNull(), + userId: text("user_id").references(() => user.id, { onDelete: "cascade" }), + expiresAt: timestamp("expires_at").notNull(), + status: text("status").notNull(), + lastPolledAt: timestamp("last_polled_at"), + pollingInterval: integer("polling_interval"), + clientId: text("client_id"), + scope: text("scope"), + }, + (table) => [ + index("device_code_device_code_idx").on(table.deviceCode), + index("device_code_user_code_idx").on(table.userCode), + index("device_code_user_id_idx").on(table.userId), + ], +); + +export const apikey = pgTable( + "apikey", + { + id: text("id").primaryKey(), + name: text("name"), + start: text("start"), + prefix: text("prefix"), + key: text("key").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + refillInterval: integer("refill_interval"), + refillAmount: integer("refill_amount"), + lastRefillAt: timestamp("last_refill_at"), + enabled: boolean("enabled").default(true), + rateLimitEnabled: boolean("rate_limit_enabled").default(true), + rateLimitTimeWindow: integer("rate_limit_time_window"), + rateLimitMax: integer("rate_limit_max"), + requestCount: integer("request_count").default(0), + remaining: integer("remaining"), + lastRequest: timestamp("last_request"), + expiresAt: timestamp("expires_at"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + permissions: text("permissions"), + metadata: text("metadata"), + }, + (table) => [ + index("apikey_key_idx").on(table.key), + index("apikey_user_id_idx").on(table.userId), + ], +); + export const userRelations = relations(user, ({ many }) => ({ sessions: many(session), accounts: many(account), + apiKeys: many(apikey), + deviceCodes: many(deviceCode), })); export const sessionRelations = relations(session, ({ one }) => ({ @@ -103,6 +162,20 @@ export const accountRelations = relations(account, ({ one }) => ({ }), })); +export const deviceCodeRelations = relations(deviceCode, ({ one }) => ({ + user: one(user, { + fields: [deviceCode.userId], + references: [user.id], + }), +})); + +export const apiKeyRelations = relations(apikey, ({ one }) => ({ + user: one(user, { + fields: [apikey.userId], + references: [user.id], + }), +})); + type ServerMeta = { arch?: string; os?: string; diff --git a/web/lib/api-auth.ts b/web/lib/api-auth.ts new file mode 100644 index 0000000..2e12ff9 --- /dev/null +++ b/web/lib/api-auth.ts @@ -0,0 +1,23 @@ +import { auth } from "@/lib/auth"; + +export async function getRequestSession(request: Request) { + return auth.api.getSession({ + headers: request.headers, + }); +} + +export async function requireRequestSession(request: Request) { + const session = await getRequestSession(request); + + if (!session) { + return { + ok: false as const, + response: Response.json({ error: "Unauthorized" }, { status: 401 }), + }; + } + + return { + ok: true as const, + session, + }; +} diff --git a/web/lib/auth-client.ts b/web/lib/auth-client.ts index dde6404..1357fba 100644 --- a/web/lib/auth-client.ts +++ b/web/lib/auth-client.ts @@ -1,5 +1,11 @@ import { createAuthClient } from "better-auth/react"; +import { + apiKeyClient, + deviceAuthorizationClient, +} from "better-auth/client/plugins"; -export const authClient = createAuthClient(); +export const authClient = createAuthClient({ + plugins: [apiKeyClient(), deviceAuthorizationClient()], +}); export const { signIn, signUp, signOut, useSession } = authClient; diff --git a/web/lib/auth.ts b/web/lib/auth.ts index 9a1ad2a..36cf7a5 100644 --- a/web/lib/auth.ts +++ b/web/lib/auth.ts @@ -1,8 +1,11 @@ import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; +import { apiKey, bearer, deviceAuthorization } from "better-auth/plugins"; import { db } from "@/db"; import * as schema from "@/db/schema"; +export const TECHULUS_CLI_CLIENT_ID = "techulus-cli"; + export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", @@ -12,4 +15,18 @@ export const auth = betterAuth({ enabled: true, disableSignUp: process.env.ALLOW_SIGNUP !== "true", }, + plugins: [ + deviceAuthorization({ + verificationUri: "/device", + validateClient: async (clientId) => clientId === TECHULUS_CLI_CLIENT_ID, + }), + apiKey({ + enableSessionForAPIKeys: true, + apiKeyHeaders: "x-api-key", + defaultPrefix: "tcl_", + enableMetadata: true, + requireName: true, + }), + bearer(), + ], }); diff --git a/web/lib/cli-manifest.ts b/web/lib/cli-manifest.ts new file mode 100644 index 0000000..00382f6 --- /dev/null +++ b/web/lib/cli-manifest.ts @@ -0,0 +1,99 @@ +import { z } from "zod"; +import { slugify } from "@/lib/utils"; + +const manifestPortSchema = z + .object({ + port: z.number().int().min(1).max(65535), + public: z.boolean().default(false), + domain: z.string().trim().min(1).optional(), + }) + .strict() + .superRefine((value, ctx) => { + if (value.public && !value.domain) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["domain"], + message: "Public HTTP ports require a domain", + }); + } + + if (!value.public && value.domain) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["domain"], + message: "Internal ports cannot define a domain", + }); + } + }); + +const manifestHealthCheckSchema = z + .object({ + cmd: z.string().trim().min(1), + interval: z.number().int().min(1).default(10), + timeout: z.number().int().min(1).default(5), + retries: z.number().int().min(1).default(3), + startPeriod: z.number().int().min(0).default(30), + }) + .strict(); + +const manifestResourcesSchema = z + .object({ + cpuCores: z.number().min(0.1).max(64).nullable().optional(), + memoryMb: z.number().int().min(64).max(65536).nullable().optional(), + }) + .strict() + .superRefine((value, ctx) => { + const hasCpu = value.cpuCores !== undefined && value.cpuCores !== null; + const hasMemory = value.memoryMb !== undefined && value.memoryMb !== null; + + if (hasCpu !== hasMemory) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Resources must set both cpuCores and memoryMb together", + }); + } + }); + +export const techulusManifestSchema = z + .object({ + apiVersion: z.literal("v1"), + project: z.string().trim().min(1), + environment: z.string().trim().min(1), + service: z + .object({ + name: z.string().trim().min(1), + source: z + .object({ + type: z.literal("image"), + image: z.string().trim().min(1), + }) + .strict(), + hostname: z.string().trim().min(1).optional(), + ports: z.array(manifestPortSchema).default([]), + replicas: z + .object({ + count: z.number().int().min(1).max(10).default(1), + }) + .strict() + .default({ count: 1 }), + healthCheck: manifestHealthCheckSchema.optional(), + startCommand: z.string().trim().min(1).optional(), + resources: manifestResourcesSchema.optional(), + }) + .strict(), + }) + .strict(); + +export type TechulusManifest = z.infer; + +export function getManifestProjectSlug(manifest: TechulusManifest) { + return slugify(manifest.project); +} + +export function getManifestEnvironmentName(manifest: TechulusManifest) { + return slugify(manifest.environment); +} + +export function getManifestServiceName(manifest: TechulusManifest) { + return manifest.service.name.trim(); +} diff --git a/web/lib/cli-service.ts b/web/lib/cli-service.ts new file mode 100644 index 0000000..eccd9bf --- /dev/null +++ b/web/lib/cli-service.ts @@ -0,0 +1,634 @@ +import { and, desc, eq, ne } from "drizzle-orm"; +import { db } from "@/db"; +import { + deployments, + environments, + projects, + rollouts, + servicePorts, + serviceVolumes, + services, +} from "@/db/schema"; +import type { TechulusManifest } from "@/lib/cli-manifest"; +import { + getManifestEnvironmentName, + getManifestProjectSlug, + getManifestServiceName, +} from "@/lib/cli-manifest"; +import { slugify } from "@/lib/utils"; +import { + createEnvironment, + createProject, + createService, + deployService, + updateServiceAutoPlace, + updateServiceConfig, + updateServiceReplicas, + updateServiceResourceLimits, + updateServiceStartCommand, + validateDockerImage, +} from "@/actions/projects"; + +export type ManifestChange = { + field: string; + from: string; + to: string; +}; + +export type ManifestApplyResult = { + project: { id: string; name: string; slug: string }; + environment: { id: string; name: string }; + serviceId: string; + action: "created" | "updated" | "noop"; + changes: ManifestChange[]; +}; + +type ManifestIdentity = { + project: string; + environment: string; + service: string; +}; + +function formatPort(port: { port: number; isPublic: boolean; domain: string | null }) { + return port.isPublic ? `${port.port} -> ${port.domain}` : `${port.port} (internal)`; +} + +function formatNullable(value: string | number | null | undefined, fallback = "(none)") { + if (value === null || value === undefined || value === "") { + return fallback; + } + + return String(value); +} + +function recordChange( + changes: ManifestChange[], + field: string, + from: string | number | null | undefined, + to: string | number | null | undefined, +) { + if ((from ?? null) === (to ?? null)) { + return; + } + + changes.push({ + field, + from: formatNullable(from), + to: formatNullable(to), + }); +} + +async function findProjectByManifest(manifest: TechulusManifest) { + const slug = getManifestProjectSlug(manifest); + return findProjectBySlug(slug); +} + +async function findProjectBySlug(slug: string) { + const [project] = await db + .select() + .from(projects) + .where(eq(projects.slug, slug)) + .limit(1); + + return project ?? null; +} + +async function findEnvironmentByManifest( + projectId: string, + manifest: TechulusManifest, +) { + const environmentName = getManifestEnvironmentName(manifest); + return findEnvironmentByName(projectId, environmentName); +} + +async function findEnvironmentByName(projectId: string, environmentName: string) { + const [environment] = await db + .select() + .from(environments) + .where( + and( + eq(environments.projectId, projectId), + eq(environments.name, environmentName), + ), + ) + .limit(1); + + return environment ?? null; +} + +async function findServiceByManifest( + projectId: string, + environmentId: string, + manifest: TechulusManifest, +) { + const serviceName = getManifestServiceName(manifest); + return findServiceByName(projectId, environmentId, serviceName); +} + +async function findServiceByName( + projectId: string, + environmentId: string, + serviceName: string, +) { + const [service] = await db + .select() + .from(services) + .where( + and( + eq(services.projectId, projectId), + eq(services.environmentId, environmentId), + eq(services.name, serviceName), + ), + ) + .limit(1); + + return service ?? null; +} + +async function syncHostname( + serviceId: string, + currentHostname: string | null, + desiredHostname: string | null, + changes: ManifestChange[], +) { + if (currentHostname === desiredHostname) { + return; + } + + if (desiredHostname) { + const [existing] = await db + .select({ id: services.id }) + .from(services) + .where( + and(eq(services.hostname, desiredHostname), ne(services.id, serviceId)), + ) + .limit(1); + + if (existing) { + throw new Error("Hostname is already in use"); + } + } + + await db + .update(services) + .set({ hostname: desiredHostname }) + .where(eq(services.id, serviceId)); + + recordChange(changes, "Hostname", currentHostname, desiredHostname); +} + +async function syncImage( + serviceId: string, + currentImage: string, + desiredImage: string, + changes: ManifestChange[], +) { + if (currentImage === desiredImage) { + return; + } + + const validation = await validateDockerImage(desiredImage); + if (!validation.valid) { + throw new Error(validation.error || "Invalid image"); + } + + await updateServiceConfig(serviceId, { + source: { type: "image", image: desiredImage }, + }); + + recordChange(changes, "Image", currentImage, desiredImage); +} + +async function syncPorts( + serviceId: string, + desiredPorts: TechulusManifest["service"]["ports"], + changes: ManifestChange[], +) { + const currentPorts = await db + .select() + .from(servicePorts) + .where(eq(servicePorts.serviceId, serviceId)); + + const currentKeys = new Map( + currentPorts.map((port) => [ + `${port.port}:${port.isPublic ? "public" : "internal"}:${port.domain ?? ""}`, + port, + ]), + ); + + const desiredKeys = new Map( + desiredPorts.map((port) => [ + `${port.port}:${port.public ? "public" : "internal"}:${port.domain ?? ""}`, + port, + ]), + ); + + const portsToRemove = currentPorts + .filter( + (port) => + !desiredKeys.has( + `${port.port}:${port.isPublic ? "public" : "internal"}:${port.domain ?? ""}`, + ), + ) + .map((port) => port.id); + + const portsToAdd = desiredPorts + .filter( + (port) => + !currentKeys.has( + `${port.port}:${port.public ? "public" : "internal"}:${port.domain ?? ""}`, + ), + ) + .map((port) => ({ + port: port.port, + isPublic: port.public, + domain: port.public ? port.domain ?? null : null, + protocol: "http" as const, + })); + + if (portsToRemove.length === 0 && portsToAdd.length === 0) { + return; + } + + await updateServiceConfig(serviceId, { + ports: { + remove: portsToRemove, + add: portsToAdd, + }, + }); + + for (const port of currentPorts.filter((item) => portsToRemove.includes(item.id))) { + changes.push({ + field: `Port ${port.port}`, + from: formatPort(port), + to: "(removed)", + }); + } + + for (const port of portsToAdd) { + changes.push({ + field: `Port ${port.port}`, + from: "(none)", + to: port.isPublic ? `${port.port} -> ${port.domain}` : `${port.port} (internal)`, + }); + } +} + +async function syncHealthCheck( + serviceId: string, + currentService: typeof services.$inferSelect, + manifest: TechulusManifest, + changes: ManifestChange[], +) { + const current = + currentService.healthCheckCmd === null + ? null + : { + cmd: currentService.healthCheckCmd, + interval: currentService.healthCheckInterval ?? 10, + timeout: currentService.healthCheckTimeout ?? 5, + retries: currentService.healthCheckRetries ?? 3, + startPeriod: currentService.healthCheckStartPeriod ?? 30, + }; + + const desired = manifest.service.healthCheck ?? null; + + if (JSON.stringify(current) === JSON.stringify(desired)) { + return; + } + + await updateServiceConfig(serviceId, { + healthCheck: desired, + }); + + recordChange( + changes, + "Health check", + current?.cmd ?? null, + desired?.cmd ?? null, + ); +} + +async function syncStartCommand( + serviceId: string, + currentStartCommand: string | null, + desiredStartCommand: string | null, + changes: ManifestChange[], +) { + if (currentStartCommand === desiredStartCommand) { + return; + } + + await updateServiceStartCommand(serviceId, desiredStartCommand); + recordChange( + changes, + "Start command", + currentStartCommand, + desiredStartCommand, + ); +} + +async function syncResources( + serviceId: string, + currentService: typeof services.$inferSelect, + manifest: TechulusManifest, + changes: ManifestChange[], +) { + const desiredCpu = manifest.service.resources?.cpuCores ?? null; + const desiredMemory = manifest.service.resources?.memoryMb ?? null; + + if ( + currentService.resourceCpuLimit === desiredCpu && + currentService.resourceMemoryLimitMb === desiredMemory + ) { + return; + } + + await updateServiceResourceLimits(serviceId, { + cpuCores: desiredCpu, + memoryMb: desiredMemory, + }); + + recordChange( + changes, + "CPU limit", + currentService.resourceCpuLimit, + desiredCpu, + ); + recordChange( + changes, + "Memory limit", + currentService.resourceMemoryLimitMb, + desiredMemory, + ); +} + +async function syncReplicas( + serviceId: string, + currentService: typeof services.$inferSelect, + desiredReplicas: number, + changes: ManifestChange[], +) { + if (!currentService.autoPlace) { + throw new Error( + "CLI v1 only supports auto-placement. This service uses manual placement.", + ); + } + + if (currentService.replicas === desiredReplicas) { + return; + } + + await updateServiceAutoPlace(serviceId, true); + await updateServiceReplicas(serviceId, desiredReplicas); + recordChange(changes, "Replicas", currentService.replicas, desiredReplicas); +} + +async function assertSupportedExistingService(serviceId: string) { + const [service] = await db + .select() + .from(services) + .where(eq(services.id, serviceId)) + .limit(1); + + if (!service) { + throw new Error("Service not found"); + } + + if (service.sourceType !== "image") { + throw new Error( + "CLI v1 only supports image-backed services. This service uses an unsupported source.", + ); + } + + if (service.stateful) { + throw new Error( + "CLI v1 does not support stateful services or volumes. Manage this service from the web UI.", + ); + } + + if (!service.autoPlace) { + throw new Error( + "CLI v1 only supports auto-placement. Manage this service from the web UI.", + ); + } + + const ports = await db + .select() + .from(servicePorts) + .where(eq(servicePorts.serviceId, serviceId)); + + if (ports.some((port) => port.protocol !== "http")) { + throw new Error( + "CLI v1 only supports HTTP ports. This service has TCP or UDP ports configured.", + ); + } + + const volumes = await db + .select({ id: serviceVolumes.id }) + .from(serviceVolumes) + .where(eq(serviceVolumes.serviceId, serviceId)); + + if (volumes.length > 0) { + throw new Error( + "CLI v1 does not support services with volumes. Manage this service from the web UI.", + ); + } + + return service; +} + +export async function applyManifest( + manifest: TechulusManifest, +): Promise { + let serviceCreated = false; + let project = await findProjectByManifest(manifest); + if (!project) { + await createProject(manifest.project.trim()); + project = await findProjectByManifest(manifest); + } + if (!project) { + throw new Error("Failed to create project"); + } + + let environment = await findEnvironmentByManifest(project.id, manifest); + if (!environment) { + await createEnvironment(project.id, manifest.environment.trim()); + environment = await findEnvironmentByManifest(project.id, manifest); + } + if (!environment) { + throw new Error("Failed to create environment"); + } + + let service = await findServiceByManifest(project.id, environment.id, manifest); + const changes: ManifestChange[] = []; + + if (!service) { + serviceCreated = true; + const validation = await validateDockerImage(manifest.service.source.image); + if (!validation.valid) { + throw new Error(validation.error || "Invalid image"); + } + + await createService({ + projectId: project.id, + environmentId: environment.id, + name: getManifestServiceName(manifest), + image: manifest.service.source.image, + }); + service = await findServiceByManifest(project.id, environment.id, manifest); + if (!service) { + throw new Error("Failed to create service"); + } + + recordChange(changes, "Image", null, manifest.service.source.image); + recordChange( + changes, + "Replicas", + null, + manifest.service.replicas.count, + ); + } + + const currentService = await assertSupportedExistingService(service.id); + + await syncHostname( + service.id, + currentService.hostname, + manifest.service.hostname ?? null, + changes, + ); + await syncImage( + service.id, + currentService.image, + manifest.service.source.image, + changes, + ); + await syncPorts(service.id, manifest.service.ports, changes); + await syncHealthCheck(service.id, currentService, manifest, changes); + await syncStartCommand( + service.id, + currentService.startCommand, + manifest.service.startCommand ?? null, + changes, + ); + await syncResources(service.id, currentService, manifest, changes); + await syncReplicas( + service.id, + currentService, + manifest.service.replicas.count, + changes, + ); + + const refreshedProject = await findProjectByManifest(manifest); + const refreshedEnvironment = await findEnvironmentByManifest(project.id, manifest); + + if (!refreshedProject || !refreshedEnvironment) { + throw new Error("Failed to reload manifest resources after apply"); + } + + return { + project: refreshedProject, + environment: refreshedEnvironment, + serviceId: service.id, + action: serviceCreated ? "created" : changes.length === 0 ? "noop" : "updated", + changes, + }; +} + +export async function deployManifest(manifest: TechulusManifest) { + const project = await findProjectByManifest(manifest); + if (!project) { + throw new Error("Project not found"); + } + + const environment = await findEnvironmentByManifest(project.id, manifest); + if (!environment) { + throw new Error("Environment not found"); + } + + const service = await findServiceByManifest(project.id, environment.id, manifest); + if (!service) { + throw new Error("Service not found"); + } + + const result = await deployService(service.id); + + return { + serviceId: service.id, + rolloutId: "rolloutId" in result ? result.rolloutId : null, + status: "migrationStarted" in result ? "migration_started" : "queued", + }; +} + +export async function getManifestStatus(identity: ManifestIdentity) { + const project = await findProjectBySlug(identity.project); + if (!project) { + return null; + } + + const environment = await findEnvironmentByName( + project.id, + slugify(identity.environment), + ); + if (!environment) { + return null; + } + + const service = await findServiceByName( + project.id, + environment.id, + identity.service.trim(), + ); + if (!service) { + return null; + } + + const [latestRollout] = await db + .select({ + id: rollouts.id, + status: rollouts.status, + currentStage: rollouts.currentStage, + createdAt: rollouts.createdAt, + }) + .from(rollouts) + .where(eq(rollouts.serviceId, service.id)) + .orderBy(desc(rollouts.createdAt)) + .limit(1); + + const serviceDeployments = await db + .select({ + id: deployments.id, + status: deployments.status, + serverId: deployments.serverId, + createdAt: deployments.createdAt, + }) + .from(deployments) + .where(eq(deployments.serviceId, service.id)) + .orderBy(desc(deployments.createdAt)); + + const ports = await db + .select({ + id: servicePorts.id, + port: servicePorts.port, + isPublic: servicePorts.isPublic, + domain: servicePorts.domain, + protocol: servicePorts.protocol, + }) + .from(servicePorts) + .where(eq(servicePorts.serviceId, service.id)); + + return { + service: { + id: service.id, + name: service.name, + image: service.image, + hostname: service.hostname, + replicas: service.replicas, + sourceType: service.sourceType, + }, + ports, + latestRollout: latestRollout ?? null, + deployments: serviceDeployments, + }; +} From 15578fde0048eba9075592db5fb4832307d97095 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 4 Apr 2026 10:34:45 +1100 Subject: [PATCH 10/16] Remove open browser cmd --- cli/src/main.ts | 27 +-------------------------- web/next.config.ts | 6 ++++++ 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/cli/src/main.ts b/cli/src/main.ts index 1b1796f..fe1c878 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -2,7 +2,6 @@ import { access, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { constants as fsConstants } from "node:fs"; -import { spawn } from "node:child_process"; import { deleteConfig, readConfig, writeConfig } from "./config.js"; import { loadManifest, slugify, type TechulusManifest } from "./manifest.js"; @@ -77,24 +76,6 @@ function printUsage() { tcloud status`); } -function openBrowser(url: string) { - const command = - process.platform === "darwin" - ? "open" - : process.platform === "win32" - ? "cmd" - : "xdg-open"; - const args = - process.platform === "win32" ? ["/c", "start", "", url] : [url]; - - const child = spawn(command, args, { - detached: true, - stdio: "ignore", - }); - - child.unref(); -} - async function ensureManifest(cwd: string) { try { return await loadManifest(cwd); @@ -154,13 +135,7 @@ async function commandAuthLogin(args: string[]) { console.log(`Visit ${deviceCode.verification_uri}`); console.log(`Enter code: ${deviceCode.user_code}`); - - try { - openBrowser(deviceCode.verification_uri_complete || deviceCode.verification_uri); - console.log("Opened your browser for approval."); - } catch { - console.log("Could not open the browser automatically."); - } + console.log("Open the verification URL in your browser to continue."); let accessToken = ""; let intervalMs = deviceCode.interval * 1000; diff --git a/web/next.config.ts b/web/next.config.ts index 398b0d5..42b02f4 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -1,7 +1,13 @@ import type { NextConfig } from "next"; +const allowedDevOrigins = (process.env.ALLOWED_DEV_ORIGINS ?? "") + .split(",") + .map((value) => value.trim()) + .filter(Boolean); + const nextConfig: NextConfig = { output: "standalone", + allowedDevOrigins, }; export default nextConfig; From 13aeac0f398aee859dc29f3015e8c25e99427655 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sun, 5 Apr 2026 00:02:16 +1100 Subject: [PATCH 11/16] Use Mintlify docs --- docs/.mintignore | 7 + docs/AGENT.md | 110 ------------ docs/AGENTS.md | 33 ++++ docs/ARCHITECTURE.md | 402 ------------------------------------------ docs/agent.mdx | 120 +++++++++++++ docs/architecture.mdx | 262 +++++++++++++++++++++++++++ docs/docs.json | 66 +++++++ docs/index.mdx | 97 ++++++++++ docs/logo/logo.png | Bin 0 -> 327331 bytes docs/mise.toml | 2 + 10 files changed, 587 insertions(+), 512 deletions(-) create mode 100644 docs/.mintignore delete mode 100644 docs/AGENT.md create mode 100644 docs/AGENTS.md delete mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/agent.mdx create mode 100644 docs/architecture.mdx create mode 100644 docs/docs.json create mode 100644 docs/index.mdx create mode 100644 docs/logo/logo.png create mode 100644 docs/mise.toml diff --git a/docs/.mintignore b/docs/.mintignore new file mode 100644 index 0000000..9922f06 --- /dev/null +++ b/docs/.mintignore @@ -0,0 +1,7 @@ +# Mintlify automatically ignores these files and directories: +# .git, .github, .claude, .agents, .idea, node_modules, +# README.md, LICENSE.md, CHANGELOG.md, CONTRIBUTING.md + +# Draft content +drafts/ +*.draft.mdx diff --git a/docs/AGENT.md b/docs/AGENT.md deleted file mode 100644 index 90fa35d..0000000 --- a/docs/AGENT.md +++ /dev/null @@ -1,110 +0,0 @@ -# Agent Architecture - -The agent runs on servers and reconciles expected state from the control plane. - -## Node Types - -The agent supports two modes: - -| Type | Flag | Traefik | Description | -|------|------|---------|-------------| -| Worker | (default) | ✗ | Runs containers only | -| Proxy | `--proxy` | ✓ | Handles TLS and public traffic | - -## State Machine - -Two-state machine for reconciliation: - -``` -┌─────────┐ ┌────────────┐ -│ IDLE │───drift detected───────▶│ PROCESSING │ -│ (poll) │◀────────────────────────│ (no poll) │ -└─────────┘ done/failed/timeout └────────────┘ -``` - -### IDLE State -- Polls control plane every 10 seconds for expected state -- Compares expected vs actual state -- If drift detected: transitions to PROCESSING - -### PROCESSING State -- Uses snapshot of expected state (no re-polling) -- Applies ONE change at a time: - 1. Stop orphan containers (no deployment ID) - 2. Start containers in "created" or "exited" state - 3. Deploy missing containers - 4. Redeploy containers with wrong image - 5. Update DNS records - 6. Update Traefik routes (proxy nodes only) - 7. Update WireGuard peers -- Timeout: 5 minutes max -- Always reports status before returning to IDLE - -## Drift Detection - -Uses hash comparisons for deterministic drift detection: -- Containers: Missing, orphaned, wrong state, or image mismatch -- DNS: Hash of sorted records -- Traefik: Hash of sorted routes (proxy nodes only) -- WireGuard: Hash of sorted peers - -## Container Labels - -The agent tracks containers using Podman labels: - -| Label | Description | -|-------|-------------| -| `techulus.deployment.id` | Links container to deployment record | -| `techulus.service.id` | Links container to service | -| `techulus.service.name` | Human-readable service name | - -Containers without `techulus.deployment.id` are considered orphans and will be cleaned up. - -## Command Line Flags - -| Flag | Default | Description | -|------|---------|-------------| -| `--url` | (required) | Control plane URL | -| `--token` | | Registration token (required for first run) | -| `--logs-endpoint` | | VictoriaLogs endpoint for log shipping | -| `--proxy` | `false` | Run as proxy node (handles TLS/public traffic) | - -## Build System - -Agents can build container images from GitHub sources: - -1. Agent polls for pending builds -2. Claims build (prevents other agents from picking it up) -3. Clones repository using GitHub App installation token -4. Runs Railpack to generate build plan (or uses existing Dockerfile) -5. Builds image via BuildKit -6. Pushes to registry -7. Updates build status - -Build logs are streamed to VictoriaLogs in real-time. - -## Work Queue - -Agents process work queue items for operations that can't be expressed via expected state: - -| Type | Description | -|------|-------------| -| `restart` | Restart a specific container | -| `stop` | Stop a specific container | -| `force_cleanup` | Force remove containers for a service | -| `cleanup_volumes` | Remove volume directories for a service | -| `deploy` | Handled via expected state reconciliation | - -## Proxy vs Worker Behavior - -### Proxy Node (`--proxy`) -- Runs Traefik for TLS termination -- Receives Traefik routes from control plane -- Handles public traffic and routes to containers via WireGuard -- Collects and ships Traefik access logs - -### Worker Node (default) -- Does not run Traefik -- Receives empty Traefik routes from control plane -- Skips all Traefik-related drift detection and reconciliation -- Lighter footprint, focused on running containers diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 0000000..cebd973 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,33 @@ +> **First-time setup**: Customize this file for your project. Prompt the user to customize this file for their project. +> For Mintlify product knowledge (components, configuration, writing standards), +> install the Mintlify skill: `npx skills add https://mintlify.com/docs` + +# Documentation project instructions + +## About this project + +- This is a documentation site built on [Mintlify](https://mintlify.com) +- Pages are MDX files with YAML frontmatter +- Configuration lives in `docs.json` +- Run `mint dev` to preview locally +- Run `mint broken-links` to check links + +## Terminology + +{/* Add product-specific terms and preferred usage */} +{/* Example: Use "workspace" not "project", "member" not "user" */} + +## Style preferences + +{/* Add any project-specific style rules below */} + +- Use active voice and second person ("you") +- Keep sentences concise — one idea per sentence +- Use sentence case for headings +- Bold for UI elements: Click **Settings** +- Code formatting for file names, commands, paths, and code references + +## Content boundaries + +{/* Define what should and shouldn't be documented */} +{/* Example: Don't document internal admin features */} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index 6cadbe3..0000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,402 +0,0 @@ -# Techulus Cloud - Architecture - -## Overview - -A stateless container deployment platform with three core principles: -1. **Workloads are disposable** - containers can be killed and recreated at any time -2. **Two node types** - proxy nodes handle public traffic, worker nodes run containers -3. **Networking is private-first** - services communicate over WireGuard mesh, public exposure via proxy nodes - -## Tech Stack - -| Component | Choice | Rationale | -|-----------|--------|-----------| -| Control Plane | Next.js (full-stack) | Single deployment, React frontend + API routes | -| Database | Postgres + Drizzle | Simple, no external deps, single file, easy backup | -| Background Jobs | Inngest (self-hosted) | Durable workflows, event-driven orchestration, retries | -| Server Agent | Go | Single binary, shells out to Podman | -| Container Runtime | Podman | Docker-compatible, daemonless, bridge networking with static IPs | -| Reverse Proxy | Traefik | Automatic HTTPS via Let's Encrypt, runs on proxy nodes only | -| Private Network | WireGuard (self-managed) | Full mesh, control plane coordinates | -| Service Discovery | Built-in DNS | Agent runs DNS server for .internal domains | -| Agent Communication | Pull-based HTTP | Agent polls for expected state, reports status | - -## Node Types - -| Type | Traefik | Public Traffic | Containers | -|------|---------|----------------|------------| -| Proxy | ✓ | Handles TLS termination | ✓ | -| Worker | ✗ | None | ✓ | - -- **Proxy nodes**: Handle incoming public traffic, TLS termination via HTTP-01 ACME, route to containers via WireGuard -- **Worker nodes**: Run containers only, no public exposure, lighter footprint - -## Architecture Diagram - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ CONTROL PLANE │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Next.js (App Router + API Routes + Postgres) │ │ -│ │ │ │ -│ │ GET /api/v1/agent/expected-state (agent polls) │ │ -│ │ POST /api/v1/agent/status (agent reports) │ │ -│ └──────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ - ▲ - │ HTTPS (poll every 10s) - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ SERVERS │ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Proxy Node 1 │ │ Worker Node 1 │ │ Worker Node 2 │ │ -│ │ │ │ │ │ │ │ -│ │ WG: 10.100.1.1 │ │ WG: 10.100.2.1 │ │ WG: 10.100.3.1 │ │ -│ │ Containers: │ │ Containers: │ │ Containers: │ │ -│ │ 10.200.1.2-254 │ │ 10.200.2.2-254 │ │ 10.200.3.2-254 │ │ -│ │ │ │ │ │ │ │ -│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ -│ │ │ Agent │ │ │ │ Agent │ │ │ │ Agent │ │ │ -│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ -│ │ │ Podman │ │ │ │ Podman │ │ │ │ Podman │ │ │ -│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ -│ │ │ Traefik │ │ │ │ - │ │ │ │ - │ │ │ -│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ -│ │ │ DNS Server │ │ │ │ DNS Server │ │ │ │ DNS Server │ │ │ -│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ -│ │ │ WireGuard │ │ │ │ WireGuard │ │ │ │ WireGuard │ │ │ -│ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │ -│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ -│ │ │ │ │ -│ └────────────────────┴────────────────────┘ │ -│ WireGuard Full Mesh │ -└─────────────────────────────────────────────────────────────────┘ - -Public Traffic Flow: - Internet → DNS → Proxy Node → Traefik (TLS) → WireGuard → Container -``` - -## Agent State Machine - -The agent uses a two-state machine to prevent race conditions during reconciliation: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ │ -│ ┌─────────┐ ┌────────────┐ │ -│ │ IDLE │───drift detected───────▶│ PROCESSING │ │ -│ │ (poll) │◀────────────────────────│ (no poll) │ │ -│ └─────────┘ done/failed/timeout └────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### IDLE State -- Poll control plane every 10 seconds for expected state -- Compare expected state vs actual state (containers, DNS, Traefik*, WireGuard) -- If no drift: send status report, stay in IDLE -- If drift detected: snapshot expected state, transition to PROCESSING - -*Traefik drift detection only on proxy nodes - -### PROCESSING State -- Stop polling (use the expected state snapshot) -- Apply ONE change at a time with verification -- After each change, re-check drift -- If no drift remains: transition to IDLE -- Timeout after 5 minutes: force transition to IDLE -- Always send status report before transitioning to IDLE - -### Drift Detection - -The agent detects drift using hash comparisons: -- **Containers**: Missing, orphaned, wrong state, or image mismatch -- **DNS**: Hash of sorted records vs current DNS server config -- **Traefik**: Hash of sorted routes vs current Traefik config (proxy nodes only) -- **WireGuard**: Hash of sorted peers vs current wg0.conf - -### Container Reconciliation - -Order of operations: -1. Stop orphan containers (no deployment ID) -2. Start containers in "created" or "exited" state -3. Deploy missing containers -4. Redeploy containers with wrong state or image mismatch -5. Update DNS records -6. Update Traefik routes (proxy nodes only) -7. Update WireGuard peers - -## Rollout Stages - -Deployments go through these stages: - -``` -pending → pulling → starting → healthy → dns_updating → traefik_updating → stopping_old → running -``` - -| Stage | Description | -|-------|-------------| -| `pending` | Deployment created, waiting for agent | -| `pulling` | Agent is pulling the container image | -| `starting` | Container started, waiting for health check | -| `healthy` | Health check passed (or no health check) | -| `dns_updating` | DNS records being updated | -| `traefik_updating` | Traefik routes being updated | -| `stopping_old` | Old deployment containers being stopped | -| `running` | Deployment complete and serving traffic | - -Special states: -- `unknown`: Agent stopped reporting this deployment (container may still exist) -- `stopped`: Container explicitly stopped -- `failed`: Deployment failed (health check, etc.) -- `rolled_back`: Rollout failed, reverted to previous deployment - -## Networking - -### IP Address Scheme - -| Range | Purpose | -|-------|---------| -| `10.100.X.1` | WireGuard IP for server X (host mesh) | -| `10.200.X.2-254` | Container IPs on server X | - -Where X = server's subnet ID (1-255). - -### WireGuard Mesh (Host-to-Host) - -Each server gets a `/24` subnet for routing: -- Server 1: `10.100.1.0/24` → WireGuard IP: `10.100.1.1` -- Server 2: `10.100.2.0/24` → WireGuard IP: `10.100.2.1` - -Full mesh topology - every server peers with every other server. AllowedIPs includes both WireGuard and container subnets: -``` -AllowedIPs = 10.100.2.0/24, 10.200.2.0/24 -``` - -### Container Network (Per-Server) - -Each server has a Podman bridge network: -```bash -podman network create \ - --driver bridge \ - --subnet 10.200.1.0/24 \ - --gateway 10.200.1.1 \ - --disable-dns \ - techulus -``` - -Containers get static IPs assigned by the control plane: -```bash -podman run -d \ - --name service-deployment \ - --network techulus \ - --ip 10.200.1.2 \ - --label techulus.deployment.id= \ - --label techulus.service.id= \ - traefik/whoami -``` - -### DNS Resolution - -Each agent runs a built-in DNS server for `.internal` domain resolution: -- Listens on the container gateway IP (e.g., `10.200.1.1`) -- Configures systemd-resolved to forward `.internal` queries -- Records pushed from control plane via expected state - -Services resolve via `.internal` domain with round-robin across replicas. - -### Traefik (Proxy Nodes Only) - -Proxy nodes run Traefik with routes and certificates pushed from control plane: -- Routes configured via file provider in `/etc/traefik/dynamic/routes.yaml` -- Certificates configured via file provider in `/etc/traefik/dynamic/tls.yaml` -- Routes: `subdomain.example.com` → container IPs (via WireGuard mesh) -- TLS: Static certificates managed by control plane -- Challenge route: `/.well-known/acme-challenge/*` → control plane for ACME validation -- Control plane only sends routes and certificates to proxy nodes - -Worker nodes do not run Traefik. - -### Multiple Proxy Nodes (Geographic Distribution) - -The platform supports multiple proxy nodes in different regions with automatic proximity steering: -- Users point custom domains to a single DNS name via GeoDNS (BunnyDNS) -- BunnyDNS routes clients to geographically nearest proxy based on their location -- BunnyDNS health checks automatically failover if a proxy goes down -- All proxies share the same TLS certificates (synced from control plane) - -Example: -``` -Proxy US: 1.2.3.4 -Proxy EU: 5.6.7.8 -Proxy SYD: 9.10.11.12 - -GeoDNS (BunnyDNS): - example.com → lb.techulus.cloud - → BunnyDNS steers to nearest proxy based on client geography - → Returns 1.2.3.4 (US), 5.6.7.8 (EU), or 9.10.11.12 (SYD) - → Health checks: exclude proxy if down, failover to next nearest - -All proxies share same TLS certificates (synced from control plane) -``` - -ACME challenges work seamlessly because: -- Let's Encrypt validates the domain via single IP (any proxy) -- Challenge hits any proxy node (they're all interchangeable) -- All proxies have identical certificates -- If one proxy goes down, others already have the cert - -### Proximity-Aware Load Balancing - -Within a proxy node, traffic is distributed to replicas using weighted round-robin: - -**Replica Selection Priority:** -1. **Local replicas** (on same proxy server) - weight 5 -2. **Remote replicas** (on other proxy servers) - weight 1 - -This means if a service has 1 local replica and 1 remote replica, the local replica receives ~83% of traffic. - -**Traffic Flow:** -``` -User (US) - → GeoDNS: nearest proxy = US (1.2.3.4) - → Traefik: weighted round-robin - - Local replicas (weight 5) ← 83% of traffic - - Remote replicas (weight 1) ← 17% of traffic (failover) - → Container -``` - -Benefits: -- **Low latency**: Requests stay on same proxy when possible -- **Failover**: If local replica fails, automatically uses remote -- **Cost-effective**: Minimizes cross-region traffic - -### ACME Certificate Management (Centralized) - -Instead of each proxy managing its own ACME certificates, the control plane handles all certificate lifecycle: - -**Challenge Flow:** -1. Control plane initiates ACME renewal for expiring certificates -2. Let's Encrypt requests validation: `GET http://domain/.well-known/acme-challenge/{token}` -3. Request hits load balancer → any proxy node (all behind same IP) -4. Traefik matches `PathPrefix(/.well-known/acme-challenge/)` → special challenge route -5. Challenge route (via middleware) rewrites path to `/api/v1/acme/challenge/{token}` -6. Traefik forwards to control plane: `https://control-plane.internal/api/v1/acme/challenge/{token}` -7. Control plane returns keyAuthorization from database -8. Let's Encrypt validates and issues certificate - -**Certificate Sync:** -1. Certificate issued and stored in `domain_certificates` table -2. Control plane includes certificates in expected state API response (proxy nodes only) -3. Agent receives certificates, writes to `/etc/traefik/certs/{domain}.crt` and `.key` -4. Agent updates `/etc/traefik/dynamic/tls.yaml` with certificate paths -5. Traefik reloads and serves TLS with new certificates - -**Renewal:** -- Cron job checks daily for certificates expiring in 30 days -- Triggers ACME renewal via acme-client library -- Challenge responses served through any proxy node -- New certificates synced to all proxies within agent poll cycle (10 seconds) - -### Traffic Flows - -**Internal (service-to-service):** -``` -Container A (10.200.1.2) - → DNS: redis.internal → 10.200.2.3 - → Packet to 10.200.2.3 - → Host routes via WireGuard to Server 2 - → Container B (10.200.2.3) -``` - -**External (public) - Custom Domain:** -``` -User domain: example.com (points to proxy IP via A record or CNAME) - → Internet → Proxy Node public IP - → Traefik: example.com → 10.200.1.2:80 (TLS terminated) - → WireGuard tunnel to target node - → Container (10.200.1.2) -``` - -**ACME Challenge (Let's Encrypt validation):** -``` -Let's Encrypt → HTTP request to example.com/.well-known/acme-challenge/{token} - → Proxy Node (any of them, all same IP) - → Traefik matches challenge route (priority 9999) - → Middleware rewrites path to /api/v1/acme/challenge/{token} - → Traefik backend: control plane HTTPS - → Returns keyAuthorization - → Let's Encrypt validates -``` - -## Components - -### 1. Control Plane (Next.js) - -**Responsibilities:** -- User authentication -- Project and service configuration -- WireGuard coordination (assigns subnets, broadcasts peer updates) -- Deployment orchestration (rollouts) -- Certificate lifecycle management (issuance, renewal, sync) -- Serves expected state to agents -- Processes status reports from agents -- Advances rollout stages based on deployment status - -**API Endpoints:** -- `GET /api/v1/agent/expected-state` - Returns containers, DNS, Traefik (proxy only), WireGuard, certificates config -- `POST /api/v1/agent/status` - Receives container status, advances rollout stages -- `GET /api/v1/acme/challenge/{token}` - Returns ACME challenge keyAuthorization for Let's Encrypt validation - -**Background Jobs (Inngest):** -- Rollout orchestration: Event-driven deployment workflow with health checks and DNS updates -- Migration orchestration: Backup, restore, and container migration workflows -- Build orchestration: Multi-architecture builds with manifest creation -- Backup/restore: Scheduled and on-demand volume backups -- Certificate renewal: ACME renewal for expiring certificates - -### 2. Server Agent (Go) - -**Responsibilities:** -- Polls control plane for expected state -- Manages containers via Podman with static IPs -- Manages local WireGuard interface -- Updates Traefik routes via file provider (proxy nodes only) -- Syncs TLS certificates to disk (proxy nodes only) -- Updates DNS records -- Reports status (resources, public IP, container health) - -**Agent Lifecycle:** -1. User creates server in control plane, receives agent token -2. User runs install script (specifies if proxy node) -3. User starts agent with token (and `--proxy` flag if proxy node) -4. Agent generates WireGuard and signing keypairs -5. Agent registers with control plane via HTTP (includes isProxy flag) -6. Control plane assigns subnet, returns WireGuard peers -7. Agent configures WireGuard, container network, DNS server, and Traefik (if proxy) -8. Agent enters IDLE state, begins polling - -### 3. Container Labels - -Containers are tracked via Podman labels: -- `techulus.deployment.id` - Links container to deployment record -- `techulus.service.id` - Links container to service -- `techulus.service.name` - Human-readable service name - -## Security Model - -1. **Agent Authentication**: HMAC signatures on all HTTP requests -2. **Request Signing**: Body + timestamp signed with server-specific secret -3. **WireGuard**: All inter-server traffic encrypted -4. **No Public Ports on Containers**: Only reachable via WireGuard mesh -5. **Traefik**: Only entry point for public traffic (proxy nodes only) - -**Registration Token:** -- One-time-use token for initial registration -- Invalidated after successful registration - -**Request Signing:** -- Agent signs request body with HMAC-SHA256 -- Includes timestamp to prevent replay attacks -- Control plane verifies using stored server secret diff --git a/docs/agent.mdx b/docs/agent.mdx new file mode 100644 index 0000000..e79a5be --- /dev/null +++ b/docs/agent.mdx @@ -0,0 +1,120 @@ +--- +title: "Agent" +description: "Agent modes, reconciliation flow, build pipeline, and work queue behavior." +--- + +# Agent Architecture + +The agent runs on servers and reconciles expected state from the control plane. + +## Node Types + +The agent supports two modes: + +| Type | Flag | Traefik | Description | +| --- | --- | --- | --- | +| Worker | Default | No | Runs containers only | +| Proxy | `--proxy` | Yes | Handles TLS and public traffic | + +## State Machine + +The agent uses a two-state reconciliation model: + +```text +┌─────────┐ ┌────────────┐ +│ IDLE │───drift detected───────▶│ PROCESSING │ +│ (poll) │◀────────────────────────│ (no poll) │ +└─────────┘ done/failed/timeout └────────────┘ +``` + +### IDLE State + +- Polls the control plane every 10 seconds for expected state. +- Compares expected state against actual state. +- Transitions to `PROCESSING` when drift is detected. + +### PROCESSING State + +- Uses a snapshot of expected state without re-polling. +- Applies one change at a time: + 1. Stop orphan containers with no deployment ID. + 2. Start containers in `created` or `exited` state. + 3. Deploy missing containers. + 4. Redeploy containers with the wrong image. + 5. Update DNS records. + 6. Update Traefik routes on proxy nodes. + 7. Update WireGuard peers. +- Times out after 5 minutes. +- Always reports status before returning to `IDLE`. + +## Drift Detection + +Drift detection is deterministic and uses hashes: + +- Containers: missing, orphaned, wrong state, or image mismatch. +- DNS: hash of sorted records. +- Traefik: hash of sorted routes on proxy nodes. +- WireGuard: hash of sorted peers. + +## Container Labels + +The agent tracks managed containers with Podman labels: + +| Label | Description | +| --- | --- | +| `techulus.deployment.id` | Links the container to a deployment | +| `techulus.service.id` | Links the container to a service | +| `techulus.service.name` | Human-readable service name | + +Containers without `techulus.deployment.id` are treated as orphans and cleaned up. + +## Command Line Flags + +| Flag | Default | Description | +| --- | --- | --- | +| `--url` | Required | Control plane URL | +| `--token` | Empty | Registration token, required on first run | +| `--logs-endpoint` | Empty | VictoriaLogs endpoint for log shipping | +| `--proxy` | `false` | Run as a proxy node | + +## Build System + +Agents can build container images directly from GitHub sources: + +1. Poll for pending builds. +2. Claim the build to prevent duplicate work. +3. Clone the repository using a GitHub App installation token. +4. Run Railpack to generate a build plan, or use the existing Dockerfile. +5. Build the image with BuildKit. +6. Push the image to the registry. +7. Update build status. + +Build logs stream to VictoriaLogs in real time. + +## Work Queue + +Agents also process queue items for operations that cannot be modeled purely as expected state: + +| Type | Description | +| --- | --- | +| `restart` | Restart a specific container | +| `stop` | Stop a specific container | +| `force_cleanup` | Force remove containers for a service | +| `cleanup_volumes` | Remove volume directories for a service | +| `deploy` | Handled through expected-state reconciliation | + +## Proxy vs Worker Behavior + +### Proxy Node + +- Runs Traefik for TLS termination. +- Receives Traefik routes from the control plane. +- Handles public traffic and routes requests to containers over WireGuard. +- Collects and ships Traefik access logs. + +### Worker Node + +- Does not run Traefik. +- Receives empty Traefik route sets from the control plane. +- Skips Traefik-related drift detection and reconciliation. +- Keeps a lighter runtime footprint focused on container workloads. diff --git a/docs/architecture.mdx b/docs/architecture.mdx new file mode 100644 index 0000000..03edb9e --- /dev/null +++ b/docs/architecture.mdx @@ -0,0 +1,262 @@ +--- +title: "Architecture" +description: "System design, networking model, rollout lifecycle, and reconciliation flow." +--- + +# Techulus Cloud Architecture + +## Overview + +Techulus Cloud is a stateless container deployment platform built around three core principles: + +1. **Workloads are disposable**: containers can be killed and recreated at any time. +2. **Two node types**: proxy nodes handle public traffic, worker nodes run containers. +3. **Networking is private-first**: services communicate over a WireGuard mesh, with public exposure routed through proxy nodes. + +## Tech Stack + +| Component | Choice | Rationale | +| --- | --- | --- | +| Control Plane | Next.js (full-stack) | Single deployment with React frontend and API routes | +| Database | Postgres + Drizzle | Simple, low operational overhead, easy backup | +| Background Jobs | Inngest (self-hosted) | Durable workflows, retries, event-driven orchestration | +| Server Agent | Go | Single binary that shells out to Podman | +| Container Runtime | Podman | Docker-compatible, daemonless, bridge networking with static IPs | +| Reverse Proxy | Traefik | Automatic HTTPS via Let's Encrypt, runs on proxy nodes only | +| Private Network | WireGuard | Full mesh coordinated by the control plane | +| Service Discovery | Built-in DNS | Agent serves `.internal` domains | +| Agent Communication | Pull-based HTTP | Agent polls expected state and reports status | + +## Node Types + +| Type | Traefik | Public Traffic | Containers | +| --- | --- | --- | --- | +| Proxy | Yes | Handles TLS termination | Yes | +| Worker | No | None | Yes | + +- **Proxy nodes** handle incoming public traffic, terminate TLS using HTTP-01 ACME, and route requests to containers over WireGuard. +- **Worker nodes** run containers only and have no public exposure. + +## Architecture Diagram + +```text +┌─────────────────────────────────────────────────────────────────┐ +│ CONTROL PLANE │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Next.js (App Router + API Routes + Postgres) │ │ +│ │ │ │ +│ │ GET /api/v1/agent/expected-state (agent polls) │ │ +│ │ POST /api/v1/agent/status (agent reports) │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + ▲ + │ HTTPS (poll every 10s) + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ SERVERS │ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Proxy Node 1 │ │ Worker Node 1 │ │ Worker Node 2 │ │ +│ │ │ │ │ │ │ │ +│ │ WG: 10.100.1.1 │ │ WG: 10.100.2.1 │ │ WG: 10.100.3.1 │ │ +│ │ Containers: │ │ Containers: │ │ Containers: │ │ +│ │ 10.200.1.2-254 │ │ 10.200.2.2-254 │ │ 10.200.3.2-254 │ │ +│ │ │ │ │ │ │ │ +│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ +│ │ │ Agent │ │ │ │ Agent │ │ │ │ Agent │ │ │ +│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ +│ │ │ Podman │ │ │ │ Podman │ │ │ │ Podman │ │ │ +│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ +│ │ │ Traefik │ │ │ │ - │ │ │ │ - │ │ │ +│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ +│ │ │ DNS Server │ │ │ │ DNS Server │ │ │ │ DNS Server │ │ │ +│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ +│ │ │ WireGuard │ │ │ │ WireGuard │ │ │ │ WireGuard │ │ │ +│ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │ +│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ +│ │ │ │ │ +│ └────────────────────┴────────────────────┘ │ +│ WireGuard Full Mesh │ +└─────────────────────────────────────────────────────────────────┘ + +Public traffic: + Internet -> DNS -> Proxy Node -> Traefik (TLS) -> WireGuard -> Container +``` + +## Agent State Machine + +The agent uses a two-state machine to prevent race conditions during reconciliation. + +```text +┌─────────────────────────────────────────────────────────────────┐ +│ │ +│ ┌─────────┐ ┌────────────┐ │ +│ │ IDLE │───drift detected───────▶│ PROCESSING │ │ +│ │ (poll) │◀────────────────────────│ (no poll) │ │ +│ └─────────┘ done/failed/timeout └────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### IDLE State + +- Poll the control plane every 10 seconds for expected state. +- Compare expected state versus actual state for containers, DNS, Traefik, and WireGuard. +- If no drift exists, send a status report and remain in `IDLE`. +- If drift is detected, snapshot expected state and transition to `PROCESSING`. + +Traefik drift detection only applies on proxy nodes. + +### PROCESSING State + +- Stop polling and work from the expected-state snapshot. +- Apply one change at a time with verification. +- Re-check drift after every change. +- Transition back to `IDLE` once drift is resolved. +- Force a return to `IDLE` after 5 minutes if reconciliation stalls. +- Always send a status report before returning to `IDLE`. + +### Drift Detection + +The agent uses hash comparisons for deterministic drift detection: + +- **Containers**: missing, orphaned, wrong state, or image mismatch. +- **DNS**: hash of sorted records versus current DNS config. +- **Traefik**: hash of sorted routes versus current Traefik config on proxy nodes. +- **WireGuard**: hash of sorted peers versus current `wg0.conf`. + +### Container Reconciliation Order + +1. Stop orphan containers with no deployment ID. +2. Start containers in `created` or `exited` state. +3. Deploy missing containers. +4. Redeploy containers with wrong state or image mismatch. +5. Update DNS records. +6. Update Traefik routes on proxy nodes. +7. Update WireGuard peers. + +## Rollout Stages + +```text +pending -> pulling -> starting -> healthy -> dns_updating -> traefik_updating -> stopping_old -> running +``` + +| Stage | Description | +| --- | --- | +| `pending` | Deployment created and waiting for an agent | +| `pulling` | Agent is pulling the container image | +| `starting` | Container started and waiting for health checks | +| `healthy` | Health check passed, or no health check is configured | +| `dns_updating` | DNS records are being updated | +| `traefik_updating` | Traefik routes are being updated | +| `stopping_old` | Old deployment containers are being stopped | +| `running` | Deployment is complete and serving traffic | + +Special states: + +- `unknown`: the agent stopped reporting this deployment and the container may still exist. +- `stopped`: the container was explicitly stopped. +- `failed`: the deployment failed, such as during health checks. +- `rolled_back`: rollout failed and reverted to the previous deployment. + +## Networking + +### IP Address Scheme + +| Range | Purpose | +| --- | --- | +| `10.100.X.1` | WireGuard IP for server `X` | +| `10.200.X.2-254` | Container IPs on server `X` | + +`X` is the server subnet ID from `1` to `255`. + +### WireGuard Mesh + +Each server gets a `/24` subnet for routing: + +- Server 1: `10.100.1.0/24` with WireGuard IP `10.100.1.1` +- Server 2: `10.100.2.0/24` with WireGuard IP `10.100.2.1` + +Every server peers with every other server. `AllowedIPs` includes both WireGuard and container subnets: + +```ini +AllowedIPs = 10.100.2.0/24, 10.200.2.0/24 +``` + +### Container Network + +Each server has a Podman bridge network: + +```bash +podman network create \ + --driver bridge \ + --subnet 10.200.1.0/24 \ + --gateway 10.200.1.1 \ + --disable-dns \ + techulus +``` + +Containers receive static IPs assigned by the control plane: + +```bash +podman run -d \ + --name service-deployment \ + --network techulus \ + --ip 10.200.1.2 \ + --label techulus.deployment.id= \ + --label techulus.service.id= \ + traefik/whoami +``` + +### DNS Resolution + +Each agent runs a built-in DNS server for `.internal` domains: + +- It listens on the container gateway IP, such as `10.200.1.1`. +- It configures `systemd-resolved` to forward `.internal` queries. +- Records are pushed from the control plane through expected state. + +Services resolve through `.internal` names with round-robin across replicas. + +### Traefik on Proxy Nodes + +Proxy nodes receive routes and certificates from the control plane: + +- Routes live in `/etc/traefik/dynamic/routes.yaml`. +- Certificates live in `/etc/traefik/dynamic/tls.yaml`. +- Routes map `subdomain.example.com` to container IPs over WireGuard. +- TLS certificates are managed centrally by the control plane. +- `/.well-known/acme-challenge/*` is routed back to the control plane for ACME validation. + +Worker nodes do not run Traefik. + +### Multiple Proxy Nodes + +The platform supports geographically distributed proxy nodes with proximity steering: + +- Users point custom domains to a single GeoDNS-managed hostname. +- GeoDNS routes clients to the nearest healthy proxy. +- Health checks fail over automatically when a proxy becomes unavailable. +- All proxies share the same TLS certificates from the control plane. + +Example: + +```text +Proxy US: 1.2.3.4 +Proxy EU: 5.6.7.8 +Proxy SYD: 9.10.11.12 + +GeoDNS: + example.com -> lb.techulus.cloud + -> route client to nearest proxy + -> fail over when a proxy is unhealthy +``` + +### Proximity-Aware Load Balancing + +Within a proxy node, traffic is distributed using weighted round-robin: + +1. Local replicas on the same proxy server use weight `5`. +2. Remote replicas on other proxy servers use weight `1`. + +That keeps the majority of traffic local whenever possible while still preserving cross-node routing. diff --git a/docs/docs.json b/docs/docs.json new file mode 100644 index 0000000..a1262d6 --- /dev/null +++ b/docs/docs.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "sequoia", + "name": "Techulus Cloud", + "colors": { + "primary": "#DE434A", + "light": "#F7C8CB", + "dark": "#A61E27" + }, + "favicon": "/favicon.svg", + "navigation": { + "tabs": [ + { + "tab": "Docs", + "groups": [ + { + "group": "Core", + "pages": [ + "index", + "architecture", + "agent" + ] + } + ] + } + ], + "global": { + "anchors": [] + } + }, + "logo": { + "light": "/logo/logo.png", + "dark": "/logo/logo.png" + }, + "navbar": { + "links": [ + { + "label": "Support", + "href": "mailto:hi@techulus.cloud" + } + ], + "primary": { + "type": "button", + "label": "Dashboard", + "href": "https://cloud.techulus.com" + } + }, + "contextual": { + "options": [ + "copy", + "view", + "chatgpt", + "claude", + "perplexity", + "mcp", + "cursor", + "vscode" + ] + }, + "footer": { + "socials": { + "github": "https://github.com/techulus/cloud" + } + }, + "description": "Simple, Scalable Container Deployment" +} diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 0000000..6b1c130 --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,97 @@ +--- +title: "Techulus Cloud" +description: "Private-first container deployment with WireGuard networking and proxy-worker orchestration." +--- + +# Simple, Scalable + +# Container Deployment + +Run your containers without the headache. Fast, reliable, and you're in control. + + + Learn how peers, proxy nodes, worker nodes, and private networking fit together. + + + + See how the agent reconciles state, builds images, and keeps deployments running. + + +## Built for Real Infrastructure + + + + No master nodes, no single points of failure. Every machine pulls its weight equally. One goes down? The others pick up the slack. + + + Separate concerns with node types. Proxy nodes handle public traffic and TLS. Worker nodes just run containers. + + + If it runs in a container, it runs here, on your metal, cloud VMs, or that Raspberry Pi. Scale as you grow. Your data, your rules, no lock-in. + + + Containers come and go, that's the point. But when you need data to stick around, volumes have you covered. + + + +## Storage & Data Safety + + + + Named volumes that survive container restarts. Your data stays put. + + + Automatic and manual backups to S3-compatible storage. Set it and forget it. + + + +## Networking & Routing + + + + All server-to-server traffic is encrypted via WireGuard. Your containers communicate over a private mesh network. + + + Services find each other via `.internal` domains. No hardcoded IPs, no service mesh complexity. Just DNS that works. + + + TLS certificates are handled automatically. Point your domain and get HTTPS without manual certificate management. + + + Not everything speaks HTTP. Expose databases, game servers, or whatever else you need. + + + Route users to the nearest proxy with automatic failover when things go wrong. + + + +## Deployments & Automation + + + + Push to your branch, watch it deploy. Connect your GitHub repo and get automatic builds and deployments on every commit. + + + Push code, we build it. Use Railpack or bring your own Dockerfile. + + + Cron-based deployments let you redeploy on a schedule without lifting a finger. + + + Production, staging, and dev all live in one project. Deploy the same service differently across environments. + + + +## Operations & Security + + + + Your services talk to each other privately. Nothing gets exposed unless you say so. Public traffic only goes through proxy nodes. + + + Inject secrets at runtime. Never bake credentials into your images. + + + Stream logs from containers, builds, and requests in one place. + + diff --git a/docs/logo/logo.png b/docs/logo/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9721280269f26f765bbfbf087f1ee5df75e09d09 GIT binary patch literal 327331 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%0DpPTLnMEx$E2v$ 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 ob#fueCGBS9J_z}+%w{46Dm| Date: Sun, 5 Apr 2026 00:10:03 +1100 Subject: [PATCH 12/16] Update nav --- docs/docs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index a1262d6..4f8cdf2 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -41,8 +41,8 @@ ], "primary": { "type": "button", - "label": "Dashboard", - "href": "https://cloud.techulus.com" + "label": "GitHub", + "href": "https://github.com/techulus/cloud" } }, "contextual": { From 8c6c68dfd807026b0721656c73ea51a34ad918c6 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sun, 5 Apr 2026 09:25:42 +1000 Subject: [PATCH 13/16] Update docs --- README.md | 2 +- docs/agent.mdx | 120 ------------------ docs/agents/architecture.mdx | 72 +++++++++++ docs/agents/introduction.mdx | 50 ++++++++ docs/agents/setup.mdx | 171 ++++++++++++++++++++++++++ docs/agents/updating.mdx | 52 ++++++++ docs/architecture.mdx | 75 ++++------- docs/deployments/compose.mdx | 34 +++++ docs/deployments/github.mdx | 67 ++++++++++ docs/deployments/scheduled.mdx | 25 ++++ docs/docs.json | 53 +++++++- docs/index.mdx | 110 ++++------------- docs/infrastructure/alerts.mdx | 34 +++++ docs/infrastructure/backups.mdx | 36 ++++++ docs/infrastructure/logging.mdx | 42 +++++++ docs/infrastructure/registry.mdx | 46 +++++++ docs/installation.mdx | 142 +++++++++++++++++++++ docs/networking/service-discovery.mdx | 32 +++++ docs/networking/tcp-udp-proxy.mdx | 27 ++++ docs/services/configuration.mdx | 61 +++++++++ docs/services/domains.mdx | 41 ++++++ docs/services/scaling.mdx | 28 +++++ docs/services/volumes.mdx | 47 +++++++ 23 files changed, 1108 insertions(+), 259 deletions(-) delete mode 100644 docs/agent.mdx create mode 100644 docs/agents/architecture.mdx create mode 100644 docs/agents/introduction.mdx create mode 100644 docs/agents/setup.mdx create mode 100644 docs/agents/updating.mdx create mode 100644 docs/deployments/compose.mdx create mode 100644 docs/deployments/github.mdx create mode 100644 docs/deployments/scheduled.mdx create mode 100644 docs/infrastructure/alerts.mdx create mode 100644 docs/infrastructure/backups.mdx create mode 100644 docs/infrastructure/logging.mdx create mode 100644 docs/infrastructure/registry.mdx create mode 100644 docs/installation.mdx create mode 100644 docs/networking/service-discovery.mdx create mode 100644 docs/networking/tcp-udp-proxy.mdx create mode 100644 docs/services/configuration.mdx create mode 100644 docs/services/domains.mdx create mode 100644 docs/services/scaling.mdx create mode 100644 docs/services/volumes.mdx diff --git a/README.md b/README.md index e17f010..3c895a8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A container deployment platform with private-first networking. -> ⚠️ **Experimental**: This is a very experimental project and is nowhere near production ready. Use at your own risk. +> **Beta**: This project is currently in beta and pre-production. Use at your own risk. ## Features diff --git a/docs/agent.mdx b/docs/agent.mdx deleted file mode 100644 index e79a5be..0000000 --- a/docs/agent.mdx +++ /dev/null @@ -1,120 +0,0 @@ ---- -title: "Agent" -description: "Agent modes, reconciliation flow, build pipeline, and work queue behavior." ---- - -# Agent Architecture - -The agent runs on servers and reconciles expected state from the control plane. - -## Node Types - -The agent supports two modes: - -| Type | Flag | Traefik | Description | -| --- | --- | --- | --- | -| Worker | Default | No | Runs containers only | -| Proxy | `--proxy` | Yes | Handles TLS and public traffic | - -## State Machine - -The agent uses a two-state reconciliation model: - -```text -┌─────────┐ ┌────────────┐ -│ IDLE │───drift detected───────▶│ PROCESSING │ -│ (poll) │◀────────────────────────│ (no poll) │ -└─────────┘ done/failed/timeout └────────────┘ -``` - -### IDLE State - -- Polls the control plane every 10 seconds for expected state. -- Compares expected state against actual state. -- Transitions to `PROCESSING` when drift is detected. - -### PROCESSING State - -- Uses a snapshot of expected state without re-polling. -- Applies one change at a time: - 1. Stop orphan containers with no deployment ID. - 2. Start containers in `created` or `exited` state. - 3. Deploy missing containers. - 4. Redeploy containers with the wrong image. - 5. Update DNS records. - 6. Update Traefik routes on proxy nodes. - 7. Update WireGuard peers. -- Times out after 5 minutes. -- Always reports status before returning to `IDLE`. - -## Drift Detection - -Drift detection is deterministic and uses hashes: - -- Containers: missing, orphaned, wrong state, or image mismatch. -- DNS: hash of sorted records. -- Traefik: hash of sorted routes on proxy nodes. -- WireGuard: hash of sorted peers. - -## Container Labels - -The agent tracks managed containers with Podman labels: - -| Label | Description | -| --- | --- | -| `techulus.deployment.id` | Links the container to a deployment | -| `techulus.service.id` | Links the container to a service | -| `techulus.service.name` | Human-readable service name | - -Containers without `techulus.deployment.id` are treated as orphans and cleaned up. - -## Command Line Flags - -| Flag | Default | Description | -| --- | --- | --- | -| `--url` | Required | Control plane URL | -| `--token` | Empty | Registration token, required on first run | -| `--logs-endpoint` | Empty | VictoriaLogs endpoint for log shipping | -| `--proxy` | `false` | Run as a proxy node | - -## Build System - -Agents can build container images directly from GitHub sources: - -1. Poll for pending builds. -2. Claim the build to prevent duplicate work. -3. Clone the repository using a GitHub App installation token. -4. Run Railpack to generate a build plan, or use the existing Dockerfile. -5. Build the image with BuildKit. -6. Push the image to the registry. -7. Update build status. - -Build logs stream to VictoriaLogs in real time. - -## Work Queue - -Agents also process queue items for operations that cannot be modeled purely as expected state: - -| Type | Description | -| --- | --- | -| `restart` | Restart a specific container | -| `stop` | Stop a specific container | -| `force_cleanup` | Force remove containers for a service | -| `cleanup_volumes` | Remove volume directories for a service | -| `deploy` | Handled through expected-state reconciliation | - -## Proxy vs Worker Behavior - -### Proxy Node - -- Runs Traefik for TLS termination. -- Receives Traefik routes from the control plane. -- Handles public traffic and routes requests to containers over WireGuard. -- Collects and ships Traefik access logs. - -### Worker Node - -- Does not run Traefik. -- Receives empty Traefik route sets from the control plane. -- Skips Traefik-related drift detection and reconciliation. -- Keeps a lighter runtime footprint focused on container workloads. diff --git a/docs/agents/architecture.mdx b/docs/agents/architecture.mdx new file mode 100644 index 0000000..fec5c24 --- /dev/null +++ b/docs/agents/architecture.mdx @@ -0,0 +1,72 @@ +--- +title: "Architecture" +description: "State machine, drift detection, build pipeline, and work queue." +--- + +## State Machine + +The agent uses a two-state reconciliation model: + +```mermaid +stateDiagram-v2 + IDLE: IDLE (poll every 10s) + PROCESSING: PROCESSING (no poll) + + IDLE --> PROCESSING: Drift detected + PROCESSING --> IDLE: Done / Failed / Timeout (5 min) +``` + +### IDLE State + +- Polls the control plane every 10 seconds for expected state. +- Compares expected state against actual state. +- Transitions to `PROCESSING` when drift is detected. + +### PROCESSING State + +- Uses a snapshot of expected state without re-polling. +- Applies one change at a time: + 1. Stop orphan containers with no deployment ID. + 2. Start containers in `created` or `exited` state. + 3. Deploy missing containers. + 4. Redeploy containers with the wrong image. + 5. Update DNS records. + 6. Update Traefik routes on proxy nodes. + 7. Update WireGuard peers. +- Times out after 5 minutes. +- Always reports status before returning to `IDLE`. + +## Drift Detection + +Drift detection is deterministic and uses hashes: + +- **Containers**: missing, orphaned, wrong state, or image mismatch. +- **DNS**: hash of sorted records. +- **Traefik**: hash of sorted routes on proxy nodes. +- **WireGuard**: hash of sorted peers. + +## Build System + +Agents can build container images directly from GitHub sources: + +1. Poll for pending builds. +2. Claim the build to prevent duplicate work. +3. Clone the repository using a GitHub App installation token. +4. Run Railpack to generate a build plan, or use the existing Dockerfile. +5. Build the image with BuildKit. +6. Push the image to the registry. +7. Update build status. + +Build logs stream to VictoriaLogs in real time. + +## Work Queue + +Agents also process queue items for operations that cannot be modeled purely as expected state: + +| Type | Description | +| --- | --- | +| `restart` | Restart a specific container | +| `stop` | Stop a specific container | +| `force_cleanup` | Force remove containers for a service | +| `cleanup_volumes` | Remove volume directories for a service | +| `deploy` | Handled through expected-state reconciliation | diff --git a/docs/agents/introduction.mdx b/docs/agents/introduction.mdx new file mode 100644 index 0000000..e571476 --- /dev/null +++ b/docs/agents/introduction.mdx @@ -0,0 +1,50 @@ +--- +title: "Introduction" +description: "What the agent does, node types, and how it communicates with the control plane." +--- + +The agent is a lightweight Go binary that runs on every server in your cluster. It polls the control plane for expected state and reconciles containers, networking, and routing automatically. + +## Node Types + +Each agent runs in one of two modes: + +| Type | Flag | Traefik | Description | +| --- | --- | --- | --- | +| Worker | Default | No | Runs containers only | +| Proxy | `--proxy` | Yes | Handles TLS and public traffic | + +### Proxy Node + +- Runs Traefik for TLS termination. +- Receives Traefik routes from the control plane. +- Handles public traffic and routes requests to containers over WireGuard. +- Collects and ships Traefik access logs. + +### Worker Node + +- Does not run Traefik. +- Receives empty Traefik route sets from the control plane. +- Skips Traefik-related drift detection and reconciliation. +- Keeps a lighter runtime footprint focused on container workloads. + +## Container Labels + +The agent tracks managed containers with Podman labels: + +| Label | Description | +| --- | --- | +| `techulus.deployment.id` | Links the container to a deployment | +| `techulus.service.id` | Links the container to a service | +| `techulus.service.name` | Human-readable service name | + +Containers without `techulus.deployment.id` are treated as orphans and cleaned up. + +## Command Line Flags + +| Flag | Default | Description | +| --- | --- | --- | +| `--url` | Required | Control plane URL | +| `--token` | Empty | Registration token, required on first run | +| `--logs-endpoint` | Empty | VictoriaLogs endpoint for log shipping | +| `--proxy` | `false` | Run as a proxy node | diff --git a/docs/agents/setup.mdx b/docs/agents/setup.mdx new file mode 100644 index 0000000..fc77ac0 --- /dev/null +++ b/docs/agents/setup.mdx @@ -0,0 +1,171 @@ +--- +title: "Setup" +description: "Install the agent and add servers to your cluster." +--- + +## Automated Setup + +The setup script installs all dependencies, registers the server, and configures systemd services. + +### Interactive + +```bash +curl -sSL https://your-control-plane.com/setup.sh | sudo bash +``` + +The script prompts for the control plane URL, registration token, and node type. + +### Non-Interactive + +```bash +export CONTROL_PLANE_URL=https://your-control-plane.com +export REGISTRATION_TOKEN=your-token +export IS_PROXY=false +curl -sSL $CONTROL_PLANE_URL/setup.sh | sudo bash +``` + +## What Gets Installed + +### All Nodes + +- **WireGuard** — encrypted mesh networking +- **Podman** — container runtime +- **BuildKit** — container image builds +- **Railpack** — build plan generation + +### Proxy Nodes Only + +- **Traefik** — reverse proxy and TLS termination +- **CrowdSec** — automated threat detection and IP banning + +The script also enables IP forwarding and configures firewall rules for ports 80, 443, and 51820 (WireGuard). + +## Registration + +Servers register with the control plane using a one-time token. Generate a token from the web UI, then pass it during the first run. + +On registration, the agent: + +1. Generates an Ed25519 signing key pair and a WireGuard key pair. +2. Sends its public keys and IP addresses to the control plane. +3. Receives a server ID, WireGuard subnet, and encryption key. +4. Saves configuration to `/var/lib/techulus-agent/config.json`. + +After registration, the token is invalidated. Subsequent runs do not require a token. + +## Manual Setup + +### Worker Node + +```bash +sudo apt update && sudo apt upgrade -y +sudo apt install wireguard wireguard-tools podman git -y + +# Install Railpack +curl -sSL https://railpack.com/install.sh | sh +sudo ln -s ~/.railpack/bin/railpack /usr/local/bin/railpack + +# Install BuildKit +curl -sSL https://github.com/moby/buildkit/releases/download/v0.26.3/buildkit-v0.26.3.linux-amd64.tar.gz \ + | sudo tar -xz -C /usr/local +``` + +### Proxy Node + +Install everything above, plus Traefik: + +```bash +TRAEFIK_VERSION="v3.2.3" +curl -fsSL "https://github.com/traefik/traefik/releases/download/${TRAEFIK_VERSION}/traefik_${TRAEFIK_VERSION}_linux_amd64.tar.gz" \ + -o /tmp/traefik.tar.gz +sudo tar -xzf /tmp/traefik.tar.gz -C /usr/local/bin traefik +rm /tmp/traefik.tar.gz +``` + +### First Run + +Worker node: + +```bash +sudo ./agent --url --token +``` + +Proxy node: + +```bash +sudo ./agent --url --token --proxy +``` + +## Running as a Service + +### Worker Node + +Create `/etc/systemd/system/techulus-agent.service`: + +```ini +[Unit] +Description=Techulus Cloud Agent +After=network.target buildkitd.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/agent --url +Restart=always +RestartSec=5 +KillMode=process + +[Install] +WantedBy=multi-user.target +``` + +### Proxy Node + +```ini +[Unit] +Description=Techulus Cloud Agent +After=network.target traefik.service buildkitd.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/agent --url --proxy +Restart=always +RestartSec=5 +KillMode=process + +[Install] +WantedBy=multi-user.target +``` + +Enable and start the service: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable techulus-agent +sudo systemctl start techulus-agent +``` + + + `KillMode=process` ensures only the agent process is stopped on restart, not the containers it manages. + + +## Troubleshooting + +### Agent restart kills containers + +Ensure `KillMode=process` is set in the systemd service file. + +### Containers stuck in "created" state + +This is normal after a restart. The agent detects drift and starts them automatically. + +### Checking agent logs + +```bash +sudo journalctl -u techulus-agent -f +``` + +### Inspecting containers + +```bash +podman ps -a --format "table {{.Names}}\t{{.State}}\t{{.Labels}}" +``` diff --git a/docs/agents/updating.mdx b/docs/agents/updating.mdx new file mode 100644 index 0000000..7fcdc68 --- /dev/null +++ b/docs/agents/updating.mdx @@ -0,0 +1,52 @@ +--- +title: "Updating" +description: "Update agents to the latest version." +--- + +## Automated Update + +Run the update script on the server: + +```bash +curl -sSL https://your-control-plane.com/update.sh | sudo bash +``` + +The script: + +1. Downloads the latest agent binary from GitHub releases. +2. Verifies the SHA256 checksum. +3. Replaces the existing binary. +4. Restarts the systemd service. + +No configuration or dependencies are changed during an update. + +## Manual Update + +Download the binary for your architecture: + +```bash +# For x86_64 +curl -fsSL https://github.com/techulus/cloud/releases/latest/download/agent-linux-amd64 \ + -o /usr/local/bin/agent +chmod +x /usr/local/bin/agent + +# For arm64 +curl -fsSL https://github.com/techulus/cloud/releases/latest/download/agent-linux-arm64 \ + -o /usr/local/bin/agent +chmod +x /usr/local/bin/agent +``` + +Restart the service: + +```bash +sudo systemctl restart techulus-agent +``` + +## Verify + +Check that the agent is running after the update: + +```bash +sudo systemctl status techulus-agent +sudo journalctl -u techulus-agent -n 20 +``` diff --git a/docs/architecture.mdx b/docs/architecture.mdx index 03edb9e..0ed9ff5 100644 --- a/docs/architecture.mdx +++ b/docs/architecture.mdx @@ -39,63 +39,36 @@ Techulus Cloud is a stateless container deployment platform built around three c ## Architecture Diagram -```text -┌─────────────────────────────────────────────────────────────────┐ -│ CONTROL PLANE │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Next.js (App Router + API Routes + Postgres) │ │ -│ │ │ │ -│ │ GET /api/v1/agent/expected-state (agent polls) │ │ -│ │ POST /api/v1/agent/status (agent reports) │ │ -│ └──────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ - ▲ - │ HTTPS (poll every 10s) - ▼ -┌─────────────────────────────────────────────────────────────────┐ -│ SERVERS │ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Proxy Node 1 │ │ Worker Node 1 │ │ Worker Node 2 │ │ -│ │ │ │ │ │ │ │ -│ │ WG: 10.100.1.1 │ │ WG: 10.100.2.1 │ │ WG: 10.100.3.1 │ │ -│ │ Containers: │ │ Containers: │ │ Containers: │ │ -│ │ 10.200.1.2-254 │ │ 10.200.2.2-254 │ │ 10.200.3.2-254 │ │ -│ │ │ │ │ │ │ │ -│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ -│ │ │ Agent │ │ │ │ Agent │ │ │ │ Agent │ │ │ -│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ -│ │ │ Podman │ │ │ │ Podman │ │ │ │ Podman │ │ │ -│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ -│ │ │ Traefik │ │ │ │ - │ │ │ │ - │ │ │ -│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ -│ │ │ DNS Server │ │ │ │ DNS Server │ │ │ │ DNS Server │ │ │ -│ │ ├─────────────┤ │ │ ├─────────────┤ │ │ ├─────────────┤ │ │ -│ │ │ WireGuard │ │ │ │ WireGuard │ │ │ │ WireGuard │ │ │ -│ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │ -│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ -│ │ │ │ │ -│ └────────────────────┴────────────────────┘ │ -│ WireGuard Full Mesh │ -└─────────────────────────────────────────────────────────────────┘ - -Public traffic: - Internet -> DNS -> Proxy Node -> Traefik (TLS) -> WireGuard -> Container +```mermaid +graph TD + Internet["Internet"] -->|"DNS"| P1 + + CP["Control Plane
Next.js + API Routes + Postgres"] + CP -- "HTTPS poll every 10s" --> P1 + CP -- "HTTPS poll every 10s" --> W1 + CP -- "HTTPS poll every 10s" --> W2 + + subgraph Servers + P1["Proxy Node
Agent · Podman · Traefik · DNS · WireGuard
WG: 10.100.1.1 · Containers: 10.200.1.2-254"] + W1["Worker Node 1
Agent · Podman · DNS · WireGuard
WG: 10.100.2.1 · Containers: 10.200.2.2-254"] + W2["Worker Node 2
Agent · Podman · DNS · WireGuard
WG: 10.100.3.1 · Containers: 10.200.3.2-254"] + P1 -. "WireGuard Mesh" .- W1 + P1 -. "WireGuard Mesh" .- W2 + W1 -. "WireGuard Mesh" .- W2 + end ``` ## Agent State Machine The agent uses a two-state machine to prevent race conditions during reconciliation. -```text -┌─────────────────────────────────────────────────────────────────┐ -│ │ -│ ┌─────────┐ ┌────────────┐ │ -│ │ IDLE │───drift detected───────▶│ PROCESSING │ │ -│ │ (poll) │◀────────────────────────│ (no poll) │ │ -│ └─────────┘ done/failed/timeout └────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ +```mermaid +stateDiagram-v2 + IDLE: IDLE (poll every 10s) + PROCESSING: PROCESSING (no poll) + + IDLE --> PROCESSING: Drift detected + PROCESSING --> IDLE: Done / Failed / Timeout (5 min) ``` ### IDLE State diff --git a/docs/deployments/compose.mdx b/docs/deployments/compose.mdx new file mode 100644 index 0000000..d3db646 --- /dev/null +++ b/docs/deployments/compose.mdx @@ -0,0 +1,34 @@ +--- +title: "Compose Import" +description: "Import existing Docker Compose files to create services." +--- + +You can import an existing Docker Compose file to create multiple services at once. The compose parser extracts service definitions and maps them to Techulus Cloud services. + +## Supported Fields + +The following Compose fields are parsed and applied: + +| Field | Mapping | +| --- | --- | +| `image` | Container image | +| `environment` | Secrets (encrypted at rest) | +| `volumes` | Named volumes with container paths | +| `ports` | Service ports (public/private, protocol) | +| `healthcheck` | Health check command, interval, timeout, retries, start period | +| `deploy.replicas` | Replica count | +| `deploy.resources.limits` | CPU and memory limits | +| `command` | Start command override | + +## How It Works + +1. Paste or upload your `docker-compose.yml` in the project settings. +2. The parser validates the file and shows a preview of services to be created. +3. Warnings are shown for any unsupported options. +4. On confirmation, services are created with their configuration, secrets, volumes, and ports. + +Each service in the compose file becomes a separate Techulus Cloud service within the same project and environment. + +## Stateful Services + +If a service in the compose file defines volumes, it is automatically marked as stateful. Stateful services are limited to 1 replica and pinned to a single server. diff --git a/docs/deployments/github.mdx b/docs/deployments/github.mdx new file mode 100644 index 0000000..3deb5e7 --- /dev/null +++ b/docs/deployments/github.mdx @@ -0,0 +1,67 @@ +--- +title: "GitHub Integration" +description: "Automatic builds and deployments from GitHub repositories." +--- + +## Setup + +Techulus Cloud integrates with GitHub through a [GitHub App](https://docs.github.com/en/apps). To enable it: + +1. Create a GitHub App and configure it with your control plane URL. +2. Set the following environment variables on the control plane: + +| Variable | Description | +| --- | --- | +| `GITHUB_APP_ID` | Your GitHub App ID | +| `GITHUB_APP_PRIVATE_KEY` | Private key (base64-encoded) | +| `GITHUB_WEBHOOK_SECRET` | Webhook secret for verifying payloads | + +3. Install the GitHub App on your GitHub account or organization. + +## Connecting a Repository + +Once the GitHub App is installed, connect a repository to a service: + +1. Set the service source type to `github`. +2. Select the repository from your installed GitHub accounts. +3. Choose the branch to deploy from (defaults to `main`). +4. Optionally set a root directory if your app isn't at the repository root. + +## Auto-Deploy + +When auto-deploy is enabled (the default), pushing to the configured branch triggers a build and deployment automatically. + +The flow: + +1. GitHub sends a push webhook to the control plane. +2. The control plane creates a build for the new commit. +3. An agent claims the build, clones the repository, and builds the image. +4. On success, a rollout deploys the new image. + +GitHub deployment statuses are updated on the commit so you can track progress from pull requests. + +## Build Process + +Agents build images using one of two methods: + +| Method | When Used | +| --- | --- | +| Railpack | No Dockerfile present — Railpack auto-detects the framework and generates a build plan | +| Dockerfile | A `Dockerfile` exists in the repository (or specified root directory) | + +Images are built with BuildKit, tagged with the commit SHA, and pushed to the [private registry](/infrastructure/registry). + +Build statuses: + +| Status | Description | +| --- | --- | +| `pending` | Waiting for an available agent | +| `claimed` | Agent has taken the build | +| `cloning` | Cloning the repository | +| `building` | Building the container image | +| `pushing` | Pushing the image to the registry | +| `completed` | Build succeeded | +| `failed` | Build failed | +| `cancelled` | Build was cancelled | + +Build logs stream to [Victoria Logs](/infrastructure/logging) in real time and are viewable from the web UI. diff --git a/docs/deployments/scheduled.mdx b/docs/deployments/scheduled.mdx new file mode 100644 index 0000000..c8f462b --- /dev/null +++ b/docs/deployments/scheduled.mdx @@ -0,0 +1,25 @@ +--- +title: "Scheduled Deployments" +description: "Cron-based automatic redeployments." +--- + +You can configure a service to redeploy automatically on a schedule. This is useful for workloads that need periodic restarts or for pulling the latest version of an image tag. + +## Configuration + +Set a deployment schedule using a cron expression in the service settings. + +Examples: + +| Schedule | Cron Expression | +| --- | --- | +| Every hour | `0 * * * *` | +| Daily at midnight UTC | `0 0 * * *` | +| Every 6 hours | `0 */6 * * *` | +| Weekdays at 9 AM UTC | `0 9 * * 1-5` | + +The platform tracks the last scheduled deployment time to prevent duplicate runs. + +## How It Works + +A background job checks for services with deployment schedules and triggers a new rollout when the schedule is due. The rollout follows the same process as a manual deployment — pulling the image, starting new containers, running health checks, and stopping old containers. diff --git a/docs/docs.json b/docs/docs.json index 4f8cdf2..f2e8e89 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1,6 +1,6 @@ { "$schema": "https://mintlify.com/docs.json", - "theme": "sequoia", + "theme": "aspen", "name": "Techulus Cloud", "colors": { "primary": "#DE434A", @@ -17,8 +17,55 @@ "group": "Core", "pages": [ "index", - "architecture", - "agent" + "architecture" + ] + }, + { + "group": "Control Plane", + "pages": [ + "installation" + ] + }, + { + "group": "Agents", + "pages": [ + "agents/introduction", + "agents/architecture", + "agents/setup", + "agents/updating" + ] + }, + { + "group": "Services", + "pages": [ + "services/configuration", + "services/volumes", + "services/scaling", + "services/domains" + ] + }, + { + "group": "Deployments", + "pages": [ + "deployments/github", + "deployments/compose", + "deployments/scheduled" + ] + }, + { + "group": "Networking", + "pages": [ + "networking/service-discovery", + "networking/tcp-udp-proxy" + ] + }, + { + "group": "Infrastructure", + "pages": [ + "infrastructure/logging", + "infrastructure/registry", + "infrastructure/backups", + "infrastructure/alerts" ] } ] diff --git a/docs/index.mdx b/docs/index.mdx index 6b1c130..526b1ef 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,97 +1,39 @@ --- -title: "Techulus Cloud" -description: "Private-first container deployment with WireGuard networking and proxy-worker orchestration." +title: "Introduction" +description: "Techulus Cloud is an open-source, self-hosted container deployment platform with private networking." --- -# Simple, Scalable +Techulus Cloud is an open-source container deployment platform that you host on your own infrastructure. It turns any set of machines — bare metal servers, cloud VMs, or Raspberry Pis — into a private container platform with encrypted networking, automatic HTTPS, and zero-downtime deployments. -# Container Deployment +## How It Works -Run your containers without the headache. Fast, reliable, and you're in control. +Every machine in your cluster runs a lightweight **agent** that pulls its expected state from a central **control plane** and reconciles containers, networking, and routing automatically. There are no master nodes or schedulers to manage — each server operates independently. - - Learn how peers, proxy nodes, worker nodes, and private networking fit together. - - - - See how the agent reconciles state, builds images, and keeps deployments running. - - -## Built for Real Infrastructure +Machines are assigned one of two roles: - - - No master nodes, no single points of failure. Every machine pulls its weight equally. One goes down? The others pick up the slack. - - - Separate concerns with node types. Proxy nodes handle public traffic and TLS. Worker nodes just run containers. - - - If it runs in a container, it runs here, on your metal, cloud VMs, or that Raspberry Pi. Scale as you grow. Your data, your rules, no lock-in. - - - Containers come and go, that's the point. But when you need data to stick around, volumes have you covered. - - +- **Proxy nodes** handle public traffic, terminate TLS, and route requests to containers. +- **Worker nodes** run containers with no public exposure. -## Storage & Data Safety +All communication between nodes happens over an encrypted **WireGuard mesh network**. Services discover each other via `.internal` DNS — no hardcoded IPs or service mesh required. - - - Named volumes that survive container restarts. Your data stays put. - - - Automatic and manual backups to S3-compatible storage. Set it and forget it. - - +## Key Features -## Networking & Routing +- **Private by default** — services communicate over WireGuard. Nothing is exposed publicly unless you configure it. +- **Build from source** — push code and build with Railpack or your own Dockerfile. Or deploy pre-built images. +- **GitHub auto-deploy** — connect a repo and deploy on every push. +- **Automatic HTTPS** — TLS certificates are provisioned and renewed automatically via Let's Encrypt. +- **Persistent volumes** — attach named volumes for stateful workloads with scheduled backups to S3-compatible storage. +- **Service discovery** — containers resolve each other by name using `.internal` domains. +- **Multi-environment** — run production, staging, and dev within the same project. +- **GeoDNS** — route users to the nearest proxy node with automatic failover. +- **TCP/UDP proxy** — expose non-HTTP services like databases or game servers. - - - All server-to-server traffic is encrypted via WireGuard. Your containers communicate over a private mesh network. - - - Services find each other via `.internal` domains. No hardcoded IPs, no service mesh complexity. Just DNS that works. - - - TLS certificates are handled automatically. Point your domain and get HTTPS without manual certificate management. - - - Not everything speaks HTTP. Expose databases, game servers, or whatever else you need. - - - Route users to the nearest proxy with automatic failover when things go wrong. - - +## Next Steps -## Deployments & Automation - - - - Push to your branch, watch it deploy. Connect your GitHub repo and get automatic builds and deployments on every commit. - - - Push code, we build it. Use Railpack or bring your own Dockerfile. - - - Cron-based deployments let you redeploy on a schedule without lifting a finger. - - - Production, staging, and dev all live in one project. Deploy the same service differently across environments. - - - -## Operations & Security + + Learn how the control plane, agents, networking, and rollout lifecycle fit together. + - - - Your services talk to each other privately. Nothing gets exposed unless you say so. Public traffic only goes through proxy nodes. - - - Inject secrets at runtime. Never bake credentials into your images. - - - Stream logs from containers, builds, and requests in one place. - - + + Understand how the agent reconciles state, builds images, and manages containers. + diff --git a/docs/infrastructure/alerts.mdx b/docs/infrastructure/alerts.mdx new file mode 100644 index 0000000..1ba170f --- /dev/null +++ b/docs/infrastructure/alerts.mdx @@ -0,0 +1,34 @@ +--- +title: "Email Alerts" +description: "Get notified about server issues, build failures, and deployment problems." +--- + +Techulus Cloud can send email alerts when things go wrong. Alerts are delivered via SMTP to a configurable list of recipients. + +## Alert Types + +| Alert | Trigger | +| --- | --- | +| Server offline | A server stops reporting to the control plane | +| Build failure | A container image build fails | +| Deployment failure | A deployment fails during rollout | +| Deployment moved | A deployment is moved to a different server | + +## Configuration + +Add the following environment variables to the control plane: + +| Variable | Description | +| --- | --- | +| `SMTP_ENABLED` | Set to `true` to enable alerts | +| `SMTP_HOST` | SMTP server hostname | +| `SMTP_PORT` | SMTP port (e.g., `587`) | +| `SMTP_USERNAME` | SMTP authentication username | +| `SMTP_PASSWORD` | SMTP authentication password | +| `SMTP_ENCRYPTION` | `starttls` or `tls` | +| `SMTP_FROM_NAME` | Sender display name (e.g., `Techulus Cloud`) | +| `SMTP_FROM_ADDRESS` | Sender email address | +| `SMTP_TIMEOUT` | Connection timeout in milliseconds (default: `10000`) | +| `SMTP_ALERT_EMAILS` | Comma-separated list of recipient email addresses | + +Works with any SMTP provider — Amazon SES, Resend, Mailgun, Postmark, or your own mail server. diff --git a/docs/infrastructure/backups.mdx b/docs/infrastructure/backups.mdx new file mode 100644 index 0000000..006a54d --- /dev/null +++ b/docs/infrastructure/backups.mdx @@ -0,0 +1,36 @@ +--- +title: "Backups" +description: "Automated database backups to S3-compatible storage." +--- + +Techulus Cloud can automatically back up databases running in your containers to S3-compatible storage. Backups are triggered on a schedule or manually from the web UI. + +## Supported Databases + +The agent detects the database type from the container image name and runs the appropriate dump command: + +| Database | Dump Command | +| --- | --- | +| PostgreSQL | `pg_dump -Fc` | +| MySQL | `mysqldump` | +| MariaDB | `mysqldump` | +| MongoDB | `mongodump --archive --gzip` | +| Redis | `redis-cli BGSAVE` | + +Backups are compressed and uploaded as `.tar.gz` archives. + +## Configuration + +Configure S3-compatible storage in the control plane environment: + +| Variable | Description | +| --- | --- | +| `BACKUP_STORAGE_PROVIDER` | Storage provider (e.g., `s3`) | +| `BACKUP_STORAGE_BUCKET` | Bucket name | +| `BACKUP_STORAGE_REGION` | AWS region | +| `BACKUP_STORAGE_ENDPOINT` | Custom endpoint for MinIO or other S3-compatible providers | +| `BACKUP_STORAGE_ACCESS_KEY` | Access key | +| `BACKUP_STORAGE_SECRET_KEY` | Secret key | +| `BACKUP_STORAGE_RETENTION_DAYS` | Number of days to retain backups (default: `7`) | + +Works with AWS S3, MinIO, DigitalOcean Spaces, and any S3-compatible provider. diff --git a/docs/infrastructure/logging.mdx b/docs/infrastructure/logging.mdx new file mode 100644 index 0000000..911752f --- /dev/null +++ b/docs/infrastructure/logging.mdx @@ -0,0 +1,42 @@ +--- +title: "Logging" +description: "Centralized log aggregation with Victoria Logs." +--- + +Techulus Cloud uses [Victoria Logs](https://docs.victoriametrics.com/victorialogs/) for centralized log aggregation. All log types — container output, HTTP access logs, build logs, and agent system logs — are collected and searchable from the web UI. + +## Log Types + +| Type | Source | Fields | +| --- | --- | --- | +| `container` | Container stdout/stderr | `deployment_id`, `service_id`, `server_id`, `stream` | +| `http` | Traefik access logs (proxy nodes) | `service_id`, `host`, `method`, `path`, `status`, `duration_ms`, `client_ip` | +| `build` | BuildKit image builds | `build_id`, `service_id`, `project_id` | +| `agent` | Agent process output | `server_id`, `level` | + +## How It Works + +Each agent ships logs directly to Victoria Logs over HTTP using the JSON Lines format. + +- **Container logs** are streamed from running containers, batched in groups of 1000, and flushed every 5 seconds. Log positions are tracked per container to prevent duplicates. +- **HTTP logs** are tailed from Traefik's access log file on proxy nodes, batched in groups of 500. +- **Build logs** are captured during image builds and streamed in real time. +- **Agent logs** intercept the agent's own stdout/stderr with automatic log level detection. + +All log batches retry up to 3 times with exponential backoff on failure. + +## Configuration + +Victoria Logs runs as a Docker container alongside the control plane. + +| Variable | Description | +| --- | --- | +| `VL_USERNAME` | Authentication username | +| `VL_PASSWORD` | Authentication password | +| `VL_RETENTION` | Log retention period (default: `7d`) | + +The control plane exposes logs at `https://logs.` with basic auth. Agents write to the internal endpoint at `http://victoria-logs:9428`. + +## Accessing Logs + +Logs are accessible from the web UI for each service, deployment, build, and server. The control plane queries Victoria Logs using LogSQL with filters for `service_id`, `deployment_id`, `server_id`, and time ranges. diff --git a/docs/infrastructure/registry.mdx b/docs/infrastructure/registry.mdx new file mode 100644 index 0000000..5c11bd5 --- /dev/null +++ b/docs/infrastructure/registry.mdx @@ -0,0 +1,46 @@ +--- +title: "Registry" +description: "Private container image registry." +--- + +Techulus Cloud runs a private [Docker Distribution Registry](https://distribution.github.io/distribution/) for storing container images built from source. + +## How It Works + +When you deploy from a GitHub repository, an agent builds the container image using BuildKit and pushes it to the registry. Other agents pull the image from the registry during deployment. + +Images are tagged with the commit SHA: + +``` +registry.example.com/{project_id}/{service_id}:{commit_sha} +``` + +## Configuration + +The registry runs as a Docker container alongside the control plane, available at `https://registry.`. + +| Variable | Description | +| --- | --- | +| `REGISTRY_USERNAME` | Basic auth username | +| `REGISTRY_PASSWORD` | Basic auth password | +| `REGISTRY_HTTP_SECRET` | Internal HTTP signing secret | + +Agents receive registry credentials automatically during [registration](/agents/setup#registration). + +## Storage + +Images are stored on the local filesystem in a persistent Docker volume (`registry-data`). Delete operations are enabled for garbage collection. + +### Garbage Collection + +To reclaim disk space from deleted images, run garbage collection manually: + +```bash +docker exec registry /bin/registry garbage-collect /etc/docker/registry/config.yml +``` + +For automatic cleanup, add a daily cron job: + +```bash +0 2 * * * docker exec registry /bin/registry garbage-collect /etc/docker/registry/config.yml +``` diff --git a/docs/installation.mdx b/docs/installation.mdx new file mode 100644 index 0000000..a3ee0be --- /dev/null +++ b/docs/installation.mdx @@ -0,0 +1,142 @@ +--- +title: "Installation" +description: "Deploy the Techulus Cloud control plane on your own infrastructure." +--- + +## Prerequisites + +- Docker and Docker Compose +- A domain name with DNS configured +- Ports 80 and 443 available + +You need three DNS records pointing to your server: + +| Record | Purpose | +| --- | --- | +| `your-domain.com` | Control plane web UI | +| `registry.your-domain.com` | Container image registry | +| `logs.your-domain.com` | Log aggregation (Victoria Logs) | + +## Quick Start + +Run the automated install script on a fresh server: + +```bash +curl -fsSL https://raw.githubusercontent.com/techulus/cloud/main/deployment/install.sh | bash +``` + +The script detects your OS, installs Docker, walks you through DNS and environment configuration, and starts all services. + +## Manual Setup + +Clone the repository and configure your environment: + +```bash +cd deployment +cp .env.example .env +``` + +Edit `.env` with your values (see below), then start the stack: + +```bash +docker compose -f compose.production.yml up -d --pull always +``` + +To use the bundled PostgreSQL instead of an external database: + +```bash +docker compose -f compose.postgres.yml up -d --pull always +``` + +## Environment Variables + +### Required + +| Variable | Description | +| --- | --- | +| `ROOT_DOMAIN` | Your domain (e.g., `cloud.example.com`) | +| `ACME_EMAIL` | Email for Let's Encrypt certificates | +| `DATABASE_URL` | PostgreSQL connection string (e.g., `postgres://user:pass@postgres:5432/techulus`) | +| `BETTER_AUTH_SECRET` | Secret key for authentication | +| `ENCRYPTION_KEY` | 32 bytes as a 64-character hex string | + +### Victoria Logs + +| Variable | Description | +| --- | --- | +| `VL_USERNAME` | Logs service username | +| `VL_PASSWORD` | Logs service password | +| `VL_RETENTION` | Log retention period (default: `7d`) | + +### Registry + +| Variable | Description | +| --- | --- | +| `REGISTRY_USERNAME` | Registry username for agents | +| `REGISTRY_PASSWORD` | Registry password for agents | +| `REGISTRY_HTTP_SECRET` | Internal registry secret | + +### Inngest + +| Variable | Description | +| --- | --- | +| `INNGEST_SIGNING_KEY` | Request verification key (prefix with `signkey-prod-`) | +| `INNGEST_EVENT_KEY` | Event API key | + +### GitHub Integration (Optional) + +| Variable | Description | +| --- | --- | +| `GITHUB_APP_ID` | GitHub App ID | +| `GITHUB_APP_PRIVATE_KEY` | GitHub App private key (base64-encoded) | +| `GITHUB_WEBHOOK_SECRET` | Webhook secret | + +## Generating Secrets + +```bash +# Encryption key (64 hex characters) +openssl rand -hex 32 + +# Auth secret +openssl rand -hex 32 + +# Inngest signing key +echo "signkey-prod-$(openssl rand -hex 32)" + +# Inngest event key +openssl rand -hex 16 +``` + +## Services + +Once running, the following services are available: + +| Service | Endpoint | +| --- | --- | +| Web | `https://` | +| Registry | `https://registry.` | +| Logs | `https://logs.` | +| PostgreSQL | Internal only | +| Inngest | Internal only | + +Traefik handles TLS termination and automatic certificate renewal via Let's Encrypt. + +## Database Migrations + +The schema is synced automatically on container startup via `drizzle-kit push`. Non-destructive changes (adding tables, columns, indexes) are applied automatically. Destructive changes like dropping columns require manual intervention. + +## Common Commands + +```bash +# Check service status +docker compose -f compose.production.yml ps + +# View logs +docker compose -f compose.production.yml logs -f + +# Stop all services +docker compose -f compose.production.yml down + +# Update to latest version +docker compose -f compose.production.yml up -d --pull always +``` diff --git a/docs/networking/service-discovery.mdx b/docs/networking/service-discovery.mdx new file mode 100644 index 0000000..7b83f0f --- /dev/null +++ b/docs/networking/service-discovery.mdx @@ -0,0 +1,32 @@ +--- +title: "Service Discovery" +description: "Internal DNS for service-to-service communication." +--- + +Services discover each other using `.internal` domain names. Every agent runs a built-in DNS server that resolves these names to container IP addresses over the [WireGuard mesh](/architecture#wireguard-mesh). + +## How It Works + +Each service gets a hostname like `my-service.internal`. The DNS server on every agent is configured with all service records pushed from the control plane. + +When a container queries `my-service.internal`, the local DNS server resolves it to the container IPs of that service. If the service has multiple replicas, responses use round-robin across all healthy containers. + +All DNS resolution happens over the private WireGuard network — no traffic leaves the mesh. + +## Configuration + +Service discovery works automatically. The DNS server: + +- Listens on the container gateway IP (e.g., `10.200.1.1`). +- Configures `systemd-resolved` to forward `.internal` queries. +- Receives record updates from the control plane as part of expected state. + +No manual configuration is needed. Services can reference each other by name immediately after deployment. + +## Example + +If you have a `postgres` service and a `web` service, the web service can connect to the database using: + +``` +postgres://user:pass@postgres.internal:5432/mydb +``` diff --git a/docs/networking/tcp-udp-proxy.mdx b/docs/networking/tcp-udp-proxy.mdx new file mode 100644 index 0000000..dfcd6ed --- /dev/null +++ b/docs/networking/tcp-udp-proxy.mdx @@ -0,0 +1,27 @@ +--- +title: "TCP/UDP Proxy" +description: "Expose non-HTTP services like databases and game servers." +--- + +Not every service speaks HTTP. Techulus Cloud supports exposing raw TCP and UDP ports through proxy nodes for services like databases, game servers, or custom protocols. + +## Configuration + +When adding a port to a service, set the protocol to `tcp` or `udp`: + +| Field | Description | +| --- | --- | +| Port | The container port your service listens on | +| Protocol | `tcp` or `udp` | +| External port | The port exposed on the proxy node | +| Public | Whether this port is accessible from the internet | + +Traffic is routed from the proxy node's external port through the WireGuard mesh to the container. + +## TLS Passthrough + +For TCP services that handle their own TLS (e.g., a database with native SSL), enable **TLS passthrough**. This forwards the encrypted connection directly to the container without Traefik terminating TLS. + +## Firewall + +The agent setup script configures firewall rules to allow traffic on the TCP/UDP proxy port ranges. If you set up servers manually, ensure the relevant ports are open. diff --git a/docs/services/configuration.mdx b/docs/services/configuration.mdx new file mode 100644 index 0000000..41265cd --- /dev/null +++ b/docs/services/configuration.mdx @@ -0,0 +1,61 @@ +--- +title: "Configuration" +description: "Environment variables, secrets, resource limits, and start commands." +--- + +## Source Type + +Each service is deployed from one of two sources: + +| Source | Description | +| --- | --- | +| `image` | Deploy a pre-built container image | +| `github` | Build from a GitHub repository | + +For GitHub sources, you can specify the branch, root directory, and whether to auto-deploy on push. See [GitHub Integration](/deployments/github) for details. + +## Environment Variables & Secrets + +Secrets are encrypted at rest and injected into containers at runtime. They are never baked into images. + +Each secret has a key and an encrypted value, scoped to a single service. You can add, update, and remove secrets from the service settings in the web UI. + +Secrets are passed as environment variables to the container when it starts. + +## Start Command + +Override the container's default entrypoint by setting a custom start command. This is useful when deploying from pre-built images that need different startup behavior. + +## Resource Limits + +You can set CPU and memory limits per service: + +| Setting | Description | +| --- | --- | +| CPU limit | Maximum CPU cores (e.g., `0.5`, `1`, `2`) | +| Memory limit | Maximum memory in MB (e.g., `256`, `512`, `1024`) | + +When no limits are set, the container uses whatever resources are available on the host. + +## Health Checks + +Health checks verify that a container is ready to receive traffic. When configured, the platform waits for the health check to pass before routing traffic to a new deployment. + +| Setting | Default | Description | +| --- | --- | --- | +| Command | — | Shell command to run inside the container | +| Interval | `10s` | Time between checks | +| Timeout | `5s` | Maximum time for a single check | +| Retries | `3` | Consecutive failures before marking unhealthy | +| Start period | `30s` | Grace period after container start before checks count | + +Health check statuses: + +| Status | Description | +| --- | --- | +| `none` | No health check configured | +| `starting` | Within the start period | +| `healthy` | Check is passing | +| `unhealthy` | Check has failed after retries | + +If no health check command is set, the deployment proceeds immediately after the container starts. diff --git a/docs/services/domains.mdx b/docs/services/domains.mdx new file mode 100644 index 0000000..1ed8ee8 --- /dev/null +++ b/docs/services/domains.mdx @@ -0,0 +1,41 @@ +--- +title: "Custom Domains" +description: "Bind custom domains with automatic HTTPS." +--- + +## Adding a Domain + +Custom domains are bound to a specific service port. When you add a domain: + +1. Point your domain's DNS to a proxy node's IP address (or your GeoDNS hostname). +2. Add the domain in the service port settings. +3. The platform automatically provisions a TLS certificate via Let's Encrypt. + +Traffic flows: `Internet → Proxy Node → Traefik (TLS) → WireGuard → Container`. + +## TLS Certificates + +Certificates are provisioned using the ACME HTTP-01 challenge. The control plane handles the challenge validation — Traefik routes `/.well-known/acme-challenge/*` requests back to the control plane. + +Certificates are: +- Issued automatically when a domain is added. +- Stored in the database and distributed to all proxy nodes. +- Renewed automatically before expiration. + +## Multiple Proxy Nodes + +When using multiple proxy nodes for geographic distribution, all proxies share the same TLS certificates from the control plane. + +Set up a GeoDNS hostname that routes clients to the nearest healthy proxy, then point your custom domains to that hostname. See the [Architecture](/architecture#multiple-proxy-nodes) page for details. + +## Protocols + +Each service port specifies a protocol: + +| Protocol | Description | +| --- | --- | +| `http` | HTTP/HTTPS traffic routed through Traefik | +| `tcp` | Raw TCP traffic — see [TCP/UDP Proxy](/networking/tcp-udp-proxy) | +| `udp` | Raw UDP traffic — see [TCP/UDP Proxy](/networking/tcp-udp-proxy) | + +For HTTP ports, you can optionally bind a custom domain. For TCP/UDP ports, traffic is exposed via an external port on the proxy node. diff --git a/docs/services/scaling.mdx b/docs/services/scaling.mdx new file mode 100644 index 0000000..420b491 --- /dev/null +++ b/docs/services/scaling.mdx @@ -0,0 +1,28 @@ +--- +title: "Scaling" +description: "Replicas, placement, and server pinning." +--- + +## Replicas + +Each service can run multiple replicas across your cluster. Set the replica count from the service settings — the platform distributes containers across available servers. + +Replica count ranges from 1 to 10 per service. + +## Auto-Placement + +By default, **auto-placement** is enabled. The control plane decides which servers run each replica based on available capacity. + +When auto-placement is disabled, you manually configure how many replicas run on each server using per-server replica assignments. + +## Server Pinning + +Stateful services (those with [volumes](/services/volumes)) are automatically pinned to a single server. This ensures the container always has access to its persistent data. + +You can also manually lock any service to a specific server by setting the locked server. This is useful for workloads that need to run on a particular machine. + +## Limitations + +- Stateful services are limited to 1 replica. +- Stateful services cannot use auto-placement — they are always pinned to their locked server. +- Maximum 10 replicas per service. diff --git a/docs/services/volumes.mdx b/docs/services/volumes.mdx new file mode 100644 index 0000000..fec75aa --- /dev/null +++ b/docs/services/volumes.mdx @@ -0,0 +1,47 @@ +--- +title: "Volumes" +description: "Persistent storage for stateful services." +--- + +Volumes provide persistent storage that survives container restarts and redeployments. + +## Adding Volumes + +Each volume has a name and a container path: + +| Field | Description | +| --- | --- | +| Name | Unique identifier for the volume | +| Container path | Where the volume is mounted inside the container (e.g., `/var/lib/postgresql/data`) | + +When you add a volume, the service automatically becomes **stateful**. Stateful services are locked to a single server and limited to 1 replica. When the last volume is removed, the service reverts to stateless. + +## Volume Backups + +Volumes can be backed up to S3-compatible storage on a schedule or on demand. + +| Setting | Description | +| --- | --- | +| Backup enabled | Toggle automatic backups | +| Backup schedule | Cron expression for backup frequency | + +Backups are compressed as `.tar.gz` archives and uploaded to the configured [backup storage](/infrastructure/backups). Each backup tracks its size, checksum, and completion status. + +Backup statuses: + +| Status | Description | +| --- | --- | +| `pending` | Backup queued | +| `uploading` | Uploading to storage | +| `completed` | Successfully stored | +| `failed` | Backup failed | + +## Restoring + +You can restore a volume from any completed backup. The restore process downloads the backup archive from storage and extracts it to the volume path on the target server. + +## Limitations + +- Services with volumes are locked to a single server — they cannot be auto-placed across multiple nodes. +- Replica count is fixed at 1 for stateful services. +- Volume data lives on the host filesystem. If the server is lost, data is only recoverable from backups. From e9e47dcfe615fd8b45dda453f277d54557d35e84 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Wed, 8 Apr 2026 18:13:43 +1000 Subject: [PATCH 14/16] Add link --- cli/src/main.ts | 198 +++++++- cli/src/manifest.ts | 4 + web/app/api/v1/manifest/link-targets/route.ts | 22 + web/app/api/v1/manifest/link/route.ts | 36 ++ web/lib/cli-service.ts | 459 ++++++++++++++++-- 5 files changed, 668 insertions(+), 51 deletions(-) create mode 100644 web/app/api/v1/manifest/link-targets/route.ts create mode 100644 web/app/api/v1/manifest/link/route.ts diff --git a/cli/src/main.ts b/cli/src/main.ts index fe1c878..db36e98 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -2,8 +2,15 @@ import { access, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { constants as fsConstants } from "node:fs"; +import { stdin as input, stdout as output } from "node:process"; +import { createInterface } from "node:readline/promises"; import { deleteConfig, readConfig, writeConfig } from "./config.js"; -import { loadManifest, slugify, type TechulusManifest } from "./manifest.js"; +import { + loadManifest, + slugify, + stringifyManifest, + type TechulusManifest, +} from "./manifest.js"; const CLI_VERSION = "0.1.0"; const CLI_CLIENT_ID = "techulus-cli"; @@ -14,6 +21,28 @@ type JsonRequestOptions = { body?: unknown; }; +type LinkServiceTarget = { + id: string; + name: string; + project: string; + environment: string; + linkSupported: boolean; + unsupportedReason: string | null; +}; + +type LinkEnvironmentTarget = { + id: string; + name: string; + services: LinkServiceTarget[]; +}; + +type LinkProjectTarget = { + id: string; + name: string; + slug: string; + environments: LinkEnvironmentTarget[]; +}; + function normalizeHost(host: string) { const trimmed = host.trim().replace(/\/$/, ""); if (!trimmed.startsWith("http://") && !trimmed.startsWith("https://")) { @@ -71,11 +100,81 @@ function printUsage() { tcloud auth logout tcloud auth whoami tcloud init + tcloud link [--force] tcloud apply tcloud deploy tcloud status`); } +async function pathExists(filePath: string) { + try { + await access(filePath, fsConstants.F_OK); + return true; + } catch { + return false; + } +} + +function countSupportedServices(projects: LinkProjectTarget[]) { + return projects.reduce( + (total, project) => + total + + project.environments.reduce( + (environmentTotal, environment) => + environmentTotal + + environment.services.filter((service) => service.linkSupported).length, + 0, + ), + 0, + ); +} + +async function selectFromList( + title: string, + items: T[], + renderItem: (item: T, index: number) => string, + getDisabledReason?: (item: T) => string | null, +) { + if (items.length === 0) { + throw new Error(`No options available for "${title}"`); + } + + if (!process.stdin.isTTY || !process.stdout.isTTY) { + throw new Error("tcloud link requires an interactive terminal."); + } + + const rl = createInterface({ input, output }); + + try { + while (true) { + console.log(`\n${title}`); + for (const [index, item] of items.entries()) { + console.log(` ${index + 1}. ${renderItem(item, index)}`); + } + + const answer = (await rl.question("> ")).trim(); + const choice = Number.parseInt(answer, 10); + + if (!Number.isInteger(choice) || choice < 1 || choice > items.length) { + console.log("Enter the number of the option you want."); + continue; + } + + const selected = items[choice - 1]; + const disabledReason = getDisabledReason?.(selected) ?? null; + + if (disabledReason) { + console.log(disabledReason); + continue; + } + + return selected; + } + } finally { + rl.close(); + } +} + async function ensureManifest(cwd: string) { try { return await loadManifest(cwd); @@ -270,6 +369,100 @@ service: console.log(`Created ${manifestPath}`); } +async function commandLink(cwd: string, args: string[]) { + const config = await requireConfig(); + const manifestPath = path.join(cwd, "techulus.yml"); + const force = args.includes("--force"); + + if ((await pathExists(manifestPath)) && !force) { + throw new Error( + "techulus.yml already exists. Run `tcloud link --force` to replace it.", + ); + } + + const targets = await requestJson<{ projects: LinkProjectTarget[] }>( + `${config.host}/api/v1/manifest/link-targets`, + { + headers: authHeaders(config.apiKey), + }, + ); + + if (countSupportedServices(targets.projects) === 0) { + throw new Error("No linkable services were found in your account."); + } + + const projectChoices = targets.projects.filter( + (project) => + project.environments.some((environment) => environment.services.length > 0), + ); + if (projectChoices.length === 0) { + throw new Error("No services were found in your account."); + } + + const project = await selectFromList( + "Select a project:", + projectChoices, + (project) => { + const serviceCount = project.environments.reduce( + (total, environment) => total + environment.services.length, + 0, + ); + return `${project.name} (${serviceCount} service${serviceCount === 1 ? "" : "s"})`; + }, + ); + + const environmentChoices = project.environments.filter( + (environment) => environment.services.length > 0, + ); + const environment = await selectFromList( + "Select an environment:", + environmentChoices, + (environment) => { + const supportedCount = environment.services.filter( + (service) => service.linkSupported, + ).length; + return `${environment.name} (${supportedCount}/${environment.services.length} linkable)`; + }, + ); + + const service = await selectFromList( + "Select a service:", + environment.services, + (service) => + service.linkSupported + ? service.name + : `${service.name} (unsupported: ${service.unsupportedReason})`, + (service) => + service.linkSupported + ? null + : service.unsupportedReason ?? "This service can't be linked.", + ); + + const result = await requestJson<{ + manifest: TechulusManifest; + service: { + id: string; + name: string; + project: string; + environment: string; + }; + }>(`${config.host}/api/v1/manifest/link`, { + method: "POST", + headers: authHeaders(config.apiKey), + body: { + serviceId: service.id, + }, + }); + + await writeFile(manifestPath, stringifyManifest(result.manifest), "utf8"); + + console.log( + `Linked ${result.service.project}/${result.service.environment}/${result.service.name}`, + ); + console.log(`Wrote ${manifestPath}`); + console.log("Next: run `tcloud status` or `tcloud apply`."); +} + function printApplyResult(result: { action: "created" | "updated" | "noop"; serviceId: string; @@ -399,6 +592,9 @@ async function main() { case "init": await commandInit(cwd); return; + case "link": + await commandLink(cwd, rest); + return; case "apply": await commandApply(cwd); return; diff --git a/cli/src/manifest.ts b/cli/src/manifest.ts index 2c91a93..89cf0a2 100644 --- a/cli/src/manifest.ts +++ b/cli/src/manifest.ts @@ -81,6 +81,10 @@ export async function loadManifest(cwd: string) { }; } +export function stringifyManifest(manifest: TechulusManifest) { + return YAML.stringify(techulusManifestSchema.parse(manifest)); +} + export function slugify(value: string) { return value .toLowerCase() diff --git a/web/app/api/v1/manifest/link-targets/route.ts b/web/app/api/v1/manifest/link-targets/route.ts new file mode 100644 index 0000000..df4c801 --- /dev/null +++ b/web/app/api/v1/manifest/link-targets/route.ts @@ -0,0 +1,22 @@ +export const dynamic = "force-dynamic"; + +import { requireRequestSession } from "@/lib/api-auth"; +import { listLinkTargets } from "@/lib/cli-service"; + +export async function GET(request: Request) { + const sessionResult = await requireRequestSession(request); + if (!sessionResult.ok) { + return sessionResult.response; + } + + try { + const result = await listLinkTargets(); + return Response.json(result); + } catch (error) { + console.error("Failed to list link targets", error); + return Response.json( + { error: "Failed to list link targets" }, + { status: 500 }, + ); + } +} diff --git a/web/app/api/v1/manifest/link/route.ts b/web/app/api/v1/manifest/link/route.ts new file mode 100644 index 0000000..e89245a --- /dev/null +++ b/web/app/api/v1/manifest/link/route.ts @@ -0,0 +1,36 @@ +export const dynamic = "force-dynamic"; + +import { z } from "zod"; +import { requireRequestSession } from "@/lib/api-auth"; +import { exportManifestForLinkedService } from "@/lib/cli-service"; + +const bodySchema = z.object({ + serviceId: z.string().trim().min(1), +}); + +export async function POST(request: Request) { + const sessionResult = await requireRequestSession(request); + if (!sessionResult.ok) { + return sessionResult.response; + } + + const body = await request.json().catch(() => null); + const parsed = bodySchema.safeParse(body); + + if (!parsed.success) { + return Response.json( + { error: parsed.error.issues[0]?.message || "Invalid request" }, + { status: 400 }, + ); + } + + try { + const result = await exportManifestForLinkedService(parsed.data.serviceId); + return Response.json(result); + } catch (error) { + return Response.json( + { error: error instanceof Error ? error.message : "Failed to link service" }, + { status: 400 }, + ); + } +} diff --git a/web/lib/cli-service.ts b/web/lib/cli-service.ts index eccd9bf..0d4e672 100644 --- a/web/lib/cli-service.ts +++ b/web/lib/cli-service.ts @@ -9,7 +9,10 @@ import { serviceVolumes, services, } from "@/db/schema"; -import type { TechulusManifest } from "@/lib/cli-manifest"; +import { + techulusManifestSchema, + type TechulusManifest, +} from "@/lib/cli-manifest"; import { getManifestEnvironmentName, getManifestProjectSlug, @@ -43,12 +46,96 @@ export type ManifestApplyResult = { changes: ManifestChange[]; }; +export type LinkServiceTarget = { + id: string; + name: string; + project: string; + environment: string; + linkSupported: boolean; + unsupportedReason: string | null; +}; + +export type LinkEnvironmentTarget = { + id: string; + name: string; + services: LinkServiceTarget[]; +}; + +export type LinkProjectTarget = { + id: string; + name: string; + slug: string; + environments: LinkEnvironmentTarget[]; +}; + +export type LinkTargetsResult = { + projects: LinkProjectTarget[]; +}; + +export type LinkManifestResult = { + manifest: TechulusManifest; + service: { + id: string; + name: string; + project: string; + environment: string; + }; +}; + type ManifestIdentity = { project: string; environment: string; service: string; }; +type ServiceCompatibilityRecord = Pick< + typeof services.$inferSelect, + | "sourceType" + | "stateful" + | "autoPlace" + | "replicas" + | "resourceCpuLimit" + | "resourceMemoryLimitMb" +>; + +type PortCompatibilityRecord = Pick< + typeof servicePorts.$inferSelect, + "protocol" | "isPublic" | "domain" +>; + +type LinkValidationService = Pick< + typeof services.$inferSelect, + | "id" + | "name" + | "projectId" + | "environmentId" + | "hostname" + | "image" + | "sourceType" + | "replicas" + | "stateful" + | "autoPlace" + | "healthCheckCmd" + | "healthCheckInterval" + | "healthCheckTimeout" + | "healthCheckRetries" + | "healthCheckStartPeriod" + | "startCommand" + | "resourceCpuLimit" + | "resourceMemoryLimitMb" +>; + +type LinkValidationPort = Pick< + typeof servicePorts.$inferSelect, + "serviceId" | "port" | "isPublic" | "domain" | "protocol" +>; + +type ServiceLinkValidation = { + service: LinkValidationService; + ports: LinkValidationPort[]; + unsupportedReason: string | null; +}; + function formatPort(port: { port: number; isPublic: boolean; domain: string | null }) { return port.isPublic ? `${port.port} -> ${port.domain}` : `${port.port} (internal)`; } @@ -145,6 +232,102 @@ async function findServiceByName( return service ?? null; } +function getUnsupportedReason( + service: ServiceCompatibilityRecord, + ports: PortCompatibilityRecord[], + volumeCount: number, +) { + if (service.sourceType !== "image") { + return "CLI v1 only supports image-backed services. This service uses an unsupported source."; + } + + if (service.stateful || volumeCount > 0) { + return "CLI v1 does not support stateful services or volumes. Manage this service from the web UI."; + } + + if (!service.autoPlace) { + return "CLI v1 only supports auto-placement. Manage this service from the web UI."; + } + + if (ports.some((port) => port.protocol !== "http")) { + return "CLI v1 only supports HTTP ports. This service has TCP or UDP ports configured."; + } + + if (ports.some((port) => port.isPublic && !port.domain)) { + return "CLI v1 requires every public HTTP port to have a domain."; + } + + if (service.replicas < 1 || service.replicas > 10) { + return "CLI v1 only supports replica counts between 1 and 10."; + } + + const hasCpu = service.resourceCpuLimit !== null; + const hasMemory = service.resourceMemoryLimitMb !== null; + + if (hasCpu !== hasMemory) { + return "CLI v1 requires both CPU and memory limits to be set together."; + } + + return null; +} + +async function getServiceLinkValidation( + serviceId: string, +): Promise { + const [service] = await db + .select({ + id: services.id, + name: services.name, + projectId: services.projectId, + environmentId: services.environmentId, + hostname: services.hostname, + image: services.image, + sourceType: services.sourceType, + replicas: services.replicas, + stateful: services.stateful, + autoPlace: services.autoPlace, + healthCheckCmd: services.healthCheckCmd, + healthCheckInterval: services.healthCheckInterval, + healthCheckTimeout: services.healthCheckTimeout, + healthCheckRetries: services.healthCheckRetries, + healthCheckStartPeriod: services.healthCheckStartPeriod, + startCommand: services.startCommand, + resourceCpuLimit: services.resourceCpuLimit, + resourceMemoryLimitMb: services.resourceMemoryLimitMb, + }) + .from(services) + .where(eq(services.id, serviceId)) + .limit(1); + + if (!service) { + return null; + } + + const [ports, volumes] = await Promise.all([ + db + .select({ + serviceId: servicePorts.serviceId, + port: servicePorts.port, + isPublic: servicePorts.isPublic, + domain: servicePorts.domain, + protocol: servicePorts.protocol, + }) + .from(servicePorts) + .where(eq(servicePorts.serviceId, serviceId)) + .orderBy(servicePorts.port), + db + .select({ id: serviceVolumes.id }) + .from(serviceVolumes) + .where(eq(serviceVolumes.serviceId, serviceId)), + ]); + + return { + service, + ports, + unsupportedReason: getUnsupportedReason(service, ports, volumes.length), + }; +} + async function syncHostname( serviceId: string, currentHostname: string | null, @@ -276,7 +459,14 @@ async function syncPorts( async function syncHealthCheck( serviceId: string, - currentService: typeof services.$inferSelect, + currentService: Pick< + LinkValidationService, + | "healthCheckCmd" + | "healthCheckInterval" + | "healthCheckTimeout" + | "healthCheckRetries" + | "healthCheckStartPeriod" + >, manifest: TechulusManifest, changes: ManifestChange[], ) { @@ -330,7 +520,10 @@ async function syncStartCommand( async function syncResources( serviceId: string, - currentService: typeof services.$inferSelect, + currentService: Pick< + LinkValidationService, + "resourceCpuLimit" | "resourceMemoryLimitMb" + >, manifest: TechulusManifest, changes: ManifestChange[], ) { @@ -365,7 +558,7 @@ async function syncResources( async function syncReplicas( serviceId: string, - currentService: typeof services.$inferSelect, + currentService: Pick, desiredReplicas: number, changes: ManifestChange[], ) { @@ -385,57 +578,16 @@ async function syncReplicas( } async function assertSupportedExistingService(serviceId: string) { - const [service] = await db - .select() - .from(services) - .where(eq(services.id, serviceId)) - .limit(1); - - if (!service) { + const validation = await getServiceLinkValidation(serviceId); + if (!validation) { throw new Error("Service not found"); } - if (service.sourceType !== "image") { - throw new Error( - "CLI v1 only supports image-backed services. This service uses an unsupported source.", - ); - } - - if (service.stateful) { - throw new Error( - "CLI v1 does not support stateful services or volumes. Manage this service from the web UI.", - ); - } - - if (!service.autoPlace) { - throw new Error( - "CLI v1 only supports auto-placement. Manage this service from the web UI.", - ); - } - - const ports = await db - .select() - .from(servicePorts) - .where(eq(servicePorts.serviceId, serviceId)); - - if (ports.some((port) => port.protocol !== "http")) { - throw new Error( - "CLI v1 only supports HTTP ports. This service has TCP or UDP ports configured.", - ); - } - - const volumes = await db - .select({ id: serviceVolumes.id }) - .from(serviceVolumes) - .where(eq(serviceVolumes.serviceId, serviceId)); - - if (volumes.length > 0) { - throw new Error( - "CLI v1 does not support services with volumes. Manage this service from the web UI.", - ); + if (validation.unsupportedReason) { + throw new Error(validation.unsupportedReason); } - return service; + return validation.service; } export async function applyManifest( @@ -632,3 +784,210 @@ export async function getManifestStatus(identity: ManifestIdentity) { deployments: serviceDeployments, }; } + +export async function listLinkTargets(): Promise { + const [projectRows, environmentRows, serviceRows, portRows, volumeRows] = + await Promise.all([ + db + .select({ + id: projects.id, + name: projects.name, + slug: projects.slug, + }) + .from(projects) + .orderBy(projects.createdAt), + db + .select({ + id: environments.id, + projectId: environments.projectId, + name: environments.name, + }) + .from(environments) + .orderBy(environments.createdAt), + db + .select({ + id: services.id, + name: services.name, + projectId: services.projectId, + environmentId: services.environmentId, + sourceType: services.sourceType, + stateful: services.stateful, + autoPlace: services.autoPlace, + replicas: services.replicas, + resourceCpuLimit: services.resourceCpuLimit, + resourceMemoryLimitMb: services.resourceMemoryLimitMb, + }) + .from(services) + .orderBy(services.createdAt), + db + .select({ + serviceId: servicePorts.serviceId, + protocol: servicePorts.protocol, + isPublic: servicePorts.isPublic, + domain: servicePorts.domain, + }) + .from(servicePorts), + db.select({ serviceId: serviceVolumes.serviceId }) + .from(serviceVolumes) + .orderBy(serviceVolumes.id), + ]); + + const projectNameById = new Map( + projectRows.map((project) => [project.id, project.name]), + ); + const environmentById = new Map( + environmentRows.map((environment) => [environment.id, environment]), + ); + + const portsByServiceId = new Map(); + for (const port of portRows) { + const current = portsByServiceId.get(port.serviceId) ?? []; + current.push(port); + portsByServiceId.set(port.serviceId, current); + } + + const volumeCountByServiceId = new Map(); + for (const volume of volumeRows) { + volumeCountByServiceId.set( + volume.serviceId, + (volumeCountByServiceId.get(volume.serviceId) ?? 0) + 1, + ); + } + + const servicesByEnvironmentId = new Map(); + for (const service of serviceRows) { + const projectName = projectNameById.get(service.projectId); + const environment = environmentById.get(service.environmentId); + if (!projectName || !environment) { + continue; + } + + const current = servicesByEnvironmentId.get(service.environmentId) ?? []; + const ports = portsByServiceId.get(service.id) ?? []; + const unsupportedReason = getUnsupportedReason( + service, + ports, + volumeCountByServiceId.get(service.id) ?? 0, + ); + + current.push({ + id: service.id, + name: service.name, + project: projectName, + environment: environment.name, + linkSupported: unsupportedReason === null, + unsupportedReason, + }); + servicesByEnvironmentId.set(service.environmentId, current); + } + + const environmentsByProjectId = new Map(); + for (const environment of environmentRows) { + const current = environmentsByProjectId.get(environment.projectId) ?? []; + current.push({ + id: environment.id, + name: environment.name, + services: servicesByEnvironmentId.get(environment.id) ?? [], + }); + environmentsByProjectId.set(environment.projectId, current); + } + + return { + projects: projectRows.map((project) => ({ + id: project.id, + name: project.name, + slug: project.slug, + environments: environmentsByProjectId.get(project.id) ?? [], + })), + }; +} + +export async function exportManifestForLinkedService( + serviceId: string, +): Promise { + const validation = await getServiceLinkValidation(serviceId); + if (!validation) { + throw new Error("Service not found"); + } + + if (validation.unsupportedReason) { + throw new Error(validation.unsupportedReason); + } + + const [project, environment] = await Promise.all([ + db + .select() + .from(projects) + .where(eq(projects.id, validation.service.projectId)) + .limit(1), + db + .select() + .from(environments) + .where(eq(environments.id, validation.service.environmentId)) + .limit(1), + ]); + + const projectRow = project[0]; + const environmentRow = environment[0]; + + if (!projectRow || !environmentRow) { + throw new Error("Failed to resolve the selected service"); + } + + const manifest = techulusManifestSchema.parse({ + apiVersion: "v1", + project: projectRow.name, + environment: environmentRow.name, + service: { + name: validation.service.name, + source: { + type: "image", + image: validation.service.image, + }, + ...(validation.service.hostname + ? { hostname: validation.service.hostname } + : {}), + ports: validation.ports.map((port) => ({ + port: port.port, + public: port.isPublic, + ...(port.isPublic && port.domain ? { domain: port.domain } : {}), + })), + replicas: { + count: validation.service.replicas, + }, + ...(validation.service.healthCheckCmd + ? { + healthCheck: { + cmd: validation.service.healthCheckCmd, + interval: validation.service.healthCheckInterval ?? 10, + timeout: validation.service.healthCheckTimeout ?? 5, + retries: validation.service.healthCheckRetries ?? 3, + startPeriod: validation.service.healthCheckStartPeriod ?? 30, + }, + } + : {}), + ...(validation.service.startCommand + ? { startCommand: validation.service.startCommand } + : {}), + ...(validation.service.resourceCpuLimit !== null && + validation.service.resourceMemoryLimitMb !== null + ? { + resources: { + cpuCores: validation.service.resourceCpuLimit, + memoryMb: validation.service.resourceMemoryLimitMb, + }, + } + : {}), + }, + }); + + return { + manifest, + service: { + id: validation.service.id, + name: validation.service.name, + project: projectRow.name, + environment: environmentRow.name, + }, + }; +} From 7cbcc218f8ba57a804151bd175301b0948de92d1 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Thu, 16 Apr 2026 18:01:18 +1000 Subject: [PATCH 15/16] Update theme and color palette in docs.json Mintlify-Source: dashboard-editor --- docs/docs.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index f2e8e89..d08cb6c 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1,11 +1,11 @@ { "$schema": "https://mintlify.com/docs.json", - "theme": "aspen", + "theme": "mint", "name": "Techulus Cloud", "colors": { - "primary": "#DE434A", - "light": "#F7C8CB", - "dark": "#A61E27" + "primary": "#3B82F6", + "light": "#60A5FA", + "dark": "#2563EB" }, "favicon": "/favicon.svg", "navigation": { From e6016446d7c292b11ec1c5c082f950831dcbe39f Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sat, 18 Apr 2026 17:01:36 +1000 Subject: [PATCH 16/16] Skip overlapping cron runs with singleton locks --- web/lib/inngest/functions/crons.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/web/lib/inngest/functions/crons.ts b/web/lib/inngest/functions/crons.ts index 7fd4189..a3fedb8 100644 --- a/web/lib/inngest/functions/crons.ts +++ b/web/lib/inngest/functions/crons.ts @@ -11,7 +11,7 @@ import { import { inngest } from "../client"; export const staleServerCheck = inngest.createFunction( - { id: "cron-stale-server-check" }, + { id: "cron-stale-server-check", singleton: { mode: "skip" } }, { cron: "*/5 * * * *" }, async ({ step }) => { await step.run("check-stale-servers", async () => { @@ -22,7 +22,7 @@ export const staleServerCheck = inngest.createFunction( ); export const scheduledDeploymentsCheck = inngest.createFunction( - { id: "cron-scheduled-deployments" }, + { id: "cron-scheduled-deployments", singleton: { mode: "skip" } }, { cron: "*/15 * * * *" }, async ({ step }) => { await step.run("check-scheduled-deployments", async () => { @@ -33,7 +33,7 @@ export const scheduledDeploymentsCheck = inngest.createFunction( ); export const certificateRenewal = inngest.createFunction( - { id: "cron-certificate-renewal" }, + { id: "cron-certificate-renewal", singleton: { mode: "skip" } }, { cron: "0 2 * * *" }, async ({ step }) => { await step.run("renew-certificates", async () => { @@ -44,7 +44,7 @@ export const certificateRenewal = inngest.createFunction( ); export const challengeCleanup = inngest.createFunction( - { id: "cron-challenge-cleanup" }, + { id: "cron-challenge-cleanup", singleton: { mode: "skip" } }, { cron: "*/10 * * * *" }, async ({ step }) => { await step.run("cleanup-challenges", async () => { @@ -54,7 +54,7 @@ export const challengeCleanup = inngest.createFunction( ); export const scheduledBackupsCheck = inngest.createFunction( - { id: "cron-scheduled-backups" }, + { id: "cron-scheduled-backups", singleton: { mode: "skip" } }, { cron: "*/15 * * * *" }, async ({ step }) => { await step.run("check-scheduled-backups", async () => { @@ -65,7 +65,7 @@ export const scheduledBackupsCheck = inngest.createFunction( ); export const oldBackupsCleanup = inngest.createFunction( - { id: "cron-old-backups-cleanup" }, + { id: "cron-old-backups-cleanup", singleton: { mode: "skip" } }, { cron: "0 3 * * *" }, async ({ step }) => { await step.run("cleanup-old-backups", async () => { @@ -76,7 +76,7 @@ export const oldBackupsCleanup = inngest.createFunction( ); export const staleItemsCleanup = inngest.createFunction( - { id: "cron-stale-items-cleanup" }, + { id: "cron-stale-items-cleanup", singleton: { mode: "skip" } }, { cron: "*/5 * * * *" }, async ({ step }) => { await step.run("cleanup-stale-items", async () => {