feat: launch official product site and one-click installers

This commit is contained in:
2026-03-06 22:28:20 +08:00
parent ceb3557dde
commit b6f46a07a0
4 changed files with 472 additions and 182 deletions

View File

@@ -1,55 +1,59 @@
# DevOps Skills
面向 **Gitea Issue 驱动交付** 的技能仓库,内置 `gitea-issue-devops-agent`,支持:
Issue-Driven DevOps 平台技能仓库,核心产品是 `gitea-issue-devops-agent`
- 根据 issue 指定分支执行修复与提测
- 分支级预览环境槽位分配与回收
- 按变更范围智能部署(避免无意义重启服务端)
- 自动 / 半自动 / 全人工 三种协作模式
- 图片类 issue 证据抓取与审阅
它把交付流程固化为:
## 文档网页
`Issue -> Branch -> Preview Slot -> Test Loop -> Human-Confirmed Merge`
- 站点文件:`site/index.html`
- 仓库内查看:`https://fun-md.com/Fun_MD/devops-skills/src/branch/main/site/index.html`
- 原始页面`https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/site/index.html`
## 公网产品页
- 产品官网`https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/site/index.html`
- 仓库入口:`https://fun-md.com/Fun_MD/devops-skills`
## 一键安装
Linux:
```bash
curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash
```
macOS:
```bash
curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash
```
Windows (PowerShell):
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.ps1 | iex"
```
安装目标目录:
- `~/.codex/skills/gitea-issue-devops-agent`
## 技能路径
- `skills/gitea-issue-devops-agent/SKILL.md`
## 安装
## 核心能力
```bash
git clone https://fun-md.com/Fun_MD/devops-skills.git
cd devops-skills
mkdir -p ~/.codex/skills
cp -r skills/gitea-issue-devops-agent ~/.codex/skills/
```
Windows PowerShell:
```powershell
git clone https://fun-md.com/Fun_MD/devops-skills.git
cd devops-skills
New-Item -ItemType Directory -Force $HOME\.codex\skills | Out-Null
Copy-Item .\skills\gitea-issue-devops-agent $HOME\.codex\skills\gitea-issue-devops-agent -Recurse -Force
```
- 三种执行模式:`automatic` / `semi-automatic` / `manual`
- issue 图片证据抓取(含 attachments/assets 三路兜底)
- 按变更范围部署(`skip` / `client_only` / `server_only` / `full_stack` / `infra_only`
- 预览槽位池分配与自动回收TTL + 关闭释放)
- 最终代码合并必须人工确认
## 核心脚本
- `skills/gitea-issue-devops-agent/scripts/issue_audit.py`
- issue 拉取、质量评分、去重、附件/图片抓取
- `skills/gitea-issue-devops-agent/scripts/change_scope.py`
- 识别 `skip/client_only/server_only/full_stack/infra_only`
- `skills/gitea-issue-devops-agent/scripts/preview_slot_allocator.py`
- 分支预览槽位分配、复用、释放、TTL 回收
## 工作流模板
仓库提供 `.gitea/workflows` 示例,可直接接入:
## .gitea/workflows 模板
- `.gitea/workflows/issue-branch-preview.yml`
- `.gitea/workflows/preview-slot-reclaim.yml`
用于实现“分配槽位 + 按变更范围部署 + 自动回收”。

38
install/install.ps1 Normal file
View File

@@ -0,0 +1,38 @@
param(
[string]$RepoUrl = "https://fun-md.com/Fun_MD/devops-skills.git",
[string]$CodexHome = "$HOME\.codex"
)
$ErrorActionPreference = "Stop"
$skillName = "gitea-issue-devops-agent"
$targetDir = Join-Path $CodexHome "skills\$skillName"
$tmpRoot = Join-Path $env:TEMP ("devops-skills-" + [Guid]::NewGuid().ToString("N"))
try {
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
throw "[install] git is required but not found."
}
Write-Host "[install] downloading $skillName from $RepoUrl"
git clone --depth 1 $RepoUrl $tmpRoot | Out-Null
$sourceDir = Join-Path $tmpRoot "skills\$skillName"
if (-not (Test-Path $sourceDir)) {
throw "[install] skill directory not found in repository."
}
New-Item -ItemType Directory -Force (Join-Path $CodexHome "skills") | Out-Null
if (Test-Path $targetDir) {
Remove-Item -Recurse -Force $targetDir
}
Copy-Item -Path $sourceDir -Destination $targetDir -Recurse -Force
Write-Host "[install] done"
Write-Host "[install] installed path: $targetDir"
}
finally {
if (Test-Path $tmpRoot) {
Remove-Item -Recurse -Force $tmpRoot
}
}

33
install/install.sh Normal file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_URL="${REPO_URL:-https://fun-md.com/Fun_MD/devops-skills.git}"
SKILL_NAME="gitea-issue-devops-agent"
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
TARGET_DIR="${CODEX_HOME}/skills/${SKILL_NAME}"
TMP_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
if ! command -v git >/dev/null 2>&1; then
echo "[install] git is required but not found."
exit 1
fi
echo "[install] downloading ${SKILL_NAME} from ${REPO_URL}"
git clone --depth 1 "$REPO_URL" "$TMP_DIR/repo" >/dev/null 2>&1
if [ ! -d "$TMP_DIR/repo/skills/${SKILL_NAME}" ]; then
echo "[install] skill directory not found in repository."
exit 1
fi
mkdir -p "${CODEX_HOME}/skills"
rm -rf "$TARGET_DIR"
cp -R "$TMP_DIR/repo/skills/${SKILL_NAME}" "$TARGET_DIR"
echo "[install] done"
echo "[install] installed path: ${TARGET_DIR}"

View File

@@ -3,202 +3,417 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Gitea Issue DevOps Agent</title>
<title>Issue-Driven DevOps Agent | Branch-Scoped Delivery Platform</title>
<style>
:root {
--bg: #f4f7fb;
--panel: #ffffff;
--ink: #0f172a;
--muted: #475569;
--brand: #0ea5e9;
--brand-2: #14b8a6;
--line: #dbe5ef;
--ok: #16a34a;
--bg-0: #070b18;
--bg-1: #101936;
--bg-2: #13274f;
--line: #30406b;
--text: #ecf3ff;
--text-soft: #b9c7e6;
--brand: #36d1ff;
--brand-2: #56f6c8;
--warn: #ffd86b;
--panel: rgba(16, 25, 54, 0.76);
--panel-strong: rgba(10, 17, 38, 0.82);
--ok: #56f6c8;
--danger: #ff7f8f;
--shadow: 0 20px 60px rgba(3, 8, 26, 0.45);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
margin: 0;
font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--ink);
color: var(--text);
font-family: "Space Grotesk", "PingFang SC", "Microsoft YaHei", sans-serif;
background:
radial-gradient(circle at 90% 0%, #d9f6ff 0%, transparent 40%),
radial-gradient(circle at 10% 10%, #e3fff4 0%, transparent 30%),
var(--bg);
line-height: 1.6;
radial-gradient(circle at 0% 0%, rgba(54, 209, 255, 0.22), transparent 30%),
radial-gradient(circle at 100% 10%, rgba(86, 246, 200, 0.18), transparent 28%),
linear-gradient(140deg, var(--bg-0) 0%, var(--bg-1) 42%, var(--bg-2) 100%);
line-height: 1.58;
}
.wrap {
max-width: 1050px;
.page {
max-width: 1140px;
margin: 0 auto;
padding: 32px 20px 64px;
padding: 24px 20px 80px;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
margin-bottom: 20px;
}
.logo {
display: inline-flex;
align-items: center;
gap: 10px;
font-weight: 700;
font-size: 15px;
letter-spacing: 0.2px;
}
.logo-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: linear-gradient(120deg, var(--brand), var(--brand-2));
box-shadow: 0 0 0 5px rgba(86, 246, 200, 0.18);
}
.nav-links {
display: flex;
gap: 14px;
flex-wrap: wrap;
}
.nav a {
color: var(--text-soft);
text-decoration: none;
border: 1px solid transparent;
padding: 8px 12px;
border-radius: 999px;
}
.nav a:hover {
color: var(--text);
border-color: var(--line);
background: rgba(255, 255, 255, 0.05);
}
.hero {
background: linear-gradient(125deg, #0f172a 0%, #0b3f63 45%, #0d7f86 100%);
color: #f8fafc;
border-radius: 20px;
padding: 28px 28px 20px;
box-shadow: 0 22px 55px rgba(2, 20, 38, 0.35);
border: 1px solid var(--line);
background:
linear-gradient(145deg, rgba(86, 246, 200, 0.12), transparent 35%),
linear-gradient(325deg, rgba(54, 209, 255, 0.18), transparent 40%),
var(--panel-strong);
border-radius: 24px;
padding: 34px 28px 28px;
box-shadow: var(--shadow);
position: relative;
overflow: hidden;
}
.hero h1 { margin: 0 0 10px; font-size: clamp(26px, 4vw, 38px); }
.hero p { margin: 0; max-width: 880px; color: #dceafd; }
.chips { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 10px; }
.chip {
background: rgba(255,255,255,0.14);
border: 1px solid rgba(255,255,255,0.24);
border-radius: 999px;
padding: 6px 12px;
font-size: 13px;
.hero::after {
content: "";
position: absolute;
right: -60px;
top: -65px;
width: 180px;
height: 180px;
background: radial-gradient(circle, rgba(86, 246, 200, 0.35), transparent 70%);
pointer-events: none;
}
.hero h1 {
margin: 0 0 10px;
font-size: clamp(30px, 4.4vw, 52px);
line-height: 1.05;
letter-spacing: -0.6px;
max-width: 880px;
}
.hero p {
margin: 0;
font-size: clamp(15px, 2.2vw, 19px);
max-width: 880px;
color: var(--text-soft);
}
.hero-cta {
margin-top: 18px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.btn {
border: 1px solid var(--line);
border-radius: 12px;
padding: 10px 14px;
color: var(--text);
text-decoration: none;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn.primary {
border-color: transparent;
background: linear-gradient(120deg, #2db6ff, #49f2ca);
color: #06253a;
font-weight: 700;
}
.btn:hover { filter: brightness(1.06); }
.stats {
margin-top: 16px;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
}
.stat {
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px;
background: rgba(18, 30, 60, 0.58);
}
.stat .v {
font-size: 24px;
font-weight: 700;
letter-spacing: -0.3px;
}
.stat .k {
color: var(--text-soft);
font-size: 12px;
margin-top: 2px;
}
section {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 16px;
margin-top: 16px;
border: 1px solid var(--line);
border-radius: 18px;
padding: 20px;
background: var(--panel);
box-shadow: var(--shadow);
}
h2 { margin: 0 0 12px; font-size: 22px; }
h3 { margin: 18px 0 8px; font-size: 18px; }
p, li { color: var(--muted); }
ul { margin: 8px 0 0 20px; padding: 0; }
code, pre {
font-family: "Cascadia Code", Consolas, Menlo, monospace;
font-size: 13px;
h2 {
margin: 0 0 12px;
font-size: 26px;
line-height: 1.12;
letter-spacing: -0.25px;
}
pre {
margin: 10px 0 0;
background: #0f172a;
color: #d9ecff;
border-radius: 12px;
padding: 12px;
overflow: auto;
border: 1px solid #263142;
h3 {
margin: 0 0 8px;
font-size: 18px;
line-height: 1.2;
}
.cards {
p { margin: 0; color: var(--text-soft); }
.value-grid, .install-grid, .tool-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
gap: 12px;
}
.value-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.install-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tool-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.card {
border: 1px solid #385082;
background: rgba(20, 34, 68, 0.82);
border-radius: 14px;
padding: 14px;
}
.card p { margin-top: 4px; }
.flow {
display: grid;
gap: 10px;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.step {
border: 1px dashed #4f6796;
border-radius: 12px;
padding: 10px;
background: rgba(20, 34, 68, 0.55);
}
.step strong {
display: inline-block;
margin-bottom: 6px;
color: #dff5ff;
}
.step p { font-size: 13px; }
.install-card code, pre, .tool-card code {
font-family: "Cascadia Code", Consolas, Menlo, monospace;
font-size: 12.8px;
}
pre {
margin: 8px 0 0;
border: 1px solid #415983;
border-radius: 10px;
padding: 10px;
overflow: auto;
background: #081126;
color: #dcf3ff;
white-space: pre-wrap;
word-break: break-all;
}
.copy {
margin-top: 8px;
border: 1px solid #4e6899;
background: rgba(255, 255, 255, 0.03);
color: var(--text);
border-radius: 8px;
padding: 6px 10px;
cursor: pointer;
font-size: 12px;
}
.copy.ok { border-color: #4fcd99; color: var(--ok); }
.list {
margin: 10px 0 0;
padding: 0;
list-style: none;
display: grid;
gap: 8px;
}
.list li {
border: 1px solid #3b4f78;
border-radius: 10px;
padding: 9px 10px;
color: #d2e0f9;
background: rgba(15, 25, 49, 0.7);
}
.tagline {
margin-top: 10px;
color: var(--warn);
font-weight: 600;
}
.footer {
margin-top: 16px;
border: 1px solid var(--line);
border-radius: 14px;
padding: 14px;
background: linear-gradient(180deg, #fbfeff 0%, #f7fbff 100%);
background: rgba(6, 11, 25, 0.66);
display: flex;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
}
.badge {
display: inline-block;
padding: 4px 8px;
border-radius: 999px;
font-size: 12px;
color: #065f46;
background: #d1fae5;
border: 1px solid #a7f3d0;
.footer a {
color: var(--brand);
text-decoration: none;
font-weight: 600;
}
.footer a:hover { text-decoration: underline; }
@media (max-width: 1040px) {
.stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.value-grid, .install-grid, .tool-grid { grid-template-columns: 1fr; }
.flow { grid-template-columns: 1fr; }
}
a { color: #0369a1; text-decoration: none; }
a:hover { text-decoration: underline; }
.ok { color: var(--ok); font-weight: 600; }
</style>
</head>
<body>
<main class="wrap">
<main class="page">
<nav class="nav">
<div class="logo"><span class="logo-dot"></span> Issue-Driven DevOps Agent</div>
<div class="nav-links">
<a href="#install">安装</a>
<a href="#tools">工具</a>
<a href="#workflow">工作流</a>
</div>
</nav>
<header class="hero">
<h1>Gitea Issue DevOps Agent</h1>
<h1> Issue 变成可追踪、可复用、可规模化的交付流水线</h1>
<p>
一个把 <strong>Issue → Branch → Preview Env → 测试闭环</strong> 固化到技能与脚本中的交付方案。
核心目标是提升交付速度,同时避免“每个分支都全量起服务”的资源浪费
我们不是“会修 bug 的脚本”,而是一个面向真实研发组织的 DevOps 交付平台:
<strong>Issue → Branch → Preview Slot → 提测闭环</strong>,并且始终保留工程师对最终合并的控制权
</p>
<div class="chips">
<span class="chip">自动 / 半自动 / 全人工</span>
<span class="chip">Issue 图片证据抓取</span>
<span class="chip">变更范围智能部署</span>
<span class="chip">槽位池自动回收</span>
<div class="hero-cta">
<a class="btn primary" href="https://fun-md.com/Fun_MD/devops-skills" target="_blank" rel="noopener noreferrer">访问仓库</a>
<a class="btn" href="#install">一键安装</a>
<a class="btn" href="../skills/gitea-issue-devops-agent/SKILL.md">查看 Skill 规范</a>
</div>
<div class="stats">
<article class="stat"><div class="v">3</div><div class="k">执行模式(自动/半自动/人工)</div></article>
<article class="stat"><div class="v">5</div><div class="k">部署范围策略skip→full_stack</div></article>
<article class="stat"><div class="v">1:1</div><div class="k">Issue/Branch/Preview Slot 绑定</div></article>
<article class="stat"><div class="v">24h+</div><div class="k">可配置 TTL 自动回收</div></article>
</div>
</header>
<section>
<section id="value">
<h2>核心价值</h2>
<div class="cards">
<div class="value-grid">
<article class="card">
<h3>1. 分支隔离提测</h3>
<p>每个 issue 绑定分支预览槽位,主干环境保持稳定,避免相互覆盖</p>
<h3>分支隔离,主干稳定</h3>
<p>每个 issue 分配独立分支预览槽位,不再发生“提测相互覆盖”,主干环境用于稳定回归</p>
</article>
<article class="card">
<h3>2. 资源按需分配</h3>
<p>根据变更范围判断 <code>client_only/server_only/full_stack</code>,不变更服务端就不重启服务端。</p>
<h3>资源智能节流</h3>
<p>根据代码变更范围自动判定部署策略。仅前端改动时不重启服务端,直接复用共享后端。</p>
</article>
<article class="card">
<h3>3. 可审计闭环</h3>
<p>每次提测都可回溯到 commit、测试结果、环境 URL、验证步骤,且合并始终由工程师人工确认</p>
<h3>证据驱动闭环</h3>
<p>自动沉淀 commit、测试结果、环境链接、验证步骤。Issue 可关闭、可回溯、可审计</p>
</article>
</div>
<p class="tagline">这是一套用于长期演进的研发基础设施,不是临时脚本集合。</p>
</section>
<section id="workflow">
<h2>工作流拓扑</h2>
<div class="flow">
<div class="step"><strong>1. 引导连接</strong><p>输入 repo_url、api_key、mode完成连通性校验。</p></div>
<div class="step"><strong>2. 质量审计</strong><p>拉取 issue 与图片附件,做质量评分与去重分组。</p></div>
<div class="step"><strong>3. 分支执行</strong><p>严格在 issue 指定分支改动,保留变更可追踪性。</p></div>
<div class="step"><strong>4. 按范围部署</strong><p>change_scope 自动判断 skip/client/server/full_stack。</p></div>
<div class="step"><strong>5. 自动回收</strong><p>槽位 TTL + 关闭释放,减少预览环境资源占用。</p></div>
</div>
<ul class="list">
<li>工作流模板:<code>.gitea/workflows/issue-branch-preview.yml</code></li>
<li>回收模板:<code>.gitea/workflows/preview-slot-reclaim.yml</code></li>
<li>合并策略:所有模式下都要求工程师人工确认最终合并</li>
</ul>
</section>
<section id="install">
<h2>一键安装Windows / macOS / Linux</h2>
<p>以下命令会把技能安装到本机 <code>~/.codex/skills/gitea-issue-devops-agent</code></p>
<div class="install-grid">
<article class="card install-card">
<h3>Linux</h3>
<pre id="cmd-linux">curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash</pre>
<button class="copy" data-target="cmd-linux">复制命令</button>
</article>
<article class="card install-card">
<h3>macOS</h3>
<pre id="cmd-macos">curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash</pre>
<button class="copy" data-target="cmd-macos">复制命令</button>
</article>
<article class="card install-card">
<h3>Windows (PowerShell)</h3>
<pre id="cmd-win">powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.ps1 | iex"</pre>
<button class="copy" data-target="cmd-win">复制命令</button>
</article>
</div>
</section>
<section>
<h2>安装指南</h2>
<h3>1) 获取技能仓库</h3>
<pre><code>git clone https://fun-md.com/Fun_MD/devops-skills.git
cd devops-skills</code></pre>
<h3>2) 安装到 Codex skills</h3>
<pre><code># Linux / macOS
mkdir -p ~/.codex/skills
cp -r skills/gitea-issue-devops-agent ~/.codex/skills/
# Windows PowerShell
New-Item -ItemType Directory -Force $HOME\.codex\skills | Out-Null
Copy-Item .\skills\gitea-issue-devops-agent $HOME\.codex\skills\gitea-issue-devops-agent -Recurse -Force</code></pre>
<h3>3) 首次引导参数</h3>
<ul>
<li><code>repo_url</code>(仓库地址)</li>
<li><code>api_key</code>(具备 issue 读写权限)</li>
<li><code>mode</code><code>automatic</code> / <code>semi-automatic</code> / <code>manual</code></li>
<li>可选:<code>reviewers</code><code>test_entry</code><code>deploy_env</code><code>health_endpoint</code><code>min_quality_score</code></li>
</ul>
<section id="tools">
<h2>核心工具</h2>
<div class="tool-grid">
<article class="card tool-card">
<h3>issue_audit.py</h3>
<p>抓取 issue、评论和图片附件完成质量评分、去重与回归候选识别。</p>
<pre>python skills/gitea-issue-devops-agent/scripts/issue_audit.py --base-url https://fun-md.com --repo FunMD/document-collab --token &lt;TOKEN&gt; --state all --download-attachments --output-dir .tmp/issue-audit</pre>
</article>
<article class="card tool-card">
<h3>change_scope.py</h3>
<p>输出部署范围建议,决定是否需要服务端重启。</p>
<pre>python skills/gitea-issue-devops-agent/scripts/change_scope.py --repo-path . --base-ref origin/main --head-ref HEAD</pre>
</article>
<article class="card tool-card">
<h3>preview_slot_allocator.py</h3>
<p>管理 preview 槽位分配、复用、释放、TTL 回收。</p>
<pre>python skills/gitea-issue-devops-agent/scripts/preview_slot_allocator.py --state-file .tmp/preview-slots.json --slots preview-a,preview-b --repo FunMD/document-collab --issue 48 --branch dev --ttl-hours 24 --url-template https://{slot}.qa.example.com --evict-oldest</pre>
</article>
</div>
</section>
<section>
<h2>工具使用说明</h2>
<h3>issue_audit.py拉取 issue + 图片证据)</h3>
<pre><code>python skills/gitea-issue-devops-agent/scripts/issue_audit.py \
--base-url https://fun-md.com \
--repo FunMD/document-collab \
--token &lt;GITEA_TOKEN&gt; \
--state all \
--download-attachments \
--output-dir .tmp/issue-audit</code></pre>
<h3>change_scope.py按改动范围决策部署</h3>
<pre><code>python skills/gitea-issue-devops-agent/scripts/change_scope.py \
--repo-path . \
--base-ref origin/main \
--head-ref HEAD</code></pre>
<h3>preview_slot_allocator.py分配 / 复用 / 释放槽位)</h3>
<pre><code>python skills/gitea-issue-devops-agent/scripts/preview_slot_allocator.py \
--state-file .tmp/preview-slots.json \
--slots preview-a,preview-b \
--repo FunMD/document-collab \
--issue 48 \
--branch dev \
--ttl-hours 24 \
--url-template https://{slot}.qa.example.com \
--evict-oldest</code></pre>
</section>
<section>
<h2>.gitea/workflows 接入</h2>
<p>
本仓库已包含示例工作流:<code>.gitea/workflows/issue-branch-preview.yml</code>
<code>.gitea/workflows/preview-slot-reclaim.yml</code>,用于完成以下自动化链路:
</p>
<ul>
<li>push 到 issue 分支后:自动分配槽位 + 变更范围识别 + 选择性部署</li>
<li>issue 关闭 / 定时任务:自动释放或回收过期槽位</li>
</ul>
<p class="ok">建议先在测试仓库验证工作流变量后再推广到生产仓库。</p>
</section>
<section>
<span class="badge">Skill Path</span>
<p><a href="../skills/gitea-issue-devops-agent/SKILL.md">skills/gitea-issue-devops-agent/SKILL.md</a></p>
</section>
<footer class="footer">
<span>Skill Path: <code>skills/gitea-issue-devops-agent/SKILL.md</code></span>
<span><a href="https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/site/index.html" target="_blank" rel="noopener noreferrer">公网页面链接</a></span>
</footer>
</main>
<script>
const copyButtons = document.querySelectorAll(".copy");
copyButtons.forEach((btn) => {
btn.addEventListener("click", async () => {
const targetId = btn.getAttribute("data-target");
const el = document.getElementById(targetId);
if (!el) return;
try {
await navigator.clipboard.writeText(el.textContent.trim());
btn.classList.add("ok");
btn.textContent = "已复制";
setTimeout(() => {
btn.classList.remove("ok");
btn.textContent = "复制命令";
}, 1400);
} catch (err) {
btn.textContent = "复制失败";
setTimeout(() => {
btn.textContent = "复制命令";
}, 1400);
}
});
});
</script>
</body>
</html>