feat: add gitea-deploy skill, tools, and sdlc foreground service rule
- skills/gitea-deploy/: new skill for Gitea deployment automation - tools/: shared utility scripts - skills/sdlc/SKILL.md: add Foreground Service Rule for long-running processes (background start + readiness polling pattern)
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "gitea-deploy",
|
||||
"description": "Deploys a self-hosted Gitea Git server on a VPS using Docker and pushes the current project to it. Use when you want to self-host git, set up Gitea on a VPS, or push a project to a private Git server.",
|
||||
"prompt": "file://prompts/gitea-deploy.md",
|
||||
"tools": ["execute_bash", "fs_read", "fs_write"],
|
||||
"allowedTools": ["fs_read"],
|
||||
"resources": [
|
||||
"skill://.kiro/skills/gitea-deploy/SKILL.md"
|
||||
],
|
||||
"welcomeMessage": "Ready to deploy Gitea on your VPS. Tell me your VPS host, SSH user, and key path to get started."
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
You are a deployment specialist for self-hosted Gitea Git servers. When the user wants to deploy Gitea or push a project to a VPS, activate the `gitea-deploy` skill and follow its instructions exactly.
|
||||
|
||||
Always collect required inputs before executing any commands: VPS host, SSH user, SSH key path, admin credentials, and repository name. Never guess credentials. Never print passwords in output.
|
||||
|
||||
When the user sends a greeting or help request (e.g., "hi", "hello", "help", "你好", "帮助", "?"), respond with:
|
||||
|
||||
---
|
||||
👋 **Gitea Deploy** — 自动化部署自托管 Git 服务器
|
||||
|
||||
**功能:**
|
||||
- 在 VPS 上通过 Docker 部署 Gitea
|
||||
- 自动安装 Docker(如未安装)
|
||||
- 通过 API 初始化 Gitea 管理员账号和仓库
|
||||
- 将当前项目推送到自托管 Gitea
|
||||
|
||||
**执行步骤:**
|
||||
1. 收集 VPS 信息(host、SSH 用户、密钥路径、管理员账号)
|
||||
2. 验证 SSH 连通性
|
||||
3. 检测并安装 Docker(如需要)
|
||||
4. 部署 Gitea(`docker compose up -d`)
|
||||
5. 等待服务就绪,通过 REST API 初始化
|
||||
6. 创建仓库,配置 git remote,推送代码
|
||||
7. 输出 Web URL 和 SSH clone URL
|
||||
|
||||
**使用示例:**
|
||||
- `Deploy Gitea on my VPS at 123.45.67.89, SSH user root`
|
||||
- `在我的 VPS 上搭建 Gitea,然后把这个项目推上去`
|
||||
- `My VPS is git.example.com, Docker is already installed, just push my project`
|
||||
---
|
||||
@@ -0,0 +1,56 @@
|
||||
# gitea-deploy
|
||||
|
||||
Automate deploying a self-hosted Gitea Git server on a VPS and pushing the current project to it.
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
## Workflow
|
||||
|
||||

