@astralyn/sash 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +22 -7
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/dist/api.js +14 -45
- package/dist/app-state.js +99 -0
- package/dist/autostart/command.js +24 -0
- package/dist/autostart/context.js +43 -0
- package/dist/autostart/files.js +60 -0
- package/dist/autostart/installation.js +48 -0
- package/dist/autostart/start.js +47 -0
- package/dist/autostart/windows-registry.js +88 -0
- package/dist/autostart/windows.js +59 -0
- package/dist/autostart-contract.js +31 -0
- package/dist/autostart-entry.js +10 -0
- package/dist/autostart.js +69 -0
- package/dist/cli.js +11 -11
- package/dist/commands/auto.js +15 -0
- package/dist/commands/lifecycle.js +5 -31
- package/dist/commands/logs.js +11 -4
- package/dist/commands/shared.js +2 -4
- package/dist/commands/status.js +6 -7
- package/dist/commands/update.js +7 -3
- package/dist/commands/web.js +19 -27
- package/dist/contracts.js +171 -335
- package/dist/core-config-validation.js +22 -38
- package/dist/core-update.js +126 -762
- package/dist/core.js +20 -46
- package/dist/daemon/app.js +132 -113
- package/dist/daemon/context.js +13 -7
- package/dist/daemon/entry.js +21 -38
- package/dist/daemon/errors.js +6 -1
- package/dist/daemon/handlers/autostart.js +18 -0
- package/dist/daemon/handlers/core.js +32 -9
- package/dist/daemon/handlers/daemon.js +32 -6
- package/dist/daemon/handlers/profiles.js +12 -2
- package/dist/daemon/handlers/settings.js +9 -36
- package/dist/daemon/router.js +40 -27
- package/dist/daemon/scheduler.js +3 -1
- package/dist/daemon/server.js +6 -3
- package/dist/daemon/web-auth.js +52 -0
- package/dist/daemon-auth.js +2 -2
- package/dist/daemon-client.js +18 -2
- package/dist/daemon-http.js +7 -0
- package/dist/daemon-lifecycle.js +19 -196
- package/dist/github.js +7 -2
- package/dist/http.js +7 -5
- package/dist/log-follow.js +2 -1
- package/dist/mihomo-config.js +7 -9
- package/dist/paths.js +5 -7
- package/dist/profile-model.js +87 -0
- package/dist/profile-service.js +250 -587
- package/dist/profiles.js +43 -171
- package/dist/runtime-lifecycle.js +112 -194
- package/dist/runtime-owner.js +37 -76
- package/dist/sash-client.js +63 -21
- package/dist/settings-service.js +29 -228
- package/dist/settings.js +61 -285
- package/dist/status.js +24 -40
- package/dist/supervisor.js +1 -14
- package/dist/sysproxy/common.js +5 -45
- package/dist/sysproxy/factory.js +24 -65
- package/dist/sysproxy/snapshot.js +24 -257
- package/dist/sysproxy.js +1 -4
- package/dist/system-proxy-manager.js +37 -35
- package/dist/ui/assets/{ConnectionsView-DNGmZBSU.js → ConnectionsView-BgXQM6Z4.js} +1 -1
- package/dist/ui/assets/{LogsView-fSiaxQ13.js → LogsView-2f542P8z.js} +1 -1
- package/dist/ui/assets/{PaginationFooter-B3kHzRfB.js → PaginationFooter-cj1tcZej.js} +1 -1
- package/dist/ui/assets/ProfileEditorDialog-C9M8uKZC.css +1 -0
- package/dist/ui/assets/ProfileEditorDialog-DD4y4GBC.js +14 -0
- package/dist/ui/assets/ProfilesView-BXU2DOA7.css +1 -0
- package/dist/ui/assets/ProfilesView-DNnzYIEY.js +7 -0
- package/dist/ui/assets/{RulesView-D9vZBiJ1.js → RulesView-CtKvlckZ.js} +1 -1
- package/dist/ui/assets/SettingsView-BaeKFF_N.js +1 -0
- package/dist/ui/assets/SettingsView-CWjIe05v.css +1 -0
- package/dist/ui/assets/{0be242294f7d791af850c6df38ac78a0-2cL6Ntwf.woff2 → e2a57555d97d0b02b45d9418eb6ee295-D9nhF3rM.woff2} +0 -0
- package/dist/ui/assets/index-CBInDvdJ.js +19 -0
- package/dist/ui/assets/index-mOLy7fkB.css +1 -0
- package/dist/ui/assets/{theme-BNq4FkXS.js → theme-D40MF8cQ.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/dist/web-bootstrap.js +113 -0
- package/docs/architecture-proposal.md +109 -0
- package/docs/autostart.md +101 -0
- package/docs/backend.md +92 -216
- package/docs/frontend.md +61 -97
- package/docs/usage.md +88 -186
- package/package.json +10 -6
- package/dist/commands/upgrade.js +0 -43
- package/dist/core-install-transaction.js +0 -114
- package/dist/core-update-coordination.js +0 -105
- package/dist/core-update-service.js +0 -245
- package/dist/managed-state-transaction.js +0 -377
- package/dist/offline-mutation.js +0 -58
- package/dist/profile-migration.js +0 -101
- package/dist/runtime-recovery.js +0 -31
- package/dist/sysproxy/darwin.js +0 -287
- package/dist/sysproxy/gnome.js +0 -181
- package/dist/sysproxy/legacy.js +0 -58
- package/dist/tun-guidance.js +0 -11
- package/dist/ui/assets/CodeEditorModal-6m0TJWyF.css +0 -1
- package/dist/ui/assets/CodeEditorModal-CFEnWsyh.js +0 -14
- package/dist/ui/assets/ProfileEditorDialog-BydoZthX.js +0 -1
- package/dist/ui/assets/ProfilesView-Btc1DOxE.js +0 -2
- package/dist/ui/assets/ProfilesView-ByqdFNHN.css +0 -1
- package/dist/ui/assets/SettingsFileDialog-CNFEAVs4.js +0 -1
- package/dist/ui/assets/SettingsView-BlDhZkXQ.js +0 -2
- package/dist/ui/assets/SettingsView-CngS3vBM.css +0 -1
- package/dist/ui/assets/index-B61V60w_.js +0 -19
- package/dist/ui/assets/index-bkyxJG8J.css +0 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { WEB_BOOTSTRAP_TTL_MS } from "./daemon/web-auth.js";
|
|
6
|
+
import { atomicWriteFileSync } from "./fs-atomic.js";
|
|
7
|
+
import { runSanitizedCommand, windowsSystemExecutable } from "./process.js";
|
|
8
|
+
const BOOTSTRAP_DIRECTORY = /^web-bootstrap-(\d{13})-[a-f0-9]{16}$/;
|
|
9
|
+
export function removeStaleBootstrapFiles(root, now = Date.now()) {
|
|
10
|
+
let entries;
|
|
11
|
+
try {
|
|
12
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const match = BOOTSTRAP_DIRECTORY.exec(entry.name);
|
|
19
|
+
if (entry.isDirectory() && !entry.isSymbolicLink() && match && Number(match[1]) <= now) {
|
|
20
|
+
removeBootstrapFile(path.join(root, entry.name, "index.html"));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function createPrivateDirectory(directory) {
|
|
25
|
+
if (process.platform !== "win32") {
|
|
26
|
+
fs.mkdirSync(directory, { mode: 0o700 });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const executable = windowsSystemExecutable("WindowsPowerShell/v1.0/powershell.exe");
|
|
30
|
+
if (!path.isAbsolute(executable))
|
|
31
|
+
throw new Error("Windows PowerShell is unavailable");
|
|
32
|
+
const encoded = Buffer.from(directory, "utf8").toString("base64");
|
|
33
|
+
const script = [
|
|
34
|
+
"$ErrorActionPreference = 'Stop'",
|
|
35
|
+
`$directory = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encoded}'))`,
|
|
36
|
+
"if (Test-Path -LiteralPath $directory) { throw 'Bootstrap directory already exists' }",
|
|
37
|
+
"$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User",
|
|
38
|
+
"$acl = [Security.AccessControl.DirectorySecurity]::new()",
|
|
39
|
+
"$acl.SetOwner($sid)",
|
|
40
|
+
"$acl.SetAccessRuleProtection($true, $false)",
|
|
41
|
+
"$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new($sid, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow'))",
|
|
42
|
+
"[IO.Directory]::CreateDirectory($directory, $acl) | Out-Null",
|
|
43
|
+
"$actual = [IO.Directory]::GetAccessControl($directory)",
|
|
44
|
+
"if (!$actual.AreAccessRulesProtected -or $actual.GetOwner([Security.Principal.SecurityIdentifier]).Value -ne $sid.Value) { throw 'Bootstrap directory is not private' }",
|
|
45
|
+
].join("; ");
|
|
46
|
+
runSanitizedCommand(executable, ["-NoProfile", "-NonInteractive", "-Command", script]);
|
|
47
|
+
}
|
|
48
|
+
export function writeBootstrapFile(layout, opts) {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
const expiresAt = Date.parse(opts.expiresAt);
|
|
51
|
+
if (!/^[a-f0-9]{64}$/.test(opts.token) ||
|
|
52
|
+
!Number.isFinite(expiresAt) ||
|
|
53
|
+
expiresAt <= now ||
|
|
54
|
+
expiresAt > now + WEB_BOOTSTRAP_TTL_MS) {
|
|
55
|
+
throw new Error("Invalid or expired browser authorization; run 'sash web' again.");
|
|
56
|
+
}
|
|
57
|
+
const target = new URL(opts.dashboardUrl);
|
|
58
|
+
if (target.protocol !== "http:" ||
|
|
59
|
+
target.hostname !== "127.0.0.1" ||
|
|
60
|
+
target.username ||
|
|
61
|
+
target.password) {
|
|
62
|
+
throw new Error("The dashboard must use the local Sash address");
|
|
63
|
+
}
|
|
64
|
+
fs.mkdirSync(layout.tempDir, { recursive: true });
|
|
65
|
+
removeStaleBootstrapFiles(layout.tempDir, now);
|
|
66
|
+
const name = `web-bootstrap-${expiresAt}-${crypto.randomBytes(8).toString("hex")}`;
|
|
67
|
+
const directory = path.join(layout.tempDir, name);
|
|
68
|
+
const filePath = path.join(directory, "index.html");
|
|
69
|
+
createPrivateDirectory(directory);
|
|
70
|
+
const handoff = JSON.stringify({ url: opts.dashboardUrl, token: opts.token }).replaceAll("<", "\\u003c");
|
|
71
|
+
const html = `<!doctype html>
|
|
72
|
+
<html lang="en">
|
|
73
|
+
<head>
|
|
74
|
+
<meta charset="utf-8" />
|
|
75
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
76
|
+
<meta name="referrer" content="no-referrer" />
|
|
77
|
+
<title>Sash</title>
|
|
78
|
+
</head>
|
|
79
|
+
<body>
|
|
80
|
+
<p>Opening the Sash dashboard...</p>
|
|
81
|
+
<script>
|
|
82
|
+
(() => {
|
|
83
|
+
const handoff = ${handoff};
|
|
84
|
+
window.location.replace(handoff.url + "#boot=" + handoff.token);
|
|
85
|
+
})();
|
|
86
|
+
</script>
|
|
87
|
+
</body>
|
|
88
|
+
</html>
|
|
89
|
+
`;
|
|
90
|
+
try {
|
|
91
|
+
atomicWriteFileSync(filePath, html, 0o600);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
removeBootstrapFile(filePath);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
return { filePath, fileUrl: pathToFileURL(filePath).href };
|
|
98
|
+
}
|
|
99
|
+
export function removeBootstrapFile(filePath) {
|
|
100
|
+
const directory = path.dirname(filePath);
|
|
101
|
+
if (path.basename(filePath) !== "index.html" ||
|
|
102
|
+
!BOOTSTRAP_DIRECTORY.test(path.basename(directory)))
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
const entry = fs.lstatSync(directory);
|
|
106
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
107
|
+
return;
|
|
108
|
+
fs.rmSync(filePath, { force: true });
|
|
109
|
+
fs.rmdirSync(directory);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Sash 高层架构
|
|
2
|
+
|
|
3
|
+
这是 2026-09-08 架构收缩提案的实施结果。目标是一个 Windows 优先、自用、容易掌控的网络工具:保留 Node.js / TypeScript、Vue 自研 WebUI 和霞鹜文楷,直接使用新状态格式,不提供旧接口或迁移层。
|
|
4
|
+
|
|
5
|
+
## 一张图
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart TB
|
|
9
|
+
CLI[CLI / 登录启动] --> API
|
|
10
|
+
UI[Vue WebUI] --> API
|
|
11
|
+
subgraph DAEMON[一个 sashd 进程]
|
|
12
|
+
API[本地 HTTP API / 鉴权 / 页面] --> APP[App:唯一状态写入者 / 一条变更队列]
|
|
13
|
+
APP --> PROFILES[Profiles:保存订阅与本地配置]
|
|
14
|
+
APP --> RUNTIME[Runtime:应用配置 / Core 启停与更新]
|
|
15
|
+
APP --> WINDOWS[Windows:系统代理 / 登录自启]
|
|
16
|
+
API --> GATEWAY[Core 查询 / 节点与连接控制 / 数据流]
|
|
17
|
+
end
|
|
18
|
+
PROFILES --> STATE[sash.json + 不可覆盖的 Profile 原文]
|
|
19
|
+
STATE --> RUNTIME
|
|
20
|
+
RUNTIME --> CONFIG[runtime/config.yaml]
|
|
21
|
+
RUNTIME --> CORE[Core 子进程]
|
|
22
|
+
CONFIG --> CORE
|
|
23
|
+
GATEWAY --> CORE
|
|
24
|
+
WINDOWS --> OS[当前用户的 Windows 设置]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
只需记住五条规则:
|
|
28
|
+
|
|
29
|
+
1. **正常写操作都进入 daemon。** CLI 负责发现、启动、请求和显示,不安装 Core、不提交配置、不接管事务。
|
|
30
|
+
2. **保存不等于应用。** 编辑、选择、订阅更新只改变保存状态;点击“应用配置”才重启 Core。
|
|
31
|
+
3. **一个元数据提交点。** 设置、Profile 列表和选择都在 `sash.json`。运行配置是可重新生成的产物。
|
|
32
|
+
4. **Core 更新只管二进制。** 不改 Profile,不退出 daemon;失败只回滚二进制及安装记录。
|
|
33
|
+
5. **桌面集成只维护 Windows。** 基础 Core / CLI 保留可移植代码,不保留 macOS / GNOME 代理和非 Windows 自启后端。
|
|
34
|
+
|
|
35
|
+
## 看代码时从哪里进入
|
|
36
|
+
|
|
37
|
+
| 要理解的事情 | 入口 | 边界 |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| 一个用户操作如何执行 | [daemon/app.ts](../src/daemon/app.ts) | 组装服务,排列步骤,取消准备工作 |
|
|
40
|
+
| 为什么不会有两个写入者 | [daemon/context.ts](../src/daemon/context.ts)、[daemon/entry.ts](../src/daemon/entry.ts) | 一个进程内队列,启动时取得实例租约 |
|
|
41
|
+
| 保存什么、如何提交 | [app-state.ts](../src/app-state.ts)、[profile-service.ts](../src/profile-service.ts) | 新原文先落盘,再原子提交元数据引用 |
|
|
42
|
+
| Apply 和启停的顺序 | [runtime-lifecycle.ts](../src/runtime-lifecycle.ts) | 校验后停止、发布、启动、恢复代理意图 |
|
|
43
|
+
| Core 更新与回滚 | [core-update.ts](../src/core-update.ts) | 固定二进制事务;下载在 [core.ts](../src/core.ts) |
|
|
44
|
+
| Windows 行为 | [system-proxy-manager.ts](../src/system-proxy-manager.ts)、[autostart.ts](../src/autostart.ts) | 代理条件恢复、当前用户登录注册 |
|
|
45
|
+
| WebUI 数据为何刷新 | [stores/runtime-actions.ts](../web/src/stores/runtime-actions.ts)、[stores/core-actions.ts](../web/src/stores/core-actions.ts) | 管理状态轮询,按当前页面加载 Core 数据 |
|
|
46
|
+
|
|
47
|
+
不引入工作流引擎、事件总线、数据库或依赖注入框架。底层文件、进程、下载、鉴权工具继续复用。
|
|
48
|
+
|
|
49
|
+
## 保存状态与运行状态
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
SASH_HOME/
|
|
53
|
+
sash.json schemaVersion: 2,设置、Profile 元数据、所选配置
|
|
54
|
+
profiles/<id>/<revision>.yaml 不可覆盖的原文版本
|
|
55
|
+
runtime/config.yaml 已生成的运行配置
|
|
56
|
+
bin/ Core;更新完成前保留 .bak
|
|
57
|
+
state/ 进程信息、安装记录、Core 更新与代理恢复记录
|
|
58
|
+
logs/
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
保存原文:解析、限制大小与结构 → 原子写入新版本 → 原子替换 `sash.json`。中途崩溃最多留下未引用的原文,已有引用仍完整。旧原文清理失败不撤销已成功的提交;这些版本用于安全发布,不提供历史版本管理。
|
|
62
|
+
|
|
63
|
+
Apply:生成候选 → Core 校验 → 恢复系统代理 → 停止已验证的旧 Core → 发布 `runtime/config.yaml` → 启动并验证新 Core → 按保存意图启用代理。
|
|
64
|
+
|
|
65
|
+
校验失败时旧 Core 继续运行。代理恢复失败时保留健康 Core。新 Core 启动失败时,保存的编辑保留,页面显示待应用;管理界面继续可用。
|
|
66
|
+
|
|
67
|
+
Core 更新:下载并验证 → 固定本次运行配置 → 停止 Core → 保留 `.bak` 并替换二进制/安装记录 → 健康检查 → 恢复原运行状态 → 清理备份。原本停止时也当场临时启动验证,随后仍停止。升级记录不包含设置或 Profile。
|
|
68
|
+
|
|
69
|
+
## 命令与界面
|
|
70
|
+
|
|
71
|
+
| 入口 | 行为 |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `sash web` | 启动管理进程、授权浏览器;Core 可以不存在或保持停止 |
|
|
74
|
+
| `sash start` | Core 停止时应用保存配置并启动;已运行时检查健康和代理意图 |
|
|
75
|
+
| `sash restart` / WebUI 应用配置 | 应用保存配置并重启 Core,保留 daemon 和浏览器会话 |
|
|
76
|
+
| WebUI 停止核心 | 停止 Core,保留管理界面 |
|
|
77
|
+
| `sash stop` | 恢复代理、停止 Core、退出 daemon |
|
|
78
|
+
| `sash update` | daemon 内完成 Core 更新与验证 |
|
|
79
|
+
| `sash auto on/off` | 设置 Windows 登录启动;不带参数只查看状态 |
|
|
80
|
+
|
|
81
|
+
Sash 程序本身更新使用 `sash stop` → npm 安装 → `sash start`。`sash upgrade`、`update --force`、在线原始设置编辑和配置热加载接口已删除。
|
|
82
|
+
|
|
83
|
+
## WebUI 保留什么、简化什么
|
|
84
|
+
|
|
85
|
+
Vue、现有页面、霞鹜文楷及字体切分构建链保留。现有懒加载、浅响应式集合、分页和日志批量更新继续使用。共享 `CoreControls` 处理首页、设置页和全局待应用提示中的启停操作。
|
|
86
|
+
|
|
87
|
+
前端只区分三种版本:`daemon.bootId` 标识管理进程;`revisions.profiles` 标识保存状态;`revisions.runtime` 标识 Core 运行变化。重命名、排序和未应用的编辑不会清空节点测速或 Core 缓存。
|
|
88
|
+
|
|
89
|
+
节点、连接、规则分别加载和记录失败。配置页不拉 Core 表,规则页只需要规则;日志只在可见日志页订阅。会话初始化只在进入或重连时进行,普通轮询无需重复请求 health。
|
|
90
|
+
|
|
91
|
+
## 保留的安全边界
|
|
92
|
+
|
|
93
|
+
进程身份无法确认就不终止;loopback 请求直接连接;子进程清理凭据;状态原子写入并保持私有权限;下载必须来自允许的地址并通过官方摘要;解压拒绝越界路径并限制大小;订阅作为不可信 YAML 处理。生成配置始终关闭 TUN,并拒绝独立 TUN listener。
|
|
94
|
+
|
|
95
|
+
普通业务只有一条队列;实例启动和 Windows 用户级 OS 操作保留必要的跨进程锁。停止/退出可取消下载与配置校验,已经开始的二进制替换按顺序完成。损坏记录不会通过默认值覆盖。daemon 被强杀后的孤儿 Core 和代理状态由下一次取得所有权的启动恢复,不增加第二个看护进程。
|
|
96
|
+
|
|
97
|
+
完整协议见 [后端架构](./backend.md),界面刷新策略见 [前端架构](./frontend.md),使用方式见 [操作指南](./usage.md)。
|
|
98
|
+
|
|
99
|
+
## 实施结果
|
|
100
|
+
|
|
101
|
+
与 `c7b2d7546ba7d478fa248f4b8f3a06a685ed7ba1` 比较,按相同范围统计已格式化源码,排除测试与生成文件:
|
|
102
|
+
|
|
103
|
+
| 范围 | 重构前 | 重构后 |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| 后端 | 92 个文件,17,329 行 | 81 个文件,11,196 行(减少 35.4%) |
|
|
106
|
+
| 前端 TS / Vue / CSS | 54 个文件,9,179 行 | 54 个文件,8,940 行 |
|
|
107
|
+
| 字体产物 | 232 个切片,8,597,176 字节 | 保持不变 |
|
|
108
|
+
|
|
109
|
+
验证覆盖类型检查、lint、完整测试、生产构建、npm 文件集与实际安装包、隔离管理进程启停,以及 Chromium / Firefox 页面与授权交互。Core/代理故障场景使用隔离测试适配器;Windows 注册表脚本只在临时测试键运行。没有对用户当前实例操作,也没有用这些结果宣称真实 Core 的 CPU 或内存改善。
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Automatic Startup
|
|
2
|
+
|
|
3
|
+
Sash can start in the background when the current Windows user signs in. Enable it from
|
|
4
|
+
**Settings → Start at Login** in the dashboard or from the CLI:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
sash auto on # enable or repair the startup entry
|
|
8
|
+
sash auto off # remove the startup entry
|
|
9
|
+
sash auto status # inspect without changing anything
|
|
10
|
+
sash auto # inspect status
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Changing automatic startup affects future logins. Use `sash start` and `sash stop`
|
|
14
|
+
to control the current runtime. Automatic startup does not open a browser.
|
|
15
|
+
|
|
16
|
+
## Installation and Data Directory
|
|
17
|
+
|
|
18
|
+
Enabling requires a built, direct npm-global installation:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install -g @astralyn/sash
|
|
22
|
+
sash auto on
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Source checkouts, `npm link`, local dependencies and temporary `npx` installations
|
|
26
|
+
cannot enable startup. They can still inspect or remove an existing entry.
|
|
27
|
+
The generated launcher records the absolute Node executable, Sash entry point and
|
|
28
|
+
current data directory. An absolute `SASH_HOME` override is preserved; it does not
|
|
29
|
+
depend on the login shell loading the same environment.
|
|
30
|
+
|
|
31
|
+
There is one startup entry per operating-system user. Enabling it with another
|
|
32
|
+
`SASH_HOME` replaces the entry to start that instance. Other instances report that
|
|
33
|
+
entry as `stale` because its target differs. Changing the Node installation or npm
|
|
34
|
+
prefix can also make the entry stale; run `sash auto on` from the new installation
|
|
35
|
+
to repair it. Upgrading Sash in the same prefix preserves the entry.
|
|
36
|
+
|
|
37
|
+
Automatic startup uses the normal `sash start` ownership and health-check flow.
|
|
38
|
+
It reads the saved settings and active profile at login, including the saved
|
|
39
|
+
system-proxy preference. Startup registration is managed by the OS, not by a
|
|
40
|
+
boolean in `sash.json`.
|
|
41
|
+
|
|
42
|
+
## Platform Behavior
|
|
43
|
+
|
|
44
|
+
| Platform | Registration | When it runs |
|
|
45
|
+
| :--- | :--- | :--- |
|
|
46
|
+
| Windows | `Sash` value in the current user's `Run` registry key; hidden WScript launcher | User sign-in, without a console window |
|
|
47
|
+
Other platforms report startup integration as unsupported.
|
|
48
|
+
|
|
49
|
+
The daemon performs registration changes. If it is stopped, `sash auto on/off`
|
|
50
|
+
starts management first, without starting Core. Registration does not require a
|
|
51
|
+
privileged system service or restart the current Core.
|
|
52
|
+
|
|
53
|
+
## Status and Diagnostics
|
|
54
|
+
|
|
55
|
+
`sash status` includes automatic startup. `sash status --json` returns an
|
|
56
|
+
`autostart` object with `state`, `canEnable` and `reason`:
|
|
57
|
+
|
|
58
|
+
| State | Meaning |
|
|
59
|
+
| :--- | :--- |
|
|
60
|
+
| `on` | The current launcher is registered and enabled by the OS |
|
|
61
|
+
| `off` | No startup entry is registered |
|
|
62
|
+
| `stale` | An entry exists, but its launcher, installation paths or data directory differ |
|
|
63
|
+
| `disabled` | The current entry is disabled by the OS |
|
|
64
|
+
| `unknown` | The OS state could not be inspected |
|
|
65
|
+
| `unsupported` | This operating system has no supported startup backend |
|
|
66
|
+
|
|
67
|
+
`canEnable` reports whether this installation can register a stable launcher.
|
|
68
|
+
`reason` explains an unavailable installation or a failed inspection. An inspection
|
|
69
|
+
failure does not erase runtime observations; status still reports them and exits
|
|
70
|
+
with code `2`. Explicit `sash auto off` does not need a successful inspection.
|
|
71
|
+
The dashboard also provides **Refresh status** and **Remove startup entry** for
|
|
72
|
+
recovery.
|
|
73
|
+
|
|
74
|
+
Each login attempt records its start and outcome in `<SASH_HOME>/logs/sash.log`.
|
|
75
|
+
The log rotates at 1 MiB, retaining one previous file as `sash.log.1`:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
sash logs --startup
|
|
79
|
+
sash logs --startup -f
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
These diagnostics remain readable when invalid settings prevented startup.
|
|
83
|
+
`--startup` cannot be combined with `--daemon` or `--errors`. If no attempt was
|
|
84
|
+
recorded, check the OS startup entry and the paths above first.
|
|
85
|
+
|
|
86
|
+
## Uninstalling
|
|
87
|
+
|
|
88
|
+
Remove startup before uninstalling the package:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
sash auto off
|
|
92
|
+
sash stop
|
|
93
|
+
npm uninstall -g @astralyn/sash
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If Sash has already been uninstalled, remove its startup entry manually:
|
|
97
|
+
|
|
98
|
+
- Windows: remove the `Sash` value under
|
|
99
|
+
`HKCU\Software\Microsoft\Windows\CurrentVersion\Run` and, if present,
|
|
100
|
+
`HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run`.
|
|
101
|
+
Then remove `%LOCALAPPDATA%\Sash\autostart\start.vbs`.
|