Documentation
¶
Index ¶
- Constants
- Variables
- func AcceptFormats(formItem map[string]interface{}) []string
- func AcceptFormatsFromResolver(resolver map[string]interface{}) []string
- func CleanMarkdown(input string) string
- func CommitAll(repo *git.Repository, message string, identity GitIdentity) (bool, error)
- func CommitDirChanges(dir, commitMessage string, identity GitIdentity) error
- func CopyDir(srcDir, dstDir string) error
- func CopyDirReplace(srcDir string, dstDir string, exclude ...string) error
- func CopyDirectory(srcDir, dstParentDir string) error
- func CopyFile(src, dst string, perm os.FileMode) error
- func DecryptAESGCM(encrypted string, key []byte) (string, error)
- func DecryptStoredSecret(encrypted string) (string, error)
- func EncryptAESGCM(plaintext string, key []byte) (string, error)
- func EnsureBareGitRepo(dir string) (*git.Repository, error)
- func EnsureGitRepo(dir string) (*git.Repository, error)
- func EscapeHTML(input string) string
- func FetchBareRepoFromRemote(ctx context.Context, repoPath, remoteName string) error
- func FindFileInGitRepo(repoPath, fileName string) (string, error)
- func FormatIOSchemaItems(raw any) map[string]any
- func FormatJSONIndent(raw []byte) ([]byte, error)
- func GenerateID() int64
- func GenerateStorePathName() (string, error)
- func GetAESKey() []byte
- func GetAnalysisDebugDir(baseDir, analysisID, version string) string
- func GetAnalysisDir(baseDir, projectId string, workflow int64) string
- func GetAnalysisNodeCacheDir(workspaceDir string) string
- func GetAnalysisNodeDir(baseDir, projectId, scriptID string) string
- func GetAnalysisNodeEnvFile(workspaceDir string) string
- func GetAnalysisNodeOutputDir(workspaceDir string) string
- func GetAnalysisURLPrefix(baseDir, outputDir string) string
- func GetCurrentTime() time.Time
- func GetDatasetDir(baseDir, projectId string, datasetID int64) string
- func GetProjectDir(baseDir, projectId string) string
- func GetProjectDocBookDir(baseDir, projectId, reportID string) string
- func GetProjectDocDir(baseDir, projectId, reportID string) string
- func GetProjectLiteratureDir(baseDir, projectId, literatureID string) string
- func GetProjectReportDir(baseDir, projectId, reportID string) string
- func GetScriptDir(baseDir, projectId string) string
- func GetScriptFile(baseDir, projectID, scriptType, scriptID string) (string, string, error)
- func GetScriptFileDir(baseDir, projectId, scriptID string) string
- func GetStoreDir(baseDir string) string
- func GetWorkflowDir(baseDir, projectId string) string
- func GetWorkflowFileDir(baseDir, projectId, workflowID string) string
- func GetWorkflowOrScriptStoreDir(baseDir, id string) string
- func InitSnowflake(workerID int64) error
- func IsPublicIP(ip net.IP) bool
- func IsSSRFWhitelisted(hostname string) bool
- func IsSystemProxy(host string) bool
- func IsValidImageURL(url string) bool
- func IsValidURL(url string) bool
- func MarshalJSONIndent(value any) ([]byte, error)
- func NewSSRFSafeHTTPClient(config SSRFSafeHTTPClientConfig) *http.Client
- func PushBareRepoToRemote(ctx context.Context, repoPath, remoteName string) (branch string, pushed bool, err error)
- func PushDirToRepo(ctx context.Context, sourceDir, targetRepoPath string) (bool, error)
- func ReadFileFromGitRepo(repoPath, relPath string) ([]byte, error)
- func ReadScriptFileContent(baseDir, projectID, scriptType, scriptID string) ([]byte, error)
- func ReadScriptIOSchema(baseDir, projectID, scriptID string) (map[string]any, error)
- func ReadScriptIOSchemaFile(baseDir, projectID, scriptID string) ([]byte, error)
- func ReadScriptReadme(baseDir, projectID, scriptID string) ([]byte, error)
- func ReadWorkflowDagDefinition(baseDir, projectID, workflowID string) (map[string]any, error)
- func ReadWorkflowDagDefinitionFile(baseDir, projectID, workflowID string) ([]byte, error)
- func ReadWorkflowIOSchema(baseDir, projectID, workflowID string) (map[string]any, error)
- func ReadWorkflowIOSchemaFile(baseDir, projectID, workflowID string) ([]byte, error)
- func ReadWorkflowReadme(baseDir, projectID, workflowID string) ([]byte, error)
- func RelPath(baseDir, path string) string
- func RelStoreDir(id string) string
- func RemoteURL(repo *git.Repository, remoteName string) string
- func ResolveConfiguredPath(configuredPath string, defaultRelativeToExecutable string) (string, error)
- func ResolveExternalPath(relativePath string) (string, error)
- func ResolveGitAuth(remoteURL string) (transport.AuthMethod, error)
- func ResolveImageDir(baseDir string) string
- func ResolvePath(baseDir, path string) string
- func RevertWorktreeFile(dir, relPath string) error
- func SSRFSafeDialContext(ctx context.Context, network, addr string) (net.Conn, error)
- func SafeFileName(fileName string) (string, error)
- func SafeObjectKey(objectKey string) error
- func SafePathUnderBase(baseDir, filePath string) (string, error)
- func SanitizeForDisplay(input string) string
- func SanitizeForLog(input string) string
- func SanitizeForLogArray(input []string) []string
- func SanitizeHTML(input string) string
- func ScriptIOSchemaPath(baseDir, projectID, scriptID string) string
- func ScriptMainFilePath(baseDir, projectID, scriptType, scriptID string) string
- func ScriptReadmePath(baseDir, projectID, scriptID string) string
- func SyncWorktreeFromRepo(ctx context.Context, targetDir, srcRepoPath string) error
- func ValidateInput(input string) (string, bool)
- func ValidateStdioArgs(args []string) error
- func ValidateStdioCommand(command string) error
- func ValidateStdioConfig(command string, args []string, envVars map[string]string) error
- func ValidateStdioEnvVars(envVars map[string]string) error
- func ValidateURLForSSRF(rawURL string) error
- func WorkflowDagDefinitionPath(baseDir, projectID, workflowID string) string
- func WorkflowIOSchemaPath(baseDir, projectID, workflowID string) string
- func WorkflowReadmePath(baseDir, projectID, workflowID string) string
- func WriteFileEnsureDir(path string, data []byte) error
- func WriteJSONFile(path string, value any) error
- func WriteScriptFileContent(baseDir, projectID, scriptType, scriptID, content string) error
- func WriteScriptIOSchema(baseDir, projectID, scriptID, content string) error
- func WriteScriptReadme(baseDir, projectID, scriptID, content string) error
- func WriteWorkflowDagDefinition(baseDir, projectID, workflowID, content string) error
- func WriteWorkflowIOSchema(baseDir, projectID, workflowID, content string) error
- func WriteWorkflowReadme(baseDir, projectID, workflowID, content string) error
- type AnalysisLayout
- type GitDiffFile
- type GitDiffResult
- type GitDiffSection
- type GitIdentity
- type GitRemote
- type GitSyncState
- type NodeLayout
- type SSRFSafeHTTPClientConfig
Constants ¶
const DagDefinitionFileName = "dag_definition.json"
DagDefinitionFileName 是工作流目录下 DAG 定义的文件名。
dag_definition 不再作为数据库字段持久化,dag_definition.json 是唯一数据源: 保存侧(SaveWorkflow / SaveWorkflowDag)把它写入工作流目录(GetWorkflowFileDir), 读取侧统一通过 ReadWorkflowDagDefinitionFile / ReadWorkflowDagDefinition 从文件获取。
const EncPrefix = "enc:v1:"
EncPrefix marks a string as AES-256-GCM encrypted
const IOSchemaFileName = "io_schema.json"
IOSchemaFileName 是脚本目录 / 工作流目录下 IO schema 的文件名。
io_schema 不再作为数据库字段持久化,io_schema.json 是唯一数据源: 脚本侧见 ScriptIOSchemaPath 等函数,工作流侧见 WorkflowIOSchemaPath 等函数, 两者共用本文件里的落盘 / 读取原语,避免各自的格式化与容错逻辑漂移。
const ReadmeFileName = "README.md"
ReadmeFileName 是脚本目录 / 工作流目录下的说明文档文件名。 README.md 是唯一数据源,不落库:写侧只有 SaveScriptReadme / SaveWorkflowReadme 落盘, 读侧统一通过本文件的函数读取。
Variables ¶
var ( // ErrGitRepoNotInitialized 表示本地目录不是 git 仓库,无法回退。 ErrGitRepoNotInitialized = stderrs.New("local directory is not a git repository") // ErrGitFileAbsent 表示该路径既不在 HEAD 也不在工作区,没有可撤回的内容。 ErrGitFileAbsent = stderrs.New("file is not part of HEAD or the worktree") // ErrGitPathInvalid 表示文件路径不是仓库内的相对路径(空、绝对路径或越界)。 ErrGitPathInvalid = stderrs.New("file path must be a relative path inside the repository") )
组件目录(脚本/工作流工作区仓库)的单文件回退能力。
与 ReadGitDiff 正好相反:diff 把工作区改动展示出来,本能力把「单个文件」的未提交 改动丢掉,恢复成最近一次提交(本地 HEAD)的版本,供 git-diff 视图里的「撤回」按钮调用。
语义等价于 `git checkout HEAD -- <path>`(新增文件则等同删除后再从索引移除):
- 文件在 HEAD 中存在:用 HEAD 的 blob 覆盖工作区文件,并把索引项指回 HEAD;
- 文件不在 HEAD 中(工作区新增):从工作区删除,并移除索引项;
- 两侧都不存在:视为没有可撤回的内容,返回 ErrGitFileAbsent。
只处理单个文件,不动其它文件的改动,也不产生新的提交(撤回的是未提交改动)。
var AllowedStdioCommands = map[string]bool{ "uvx": true, "npx": true, }
AllowedStdioCommands defines the whitelist of allowed commands for MCP stdio transport These are the standard MCP server launchers that are considered safe
var DangerousArgPatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)^-c$`), regexp.MustCompile(`(?i)^--command$`), regexp.MustCompile(`(?i)^-e$`), regexp.MustCompile(`(?i)^--eval$`), regexp.MustCompile(`(?i)[;&|]`), regexp.MustCompile(`(?i)\$\(`), regexp.MustCompile("(?i)`"), regexp.MustCompile(`(?i)>\s*[/~]`), regexp.MustCompile(`(?i)<\s*[/~]`), regexp.MustCompile(`(?i)^/bin/`), regexp.MustCompile(`(?i)^/usr/bin/`), regexp.MustCompile(`(?i)^/sbin/`), regexp.MustCompile(`(?i)^/usr/sbin/`), regexp.MustCompile(`(?i)^\.\./`), regexp.MustCompile(`(?i)/\.\./`), regexp.MustCompile(`(?i)^(bash|sh|zsh|ksh|csh|tcsh|fish|dash)$`), regexp.MustCompile(`(?i)^(curl|wget|nc|netcat|ncat)$`), regexp.MustCompile(`(?i)^(rm|dd|mkfs|fdisk)$`), }
DangerousArgPatterns contains patterns that indicate potentially dangerous arguments
var DangerousEnvVarPatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)^LD_PRELOAD$`), regexp.MustCompile(`(?i)^LD_LIBRARY_PATH$`), regexp.MustCompile(`(?i)^DYLD_`), regexp.MustCompile(`(?i)^PATH$`), regexp.MustCompile(`(?i)^PYTHONPATH$`), regexp.MustCompile(`(?i)^NODE_OPTIONS$`), regexp.MustCompile(`(?i)^BASH_ENV$`), regexp.MustCompile(`(?i)^ENV$`), regexp.MustCompile(`(?i)^SHELL$`), }
DangerousEnvVarPatterns contains patterns for dangerous environment variable names or values
var ErrEncryptedDataMissingKey = errors.New("encrypted data found but SYSTEM_AES_KEY is not set or has wrong length")
ErrEncryptedDataMissingKey is returned by DecryptStoredSecret when the value carries the enc:v1: prefix but no AES key is available to decrypt it. This signals an operator misconfiguration (typically a rotated or unset SYSTEM_AES_KEY) and must propagate so the system fails loudly instead of silently using ciphertext as a credential.
var ErrGitRepoHasNoCommit = stderrs.New("git repository has no commit to push")
ErrGitRepoHasNoCommit 表示仓库还没有任何提交(HEAD 未出生),没有内容可以推送。 调用方(发布到远程)应据此提示「先把组件发布到 store」而不是当成 500。
var ErrSSRFRedirectBlocked = fmt.Errorf("redirect blocked: target URL failed SSRF validation")
ErrSSRFRedirectBlocked is returned when a redirect target is blocked due to SSRF protection
Functions ¶
func AcceptFormats ¶ added in v0.1.5
AcceptFormats 读取 form item 的 `resolver.accept_formats`,也就是这个输入接受哪些 角色(`input_type=assay` 匹配 `go_assay.role`,`input_type=file` 匹配 `go_file.role`)。返回去空白、去重后的角色列表并保持声明顺序; form item / resolver / accept_formats 缺失或类型不对时返回 nil。
约定:返回空切片(nil)表示“没有声明角色”,调用方据此决定是“不过滤”还是“没有候选”, 例如 ListAssayByProjectID 收到空 roles 时不加 role 条件。
func AcceptFormatsFromResolver ¶ added in v0.1.5
AcceptFormatsFromResolver 同 AcceptFormats,但直接接收 `resolver` map。 同时接受 `[]string`(程序内构造的 formJSON)与 `[]interface{}`(JSON 解析结果)。
func CommitAll ¶ added in v0.1.4
func CommitAll(repo *git.Repository, message string, identity GitIdentity) (bool, error)
CommitAll 把仓库工作区的全部变更(新增/修改/删除)暂存并提交为一个新的 commit。
- 工作区无变更时返回 (false, nil),不会产生空提交;
- message 为空时使用默认文案 "update repository";
- identity 的 Name/Email 必须非空(调用方应通过 config.ResolveGitIdentity 取值, 未配置 git 段时会回退到默认身份)。
仓库首次提交(HEAD 尚未出生)也能正常工作:会在当前分支上创建根提交。
func CommitDirChanges ¶ added in v0.1.4
func CommitDirChanges(dir, commitMessage string, identity GitIdentity) error
CommitDirChanges 确保 dir 是一个 git 仓库(不存在则初始化,默认分支 main), 并把当前工作区改动提交为一个 commit(无变更时不会产生空提交)。
这是脚本/工作流导出目录共用的提交步骤:提交路径(Codec 的 WriteCommitScriptFiles / WriteCommitWorkflowFiles)落盘后调用它,保证目录内容始终有对应的 git 提交, 供后续 PushDirToRepo 推送到 store。
func CopyDir ¶
CopyDir copies the contents of srcDir into dstDir, preserving the directory structure. dstDir is created if it does not exist. Existing files in dstDir may be overwritten.
func CopyDirReplace ¶ added in v0.1.4
CopyDirReplace 用 srcDir 的内容完全替换 dstDir(先删除 dstDir 再重建后拷贝)。
用途是导出/安装两侧的目录快照与还原,例如把脚本目录快照进 workflow 目录, 或把 store 同步回来的脚本目录还原到本地脚本目录。
exclude 里的名称在任意层级都会被跳过(目录则整棵跳过),例如脚本目录自身的 ".git": 把脚本快照进 workflow 目录时不能把脚本仓库一起塞进去。
srcDir 不存在时视为无可拷贝内容,直接返回 nil(导出时脚本尚未落盘不应让整次保存失败)。
func CopyDirectory ¶
CopyDirectory copies the entire srcDir directory (not just its contents) into dstParentDir. For example, CopyDirectory("/a/foo", "/b") creates /b/foo/ with all files/dirs from /a/foo.
func DecryptAESGCM ¶
DecryptAESGCM decrypts an AES-256-GCM encrypted string. If the string lacks the enc:v1: prefix, it's treated as legacy plaintext and returned as-is.
func DecryptStoredSecret ¶
DecryptStoredSecret decrypts a value loaded from the database with strict error propagation. Use this from GORM Scan/AfterFind hooks for any field that stores AES-encrypted secrets (API keys, passwords, app secrets).
Behaviour:
- empty input -> empty output, no error.
- no enc:v1: prefix -> returned as-is (legacy plaintext column), no error.
- has enc:v1: prefix and SYSTEM_AES_KEY is missing or wrong length -> returns ErrEncryptedDataMissingKey. Callers MUST propagate this so the load fails loudly instead of silently surfacing ciphertext as a credential.
- has enc:v1: prefix and key is set -> decrypts, returns any decryption error verbatim (e.g. base64 decode failure, GCM auth tag mismatch from a rotated key).
The previous lenient pattern (`if decrypted, err := ...; err == nil { ... }`) hid the rotated-key case and caused the encrypted ciphertext to be sent upstream as the actual API key, surfacing as 401/403 from third-party vendors. Always prefer this helper over calling DecryptAESGCM directly when loading from the database.
func EncryptAESGCM ¶
EncryptAESGCM encrypts plaintext with AES-256-GCM. Returns the original string if empty, already encrypted, or key is nil.
func EnsureBareGitRepo ¶ added in v0.1.4
func EnsureBareGitRepo(dir string) (*git.Repository, error)
EnsureBareGitRepo 保证 dir 是一个裸(bare)git 仓库并返回它,用作本地推送目标:
- dir 不存在:初始化裸仓库(默认分支 main)
- dir 已是裸仓库:直接复用,保留历史,便于多次 publish 增量推送
- dir 存在但不是裸仓库(例如旧版本留下的普通文件产物):清空后重新初始化
DefaultBranch 固定为 main,避免受本机 git 默认分支配置影响。
func EnsureGitRepo ¶ added in v0.1.4
func EnsureGitRepo(dir string) (*git.Repository, error)
EnsureGitRepo 在 dir 目录下初始化一个非裸(带工作区)的 git 仓库。 若 dir 下已存在仓库,则直接打开并复用,不会清空已有历史。 DefaultBranch 固定为 main,避免受本机 git 默认分支配置影响。
func FetchBareRepoFromRemote ¶ added in v0.1.5
FetchBareRepoFromRemote 把裸仓库更新到指定 remote 的最新提交。
裸仓库没有工作区,Worktree().Pull 不可用(会报 worktree not available),因此等价实现为: force fetch <remoteName>(刷新 refs/remotes/<remoteName>/*)后,把本地分支指向远端分支, 并把 HEAD 切到该分支。remoteName 为空时回退 storeRemoteName(origin)。
分支选取:优先本地 store 当前的分支(resolveRepoBranch,HEAD 悬挂也能容错); 远端没有同名分支时跟随远端实际存在的分支(只有一个分支,或 main / master), 并把 store 的 HEAD 一起切过去,保证后续「publish 跟随本地分支、install 跟随 store 分支」 这条链路始终自洽。
store 裸仓库上可以配置多个远端(origin / github / gitee ...,见 ReadGitRemotes), 「检查更新」(/store/redownload)因此可以指定从哪个 remote 拉取。
远端没有新提交时返回 git.NoErrAlreadyUpToDate,与 Worktree().Pull 的语义一致, 调用方可用 stderrs.Is(err, git.NoErrAlreadyUpToDate) 判断“已是最新”。
func FindFileInGitRepo ¶ added in v0.1.4
FindFileInGitRepo 在仓库 HEAD 提交的文件树里按文件名查找文件,返回仓库内的相对路径 ("/" 分隔),可直接交给 ReadFileFromGitRepo 读取。
查找口径与旧的文件系统查找保持一致:根目录优先,其次按路径顺序查找(忽略大小写)。 同时支持裸仓库与普通仓库:store 是裸仓库,没有工作区文件,只能从 git 对象里找。 HEAD 悬挂(历史遗留的 store)时按 resolveRepoBranch 回退到实际分支。
func FormatIOSchemaItems ¶ added in v0.1.5
FormatIOSchemaItems 把 io_schema 的 inputs / outputs 数组转成以 name 为键的 map: 每项去掉 name 后,其余字段(type / required / description 等)原样保留。
例: [{"name":"tsv","type":"file"},{"name":"plot","type":"file"}]
→ {"tsv":{"type":"file"},"plot":{"type":"file"}}
该形态与 analysis_nodes.inputs_patterns / output_patterns 的存储结构一致, 因此也是脚本可视化节点(buildScriptVisItem)与独立节点 OutputPatterns 的公共构造逻辑。 name 为空或类型不符的项被跳过(无法作为 handle 键);raw 不是数组时返回空 map。
func FormatJSONIndent ¶ added in v0.1.5
FormatJSONIndent 把原始 JSON 文本重新缩进为两空格缩进。
与 MarshalJSONIndent 的区别:这里只重排空白,**不改动任何 token 字面量**, 因此能保留原始键顺序、数字写法(如 1e7 不会被改写成 1e+07)与字符串转义, 适合「已经是 JSON 文本、只想格式化」的场景;内容非法 JSON 时返回错误。
func GenerateID ¶
func GenerateID() int64
func GenerateStorePathName ¶ added in v0.1.5
GenerateStorePathName 生成 store 目录的随机相对标识(types.Store.PathName)。
store 目录名不再复用业务 ID(workflow_id / script_id / git 地址里的 "<owner>/<repo>"): 业务 ID 在重新发布、复制安装时会变化甚至被多个组件复用,git 地址则会随仓库改名漂移, 还会把上游仓库信息暴露到本地路径里;而目录名一旦落盘(并作为裸仓库位置)就不该再变。 因此发布与下载统一在「首次创建 store」时调用本函数生成一次随机标识,之后的更新 (重新发布 / 重新下载)只沿用既有值,绝不重新生成。
func GetAESKey ¶
func GetAESKey() []byte
GetAESKey reads the 32-byte AES key from SYSTEM_AES_KEY env. Returns nil if not set or not exactly 32 bytes.
func GetAnalysisDebugDir ¶
func GetAnalysisDir ¶
func GetAnalysisNodeCacheDir ¶ added in v0.1.4
func GetAnalysisNodeDir ¶
func GetAnalysisNodeEnvFile ¶ added in v0.1.4
func GetAnalysisNodeOutputDir ¶ added in v0.1.4
func GetAnalysisURLPrefix ¶ added in v0.1.5
GetAnalysisURLPrefix converts an absolute analysis / analysis node output directory into the /data-analysis URL prefix that serves it.
Storage paths are persisted relative to storage.base_dir and the router exposes <baseDir>/data at /data-analysis (see router.serveStatic), so the prefix is simply the output directory with the <baseDir>/data part stripped. Directories living outside <baseDir>/data are left untouched so the caller still gets a usable, if unusual, URL instead of an empty one.
func GetCurrentTime ¶
func GetDatasetDir ¶ added in v0.1.4
func GetProjectDir ¶
func GetProjectDocBookDir ¶ added in v0.1.5
GetProjectDocBookDir 返回某个 ProjectReport 的文档站点(构建产物)目录: <baseDir>/data/<projectId>/docs/<reportId>/book。
func GetProjectDocDir ¶
GetProjectDocDir 返回某个 ProjectReport 的文档源目录: <baseDir>/data/<projectId>/docs/<reportId>/src。
func GetProjectLiteratureDir ¶ added in v0.1.2
func GetProjectReportDir ¶ added in v0.1.2
func GetScriptDir ¶
func GetScriptFile ¶
func GetScriptFileDir ¶
func GetStoreDir ¶ added in v0.1.4
func GetWorkflowDir ¶
func GetWorkflowFileDir ¶ added in v0.1.4
func GetWorkflowOrScriptStoreDir ¶ added in v0.1.4
GetWorkflowOrScriptStoreDir resolves the absolute store directory of a published workflow/script or of a downloaded remote repo, from its types.Store.PathName (a random identifier generated once at store creation time; legacy rows may still hold a workflow/script id or "<owner>/<repo>").
The single source of truth for the relative layout is RelStoreDir; keep both in sync by always deriving one from the other.
func InitSnowflake ¶
func IsPublicIP ¶
IsPublicIP returns true if the IP is safe for outbound fetch (not private, loopback, link-local, etc.). Used for DNS pinning: after resolving a hostname we pick the first public IP and pin all requests to it.
func IsSSRFWhitelisted ¶
IsSSRFWhitelisted checks whether the given hostname (or IP string) is covered by the SSRF_WHITELIST environment variable.
func MarshalJSONIndent ¶ added in v0.1.5
MarshalJSONIndent 把 value 序列化为两空格缩进的 JSON 字节。 项目内所有「写盘的 JSON」都走这里,保证导出文件 / schema 文件 / 参数文件的缩进风格一致。
func NewSSRFSafeHTTPClient ¶
func NewSSRFSafeHTTPClient(config SSRFSafeHTTPClientConfig) *http.Client
NewSSRFSafeHTTPClient creates an HTTP client that validates redirect targets against SSRF protections. This prevents SSRF attacks via HTTP redirects where an attacker's server redirects to internal services.
func PushBareRepoToRemote ¶ added in v0.1.5
func PushBareRepoToRemote(ctx context.Context, repoPath, remoteName string) (branch string, pushed bool, err error)
PushBareRepoToRemote 把 store 裸仓库「当前分支」推送到已配置的 remote。
与 PushDirToRepo(推本地路径的 store 镜像)不同,这里推的是用户填写的真正远程地址 (github / gitee / gitlab,或测试用的本地路径),等价于 `git push --force <remote> <branch>`:
- 分支跟随 store 当前使用的分支(resolveRepoBranch,HEAD 悬挂也能容错),并推成远端 同名分支。store 是组件的发布镜像,远端分支必须与本地完全一致,因此用 "+" 前缀 (允许非快进更新)覆盖远端历史 —— GitHub 建仓时自动生成的 README 提交会被顶掉;
- 远端分支已经是同一个提交时 go-git 返回 git.NoErrAlreadyUpToDate,这里转成 (false, nil):没有新内容可推送属于幂等成功,调用方据此提示「已是最新」而不是报错;
- 认证方式由 remote 地址推导(见 ResolveGitAuth),本地路径 / file:// 走匿名, 因此单测可以直接把临时裸仓库当远端;ssh 地址取不到凭据时返回明确错误而不是 交给 go-git 匿名(匿名 ssh 不存在,见 resolveSSHAuth)。
仓库还没有提交(组件尚未发布到 store)时返回 ErrGitRepoHasNoCommit。
func PushDirToRepo ¶ added in v0.1.4
PushDirToRepo 把 sourceDir 仓库的当前分支推送到 targetRepoPath 对应的本地仓库。
等价于 `git remote add/ set-url <origin> <targetRepoPath>` 后再 `git push`: 每次调用都会把名为 origin 的 remote 指向最新的 targetRepoPath(base_dir 变更后 无需手工改 remote 配置),并以 force 方式推送,使目标仓库与源分支内容完全一致。
返回值 pushed 表示目标仓库这次是否真的被更新:
- true:远端分支前移了;
- false:远端分支已经是同一个提交(go-git 返回 git.NoErrAlreadyUpToDate), 「没有新内容可推送」属于幂等成功,调用方应据此给用户提示而不是报错 (例如重复 publish 同一个未再修改的脚本/工作流)。
发布后会把目标裸仓库(store)的 HEAD 切到本次发布的分支:store 的分支跟随工作目录 (publish(dev) => store 变成 dev),不再固定为初始化时的 main。这样 install / 读导出 文件 / 检查更新都只需要认 store 的 HEAD,不必依赖源目录或 store 的历史分支名; 之前没有这一步时,从非 main 分支发布会让 store 的 HEAD 悬空(reference not found) 或读到旧分支上的旧内容。
源仓库没有 commit(HEAD 未出生)时返回错误,调用方应提示先保存。
func ReadFileFromGitRepo ¶ added in v0.1.4
ReadFileFromGitRepo 读取仓库 HEAD 提交中指定相对路径("/" 分隔)的文件内容。
同时支持裸仓库与普通仓库:本地发布的 store 是裸仓库,没有工作区文件,封面图等 只能从 git 对象里读取。HEAD 悬挂(历史遗留的 store)时按 resolveRepoBranch 回退到 实际分支,不直接报 reference not found。
func ReadScriptFileContent ¶ added in v0.1.5
ReadScriptFileContent 读取脚本主文件内容。 文件不存在时返回 nil(不视为错误),便于调用方按“空脚本”处理。
func ReadScriptIOSchema ¶ added in v0.1.5
ReadScriptIOSchema 读取并解析脚本目录下的 io_schema.json,缺失 / 空内容返回空 map。
func ReadScriptIOSchemaFile ¶ added in v0.1.5
ReadScriptIOSchemaFile 读取脚本目录下 io_schema.json 的原始内容,文件不存在时返回 nil。
func ReadScriptReadme ¶ added in v0.1.5
ReadScriptReadme 读取脚本目录下的 README.md。 文件不存在时返回 nil(不视为错误),便于调用方按“空 README”处理。
func ReadWorkflowDagDefinition ¶ added in v0.1.5
ReadWorkflowDagDefinition 读取并解析工作流目录下的 dag_definition.json,缺失 / 空内容返回空 map。
func ReadWorkflowDagDefinitionFile ¶ added in v0.1.5
ReadWorkflowDagDefinitionFile 读取工作流目录下 dag_definition.json 的原始内容,文件不存在时返回 nil。
func ReadWorkflowIOSchema ¶ added in v0.1.5
ReadWorkflowIOSchema 读取并解析工作流目录下的 io_schema.json,缺失 / 空内容返回空 map。
func ReadWorkflowIOSchemaFile ¶ added in v0.1.5
ReadWorkflowIOSchemaFile 读取工作流目录下 io_schema.json 的原始内容,文件不存在时返回 nil。
func ReadWorkflowReadme ¶ added in v0.1.5
ReadWorkflowReadme 读取工作流目录下的 README.md。 文件不存在时返回 nil(不视为错误),便于调用方按“空 README”处理。
func RelPath ¶ added in v0.1.4
RelPath converts an absolute storage path into the relative form that gets persisted in the database.
Persisted storage paths are always relative to storage.base_dir so that relocating the storage root only requires copying the directory tree, not rewriting database rows. Paths that are already relative (or that live outside baseDir, which we do not own) are returned untouched so we never silently mangle data.
func RelStoreDir ¶ added in v0.1.4
RelStoreDir is the base_dir-relative location of a store entry.
func RemoteURL ¶ added in v0.1.5
func RemoteURL(repo *git.Repository, remoteName string) string
RemoteURL 返回 repo 中指定 remote 的第一个地址,取不到时返回空串。 调用方(handler 的 clone / pull 路径)用它把地址交给 ResolveGitAuth 推导凭据。
func ResolveConfiguredPath ¶
func ResolveConfiguredPath(configuredPath string, defaultRelativeToExecutable string) (string, error)
ResolveConfiguredPath resolves storage path by this rule: - when configuredPath is empty, use executableDir/defaultRelativeToExecutable - otherwise, use configuredPath directly.
func ResolveExternalPath ¶
ResolveExternalPath resolves project external file paths. If BRAVE_CONFIG_DIR is set, the path is resolved relative to it; otherwise, it is resolved relative to current working directory.
func ResolveGitAuth ¶ added in v0.1.5
func ResolveGitAuth(remoteURL string) (transport.AuthMethod, error)
ResolveGitAuth 根据 git 地址推导传输层认证方式,push / fetch / clone 共用。
认证配置有意保持「零配置」:没有专门的凭据配置段,凭据要么写在 remote 地址里,要么 交给进程环境。
- http(s) 地址里带用户名(https://<user>:<token>@github.com/owner/repo.git): 用 BasicAuth 把 token 当密码提交。github / gitee / gitlab 的 personal access token 都是这种用法,也是本服务无需额外配置即可带上凭据的方式;
- ssh 地址(git@host:owner/repo.git、ssh://git@host/owner/repo.git):见 resolveSSHAuth;
- 其余(本地路径 / file:// / 不带凭据的 https 公共仓库):返回 nil,由 go-git 匿名访问, 认证失败时 go-git 会返回 ErrAuthenticationRequired / ErrAuthorizationFailed。
func ResolveImageDir ¶
func ResolvePath ¶ added in v0.1.4
ResolvePath turns a persisted path back into an absolute path.
It is deliberately tolerant: an empty value stays empty and legacy rows that still hold an absolute path are returned as-is, so mixed old/new data keeps working while the migration is rolling out.
func RevertWorktreeFile ¶ added in v0.1.5
RevertWorktreeFile 把 dir(脚本/工作流工作区仓库)里的单个文件回退到本地 HEAD 版本。
relPath 是以 "/" 分隔、相对仓库根目录的文件路径(与 git-diff 返回的 file.path 一致)。 调用方需自行把「非 git 仓库 / 路径非法 / 无可撤回内容」映射为合适的接口错误, 因此这里返回可判定的哨兵错误(ErrGitRepoNotInitialized / ErrGitPathInvalid / ErrGitFileAbsent)。
func SSRFSafeDialContext ¶
SSRFSafeDialContext is a custom dial function that validates the resolved IP addresses before establishing a connection. This provides an additional layer of SSRF protection against DNS rebinding attacks during the connection phase.
func SafeFileName ¶
SafeFileName 校验并返回安全的“仅文件名”部分,防止路径遍历。 仅保留最后一个路径成分,禁止 ".."、空名或仅含点,用于 SaveBytes 等场景。
func SafeObjectKey ¶
SafeObjectKey 校验对象存储的 key(如 COS/MinIO objectName),禁止包含 ".." 等路径遍历
func SafePathUnderBase ¶
SafePathUnderBase 校验 filePath 是否落在 baseDir 下,防止路径遍历(如 ../../)。 返回规范化的绝对路径;若路径逃逸出 baseDir 则返回错误。
func SanitizeForLog ¶
SanitizeForLog 清理日志输入,防止日志注入攻击 日志注入攻击是指攻击者通过在输入中插入换行符和其他控制字符, 伪造日志条目,可能导致日志分析工具误判或隐藏恶意活动
func SanitizeForLogArray ¶
SanitizeForLogArray 清理日志输入数组,防止日志注入攻击
func ScriptIOSchemaPath ¶ added in v0.1.5
ScriptIOSchemaPath 返回脚本目录下 io_schema.json 的绝对路径。
func ScriptMainFilePath ¶ added in v0.1.5
ScriptMainFilePath 返回脚本主文件(main.R / main.py / main.sh / ...)的绝对路径。 主文件名由 script_type 决定,脚本目录与类型无关(与 io_schema.json 同目录)。
func ScriptReadmePath ¶ added in v0.1.5
ScriptReadmePath 返回脚本目录(GetScriptFileDir)下 README.md 的绝对路径。
func SyncWorktreeFromRepo ¶ added in v0.1.4
SyncWorktreeFromRepo 让 targetDir 的工作区与 srcRepoPath 仓库保持一致:
- targetDir 已是 git 仓库:从 srcRepoPath fetch 后把 HEAD 切到源仓库的分支再 reset --hard
- targetDir 不存在或为空目录:从 srcRepoPath clone(非裸克隆,带工作区)
- targetDir 存在已有内容但不是 git 仓库(旧版本安装留下的普通文件目录): 就地初始化仓库后再 reset --hard,语义同"覆盖本地"
三种情况都以源仓库(install 场景下是 store 裸仓库)的分支为准:执行完 targetDir 的 当前分支与源仓库一致(见 resolveRepoBranch / resetWorktreeToRemote),目标目录原有的 其它分支保留不删,只是不再被 HEAD 指向。
srcRepoPath 既可以是裸仓库(publish 产物),也可以是普通工作区仓库。
func ValidateStdioArgs ¶
ValidateStdioArgs validates the arguments for MCP stdio transport Returns an error if any argument contains dangerous patterns
func ValidateStdioCommand ¶
ValidateStdioCommand validates the command for MCP stdio transport Returns an error if the command is not in the whitelist or contains dangerous patterns
func ValidateStdioConfig ¶
ValidateStdioConfig performs comprehensive validation of stdio configuration This should be called before creating or executing any stdio-based MCP client
func ValidateStdioEnvVars ¶
ValidateStdioEnvVars validates environment variables for MCP stdio transport Returns an error if any env var name or value is dangerous
func ValidateURLForSSRF ¶
ValidateURLForSSRF is the centralised entry-point that all handlers should call to validate a user-supplied URL. It first checks the SSRF_WHITELIST; whitelisted hosts skip the full isSSRFSafeURL check.
rawURL may be a full URL ("https://example.com/v1") or a bare host/host:port (for cases like ReconnectDocReader). If a scheme is missing the function prepends "https://" before parsing so that net/url can extract the host.
Returns nil when the URL is safe, or an error describing the problem.
func WorkflowDagDefinitionPath ¶ added in v0.1.5
WorkflowDagDefinitionPath 返回工作流目录下 dag_definition.json 的绝对路径。 路径只取决于 baseDir / projectID / workflowID(都参与目录拼接)。
func WorkflowIOSchemaPath ¶ added in v0.1.5
WorkflowIOSchemaPath 返回工作流目录下 io_schema.json 的绝对路径。
func WorkflowReadmePath ¶ added in v0.1.5
WorkflowReadmePath 返回工作流目录(GetWorkflowFileDir)下 README.md 的绝对路径。
func WriteFileEnsureDir ¶ added in v0.1.5
WriteFileEnsureDir 把 data 写入 path(权限 0o644),父目录不存在时自动创建(0o755)。
func WriteJSONFile ¶ added in v0.1.5
WriteJSONFile 把 value 以两空格缩进的 JSON 写入 path,父目录不存在时自动创建。 调用方不必先建目录;序列化失败时不落盘。
func WriteScriptFileContent ¶ added in v0.1.5
WriteScriptFileContent 覆盖写入脚本主文件内容,目录不存在时自动创建。
func WriteScriptIOSchema ¶ added in v0.1.5
WriteScriptIOSchema 将 io_schema 内容写入脚本目录下的 io_schema.json(详见 writeIOSchemaFile)。
func WriteScriptReadme ¶ added in v0.1.5
WriteScriptReadme 覆盖写入脚本目录下的 README.md,目录不存在时自动创建。
func WriteWorkflowDagDefinition ¶ added in v0.1.5
WriteWorkflowDagDefinition 将 dag_definition 内容格式化(两空格缩进)后写入工作流目录下的 dag_definition.json(详见 writeDagDefinitionFile)。
func WriteWorkflowIOSchema ¶ added in v0.1.5
WriteWorkflowIOSchema 将 io_schema 内容写入工作流目录下的 io_schema.json(详见 writeIOSchemaFile)。
func WriteWorkflowReadme ¶ added in v0.1.5
WriteWorkflowReadme 覆盖写入工作流目录下的 README.md,目录不存在时自动创建。
Types ¶
type AnalysisLayout ¶ added in v0.1.4
type AnalysisLayout struct {
// Dir is the analysis root directory.
Dir string
// ParamsPath is the request params payload written at save time.
ParamsPath string
// CommandPath is the generated run.sh path.
CommandPath string
// CommandLogPath is the captured run log path.
CommandLogPath string
// TraceFile is the nextflow trace log path.
TraceFile string
// WorkflowLogFile is the nextflow workflow log path.
WorkflowLogFile string
// ExecutorLogFile is the nextflow executor log path.
ExecutorLogFile string
}
AnalysisLayout is the on-disk contract of a single analysis.
Every artifact path an analysis row exposes is derived here, so writers and readers cannot drift apart.
func AnalysisLayoutFor ¶ added in v0.1.4
func AnalysisLayoutFor(workspaceDir string) AnalysisLayout
AnalysisLayoutFor derives the analysis layout from its root directory. An empty root yields a zero layout instead of dangling filenames.
type GitDiffFile ¶ added in v0.1.5
type GitDiffFile struct {
Path string `json:"path"`
Status string `json:"status"`
Binary bool `json:"binary,omitempty"`
Omitted bool `json:"omitted,omitempty"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Diff string `json:"diff,omitempty"`
}
GitDiffFile 描述一个文件在工作区/两个提交之间的变化。
Diff 是该文件的 unified diff 文本(含 ---/+++ 头,不含 `diff --git` 行); 二进制或超大文件内容不参与 diff,只通过 Binary/Omitted 标记。
type GitDiffResult ¶ added in v0.1.5
type GitDiffResult struct {
RepoDir string `json:"repo_dir"`
Initialized bool `json:"initialized"`
HeadCommit string `json:"head_commit,omitempty"`
HasChanges bool `json:"has_changes"`
Worktree GitDiffSection `json:"worktree"`
Unpublished *GitDiffSection `json:"unpublished,omitempty"`
}
GitDiffResult 是组件目录的本地变化汇总。
HasChanges = Worktree.HasChanges || Unpublished.HasChanges,即「本地有变化」的两种形态。
func ReadGitDiff ¶ added in v0.1.5
func ReadGitDiff(localDir, storeDir string) GitDiffResult
ReadGitDiff 读取 localDir(脚本/工作流目录)的本地变化:
- 工作区相对本地 HEAD 的未提交改动(含未跟踪文件、HEAD 尚未出生时的全部文件);
- 本地 HEAD 相对 storeDir HEAD 的提交差异(两侧都有提交且 commit 不同时才有)。
localDir 不是 git 仓库时返回 Initialized=false 的空结果,便于接口在「从未保存」 的情况下也能安全返回。
type GitDiffSection ¶ added in v0.1.5
type GitDiffSection struct {
BaseCommit string `json:"base_commit,omitempty"`
TargetCommit string `json:"target_commit,omitempty"`
HasChanges bool `json:"has_changes"`
FileCount int `json:"file_count"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Truncated bool `json:"truncated"`
Files []GitDiffFile `json:"files"`
Patch string `json:"patch"`
}
GitDiffSection 是一组「旧快照 -> 新快照」的 diff 结果。
BaseCommit/TargetCommit 为空表示该侧没有提交(HEAD 尚未出生), 此时快照按空内容处理(工作区文件全部表现为 added)。
type GitIdentity ¶ added in v0.1.4
GitIdentity 是 git 提交时使用的身份信息,对应 config.GitConfig。
type GitRemote ¶ added in v0.1.5
GitRemote 是仓库中配置的一个 git remote:名称 + 全部地址。
同一 remote 可以配置多个 URL(git 的 remote.<name>.url 可重复),因此这里用切片表达。 store 表不再保存「目标远程地址」列,发布到远程时写入的信息全部保存在 store 裸仓库的 remote 配置里,读取侧(git_state.remotes)也从这里实时推导,不落库。
func EnsureGitRemote ¶ added in v0.1.5
EnsureGitRemote 保证 repoPath 仓库里存在指向 remoteURL 的 remote。
- remoteURL 已经配置在某个 remote 上:直接复用该 remote,added=false(跳过添加);
- 否则按地址主机名推导 remote 名(github.com → github、gitee.com → gitee, 其余取主机名首段,取不到时用 "remote"),同名被其它地址占用时追加 "-2"、"-3"… 保证名称唯一,added=true。
只写仓库配置、不做任何网络操作;真正的 push 由调用方后续实现。 store 是裸仓库,配置文件就是仓库本身,因此这里对裸仓库同样适用。
func ReadGitRemotes ¶ added in v0.1.5
ReadGitRemotes 读取仓库配置里的 remote 列表(按名称排序,输出稳定)。
目录不存在、不是仓库或没有 remote 时返回 nil(不报错), 便于接口在「从未发布 / 从未配置远程」的情况下安全返回。
type GitSyncState ¶ added in v0.1.4
type GitSyncState struct {
LocalDir string `json:"local_dir,omitempty"`
StoreDir string `json:"store_dir,omitempty"`
// LocalInitialized 本地目录是否已是一个 git 仓库(脚本/工作流的版本目录)。
LocalInitialized bool `json:"local_initialized"`
// StoreInitialized 发布目标是否已是一个 git 仓库(即是否已发布过)。
StoreInitialized bool `json:"store_initialized"`
// LocalCommit 本地工作区 HEAD 的提交 hash(尚无提交时为空)。
LocalCommit string `json:"local_commit,omitempty"`
// StoreCommit 发布目标 HEAD 的提交 hash(未发布时为空)。
StoreCommit string `json:"store_commit,omitempty"`
// LocalDirty 本地工作区存在未提交改动(新增/修改/删除,含未跟踪文件)。
LocalDirty bool `json:"local_dirty"`
// LocalAhead 本地 HEAD 领先 store:存在已提交但未发布的改动。
LocalAhead bool `json:"local_ahead"`
// StoreAhead store 领先本地:远端有本地没有的提交(需要 install/同步)。
StoreAhead bool `json:"store_ahead"`
// HasLocalChanges 本地有未发布改动 = 未提交改动 或 本地领先 store。
HasLocalChanges bool `json:"has_local_changes"`
// HasStoreChanges store 有本地未同步的提交(远端有更新)。
HasStoreChanges bool `json:"has_store_changes"`
// InSync 本地干净且两侧 commit 一致。
InSync bool `json:"in_sync"`
// Remotes 是 store 裸仓库上配置的远程仓库列表(github / gitee / origin ...),
// 从磁盘 git 配置实时读取、不落库:发布到远程(PublishStoreRemote)只把地址写成
// store 仓库的 remote,不经数据库,因此这里就是「已配置的远程仓库」的唯一数据源。
// 没有 store 仓库或未配置任何 remote 时为空。
Remotes []GitRemote `json:"remotes,omitempty"`
}
GitSyncState 描述「本地工作目录仓库」与「发布目标仓库(store 裸仓库)」之间的同步状态。
设计取舍:状态完全由磁盘上的 git 元数据通过 go-git 实时推导,不落库。 原因:
- “本地有更新”天然包含未提交的工作区改动(脚本文件被直接编辑), 任何 DB 字段都无法表达;
- SaveScript / PublishScript / InstallScript 三个写入口都要同步维护 commit_id, 而 EnsureBareGitRepo 会重建非裸目录、InstallScript 会 reset --hard, 落库字段极易与真实仓库状态漂移;
- 本地目录是小仓库,Worktree.Status 与 HEAD 读取成本很低,无需缓存。
func ReadGitSyncState ¶ added in v0.1.4
func ReadGitSyncState(localDir, storeDir string) GitSyncState
ReadGitSyncState 读取 localDir(脚本/工作流目录)与 storeDir(发布裸仓库)的同步状态。
两个目录都可以不存在或不是 git 仓库:此时返回零值语义的状态(不报错), 便于接口在“从未保存/从未发布”的情况下也能安全返回。
type NodeLayout ¶ added in v0.1.4
type NodeLayout struct {
// Dir is the node root directory.
Dir string
// OutputDir receives produced artifacts.
OutputDir string
// CacheDir holds intermediate cache artifacts.
CacheDir string
// ParamsPath is the generated params.json path.
ParamsPath string
// CommandPath is the generated run.sh path.
CommandPath string
// LogPath is the captured command log path.
LogPath string
}
NodeLayout is the on-disk contract of a single analysis node.
func NodeLayoutFor ¶ added in v0.1.4
func NodeLayoutFor(workspaceDir string) NodeLayout
NodeLayoutFor derives the node layout from its root directory. An empty root yields a zero layout instead of dangling filenames.
type SSRFSafeHTTPClientConfig ¶
type SSRFSafeHTTPClientConfig struct {
Timeout time.Duration
MaxRedirects int
DisableKeepAlives bool
DisableCompression bool
}
SSRFSafeHTTPClientConfig contains configuration for the SSRF-safe HTTP client
func DefaultSSRFSafeHTTPClientConfig ¶
func DefaultSSRFSafeHTTPClientConfig() SSRFSafeHTTPClientConfig
DefaultSSRFSafeHTTPClientConfig returns the default configuration
Source Files
¶
- accept_formats.go
- analysis_url.go
- copy.go
- copy_dir.go
- crypto.go
- get_script_file.go
- git.go
- git_diff.go
- git_revert.go
- git_state.go
- io_schema.go
- json_file.go
- path.go
- paths.go
- readme_file.go
- runtime.go
- script_file_content.go
- script_io_schema.go
- security.go
- snowflake.go
- store_path_name.go
- time.go
- workflow_dag_definition.go
- workflow_io_schema.go