Compare commits

..

No commits in common. "460088fa4326b3575b406a02941613da3d15d108" and "9e9c6be5ce53fd1580715c8bcca2145b38e4f245" have entirely different histories.

2 changed files with 10 additions and 83 deletions

View File

@ -3,9 +3,7 @@ name: deploy-to-linux
description: | description: |
将 yggdrasil 部署/更新到任意 Linux 服务器时使用。本地 arm64 用 Docker Rosetta 构建 将 yggdrasil 部署/更新到任意 Linux 服务器时使用。本地 arm64 用 Docker Rosetta 构建
x86 镜像导出传输到目标服务器Docker 或 Podman后用 docker compose 运行, x86 镜像导出传输到目标服务器Docker 或 Podman后用 docker compose 运行,
前置反代自动签发 HTTPS。支持自动部署agent 直接执行与手动部署agent 探测后输出 前置反代自动签发 HTTPS。触发关键词"部署"、"deploy"、"发布到服务器"、"上线"、"docker compose"。
完整命令清单,用户自行执行)两种模式。
触发关键词:"部署"、"deploy"、"发布到服务器"、"上线"、"docker compose"、"手动部署"。
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@ -21,23 +19,6 @@ metadata:
从本地 arm64 构建器构建 `linux/amd64` 镜像,传输到目标 Linux 服务器并以 docker compose 运行。前置反代负责 HTTPS。 从本地 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 编排是通用流程。 **这是项目专属 skill**:镜像名、`LANGUAGES` 注册表硬编码、runner 沙箱、nginx-proxy 约定都绑定到 yggdrasil 本身但服务器探测、构建、传输、compose 编排是通用流程。
## 第 0 步:探测目标服务器(每次部署必做) ## 第 0 步:探测目标服务器(每次部署必做)

View File

@ -8,36 +8,18 @@ pub mod server {
use std::sync::LazyLock; use std::sync::LazyLock;
use syntect::html::{ClassStyle, ClassedHTMLGenerator}; use syntect::html::{ClassStyle, ClassedHTMLGenerator};
use syntect::parsing::{SyntaxDefinition, SyntaxSet}; use syntect::parsing::SyntaxSet;
use syntect::util::LinesWithEndings; use syntect::util::LinesWithEndings;
/// 编译期内嵌的自定义语法定义(文件名 stem → .sublime-syntax 内容)。 /// 全局语法集合,懒加载时合并内置语法与 `syntaxes/` 目录下的自定义语法。
/// static SYNTAX_SET: LazyLock<SyntaxSet> = LazyLock::new(|| {
/// 生产镜像是 `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(); let mut builder = SyntaxSet::load_defaults_newlines().into_builder();
for (name, src) in CUSTOM_SYNTAXES { // 使用 CARGO_MANIFEST_DIR 派生的绝对路径,避免运行时工作目录不确定导致加载失败
match SyntaxDefinition::load_from_str(src, true, Some(name)) { let syntaxes_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/syntaxes");
Ok(def) => { tracing::info!("Loading custom syntaxes from: {}", syntaxes_dir);
builder.add(def); match builder.add_from_folder(syntaxes_dir, true) {
} Ok(()) => tracing::info!("Custom syntaxes loaded successfully"),
Err(e) => tracing::warn!("Failed to load embedded syntax {}: {:?}", name, e), Err(e) => tracing::warn!("Failed to load custom syntaxes: {:?}", e),
}
} }
let built = builder.build(); let built = builder.build();
tracing::info!( tracing::info!(
@ -141,42 +123,6 @@ pub mod server {
mod tests { mod tests {
use super::server::*; 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] #[test]
fn highlight_code_rust() { fn highlight_code_rust() {
let result = highlight_code("fn main() {}", Some("rust")); let result = highlight_code("fn main() {}", Some("rust"));