@astralyn/sash 0.1.1 → 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 +25 -0
- package/README.md +17 -5
- 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 -15
- package/dist/commands/logs.js +11 -4
- package/dist/commands/shared.js +2 -4
- package/dist/commands/status.js +5 -1
- package/dist/commands/update.js +7 -3
- package/dist/commands/web.js +14 -50
- package/dist/contracts.js +168 -348
- 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 +129 -117
- package/dist/daemon/context.js +13 -7
- package/dist/daemon/entry.js +21 -38
- package/dist/daemon/errors.js +3 -0
- package/dist/daemon/handlers/autostart.js +18 -0
- package/dist/daemon/handlers/core.js +32 -9
- package/dist/daemon/handlers/daemon.js +17 -6
- package/dist/daemon/handlers/profiles.js +12 -2
- package/dist/daemon/handlers/settings.js +9 -36
- package/dist/daemon/router.js +26 -21
- package/dist/daemon/scheduler.js +3 -1
- package/dist/daemon/server.js +5 -2
- package/dist/daemon-client.js +13 -3
- package/dist/daemon-http.js +6 -0
- package/dist/daemon-lifecycle.js +19 -196
- package/dist/github.js +7 -2
- package/dist/http.js +7 -5
- package/dist/mihomo-config.js +2 -1
- 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 +40 -19
- package/dist/settings-service.js +29 -224
- package/dist/settings.js +61 -289
- package/dist/status.js +24 -16
- 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-CfaOG2JV.js → ConnectionsView-BgXQM6Z4.js} +1 -1
- package/dist/ui/assets/{LogsView-BDiRATXN.js → LogsView-2f542P8z.js} +1 -1
- package/dist/ui/assets/{PaginationFooter-DTytr1iu.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-CjEyCN1A.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/index-CBInDvdJ.js +19 -0
- package/dist/ui/assets/index-mOLy7fkB.css +1 -0
- package/dist/ui/assets/{theme-BwDBMsKO.js → theme-D40MF8cQ.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/docs/architecture-proposal.md +109 -0
- package/docs/autostart.md +101 -0
- package/docs/backend.md +92 -224
- package/docs/frontend.md +60 -106
- package/docs/usage.md +92 -164
- package/package.json +3 -3
- 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 -375
- 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/ui/assets/CodeEditorModal-6m0TJWyF.css +0 -1
- package/dist/ui/assets/CodeEditorModal-D1zn-jRS.js +0 -14
- package/dist/ui/assets/ProfileEditorDialog-CyyP6OF1.js +0 -1
- package/dist/ui/assets/ProfilesView-ByqdFNHN.css +0 -1
- package/dist/ui/assets/ProfilesView-hFa7-O72.js +0 -2
- package/dist/ui/assets/SettingsFileDialog-CjBSyH4K.js +0 -1
- package/dist/ui/assets/SettingsView-D5QvJId6.css +0 -1
- package/dist/ui/assets/SettingsView-DsRntRfn.js +0 -2
- package/dist/ui/assets/index-CxQTf_P9.css +0 -1
- package/dist/ui/assets/index-_mb4OfYp.js +0 -19
package/docs/frontend.md
CHANGED
|
@@ -1,135 +1,89 @@
|
|
|
1
|
-
# Frontend Architecture
|
|
2
|
-
|
|
3
|
-
The WebUI is a Vue 3 application built with Vite and bundled into `dist/ui/`. sashd serves it at `http://127.0.0.1:19090/ui/`; `<root>/ui/index.html` can override the bundled dashboard.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Build and Quality Gates
|
|
8
|
-
|
|
9
|
-
- `npm run build:ui` calls the Vite Node API through `scripts/build-ui.mjs`.
|
|
10
|
-
- `npm run typecheck:web` runs `vue-tsc`, including Vue templates.
|
|
11
|
-
- `npm test` runs server type checking, WebUI type checking, backend tests and WebUI TypeScript tests.
|
|
12
|
-
- Biome checks WebUI TypeScript and Vite configuration. Vue templates are type-checked by `vue-tsc` and compiled by Vite.
|
|
13
|
-
- Vite 6 builds the bundled dashboard on the declared Node.js 24 baseline.
|
|
14
|
-
- CI runs lint, server/WebUI type checks, all tests, production builds and actual tarball pack/install/CLI/UI smoke on Windows, macOS and Linux with Node.js 24.
|
|
15
|
-
|
|
16
|
-
The `cn-font-split` dependency has a scoped `koffi: 2.16.3` override. The [Koffi changelog](https://koffi.dev/changelog) documents Node.js 24.14+ teardown fixes relevant to the native font splitter.
|
|
17
|
-
|
|
18
|
-
Workflows using `npm ci --ignore-scripts` explicitly initialize the tested native font subsetter with `node node_modules/cn-font-split/dist/cli.js i default@7.6.8` before building. The npm wrapper and native subsetter have separate versions; 7.6.8 is the native release used by the validated builds. This runs the selected build tool directly while keeping automatic dependency lifecycle hooks disabled.
|
|
19
|
-
|
|
20
|
-
`npx tsx scripts/web-auth-ui-verify.mts` checks the real private-file/browser/daemon authorization exchange in both engines, including bare URLs, refresh, replay, daemon restart, reauthorization and the Settings/Overview layouts without TUN controls. It uses isolated temporary roots and ports, a fake Core and system proxy. Screenshots and a report remain in the OS temporary directory.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## 2. Source Layout
|
|
25
|
-
|
|
26
|
-
```text
|
|
27
|
-
web/src/
|
|
28
|
-
├── api/index.ts typed REST client and WebSocket reconnect logic
|
|
29
|
-
├── api/session.ts bootstrap exchange and per-tab credential ownership
|
|
30
|
-
├── components/
|
|
31
|
-
│ ├── AppSidebar.vue
|
|
32
|
-
│ ├── ConfirmDialog.vue
|
|
33
|
-
│ ├── Icon.vue semantic icon wrapper
|
|
34
|
-
│ ├── icons.ts tree-shaken Remix Icon component mapping
|
|
35
|
-
│ ├── ProxyGroupSection.vue reusable group header/node grid
|
|
36
|
-
│ ├── TrafficChart.vue
|
|
37
|
-
│ └── ...
|
|
38
|
-
├── i18n/ Chinese source messages and typed English mirror
|
|
39
|
-
├── stores/index.ts runtime state, refresh actions and polling
|
|
40
|
-
├── stores/state-ownership.ts pure generations, cleanup and sync helpers
|
|
41
|
-
├── styles/main.css design tokens and shared utility/component styles
|
|
42
|
-
├── types/index.ts core-controller response types; daemon types are shared
|
|
43
|
-
├── views/
|
|
44
|
-
│ ├── ConnectionView.vue read-only browser authorization instructions
|
|
45
|
-
│ ├── OverviewView.vue status, traffic, modes and proxy groups
|
|
46
|
-
│ ├── ProfilesView.vue download/import/update/select/delete profiles
|
|
47
|
-
│ ├── LogsView.vue
|
|
48
|
-
│ ├── ConnectionsView.vue
|
|
49
|
-
│ ├── RulesView.vue
|
|
50
|
-
│ └── SettingsView.vue
|
|
51
|
-
├── App.vue shell, view selection and stream lifecycle
|
|
52
|
-
├── theme.ts persisted system/light/dark theme state
|
|
53
|
-
└── router.ts small hash router
|
|
54
|
-
```
|
|
1
|
+
# Frontend Architecture
|
|
55
2
|
|
|
56
|
-
|
|
3
|
+
Sash retains its Vue 3 / Vite WebUI, built into `dist/ui/` and served by the daemon. Existing LXGW WenKai Lite font assets, appearance and Unicode-range splitting remain unchanged. No framework replacement or new UI/state framework is introduced.
|
|
57
4
|
|
|
58
|
-
|
|
5
|
+
## State ownership
|
|
59
6
|
|
|
60
|
-
|
|
7
|
+
`stores/state.ts` is the single shared shallow-reactive state. Large collections are replaced by reference. `stores/index.ts` only exports the public actions/selectors; it is not another state store.
|
|
61
8
|
|
|
62
|
-
|
|
9
|
+
| Module | Responsibility |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `stores/runtime-actions.ts` | Non-overlapping status polling, boot changes and settings actions |
|
|
12
|
+
| `stores/profile-actions.ts` | Saved profile mutations and metadata refresh |
|
|
13
|
+
| `stores/core-actions.ts` | Independent Core resource loads and mode/node/connection controls |
|
|
14
|
+
| `stores/state-ownership.ts` | Request generations, runtime identity and pure selectors |
|
|
15
|
+
| `stores/telemetry.ts` | Traffic history and batched, bounded log records |
|
|
16
|
+
| `api/session.ts` | One-time bootstrap, per-tab session and daemon identity |
|
|
17
|
+
| `api/index.ts` | Shared daemon client, Core queries and streams |
|
|
63
18
|
|
|
64
|
-
|
|
19
|
+
Three values determine refresh ownership:
|
|
65
20
|
|
|
66
|
-
|
|
21
|
+
- `daemon.bootId`: a new daemon invalidates old authorization and metadata revision comparisons.
|
|
22
|
+
- `revisions.profiles`: saved-state changes refresh profile metadata. It does not invalidate Core resources.
|
|
23
|
+
- `revisions.runtime`: with `bootId`, identifies the Core runtime. Replacement clears Core resources, traffic and manual latency results.
|
|
67
24
|
|
|
68
|
-
-
|
|
69
|
-
- `refreshConnections()`
|
|
70
|
-
- `refreshProxies()`
|
|
71
|
-
- `refreshProfiles()`
|
|
72
|
-
- `refreshRuntimeState()`
|
|
73
|
-
- profile mutation actions such as `updateProfile()` and `activateProfile()`
|
|
74
|
-
- runtime intent actions such as `setOutboundMode()` and `selectGroupProxy()`
|
|
75
|
-
- `startRuntimePolling()`
|
|
25
|
+
Each resource has its own loaded flag and error. A failed rules query does not discard working node data. Failures for the same runtime retain prior data and show degradation; a new runtime cannot inherit old data. Per-domain request generations discard late successes and errors. Mutation results from a replaced daemon/Core cannot overwrite its successor.
|
|
76
26
|
|
|
77
|
-
|
|
27
|
+
## Refresh and performance
|
|
78
28
|
|
|
79
|
-
|
|
29
|
+
Status polls every two seconds after the previous cycle finishes. Hidden or unauthorized tabs use a slower interval; returning to the foreground refreshes promptly. Session initialization runs on entry/reconnect, with public status boot identity detecting daemon restarts.
|
|
80
30
|
|
|
81
|
-
|
|
31
|
+
| Visible page | Core requests |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| Overview | Config/mode and proxies approximately every third cycle; connections approximately every fifth cycle |
|
|
34
|
+
| Connections | Connections each cycle |
|
|
35
|
+
| Rules | Rules on entry/runtime change, then cached |
|
|
36
|
+
| Profiles / Settings | No background Core tables |
|
|
37
|
+
| Logs | Log stream while visible |
|
|
82
38
|
|
|
83
|
-
|
|
39
|
+
Entering a page loads its resources immediately. Profile saves only refresh management metadata. Explicit Apply refreshes the resources visible after Core replacement. Traffic is one shared stream for visible consumers; traffic/log sockets pause when the page is hidden or the runtime/session is unavailable.
|
|
84
40
|
|
|
85
|
-
|
|
41
|
+
Existing optimizations remain: async route/editor chunks, shallow collections, collapsed node groups unmounting their cards, paginated connections/rules, separate manual latency results, and roughly 100 ms log batches capped at 600 rows. Font slices load only for rendered glyph ranges.
|
|
86
42
|
|
|
87
|
-
|
|
43
|
+
## Views and shared controls
|
|
88
44
|
|
|
89
|
-
|
|
45
|
+
`App.vue` owns the shell, route composition, session gate, global pending-configuration bar and stream lifetime. Existing Overview, Profiles, Logs, Connections, Rules and Settings pages remain.
|
|
90
46
|
|
|
91
|
-
|
|
47
|
+
`CoreControls.vue` and `useCoreControl()` share busy state and Apply/start/stop actions across the Overview, Settings and pending bar. Applying while running asks for confirmation because it restarts Core. Stopping Core keeps management open.
|
|
92
48
|
|
|
93
|
-
|
|
94
|
-
- local YAML import
|
|
95
|
-
- Update All and per-profile update
|
|
96
|
-
- active profile selection
|
|
97
|
-
- quota/expiry display from provider metadata
|
|
98
|
-
- persisted update errors
|
|
99
|
-
- delete confirmation
|
|
49
|
+
Profile selection and edits are saved first. The pending bar indicates that the running configuration differs. Overview displays the applied profile/port; Profiles shows the saved selection. The YAML editor submits the content revision read on open, preventing another tab's later edits from being overwritten. The raw `sash.json` editor is removed.
|
|
100
50
|
|
|
101
|
-
|
|
51
|
+
Settings drafts remain local until Save and survive status polling. A saved port/LAN change waits for Apply. The system-proxy switch performs its own operation and remains available for recovery when Core is stopped. The login-startup card uses the observed OS registration and explicit enable/remove actions.
|
|
102
52
|
|
|
103
|
-
|
|
53
|
+
`PageHeader`, `ProxyGroupSection`, the shared code editor, pagination, confirmation service and focus/scroll-lock composables remain reusable building blocks. Light/dark themes, Chinese/English copy and mobile navigation are retained. There is no generic table framework or event bus.
|
|
104
54
|
|
|
105
|
-
|
|
55
|
+
## Authorization and transport
|
|
106
56
|
|
|
107
|
-
|
|
57
|
+
`src/contracts.ts` and `src/sash-client.ts` define the browser-safe daemon protocol. Runtime imports do not pull Node-specific implementations into the browser. Core types describe only the data the UI uses.
|
|
108
58
|
|
|
109
|
-
|
|
59
|
+
The browser consumes and immediately removes the private handoff fragment, exchanges it once, and stores its session with the issuing daemon identity in `sessionStorage`. Concurrent initialization shares the exchange; storage denial falls back to memory. Old responses cannot resurrect or revoke a newer session. A bare URL shows connection instructions and never gains control through public health.
|
|
110
60
|
|
|
111
|
-
`
|
|
61
|
+
Core HTTP requests carry `X-Sash-Token`; streams use private WebSocket subprotocol authentication. The daemon replaces these credentials with its internal controller bearer. Frames require finite nonnegative counters or known textual log records. Each stream owns one reconnect timer and ignores frames from older runtime generations.
|
|
112
62
|
|
|
113
|
-
|
|
114
|
-
- reads an error response body once and surfaces JSON or plain-text messages;
|
|
115
|
-
- parses WebSocket frames separately from consumer callbacks, validates finite non-negative traffic counters and known textual log records, and silently drops malformed frames;
|
|
116
|
-
- maintains at most one reconnect timer per stream;
|
|
117
|
-
- binds stream callbacks to the current runtime generation and stops stream ownership when the daemon session or healthy Core is unavailable;
|
|
118
|
-
- resets stale traffic rates/history when the current traffic stream disconnects without allowing an older socket's close event to clear a replacement stream;
|
|
119
|
-
- sends `X-Sash-Token` on HTTP requests after initialization.
|
|
63
|
+
## Development and verification
|
|
120
64
|
|
|
121
|
-
|
|
65
|
+
```sh
|
|
66
|
+
npm run typecheck
|
|
67
|
+
npm run lint
|
|
68
|
+
npm test
|
|
69
|
+
npm run build
|
|
70
|
+
node --import tsx scripts/ui-verify.mts
|
|
71
|
+
node --import tsx scripts/profile-ui-verify.mts
|
|
72
|
+
node --import tsx scripts/web-auth-ui-verify.mts
|
|
73
|
+
node --import tsx scripts/autostart-ui-verify.mts
|
|
74
|
+
```
|
|
122
75
|
|
|
123
|
-
-
|
|
124
|
-
- `api.connectLogs(callback)` → `/core/api/logs`
|
|
76
|
+
The browser scripts use isolated data, non-default ports and fake Core/OS adapters in Chromium and Firefox. The main script checks saved/applied state, authentication, font loading, request counts and layouts with 300 nodes, 10,000 rules and 500 connections. Other scripts retain focused profile, private-file authorization and autostart interaction coverage. These are behavioral checks, not a real-Core CPU/memory benchmark.
|
|
125
77
|
|
|
126
|
-
|
|
78
|
+
For local source development:
|
|
127
79
|
|
|
128
|
-
|
|
80
|
+
```sh
|
|
81
|
+
node scripts/dev.mjs web
|
|
82
|
+
node scripts/dev.mjs build # rebuild UI, then refresh the page
|
|
83
|
+
node scripts/dev.mjs stop # before loading backend changes
|
|
84
|
+
node scripts/dev.mjs web
|
|
85
|
+
```
|
|
129
86
|
|
|
130
|
-
|
|
87
|
+
The launcher uses a separate `-dev` data directory and initial ports `18890`, `18990`, `28990`. `SASH_DEV_HOME` selects another absolute directory. It passes development defaults to the daemon without writing settings itself. `web` starts management only; `restart` applies configuration to Core. A backend code change requires stopping and starting the daemon.
|
|
131
88
|
|
|
132
|
-
|
|
133
|
-
- Logs receive monotonic IDs before entering the capped 600-row buffer, providing stable Vue keys and an update sequence even when length remains constant.
|
|
134
|
-
- The global confirm service settles a previous pending Promise before opening another dialog; Escape, route changes and component unmount cancel the active confirmation.
|
|
135
|
-
- The global banner distinguishes an unreachable daemon from a degraded same-owner Core snapshot and an unavailable new-owner snapshot.
|
|
89
|
+
The font build continues to use `cn-font-split`, native subsetter `default@7.6.8` and the scoped `koffi: 2.16.3` override. Install the native subsetter explicitly when dependency lifecycle scripts are disabled. Bundled Vue/Remix Icon licenses remain in `THIRD_PARTY_NOTICES.md` and `docs/remix-icon-license.txt`.
|
package/docs/usage.md
CHANGED
|
@@ -1,201 +1,129 @@
|
|
|
1
1
|
# Sash User & Operations Guide
|
|
2
2
|
|
|
3
|
-
Sash is a lightweight
|
|
3
|
+
Sash is a lightweight network toolbox for developers, learning and research, with a CLI and built-in web dashboard. Windows is the primary desktop platform.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## 1. Quick Start
|
|
5
|
+
## Start and configure
|
|
8
6
|
|
|
9
7
|
```sh
|
|
10
|
-
sash start
|
|
11
|
-
sash
|
|
8
|
+
sash web # start management and authorize the browser; Core stays stopped
|
|
9
|
+
sash start # install Core if missing, apply saved configuration and start
|
|
12
10
|
sash status
|
|
11
|
+
sash stop # restore the prior proxy, stop Core and exit the daemon
|
|
13
12
|
```
|
|
14
13
|
|
|
15
|
-
Profiles,
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 2. CLI Command Reference
|
|
20
|
-
|
|
21
|
-
### Lifecycle Management
|
|
22
|
-
|
|
23
|
-
| Command | Description |
|
|
24
|
-
| :--- | :--- |
|
|
25
|
-
| `sash start` | Install missing components, ensure the background daemon is running, then reconcile/start the core. It is safe to repeat. |
|
|
26
|
-
| `sash stop` | Restore the pre-Sash system proxy, stop the Core and shut down the daemon; exits with an error if safe shutdown cannot be verified. |
|
|
27
|
-
| `sash restart` | Restart the whole runtime: the daemon exits through its maintenance boundary and a fresh daemon starts the core. |
|
|
28
|
-
| `sash status [--json]` | Show daemon/core state, active profile, endpoints and system proxy state; incomplete observations exit with code 2. |
|
|
29
|
-
| `sash logs [-n N] [-f] [--errors] [--daemon]` | View core or daemon logs; `-f` follows new output. |
|
|
30
|
-
|
|
31
|
-
### Status JSON and exit codes
|
|
32
|
-
|
|
33
|
-
`sash status --json` emits the versioned `schemaVersion: 1` contract below. Unobservable runtime values are `null`; they are never changed to `false` merely because a query timed out.
|
|
34
|
-
|
|
35
|
-
```json
|
|
36
|
-
{
|
|
37
|
-
"schemaVersion": 1,
|
|
38
|
-
"complete": true,
|
|
39
|
-
"healthy": true,
|
|
40
|
-
"queryError": null,
|
|
41
|
-
"daemon": {
|
|
42
|
-
"state": "healthy",
|
|
43
|
-
"running": true,
|
|
44
|
-
"healthy": true,
|
|
45
|
-
"pid": 1234,
|
|
46
|
-
"port": 19090
|
|
47
|
-
},
|
|
48
|
-
"core": {
|
|
49
|
-
"running": true,
|
|
50
|
-
"healthy": true,
|
|
51
|
-
"pid": 1235,
|
|
52
|
-
"version": "v1.19.30",
|
|
53
|
-
"installedVersion": "v1.19.30"
|
|
54
|
-
},
|
|
55
|
-
"systemProxy": {
|
|
56
|
-
"desired": false,
|
|
57
|
-
"daemonApplied": false,
|
|
58
|
-
"osObserved": {
|
|
59
|
-
"supported": true,
|
|
60
|
-
"enabled": false,
|
|
61
|
-
"server": null,
|
|
62
|
-
"details": null
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
"uiInstalled": true,
|
|
66
|
-
"endpoints": {
|
|
67
|
-
"mixedProxy": "127.0.0.1:7890",
|
|
68
|
-
"controller": "127.0.0.1:9090",
|
|
69
|
-
"daemonApi": "http://127.0.0.1:19090",
|
|
70
|
-
"dashboard": "http://127.0.0.1:19090/ui/"
|
|
71
|
-
},
|
|
72
|
-
"activeProfile": null,
|
|
73
|
-
"tun": {
|
|
74
|
-
"desired": false,
|
|
75
|
-
"active": false
|
|
76
|
-
},
|
|
77
|
-
"paths": {
|
|
78
|
-
"root": "<data directory>",
|
|
79
|
-
"config": "<data directory>/config.yaml"
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
```
|
|
14
|
+
In **Profiles**, import YAML or download a remote profile, then select it. Selection, content edits, downloads and scheduled updates are saved without changing the running Core. Click **Apply configuration** to use the saved configuration. This restarts Core and briefly interrupts connections. Overview shows the profile and port actually applied.
|
|
83
15
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
| Exit code | Meaning |
|
|
87
|
-
| :--- | :--- |
|
|
88
|
-
| `0` | The status is complete, including a known stopped state. |
|
|
89
|
-
| `2` | Status output was produced, but daemon/Core/OS state could not be fully observed. |
|
|
90
|
-
| `1` | The command itself failed, for example because local state is corrupt. |
|
|
16
|
+
If validation fails, the previous Core keeps running. If starting the new configuration fails, your saved edits remain and the dashboard stays available for correction. There is no automatic rollback of saved edits.
|
|
91
17
|
|
|
92
|
-
|
|
18
|
+
## Commands
|
|
93
19
|
|
|
94
|
-
|
|
20
|
+
| Command | Behavior |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `sash start` | Ensure management exists; start with saved configuration if stopped. Repeated starts check the running Core and proxy intent. |
|
|
23
|
+
| `sash restart` | Apply saved configuration and restart Core; keep the daemon and browser sessions. |
|
|
24
|
+
| `sash stop` | Restore proxy, stop Core and exit management. Report an error if safe shutdown cannot be verified. |
|
|
25
|
+
| `sash status [--json]` | Read runtime, endpoint, saved profile, proxy and autostart observations. Bare `sash` does the same. |
|
|
26
|
+
| `sash web` | Start management if needed and authorize/open the dashboard. |
|
|
27
|
+
| `sash web --no-open` | Start management and print its address without authorizing a browser. |
|
|
28
|
+
| `sash update [--version TAG]` | Download, verify and install a Core release through the daemon. |
|
|
29
|
+
| `sash auto [on\|off\|status]` | Set or inspect Windows login startup. No argument means status. |
|
|
30
|
+
| `sash logs [-n N] [-f] [--errors] [--daemon]` | Read Core or daemon logs; follow waits for creation and handles rotation. |
|
|
31
|
+
| `sash logs --startup [-n N] [-f]` | Read login attempts, including settings errors before daemon startup. |
|
|
32
|
+
| `sash version` | Print the package version. |
|
|
95
33
|
|
|
96
|
-
|
|
34
|
+
WebUI **Stop Core** keeps the management process open. To reload updated Sash program code, use stop followed by start/web. `restart` only replaces Core.
|
|
97
35
|
|
|
98
|
-
|
|
36
|
+
## Browser access
|
|
99
37
|
|
|
100
|
-
|
|
38
|
+
Run `sash web` as the same user and with the same `SASH_HOME` as the instance. A private local handoff authorizes the browser without printing credentials. It expires after 90 seconds and works once; rerun the command if needed.
|
|
101
39
|
|
|
102
|
-
|
|
103
|
-
| :--- | :--- |
|
|
104
|
-
| `sash web` | Authorize this browser and open `http://127.0.0.1:19090/ui/`. |
|
|
105
|
-
| `sash web --no-open` | Print the dashboard URL without opening or authorizing a browser. |
|
|
40
|
+
An authorized tab survives refresh and Core restarts/updates. A daemon restart needs a new authorization. If browser storage is disabled, authorization lasts only for the current page. Opening a bare dashboard address displays connection instructions.
|
|
106
41
|
|
|
107
|
-
|
|
42
|
+
## Settings and profiles
|
|
108
43
|
|
|
109
|
-
|
|
44
|
+
The Settings page saves the mixed proxy port and LAN access for the next Apply. The system-proxy switch takes effect separately and requires a healthy Core to enable. A failed disable keeps the saved off intent; retry it after resolving the OS problem.
|
|
110
45
|
|
|
111
|
-
|
|
46
|
+
Remote profiles use the provider's update interval, defaulting to 24 hours. The daemon checks for due updates every 15 minutes. Updates save new content and indicate pending Apply. Identical content does not create a new content revision. Rename and reorder do not affect running data or latency results.
|
|
112
47
|
|
|
113
|
-
|
|
48
|
+
The profile editor rejects a save if another edit changed its content revision. Reopen the current content before retrying. The raw application settings file has no online editor.
|
|
114
49
|
|
|
115
|
-
|
|
50
|
+
Login startup is managed through the OS registration, not a boolean in the settings file. Enabling it requires a direct global npm installation. See [Automatic Startup](./autostart.md).
|
|
116
51
|
|
|
117
|
-
|
|
52
|
+
## State format and data
|
|
118
53
|
|
|
119
|
-
|
|
120
|
-
| :--- | :--- |
|
|
121
|
-
| `sash update [--version V] [--force]` | Download and validate a replacement core, then swap it in transactionally. |
|
|
122
|
-
| `sash upgrade [--version V]` | Upgrade the Sash package through npm; requires `sashd` to be stopped first. |
|
|
123
|
-
| `sash version` | Print the Sash package version. |
|
|
54
|
+
This branch requires a new schema-2 manifest and provides no migration from older releases. Stop the old instance using its existing installation, keep any profile YAML you need, then use a fresh data directory and import those files normally. Old state is never silently overwritten.
|
|
124
55
|
|
|
125
|
-
|
|
56
|
+
`sash.json` contains `{schemaVersion: 2, revision, settings, profiles}`. `profiles` contains the saved `activeId` and metadata list. The following keys live inside `settings`:
|
|
126
57
|
|
|
127
|
-
|
|
58
|
+
| Key | Initial value | Meaning |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `mixedPort` | `7890` | HTTP/SOCKS mixed proxy port |
|
|
61
|
+
| `allowLan` | `false` | Accept proxy traffic from other devices |
|
|
62
|
+
| `systemProxy` | `false` | Desired Windows system-proxy state |
|
|
63
|
+
| `controller` | `127.0.0.1:9090` | Internal loopback controller address |
|
|
64
|
+
| `daemonPort` | `19090` | Management API and dashboard port |
|
|
65
|
+
| `secret` | random | Private controller credential |
|
|
66
|
+
| `daemonSecret` | random | Private CLI credential |
|
|
128
67
|
|
|
129
|
-
|
|
68
|
+
Controller/daemon addresses and credentials can be edited only while Sash is stopped, then read at daemon startup. All three ports must differ. Secrets cannot be blank. Invalid, unknown-field, oversized or unsupported-format state is rejected intact. There are no TUN or legacy subscription settings.
|
|
130
69
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
| `mixedPort` | `7890` | Local HTTP/SOCKS5 mixed inbound port. |
|
|
137
|
-
| `controller` | `127.0.0.1:9090` | Internal controller listen address; only loopback hosts are accepted. |
|
|
138
|
-
| `daemonPort` | `19090` | Daemon API and WebUI port. |
|
|
139
|
-
| `secret` | *(random)* | Internal controller secret. It is never returned by the public status API. |
|
|
140
|
-
| `daemonSecret` | *(random)* | CLI bearer secret for state-changing daemon requests. |
|
|
141
|
-
| `systemProxy` | `false` | Desired OS-level system proxy state. |
|
|
142
|
-
| `tun` | `false` | Legacy compatibility field; always off in 0.1.1. Existing true values migrate to false on load; new true values are rejected. |
|
|
143
|
-
| `allowLan` | `false` | Accept proxy traffic from other devices. |
|
|
144
|
-
|
|
145
|
-
A legacy `subscriptionUrl` key is migrated once into `profiles/index.json` and then removed. It has priority over legacy `config.yaml` import. If no `profiles/index.json` has ever been created, startup/offline initialization may import an existing `config.yaml` once as the active local profile named `Imported config` (`url: ""`, updates disabled). A present empty index opts out. To avoid importing Sash's own generated default, the file must be valid core-format YAML and contain non-default routing content after managed keys are removed: nonempty proxies/providers, or nonempty rules/groups differing from the DIRECT-only default. The runtime `config.yaml` is kept unchanged during import; later profile application re-renders and validates it. Invalid YAML/config fails closed without overwriting the file.
|
|
70
|
+
| Platform | Default directory |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| Windows | `%LOCALAPPDATA%\Sash` |
|
|
73
|
+
| macOS | `~/Library/Application Support/Sash` |
|
|
74
|
+
| Linux | `$XDG_DATA_HOME/sash` or `~/.local/share/sash` |
|
|
146
75
|
|
|
147
|
-
|
|
76
|
+
Use an absolute `SASH_HOME` to select another directory. Use a local filesystem supporting atomic rename and hard links.
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
sash.json settings, profiles, selection and saved-state revision
|
|
80
|
+
profiles/<id>/<revision>.yaml immutable source content
|
|
81
|
+
runtime/config.yaml generated runtime configuration
|
|
82
|
+
bin/ Core executable and temporary update backup
|
|
83
|
+
state/install.json installed version
|
|
84
|
+
state/core-update-transaction.json active binary update/recovery
|
|
85
|
+
state/system-proxy.json original proxy snapshot and recovery phase
|
|
86
|
+
state/sash.pid, state/sashd.pid process discovery records
|
|
87
|
+
state/sashd*.lock daemon singleton/startup ownership
|
|
88
|
+
logs/ Core, daemon and login diagnostics
|
|
89
|
+
ui/ optional custom dashboard override
|
|
90
|
+
```
|
|
148
91
|
|
|
149
|
-
|
|
92
|
+
The manifest and sources use atomic publication. Old source revisions may be cleaned after successful saves; this is not a version-history feature. Do not edit generated runtime configuration. POSIX private state/logs use `0600`.
|
|
150
93
|
|
|
151
|
-
|
|
94
|
+
## Updates
|
|
152
95
|
|
|
153
|
-
|
|
96
|
+
Core updates keep the dashboard available and preserve whether Core was running. Even an initially stopped update performs a temporary startup/health check, then stops again. `.bak` is retained until the new binary passes verification and the original running state is restored. Failure rolls back the executable and install record; saved profiles/settings are not part of this transaction.
|
|
154
97
|
|
|
155
|
-
|
|
98
|
+
Downloads require official SHA-256 metadata, trusted HTTPS origins and bounded extraction. If verification cannot complete, the update fails rather than executing unverifiable bytes.
|
|
156
99
|
|
|
157
|
-
|
|
100
|
+
Update Sash itself through npm:
|
|
158
101
|
|
|
159
|
-
|
|
102
|
+
```sh
|
|
103
|
+
sash stop
|
|
104
|
+
npm install -g @astralyn/sash
|
|
105
|
+
sash start
|
|
106
|
+
sash web
|
|
107
|
+
```
|
|
160
108
|
|
|
161
|
-
|
|
109
|
+
`sash upgrade` and `sash update --force` are removed. Damaged installations are diagnosed and preserved; use a clean data directory for reinstalling after stopping the existing instance.
|
|
162
110
|
|
|
163
|
-
##
|
|
111
|
+
## Status and troubleshooting
|
|
164
112
|
|
|
165
|
-
|
|
166
|
-
| :--- | :--- |
|
|
167
|
-
| Windows | `%LOCALAPPDATA%\Sash` |
|
|
168
|
-
| macOS | `~/Library/Application Support/Sash` |
|
|
169
|
-
| Linux | `$XDG_DATA_HOME/sash` or `~/.local/share/sash` |
|
|
113
|
+
`sash status --json` uses `schemaVersion: 2`. It includes `complete`, `healthy`, `queryError`, daemon/Core state, desired/applied/observed proxy state, autostart, endpoints, saved active profile and paths. Unknown observations remain `null`; no TUN fields are emitted. The running proxy endpoint comes from applied settings.
|
|
170
114
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
- `
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
State files are written with mode `0o600` on POSIX where applicable. `SASH_HOME` must be on a local filesystem supporting atomic rename and hard links.
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
## 6. Troubleshooting
|
|
192
|
-
|
|
193
|
-
- **System proxy recovery is blocked:** another application changed managed values or the ownership journal is corrupt. Keep the Core running, inspect `state/system-proxy.json` and the current OS proxy, then repair explicitly; Sash will not overwrite an unrecognized state.
|
|
194
|
-
- **Daemon/Core ownership is corrupt:** inspect `state/*.lock` and PID records. Sash intentionally fails closed instead of deleting uncertain ownership records.
|
|
195
|
-
- **Profile update failed:** inspect the profile card's error or use its update button; generated candidates are checked by the installed Core before commit, and the last valid running config remains active on validation/reload failure.
|
|
196
|
-
- **Corrupt settings/profile index:** repair the JSON file or move it aside; Sash intentionally does not overwrite corrupt state.
|
|
197
|
-
- **Daemon errors:** `sash logs --daemon --errors`.
|
|
198
|
-
- **Core errors:** `sash logs --errors`. Log tails and follow-mode reads use bounded chunks, so large logs do not require one whole-file allocation.
|
|
199
|
-
- **Shutdown returned an error:** cleanup was not completed; the daemon remains listening and scheduled profile updates remain active. Resolve the reported proxy/Core issue and retry `sash stop`.
|
|
200
|
-
- **Core binary/metadata mismatch:** Sash will not execute a binary unless `state/install.json` is valid and agrees that an installation exists. An interrupted `.unlock-probe` is restored automatically when it is the only copy; if both files exist with different bytes, Sash preserves both and fails closed. Inspect them explicitly or run `sash update --force` after resolving the conflict.
|
|
201
|
-
- **Force a validated core reinstall:** `sash update --force`.
|
|
115
|
+
| Exit code | Meaning |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| `0` | Complete observation, including a known stopped state |
|
|
118
|
+
| `2` | Output produced, but some runtime/OS observation is unavailable |
|
|
119
|
+
| `1` | The command failed, for example due to corrupt local state |
|
|
120
|
+
|
|
121
|
+
- **Pending configuration:** click Apply or run `sash restart`. Saving alone does not change Core.
|
|
122
|
+
- **Apply failed:** inspect the displayed error and `sash logs --errors`; correct the saved profile and apply again.
|
|
123
|
+
- **Proxy restoration blocked:** keep the ownership journal and inspect the current Windows settings. Sash will not overwrite third-party changes or stop a healthy Core while restoration fails.
|
|
124
|
+
- **Daemon ownership unknown:** inspect its logs and PID/lease records; Sash will not kill an unverified process or start a competitor.
|
|
125
|
+
- **Interrupted update:** stop and start the daemon so its startup recovery can run. Corrupt or unrecognized backup/metadata files are preserved for inspection.
|
|
126
|
+
- **Login startup failed:** read `sash auto status` and `sash logs --startup`; repair the entry with `sash auto on`.
|
|
127
|
+
- **Shutdown failed:** the management API remains available for retry. Resolve the reported proxy/Core failure and repeat `sash stop`.
|
|
128
|
+
|
|
129
|
+
Windows proxy/PAC restoration and login startup are the only desktop integrations. Basic Core/CLI operation remains portable. TUN and service mode are outside this branch; generated configuration always disables TUN and rejects a separate TUN listener.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astralyn/sash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A network toolbox for developers, learning, and research, with a command-line companion and built-in web dashboard.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ming-kang",
|
|
@@ -55,7 +55,6 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"adm-zip": "^0.6.0",
|
|
57
57
|
"commander": "^13.1.0",
|
|
58
|
-
"cross-spawn": "^7.0.6",
|
|
59
58
|
"undici": "^7.29.0",
|
|
60
59
|
"yaml": "^2.7.0"
|
|
61
60
|
},
|
|
@@ -70,13 +69,14 @@
|
|
|
70
69
|
"@codemirror/theme-one-dark": "^6.1.3",
|
|
71
70
|
"@remixicon/vue": "^4.9.0",
|
|
72
71
|
"@types/adm-zip": "^0.5.7",
|
|
73
|
-
"@types/cross-spawn": "^6.0.6",
|
|
74
72
|
"@types/node": "^24.0.0",
|
|
73
|
+
"@types/sortablejs": "1.15.9",
|
|
75
74
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
76
75
|
"cn-font-split": "^7.4.3",
|
|
77
76
|
"codemirror": "^6.0.2",
|
|
78
77
|
"happy-dom": "20.13.0",
|
|
79
78
|
"playwright": "^1.62.1",
|
|
79
|
+
"sortablejs": "1.15.7",
|
|
80
80
|
"tsx": "^4.19.3",
|
|
81
81
|
"typescript": "^5.8.2",
|
|
82
82
|
"vite": "^6.4.3",
|
package/dist/commands/upgrade.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import spawn from "cross-spawn";
|
|
2
|
-
import { log } from "../log.js";
|
|
3
|
-
import { buildSanitizedEnv } from "../process.js";
|
|
4
|
-
import { resolveRuntimeOwner } from "../runtime-owner.js";
|
|
5
|
-
import { runtimeContext } from "./shared.js";
|
|
6
|
-
const PACKAGE_NAME = "@astralyn/sash";
|
|
7
|
-
const STRICT_SEMVER = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
8
|
-
const SAFE_DIST_TAG = /^[A-Za-z][A-Za-z0-9._-]{0,63}$/;
|
|
9
|
-
export function validateUpgradeVersion(value) {
|
|
10
|
-
if (value !== value.trim() || !value) {
|
|
11
|
-
throw new Error(`Invalid Sash package version or dist-tag: ${JSON.stringify(value)}`);
|
|
12
|
-
}
|
|
13
|
-
if (STRICT_SEMVER.test(value))
|
|
14
|
-
return value;
|
|
15
|
-
if (SAFE_DIST_TAG.test(value) && !/^v?\d+(?:\.|$)/i.test(value))
|
|
16
|
-
return value;
|
|
17
|
-
throw new Error(`Invalid Sash package version or dist-tag: ${JSON.stringify(value)}`);
|
|
18
|
-
}
|
|
19
|
-
export function buildUpgradeSpawnOptions(sourceEnv = process.env) {
|
|
20
|
-
return { stdio: "inherit", env: buildSanitizedEnv(sourceEnv) };
|
|
21
|
-
}
|
|
22
|
-
export async function runUpgrade(opts = {}) {
|
|
23
|
-
const ctx = runtimeContext();
|
|
24
|
-
const owner = await resolveRuntimeOwner(ctx);
|
|
25
|
-
if (owner.kind !== "offline") {
|
|
26
|
-
throw new Error("stop Sash with `sash stop` before upgrading the package");
|
|
27
|
-
}
|
|
28
|
-
const version = validateUpgradeVersion(opts.version ?? "latest");
|
|
29
|
-
const target = `${PACKAGE_NAME}@${version}`;
|
|
30
|
-
log.info(`upgrading Sash via npm: ${target}`);
|
|
31
|
-
const code = await new Promise((resolve) => {
|
|
32
|
-
const child = spawn("npm", ["install", "-g", target], buildUpgradeSpawnOptions());
|
|
33
|
-
child.on("error", (err) => {
|
|
34
|
-
log.error(`failed to launch npm: ${err.message}`);
|
|
35
|
-
resolve(1);
|
|
36
|
-
});
|
|
37
|
-
child.on("close", (exitCode) => resolve(exitCode ?? 1));
|
|
38
|
-
});
|
|
39
|
-
if (code !== 0) {
|
|
40
|
-
throw new Error(`npm exited with code ${code}. Try running the command manually: npm install -g ${target}`);
|
|
41
|
-
}
|
|
42
|
-
log.ok("Sash upgraded. Run `sash version` to verify, then start it normally.");
|
|
43
|
-
}
|