{"id":50967473,"url":"https://github.com/almightyyantao/it-iai","last_synced_at":"2026-06-18T22:01:21.685Z","repository":{"id":361980088,"uuid":"1241936362","full_name":"almightyYantao/it-iai","owner":"almightyYantao","description":"Internal one-click deploy platform. Tell Claude \"deploy this\" → SSO-protected HTTPS URL in 3 min. Auto-provisions per-project Postgres/Redis/S3.","archived":false,"fork":false,"pushed_at":"2026-06-02T03:25:09.000Z","size":32345,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T05:12:44.247Z","etag":null,"topics":["claude-code","claude-skill","deploy-platform","developer-experience","golang","internal-tools","k3s","kubernetes","oidc","paas","react","self-hosted","typescript"],"latest_commit_sha":null,"homepage":"https://github.com/almightyYantao/it-iai/blob/main/docs/技术架构.md","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/almightyYantao.png","metadata":{"files":{"readme":"README.en.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-18T01:33:23.000Z","updated_at":"2026-06-02T03:25:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/almightyYantao/it-iai","commit_stats":null,"previous_names":["almightyyantao/it-iai"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/almightyYantao/it-iai","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almightyYantao%2Fit-iai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almightyYantao%2Fit-iai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almightyYantao%2Fit-iai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almightyYantao%2Fit-iai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/almightyYantao","download_url":"https://codeload.github.com/almightyYantao/it-iai/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almightyYantao%2Fit-iai/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34508867,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","claude-skill","deploy-platform","developer-experience","golang","internal-tools","k3s","kubernetes","oidc","paas","react","self-hosted","typescript"],"created_at":"2026-06-18T22:01:19.736Z","updated_at":"2026-06-18T22:01:21.671Z","avatar_url":"https://github.com/almightyYantao.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"docs/images/logo.svg\" alt=\"iai\" width=\"120\" /\u003e\n\n# iai · 爱 AI\n\n**Internal one-click deploy platform**\n\nTell Claude \"deploy this\", get an SSO-protected HTTPS URL in 1–3 minutes.\u003cbr/\u003e\nNo Dockerfile. No K8s. No DNS dance.\n\n[简体中文](README.md) · [English](README.en.md)\n\n\u003cbr/\u003e\n\n![Go](https://img.shields.io/badge/Go-1.22+-00ADD8?logo=go\u0026logoColor=white)\n![React](https://img.shields.io/badge/React-18-61DAFB?logo=react\u0026logoColor=white)\n![K3s](https://img.shields.io/badge/K3s-DaemonSet%20Traefik-FFC61C?logo=k3s\u0026logoColor=white)\n![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-336791?logo=postgresql\u0026logoColor=white)\n![Keycloak](https://img.shields.io/badge/Keycloak-OIDC-4D4D4D?logo=keycloak\u0026logoColor=white)\n![License](https://img.shields.io/badge/license-Internal-blue)\n\n\u003cbr/\u003e\n\n\u003cimg src=\"docs/images/banner.svg\" alt=\"iai banner\" width=\"100%\" /\u003e\n\n\u003c/div\u003e\n\n---\n\n## ✨ What it does\n\n```bash\ncd \u003cproject\u003e          # any project directory\nclaude                # in Claude Code, just say\n\u003e deploy this\n                      # 1–3 minutes later:\n                      # 🚀 https://my-app.example.com\n```\n\nTurn internal tools / demos / AI agents from \"runs only on my laptop\" into \"a URL my teammates can open\", with:\n\n- 🔐 **Enterprise SSO** — Keycloak / OIDC integration so outsiders can't reach the app\n- 🌐 **HTTPS + wildcard cert** — TLS built in, auto-redirect 80 → 443\n- 🎯 **IP allow-list** — globally-named presets (admin-curated) + per-project custom\n- 🪪 **Vanity subdomain** — `my-app.example.com` instead of random chars\n- 🗄️ **Auto-provisioned databases** — `postgres = true` in the manifest → platform creates a DB and injects `DATABASE_URL`; users don't have to file tickets\n- 🪶 **SQLite never loses data** — a Litestream sidecar streams `/data/app.db` to S3 in real time; pod restarts / node migrations auto-restore from S3\n- 🔑 **Encrypted env vars** — edit secrets in the admin UI (KEK-encrypted at rest), auto-injected into the pod; nothing in the user's git\n- 🤝 **Collaborators** — invite teammates to co-maintain\n- 📊 **Live logs** — SSE stream, build output line by line\n- 🔁 **Self-healing** — failed pods auto-recover, DB state auto-reconciled with cluster\n\n---\n\n## 📚 Documentation\n\n| Audience | Read this |\n| :--- | :--- |\n| Business users / first-timers | [📖 Usage Guide](docs/使用手册.md) — plain-language, 15 minutes |\n| Engineers / want to understand internals | [🏗️ Architecture](docs/技术架构.md) — components, lifecycle, design decisions |\n| SREs / backup / scaling | [🔧 Ops Runbook](docs/运维手册.md) — backups, externalising PG/MinIO/Registry, multi-platform-node HA |\n| Ops / want to run it yourselves | [ECS Deployment section below](#-deploy-on-ecs) |\n| Developers / want to hack on it | [Local Development section below](#-local-development) |\n\n\u003e The deeper docs (`docs/`) are currently Chinese-only. Translation PRs welcome.\n\n---\n\n## 🚀 Quick install (developer laptop)\n\nCopy and paste the whole line into a terminal:\n\n```bash\nrm -rf ~/iai-skill \u0026\u0026 git clone https://github.com/almightyYantao/it-iai-skill.git ~/iai-skill \u0026\u0026 bash ~/iai-skill/install.sh install\n```\n\nIdempotent — paste the same line to upgrade later.\n\nSee [the Usage Guide](docs/使用手册.md) for detailed walkthroughs.\n\n---\n\n## 📸 Screenshots\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\"\u003e\n\u003cimg src=\"docs/images/screenshot-overview.png\" alt=\"Overview\" /\u003e\n\u003cp align=\"center\"\u003e\u003csub\u003eOverview — cluster health at a glance\u003c/sub\u003e\u003c/p\u003e\n\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\n\u003cimg src=\"docs/images/screenshot-project.png\" alt=\"Project detail\" /\u003e\n\u003cp align=\"center\"\u003e\u003csub\u003eProject detail — pod status, access control, collaborators\u003c/sub\u003e\u003c/p\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\"\u003e\n\u003cimg src=\"docs/images/screenshot-deployment.png\" alt=\"Deployment detail\" /\u003e\n\u003cp align=\"center\"\u003e\u003csub\u003eDeployment — live build logs\u003c/sub\u003e\u003c/p\u003e\n\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\n\u003cimg src=\"docs/images/screenshot-settings.png\" alt=\"Settings\" /\u003e\n\u003cp align=\"center\"\u003e\u003csub\u003eSettings — Keycloak / access-preset hot reload\u003c/sub\u003e\u003c/p\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e🎬 Watch a deploy happen end-to-end (click to expand)\u003c/b\u003e\u003c/summary\u003e\n\n\u003cimg src=\"docs/images/skill-push.gif\" alt=\"Skill push demo\" width=\"100%\" /\u003e\n\n\u003c/details\u003e\n\n---\n\n## 🏗️ Architecture\n\n\u003cimg src=\"docs/images/architecture.svg\" alt=\"Architecture diagram\" width=\"100%\" /\u003e\n\n```\n Developer laptop      Platform node                       Worker nodes\n\n ┌──────────┐        ┌──────────┐  ┌──────────┐\n │ Claude   │ ─API─▶ │ control- │──┤   PG     │           ┌────────────┐\n │ Code +   │        │ plane    │  │  MinIO   │           │ K3s agent  │\n │ Skill    │        ├──────────┤  │ Registry │           │            │\n └──────────┘        │ build-   │  │  Redis   │           │ user pods  │\n                     │ service  │  │ user-PG  │ ← auto-   │ (proj-xxx) │\n                     └──────────┘  └──────────┘  prov'd   └────────────┘\n                          │                                       ▲\n                          │       K3s server + Traefik            │\n                          │       (DaemonSet, hostNetwork)        │\n                          └──────────►─────────────────────────────┘\n                                          ↑\n                                          │  HTTPS 443\n                          ┌───────────────┴────────────────┐\n                          │ *.example.com    user apps │\n                          │ admin.example.com  admin   │\n                          │ auth.example.com   SSO     │\n                          └────────────────────────────────┘\n```\n\nFull lifecycle, inter-component protocols, and \"why this and not that\" decisions live in [Architecture](docs/技术架构.md).\n\n---\n\n## 🗄️ Data isolation \u0026 auto-provisioning\n\nDeclare `postgres = true` / `redis = true` / `s3 = true` in the manifest and the platform creates the backing resource on first deploy, encrypts the credentials, and injects them into the pod. Business users don't file tickets, don't pick passwords — and **every project gets a real isolated slice**, not shared creds with a \"be nice\" naming convention.\n\n\u003cimg src=\"docs/images/data-isolation.svg\" alt=\"Per-project data isolation\" width=\"100%\" /\u003e\n\n| Service | Shared backbone | Per-project | Isolation |\n| :--- | :--- | :--- | :--- |\n| **PostgreSQL** | `user-postgres` container (separate from the control-plane DB) | Own database `proj_\u003cslug\u003e` + own role + random password | SQL: `GRANT` to own DB only — can't even `\\c` siblings |\n| **Redis** | Shared `redis` container (Redis 6 ACL) | ACL user `proj-\u003cslug\u003e` + key pattern `~proj-\u003cslug\u003e:*` + `-@dangerous` | ACL: writing a non-prefixed key returns `NOPERM` |\n| **MinIO / S3** | Shared `minio` container (reuses platform storage) | Own bucket `proj-\u003cslug\u003e` + own IAM user + bucket-only policy | IAM: policy scoped — listing siblings returns 403 |\n| **SQLite** | Shared MinIO as the Litestream replication target (no separate backbone) | emptyDir `/data` in the pod + Litestream sidecar + init container that restores from S3 | Each project's WAL lives in its own bucket; inherits the S3 IAM isolation |\n\nEnv vars injected into the pod:\n\n```bash\nDATABASE_URL             # postgres://proj_\u003cslug\u003e:****@\u003chost\u003e:5433/proj_\u003cslug\u003e\nREDIS_URL                # redis://proj-\u003cslug\u003e:****@\u003chost\u003e:6379/0\nREDIS_KEY_PREFIX         # proj-\u003cslug\u003e:\nS3_ENDPOINT              # \u003chost\u003e:9000\nS3_REGION                # us-east-1\nS3_ACCESS_KEY_ID         # proj-\u003cslug\u003e\nS3_SECRET_ACCESS_KEY     # ****\nS3_BUCKET                # proj-\u003cslug\u003e\nS3_USE_SSL               # false\nSQLITE_PATH              # /data/app.db    (only when needs.sqlite=true)\n```\n\nValues are KEK-encrypted at rest in the platform DB, decrypted at deploy time, and dropped into a K8s Secret — the pod just reads `os.environ[\"DATABASE_URL\"]`.\n\nOn project deletion: PG database, Redis ACL user, and S3 user/policy are **dropped automatically**. The S3 bucket is **preserved** (so a misclick can't wipe years of objects) — admin confirms then `mc rb --force` manually.\n\n---\n\n## 🛠️ Deploy on ECS\n\nThree steps: install the platform node → install workers → wire TLS + SSO. Every script is idempotent — safe to re-run.\n\n### 1. Platform node\n\n```bash\ngit clone --recursive https://github.com/almightyYantao/it-iai.git /opt/it-iai\n# Forgot --recursive? Run:  git submodule update --init\ncd /opt/it-iai\n\nsudo BASE_DOMAIN=example.com \\\n     deploy/install-platform.sh\n```\n\nInstalls Docker + K3s server + the docker-compose stack (PG / MinIO / Registry / Redis / control-plane / build-service / web nginx) and prints both the **bootstrap Deploy Token** and the **worker join command**.\n\n### 2. Worker nodes (one-off per machine)\n\nUse the join command from the platform output:\n\n```bash\nsudo K3S_URL=https://\u003cplatform-ip\u003e:6443 \\\n     K3S_TOKEN=\u003ctoken\u003e                  \\\n     PLATFORM_IP=\u003cplatform-ip\u003e          \\\n     REGISTRY_PULL_HOST=\u003cplatform-ip\u003e:5001 \\\n     deploy/install-worker.sh\n```\n\nHealth checks:\n\n```bash\nsudo /opt/it-iai/deploy/check-k3s.sh    # on platform\nsudo bash deploy/check-agent.sh         # on each worker\n```\n\n### 3. SSO + TLS\n\nFill in Keycloak OIDC config on the Web Settings page → Save (hot-reloaded, no restart). Then pick **one** TLS mode:\n\n**A. Wildcard cert (DNS-01 / bring-your-own)** — one cert covers every app subdomain.\n\n```bash\n# Drop wildcard cert into /opt/it-iai/tls/ as *.crt + *.key\nsudo /opt/it-iai/deploy/install-tls.sh\n```\n\n**B. Per-project on-demand (HTTP-01)** — install cert-manager once, project owners\nflip \"Enable HTTPS\" in Project Settings, each app gets its own Let's Encrypt cert\nthat renews automatically. No DNS API required.\n\n```bash\n# First-time install of cert-manager + letsencrypt-prod / letsencrypt-staging issuers\nsudo ACME_EMAIL=you@example.com /opt/it-iai/deploy/install-cert-manager.sh\n```\n\n\u003e ⚠️ HTTP-01 needs :80 reachable from the public internet and every app hostname\n\u003e resolving to the platform IP. Let's Encrypt does not support wildcards over\n\u003e HTTP-01 — use mode A or a DNS-01 setup if you need `*.\u003cdomain\u003e` certs.\n\nThen in either mode:\n\n```bash\n# Install oauth2-proxy + Traefik ForwardAuth middlewares\nsudo /opt/it-iai/deploy/install-oauth2-proxy.sh\n\n# Expose admin UI on admin.\u003cbase-domain\u003e\nsudo /opt/it-iai/deploy/install-admin-ui-tls.sh\n```\n\nDNS: point `*.\u003cBASE_DOMAIN\u003e`, `auth.\u003cBASE_DOMAIN\u003e`, and `admin.\u003cBASE_DOMAIN\u003e` to the platform node IP.\n\n### Upgrade\n\n```bash\ncd /opt/it-iai\nsudo git pull\n\n# --build rebuilds every service with a build context\n# (control-plane / build-service / web). Building only control-plane\n# would miss build-service / frontend updates.\nsudo docker compose up -d --build\n```\n\nControl-plane runs new migrations on boot. Pods on workers are unaffected.\n\n### Network ports\n\n\u003cdetails\u003e\n\u003csummary\u003eExpand\u003c/summary\u003e\n\nPlatform ↔ workers (**private subnet**):\n\n| Port  | Proto | Why |\n| :--- | :--- | :--- |\n| 6443  | tcp | K3s API |\n| 10250 | tcp | kubelet |\n| 8472  | udp | flannel VXLAN |\n| 5001  | tcp | image registry (workers pull) |\n\nExternal (**platform node only**, but Traefik runs on every node):\n\n| Port | Proto | Why |\n| :--- | :--- | :--- |\n| 80  | tcp | Traefik HTTP (auto 302 → HTTPS) |\n| 443 | tcp | Traefik HTTPS (wildcard cert) |\n\n\u003c/details\u003e\n\n### Uninstall\n\n```bash\nsudo deploy/uninstall.sh\n```\n\n---\n\n## 🔧 Backup \u0026 scaling\n\nThe second-most important thing to do once it's running. Three steps, sorted by value-per-effort:\n\n### 1. Backup (**do this**)\n\nBare minimum: daily `pg_dump` + `.env` + `tls/` copied off-host. One cron script, full version in [Ops Runbook §1](docs/运维手册.md#1-%E5%A4%87%E4%BB%BD).\n\n⚠️ The `CP_KEK_BASE64` in `.env` is the root key for every encrypted field in the DB (tokens, secrets). **Lose it and every Deploy Token is unrecoverable** — store a copy somewhere besides the same disk (password manager, encrypted vault).\n\n### 2. Externalize state (**strongly recommended**)\n\nBefore the platform node's disk fills up or you want HA, move the three stateful components out:\n\n| Component | Move to | Effort |\n|---|---|---|\n| Postgres | Aliyun RDS PostgreSQL, or a dedicated ECS | Edit `CP_DATABASE_URL` in `.env` |\n| MinIO | Aliyun OSS (S3-compatible) | Edit the `CP_S3_*` block |\n| Registry | Aliyun ACR | Edit `CP_REGISTRY_HOST*` + each worker's `registries.yaml` |\n\nComponents already talk to each other over the network — externalising is an env edit, **not a code change**. Step-by-step migration + verification checklists: [Ops Runbook §2-§4](docs/运维手册.md#2-pg-%E5%A4%96%E7%BD%AE%E6%90%AC%E5%88%B0-rds--%E7%8B%AC%E7%AB%8B-ecs).\n\nAfter this the platform node becomes **stateless** — if its disk dies, install a fresh ECS, `git pull`, `docker compose up -d --build`, drop `.env` + `tls/` back in, fully recovered.\n\n### 3. Multi-platform node + HA (optional)\n\nOnly after externalising state. Two or three platform nodes behind an SLB, K3s server upgraded to a 3-node etcd cluster. See [Ops Runbook §5](docs/运维手册.md#5-%E5%A4%9A%E5%B9%B3%E5%8F%B0%E8%8A%82%E7%82%B9--ha).\n\n\u003e A single platform node holds up to ~20-person / ~50-project teams. Get §1 §2 solid before reaching for HA.\n\n---\n\n## 💻 Local development\n\nFor hacking on the Go / TypeScript code. Starts a k3d cluster + the docker-compose stack on your machine:\n\n```bash\nmake dev\n```\n\nAfter it finishes:\n\n| URL | What |\n| :--- | :--- |\n| `http://localhost:5173` | Admin UI |\n| `http://localhost:8080` | Control Plane REST API |\n| `http://localhost:9001` | MinIO console |\n| `http://localhost:5001` | Local image registry |\n\nPush a sample app:\n\n```bash\nexport VIBEDEPLOY_TOKEN=\u003ctoken\u003e\nexport VIBEDEPLOY_API=http://localhost:8080\n\ncd examples/hello-node\nbash ../../it-iai-skill/scripts/push.sh   # or wherever you cloned it-iai-skill\n```\n\nClean up: `make destroy`\n\n---\n\n## 📂 Repo layout\n\n```\n.\n├── cmd/control-plane/       Go server entry\n├── cmd/build-service/       Build worker\n├── internal/\n│   ├── api/                 HTTP handlers + middleware\n│   ├── auth/                JWT + KEK + Deploy Token\n│   ├── config/              env + runtime config (hot reload)\n│   ├── k8sdriver/           client-go wrapper + Traefik Middleware CR\n│   ├── model/               domain types\n│   └── store/               pgxpool + per-table CRUD\n├── migrations/              0001-0005 sequential SQL\n├── deploy/                  ECS multi-node installers + audit scripts\n├── skill/                   Claude Code Skill\n├── web/                     Vite + React + Tailwind admin UI\n├── docs/\n│   ├── 技术架构.md          architecture (engineer-facing)\n│   ├── 使用手册.md          usage guide (business-facing)\n│   └── images/              README assets (drop screenshots here)\n├── examples/                end-to-end smoke samples\n└── docker-compose.yml       platform-node service stack\n```\n\n---\n\n## 🤝 Contributing\n\nCommit message style: `feat(scope): ...` / `fix(scope): ...` / `docs: ...`. See `git log --oneline` for examples.\n\nBefore pushing: `go build ./...` and `cd web \u0026\u0026 npx tsc --noEmit` should both pass.\n\n---\n\n## ⭐ Star History\n\n\u003ca href=\"https://star-history.com/#almightyYantao/it-iai\u0026Date\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=almightyYantao/it-iai\u0026type=Date\u0026theme=dark\" /\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=almightyYantao/it-iai\u0026type=Date\" /\u003e\n    \u003cimg alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=almightyYantao/it-iai\u0026type=Date\" /\u003e\n  \u003c/picture\u003e\n\u003c/a\u003e\n\nBefore pushing: `go build ./...` and `cd web \u0026\u0026 npx tsc --noEmit` should both pass.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmightyyantao%2Fit-iai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falmightyyantao%2Fit-iai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmightyyantao%2Fit-iai/lists"}