|
||||
|
||||
## When to Use
|
||||
|
||||
- You want to self-host a private Git server on your own VPS
|
||||
- You need to push a project to a Gitea instance (new or existing)
|
||||
- You want to migrate away from GitHub/GitLab to a self-hosted solution
|
||||
|
||||
Trigger phrases: "deploy gitea", "self-host git", "setup git server", "push to my vps", "搭建git服务器", "部署gitea"
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Collects VPS connection info and credentials from the user
|
||||
2. Validates local git repo and SSH connectivity
|
||||
3. Installs Docker on the VPS if not present
|
||||
4. Deploys Gitea via `docker compose up -d`
|
||||
5. Polls until Gitea is healthy, then initializes via REST API
|
||||
6. Creates the target repository and pushes all local branches
|
||||
7. Reports the web URL and SSH clone URL
|
||||
|
||||
## Requirements
|
||||
|
||||
- SSH access to a Linux VPS
|
||||
- Local project must be (or become) a git repository
|
||||
- Gitea web port (default `3000`) and SSH port (default `2222`) must be open on the VPS firewall
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
skills/gitea-deploy/
|
||||
├── SKILL.md
|
||||
├── README.md
|
||||
├── assets/
|
||||
│ ├── architecture.puml
|
||||
│ ├── gitea-deploy-architecture.svg
|
||||
│ ├── workflow.puml
|
||||
│ └── gitea-deploy-workflow.svg
|
||||
└── evals/
|
||||
└── evals.json
|
||||
```
|
||||
|
||||
## Evals
|
||||
|
||||
```bash
|
||||
python scripts/run_evals.py gitea-deploy
|
||||
```
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
name: gitea-deploy
|
||||
description: Automates deploying a self-hosted Gitea Git server on a VPS using Docker, then pushes the current project to it. Use when the user wants to self-host a Git server, set up Gitea on a VPS, push a project to a private Git server, or migrate away from GitHub/GitLab. Triggers on phrases like "deploy gitea", "self-host git", "setup git server", "push to my vps", "host my own git", "搭建git服务器", "自托管git", "部署gitea", "推送到vps".
|
||||
metadata:
|
||||
author: common-skills
|
||||
version: "1.0"
|
||||
compatibility:
|
||||
tools:
|
||||
- execute_bash
|
||||
requirements:
|
||||
- SSH access to a VPS (Linux)
|
||||
- Docker available or installable on VPS
|
||||
- git initialized in the local project
|
||||
---
|
||||
|
||||
# Gitea Deploy
|
||||
|
||||
Automate deploying a self-hosted Gitea instance on a VPS and pushing the current project to it.
|
||||
|
||||
## Inputs
|
||||
|
||||
Collect from the user before starting:
|
||||
|
||||
| Field | Example |
|
||||
|---|---|
|
||||
| VPS host | `123.45.67.89` or `git.example.com` |
|
||||
| SSH user | `root` or `ubuntu` |
|
||||
| SSH private key path | `~/.ssh/id_rsa` |
|
||||
| Gitea admin username | `admin` |
|
||||
| Gitea admin password | (user provides, never log it) |
|
||||
| Gitea web port | `3000` (default) |
|
||||
| SSH port for Gitea | `2222` (default) |
|
||||
| Repository name | defaults to current directory name |
|
||||
|
||||
If any field is missing, ask for it. Never guess credentials.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Validate Local Prerequisites
|
||||
|
||||
```bash
|
||||
git rev-parse --is-inside-work-tree # confirm we're in a git repo
|
||||
git status --short # show current state
|
||||
```
|
||||
|
||||
If not a git repo, run `git init && git add -A && git commit -m "chore: initial commit"` after confirming with the user.
|
||||
|
||||
### 2. Test SSH Connectivity
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -o StrictHostKeyChecking=no -o ConnectTimeout=10 <user>@<host> "echo OK"
|
||||
```
|
||||
|
||||
If this fails, stop and report the error. Do not proceed.
|
||||
|
||||
### 3. Check / Install Docker on VPS
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> <user>@<host> "docker --version 2>/dev/null || (curl -fsSL https://get.docker.com | sh)"
|
||||
```
|
||||
|
||||
Report the Docker version found or installed.
|
||||
|
||||
### 4. Deploy Gitea via Docker Compose
|
||||
|
||||
Upload and run a `docker-compose.yml` on the VPS:
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> <user>@<host> "mkdir -p ~/gitea"
|
||||
|
||||
cat <<'EOF' | ssh -i <key_path> <user>@<host> "cat > ~/gitea/docker-compose.yml"
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:latest
|
||||
restart: always
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
ports:
|
||||
- "<web_port>:3000"
|
||||
- "<ssh_port>:22"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
EOF
|
||||
|
||||
ssh -i <key_path> <user>@<host> "cd ~/gitea && docker compose up -d"
|
||||
```
|
||||
|
||||
### 5. Wait for Gitea to Be Ready
|
||||
|
||||
Poll until the health endpoint responds (max 60 seconds):
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> <user>@<host> \
|
||||
"for i in \$(seq 1 12); do curl -sf http://localhost:<web_port>/api/healthz && break || sleep 5; done"
|
||||
```
|
||||
|
||||
If not ready after 60 s, show the container logs:
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> <user>@<host> "docker logs gitea-gitea-1 --tail 30"
|
||||
```
|
||||
|
||||
### 6. Initialize Gitea via API
|
||||
|
||||
Create the admin user and the target repository using the Gitea API:
|
||||
|
||||
```bash
|
||||
# Create admin user (first-time setup)
|
||||
ssh -i <key_path> <user>@<host> \
|
||||
"docker exec gitea-gitea-1 gitea admin user create \
|
||||
--username <admin_user> --password '<admin_pass>' \
|
||||
--email admin@localhost --admin --must-change-password=false"
|
||||
|
||||
# Create repository
|
||||
curl -sf -X POST "http://<host>:<web_port>/api/v1/user/repos" \
|
||||
-u "<admin_user>:<admin_pass>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"<repo_name>\",\"private\":true,\"auto_init\":false}"
|
||||
```
|
||||
|
||||
If the admin user already exists, skip creation (exit code 1 is acceptable).
|
||||
|
||||
### 7. Configure Local Git Remote and Push
|
||||
|
||||
```bash
|
||||
# Add or update remote
|
||||
git remote remove gitea 2>/dev/null || true
|
||||
git remote add gitea "ssh://git@<host>:<ssh_port>/<admin_user>/<repo_name>.git"
|
||||
|
||||
# Push all branches
|
||||
git push -u gitea --all
|
||||
git push gitea --tags
|
||||
```
|
||||
|
||||
If push fails due to host key, add `-o StrictHostKeyChecking=no` to the SSH command used by git:
|
||||
|
||||
```bash
|
||||
GIT_SSH_COMMAND="ssh -i <key_path> -o StrictHostKeyChecking=no" git push -u gitea --all
|
||||
```
|
||||
|
||||
### 8. Verify and Report
|
||||
|
||||
```bash
|
||||
curl -sf "http://<host>:<web_port>/api/v1/repos/<admin_user>/<repo_name>" \
|
||||
-u "<admin_user>:<admin_pass>" | python3 -m json.tool
|
||||
```
|
||||
|
||||
Report to the user:
|
||||
- Gitea web URL: `http://<host>:<web_port>`
|
||||
- Repository URL: `http://<host>:<web_port>/<admin_user>/<repo_name>`
|
||||
- SSH clone URL: `ssh://git@<host>:<ssh_port>/<admin_user>/<repo_name>.git`
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **Docker already installed**: Step 3 detects and skips installation.
|
||||
- **Gitea already running**: Step 4 `docker compose up -d` is idempotent; existing containers are reused.
|
||||
- **Admin user already exists**: Step 6 catches the error and continues.
|
||||
- **Remote `gitea` already exists**: Step 7 removes and re-adds it.
|
||||
- **No commits yet**: Step 1 detects and offers to create an initial commit.
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Never print the admin password in command output. Reference it as `<admin_pass>`.
|
||||
- Use `-o StrictHostKeyChecking=no` only for the initial setup; advise the user to remove it afterward.
|
||||
- Recommend the user change the admin password after first login.
|
||||
- For production use, suggest adding Nginx + Let's Encrypt for HTTPS.
|
||||
@@ -0,0 +1,35 @@
|
||||
@startuml gitea-deploy-architecture
|
||||
skinparam componentStyle rectangle
|
||||
skinparam defaultFontName Arial
|
||||
skinparam backgroundColor #FAFAFA
|
||||
|
||||
package "gitea-deploy Skill" {
|
||||
component "SKILL.md\n(instructions)" as SKILL
|
||||
component "evals/evals.json" as EVALS
|
||||
}
|
||||
|
||||
package "Local Machine" {
|
||||
component "git repo\n(project)" as REPO
|
||||
component "SSH private key" as KEY
|
||||
}
|
||||
|
||||
package "VPS" {
|
||||
component "Docker Engine" as DOCKER
|
||||
component "docker-compose.yml" as COMPOSE
|
||||
database "Gitea Data\n(~/gitea/data)" as DATA
|
||||
component "Gitea Container\n(:3000, :22)" as GITEA
|
||||
}
|
||||
|
||||
actor Developer
|
||||
|
||||
Developer --> SKILL : triggers skill
|
||||
SKILL --> KEY : uses for SSH auth
|
||||
SKILL --> DOCKER : installs if missing
|
||||
SKILL --> COMPOSE : uploads & runs
|
||||
COMPOSE --> GITEA : starts container
|
||||
GITEA --> DATA : persists data
|
||||
SKILL --> GITEA : initializes via REST API
|
||||
SKILL --> REPO : git remote add + push
|
||||
REPO --> GITEA : git push (SSH)
|
||||
SKILL --> Developer : reports URLs
|
||||
@enduml
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,45 @@
|
||||
@startuml gitea-deploy-workflow
|
||||
skinparam defaultFontName Arial
|
||||
skinparam backgroundColor #FAFAFA
|
||||
|
||||
actor Developer
|
||||
participant "gitea-deploy\nSkill" as SKILL
|
||||
participant "Local git" as GIT
|
||||
participant "VPS (SSH)" as VPS
|
||||
participant "Gitea API" as API
|
||||
|
||||
Developer -> SKILL : deploy gitea + push project
|
||||
SKILL -> Developer : collect VPS host, SSH user, key, credentials
|
||||
|
||||
SKILL -> GIT : git rev-parse (check repo)
|
||||
alt not a git repo
|
||||
SKILL -> Developer : offer git init
|
||||
Developer -> GIT : confirm → git init + initial commit
|
||||
end
|
||||
|
||||
SKILL -> VPS : ssh echo OK (connectivity test)
|
||||
alt SSH fails
|
||||
SKILL -> Developer : report error, stop
|
||||
end
|
||||
|
||||
SKILL -> VPS : docker --version
|
||||
alt Docker not installed
|
||||
VPS -> VPS : curl get.docker.com | sh
|
||||
end
|
||||
|
||||
SKILL -> VPS : upload docker-compose.yml
|
||||
SKILL -> VPS : docker compose up -d
|
||||
|
||||
loop poll every 5s (max 60s)
|
||||
SKILL -> VPS : curl /api/healthz
|
||||
end
|
||||
|
||||
SKILL -> VPS : gitea admin user create
|
||||
SKILL -> API : POST /api/v1/user/repos
|
||||
|
||||
SKILL -> GIT : git remote add gitea ssh://...
|
||||
SKILL -> GIT : git push -u gitea --all
|
||||
|
||||
SKILL -> API : GET /api/v1/repos/... (verify)
|
||||
SKILL -> Developer : report web URL + clone URL
|
||||
@enduml
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"skill_name": "gitea-deploy",
|
||||
"evals": [
|
||||
{
|
||||
"id": 1,
|
||||
"prompt": "I want to self-host my git server on my VPS at 192.168.1.100. SSH user is root, key is ~/.ssh/id_rsa. Set up Gitea and push this project to it.",
|
||||
"expected_output": "Collects missing info (admin credentials, repo name), tests SSH, installs Docker if needed, deploys Gitea via docker compose, initializes via API, adds git remote, pushes, and reports the web URL and clone URL."
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"prompt": "Deploy Gitea on my server git.mycompany.com. Docker is already installed. Admin user should be 'devops'.",
|
||||
"expected_output": "Skips Docker installation (detects it's already present), deploys Gitea, creates the devops admin user, creates the repo, pushes the project, and reports success."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"prompt": "我想把这个项目推送到我自己VPS上的Gitea,VPS是 45.77.1.23,SSH用户是ubuntu。",
|
||||
"expected_output": "Asks for missing fields (SSH key path, admin credentials), then executes the full deployment workflow in order, reporting the final Gitea URL in Chinese."
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"prompt": "Set up Gitea on my VPS but the current directory is not a git repo yet.",
|
||||
"expected_output": "Detects no git repo, offers to run git init + initial commit, then proceeds with the full deployment after user confirms."
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"prompt": "I already have Gitea running on port 3000 on my VPS. Just push my project to it.",
|
||||
"expected_output": "Skips Docker/Gitea installation steps, goes directly to creating the repo via API and pushing via git remote."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -220,6 +220,48 @@ Output format — `impl-plan.md`:
|
||||
- Update `specs/STATUS.md` to reflect current progress.
|
||||
- Do not proceed to Phase 6 until all tasks in impl-plan are implemented.
|
||||
|
||||
### Foreground Service Rule
|
||||
|
||||
Some tasks require starting a long-running foreground service (e.g. `uvicorn`, `npm run dev`, `docker run`, `flask run`). These commands block the terminal and will stall the implementation plan if run synchronously.
|
||||
|
||||
**Rule: Always start foreground services in the background and verify readiness before continuing.**
|
||||
|
||||
Pattern:
|
||||
```bash
|
||||
# Start in background, redirect logs
|
||||
<command> > /tmp/<service>.log 2>&1 &
|
||||
SERVICE_PID=$!
|
||||
|
||||
# Wait for readiness (poll the health endpoint or log output)
|
||||
for i in $(seq 1 30); do
|
||||
if <readiness-check>; then
|
||||
echo "<service> is ready"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
```
|
||||
|
||||
Readiness check by service type:
|
||||
|
||||
| Service | Readiness Check |
|
||||
|---|---|
|
||||
| HTTP server (uvicorn, flask, fastapi, express, vite) | `curl -sf http://localhost:<port>` |
|
||||
| Vite dev server | `curl -sf http://localhost:5173` (or configured port) |
|
||||
| Docker container | `docker inspect --format='{{.State.Health.Status}}' <name>` == `healthy`; or `curl` the exposed port |
|
||||
| PostgreSQL | `pg_isready -h localhost -p 5432` |
|
||||
| Redis | `redis-cli ping` |
|
||||
| Generic TCP port | `nc -z localhost <port>` |
|
||||
|
||||
After the readiness check passes, continue with the next implementation step. If the service fails to become ready within 30 seconds, print the last 20 lines of the log file and stop:
|
||||
```bash
|
||||
echo "Service failed to start. Last logs:"
|
||||
tail -20 /tmp/<service>.log
|
||||
exit 1
|
||||
```
|
||||
|
||||
**Never use `&& sleep N` as a substitute for a real readiness check.** Fixed sleeps are fragile — use polling loops instead.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Verification
|
||||
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# kp-get.sh — 安全从 KeePassXC 数据库获取条目属性
|
||||
# 用法: ./kp-get.sh "Entry Title" [Password|UserName|URL]
|
||||
#
|
||||
# 主密码优先级:
|
||||
# 1. KEEPASS_MASTER 环境变量
|
||||
# 2. 系统密钥环 (secret-tool, Linux desktop)
|
||||
# 3. GPG 加密文件 (~/.keepass/.master.gpg)
|
||||
# 4. 交互输入
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DB="${KEEPASS_DB:-$HOME/.keepass/passwords.kdbx}"
|
||||
ENTRY="${1:?用法: $0 \"Entry Title\" [Password|UserName|URL]}"
|
||||
ATTR="${2:-Password}"
|
||||
|
||||
if [[ -z "${KEEPASS_MASTER:-}" ]]; then
|
||||
KEEPASS_MASTER="$(secret-tool lookup service keepassxc account default 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "${KEEPASS_MASTER:-}" && -f "$HOME/.keepass/.master.gpg" ]]; then
|
||||
KEEPASS_MASTER="$(gpg -q -d "$HOME/.keepass/.master.gpg" 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "${KEEPASS_MASTER:-}" && -f "$HOME/.keepass/.master.enc" ]]; then
|
||||
KEEPASS_MASTER="$(openssl enc -d -aes-256-cbc -pbkdf2 -in "$HOME/.keepass/.master.enc" 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "${KEEPASS_MASTER:-}" && -f "$HOME/.keepass/.master" ]]; then
|
||||
KEEPASS_MASTER="$(cat "$HOME/.keepass/.master")"
|
||||
fi
|
||||
|
||||
if [[ -z "${KEEPASS_MASTER:-}" ]]; then
|
||||
read -rsp "Master password: " KEEPASS_MASTER
|
||||
echo
|
||||
fi
|
||||
|
||||
echo "$KEEPASS_MASTER" | keepassxc-cli show -q "$DB" "$ENTRY" -a "$ATTR"
|
||||
Reference in New Issue
Block a user