Compare commits
2 Commits
9e9c6be5ce
...
460088fa43
| Author | SHA1 | Date | |
|---|---|---|---|
| 460088fa43 | |||
| ccee9131a9 |
@ -3,7 +3,9 @@ name: deploy-to-linux
|
||||
description: |
|
||||
将 yggdrasil 部署/更新到任意 Linux 服务器时使用。本地 arm64 用 Docker Rosetta 构建
|
||||
x86 镜像,导出传输到目标服务器(Docker 或 Podman)后用 docker compose 运行,
|
||||
前置反代自动签发 HTTPS。触发关键词:"部署"、"deploy"、"发布到服务器"、"上线"、"docker compose"。
|
||||
前置反代自动签发 HTTPS。支持自动部署(agent 直接执行)与手动部署(agent 探测后输出
|
||||
完整命令清单,用户自行执行)两种模式。
|
||||
触发关键词:"部署"、"deploy"、"发布到服务器"、"上线"、"docker compose"、"手动部署"。
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@ -19,6 +21,23 @@ metadata:
|
||||
|
||||
从本地 arm64 构建器构建 `linux/amd64` 镜像,传输到目标 Linux 服务器并以 docker compose 运行。前置反代负责 HTTPS。
|
||||
|
||||
## 部署模式(先确认用哪种)
|
||||
|
||||
| 模式 | 触发 | 行为 |
|
||||
|---|---|---|
|
||||
| **自动部署**(默认) | "部署到 X"、"更新线上" | agent 逐步执行下文全部 ssh/本地命令,直接完成部署 |
|
||||
| **手动部署** | "手动部署"、"给我部署命令"、"我自己来执行" | agent 只做**第 0 步探测**(ssh 只读信息采集),然后根据探测结果把**完整部署命令清单**输出给用户,用户自己复制执行 |
|
||||
|
||||
### 手动部署模式的输出要求
|
||||
|
||||
探测完成后,输出一份**可直接复制执行**的命令清单,必须满足:
|
||||
|
||||
1. **分块标注执行位置**:`# 本地执行`(构建、导出、scp)与 `# 服务器执行`(导入、tag、compose up)分开成独立代码块,用户在不同终端跑。
|
||||
2. **占位符全部填实**:`<host>`、socket 路径(`/var/run/docker.sock` vs `/run/podman/podman.sock`)、反代方案(A/B/C)、部署目录(root vs 非 root)按探测结果定死,不留待定项。只有域名/邮箱这类用户秘密可以留 `<你的域名>` 占位,并单独列在清单开头的「需要你替换的值」里。
|
||||
3. **按本文顺序串联**:本地构建 → 构建验证 → 导出传输 → 服务器导入 → runner 去前缀 → compose 编排(含生成好的 `.env` 和按探测结果裁剪过的 `docker-compose.yml` 完整内容)→ 启动 → 验证清单 → 清理。更新场景只给"更新流程"那一节。
|
||||
4. **命令原样可用**:服务器端命令去掉 `ssh <host> '...'` 包装(用户已登录服务器直接跑),保留单引号内的原始命令;fish 兼容性约束不变。
|
||||
5. **不替用户执行任何写操作**:agent 只跑第 0 步探测命令(只读),构建/传输/启动都由用户手动执行。
|
||||
|
||||
**这是项目专属 skill**:镜像名、`LANGUAGES` 注册表硬编码、runner 沙箱、nginx-proxy 约定都绑定到 yggdrasil 本身,但服务器探测、构建、传输、compose 编排是通用流程。
|
||||
|
||||
## 第 0 步:探测目标服务器(每次部署必做)
|
||||
|
||||
@ -8,18 +8,36 @@ pub mod server {
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use syntect::html::{ClassStyle, ClassedHTMLGenerator};
|
||||
use syntect::parsing::SyntaxSet;
|
||||
use syntect::parsing::{SyntaxDefinition, SyntaxSet};
|
||||
use syntect::util::LinesWithEndings;
|
||||
|
||||
/// 全局语法集合,懒加载时合并内置语法与 `syntaxes/` 目录下的自定义语法。
|
||||
static SYNTAX_SET: LazyLock<SyntaxSet> = LazyLock::new(|| {
|
||||
/// 编译期内嵌的自定义语法定义(文件名 stem → .sublime-syntax 内容)。
|
||||
///
|
||||
/// 生产镜像是 `FROM scratch` 的静态 musl 二进制,容器内不存在 `syntaxes/`
|
||||
/// 目录;而 `CARGO_MANIFEST_DIR` 烘焙的是构建机路径(Docker 里是 /build),
|
||||
/// 运行时 `add_from_folder` 注定失败、这些语言静默回退为纯文本。
|
||||
/// 因此改为 `include_str!` 编译期嵌入,彻底消除运行时文件依赖。
|
||||
/// 列表与 `syntaxes/` 目录的一致性由测试 `custom_syntax_list_matches_directory` 保证。
|
||||
pub(crate) const CUSTOM_SYNTAXES: &[(&str, &str)] = &[
|
||||
("JSX", include_str!("../syntaxes/JSX.sublime-syntax")),
|
||||
("Kotlin", include_str!("../syntaxes/Kotlin.sublime-syntax")),
|
||||
("Swift", include_str!("../syntaxes/Swift.sublime-syntax")),
|
||||
("TSX", include_str!("../syntaxes/TSX.sublime-syntax")),
|
||||
("TypeScript", include_str!("../syntaxes/TypeScript.sublime-syntax")),
|
||||
("Vue", include_str!("../syntaxes/Vue.sublime-syntax")),
|
||||
("Zig", include_str!("../syntaxes/Zig.sublime-syntax")),
|
||||
];
|
||||
|
||||
/// 全局语法集合,懒加载时合并内置语法与内嵌的自定义语法。
|
||||
pub(crate) static SYNTAX_SET: LazyLock<SyntaxSet> = LazyLock::new(|| {
|
||||
let mut builder = SyntaxSet::load_defaults_newlines().into_builder();
|
||||
// 使用 CARGO_MANIFEST_DIR 派生的绝对路径,避免运行时工作目录不确定导致加载失败
|
||||
let syntaxes_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/syntaxes");
|
||||
tracing::info!("Loading custom syntaxes from: {}", syntaxes_dir);
|
||||
match builder.add_from_folder(syntaxes_dir, true) {
|
||||
Ok(()) => tracing::info!("Custom syntaxes loaded successfully"),
|
||||
Err(e) => tracing::warn!("Failed to load custom syntaxes: {:?}", e),
|
||||
for (name, src) in CUSTOM_SYNTAXES {
|
||||
match SyntaxDefinition::load_from_str(src, true, Some(name)) {
|
||||
Ok(def) => {
|
||||
builder.add(def);
|
||||
}
|
||||
Err(e) => tracing::warn!("Failed to load embedded syntax {}: {:?}", name, e),
|
||||
}
|
||||
}
|
||||
let built = builder.build();
|
||||
tracing::info!(
|
||||
@ -123,6 +141,42 @@ pub mod server {
|
||||
mod tests {
|
||||
use super::server::*;
|
||||
|
||||
#[test]
|
||||
fn custom_syntax_list_matches_directory() {
|
||||
// CUSTOM_SYNTAXES 必须与 syntaxes/ 目录下的 .sublime-syntax 文件一一对应,
|
||||
// 防止新增/删除语法文件后忘记同步内嵌列表(仿 migrations 数组的编译期校验)。
|
||||
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/syntaxes");
|
||||
let mut on_disk: Vec<String> = std::fs::read_dir(dir)
|
||||
.expect("syntaxes/ 目录应存在")
|
||||
.filter_map(|e| {
|
||||
let p = e.ok()?.path();
|
||||
if p.extension()? == "sublime-syntax" {
|
||||
Some(p.file_stem()?.to_string_lossy().into_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
on_disk.sort();
|
||||
let mut embedded: Vec<&str> = CUSTOM_SYNTAXES.iter().map(|(name, _)| *name).collect();
|
||||
embedded.sort();
|
||||
assert_eq!(embedded, on_disk, "CUSTOM_SYNTAXES 与 syntaxes/ 目录不一致");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_syntaxes_are_loaded() {
|
||||
// 内嵌语法必须真正进入 SyntaxSet(守护生产环境回退纯文本的回归)。
|
||||
for (name, _) in CUSTOM_SYNTAXES {
|
||||
let lower = name.to_lowercase();
|
||||
assert!(
|
||||
SYNTAX_SET.find_syntax_by_name(name).is_some()
|
||||
|| SYNTAX_SET.find_syntax_by_extension(&lower).is_some(),
|
||||
"自定义语法 {} 未加载",
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlight_code_rust() {
|
||||
let result = highlight_code("fn main() {}", Some("rust"));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user