@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
package/docs/frontend.md
CHANGED
|
@@ -1,125 +1,89 @@
|
|
|
1
|
-
# Frontend Architecture
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
---
|
|
17
|
-
|
|
18
|
-
## 2. Source Layout
|
|
19
|
-
|
|
20
|
-
```text
|
|
21
|
-
web/src/
|
|
22
|
-
├── api/index.ts typed REST client and WebSocket reconnect logic
|
|
23
|
-
├── components/
|
|
24
|
-
│ ├── AppSidebar.vue
|
|
25
|
-
│ ├── ConfirmDialog.vue
|
|
26
|
-
│ ├── Icon.vue semantic icon wrapper
|
|
27
|
-
│ ├── icons.ts tree-shaken Remix Icon component mapping
|
|
28
|
-
│ ├── ProxyGroupSection.vue reusable group header/node grid
|
|
29
|
-
│ ├── TrafficChart.vue
|
|
30
|
-
│ └── ...
|
|
31
|
-
├── i18n/ Chinese source messages and typed English mirror
|
|
32
|
-
├── stores/index.ts runtime state, refresh actions and polling
|
|
33
|
-
├── stores/state-ownership.ts pure generations, cleanup and sync helpers
|
|
34
|
-
├── styles/main.css design tokens and shared utility/component styles
|
|
35
|
-
├── types/index.ts core-controller response types; daemon types are shared
|
|
36
|
-
├── views/
|
|
37
|
-
│ ├── OverviewView.vue status, traffic, modes and proxy groups
|
|
38
|
-
│ ├── ProfilesView.vue download/import/update/select/delete profiles
|
|
39
|
-
│ ├── LogsView.vue
|
|
40
|
-
│ ├── ConnectionsView.vue
|
|
41
|
-
│ ├── RulesView.vue
|
|
42
|
-
│ └── SettingsView.vue
|
|
43
|
-
├── App.vue shell, view selection and stream lifecycle
|
|
44
|
-
├── theme.ts persisted system/light/dark theme state
|
|
45
|
-
└── router.ts small hash router
|
|
46
|
-
```
|
|
1
|
+
# Frontend Architecture
|
|
2
|
+
|
|
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.
|
|
47
4
|
|
|
48
|
-
|
|
5
|
+
## State ownership
|
|
49
6
|
|
|
50
|
-
|
|
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.
|
|
51
8
|
|
|
52
|
-
|
|
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 |
|
|
53
18
|
|
|
54
|
-
|
|
19
|
+
Three values determine refresh ownership:
|
|
55
20
|
|
|
56
|
-
|
|
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.
|
|
57
24
|
|
|
58
|
-
|
|
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.
|
|
59
26
|
|
|
60
|
-
|
|
61
|
-
- `refreshConnections()`
|
|
62
|
-
- `refreshProxies()`
|
|
63
|
-
- `refreshProfiles()`
|
|
64
|
-
- `refreshRuntimeState()`
|
|
65
|
-
- profile mutation actions such as `updateProfile()` and `activateProfile()`
|
|
66
|
-
- runtime intent actions such as `setOutboundMode()` and `selectGroupProxy()`
|
|
67
|
-
- `startRuntimePolling()`
|
|
27
|
+
## Refresh and performance
|
|
68
28
|
|
|
69
|
-
|
|
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.
|
|
70
30
|
|
|
71
|
-
|
|
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 |
|
|
72
38
|
|
|
73
|
-
|
|
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.
|
|
74
40
|
|
|
75
|
-
|
|
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.
|
|
76
42
|
|
|
77
|
-
Views
|
|
43
|
+
## Views and shared controls
|
|
78
44
|
|
|
79
|
-
|
|
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.
|
|
80
46
|
|
|
81
|
-
|
|
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.
|
|
82
48
|
|
|
83
|
-
The Profiles
|
|
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.
|
|
84
50
|
|
|
85
|
-
-
|
|
86
|
-
- local YAML import
|
|
87
|
-
- Update All and per-profile update
|
|
88
|
-
- active profile selection
|
|
89
|
-
- quota/expiry display from provider metadata
|
|
90
|
-
- persisted update errors
|
|
91
|
-
- delete confirmation
|
|
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.
|
|
92
52
|
|
|
93
|
-
|
|
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.
|
|
94
54
|
|
|
95
|
-
|
|
55
|
+
## Authorization and transport
|
|
96
56
|
|
|
97
|
-
|
|
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.
|
|
98
58
|
|
|
99
|
-
|
|
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.
|
|
100
60
|
|
|
101
|
-
`
|
|
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.
|
|
102
62
|
|
|
103
|
-
|
|
104
|
-
- reads an error response body once and surfaces JSON or plain-text messages;
|
|
105
|
-
- parses WebSocket frames separately from consumer callbacks, validates finite non-negative traffic counters and known textual log records, and silently drops malformed frames;
|
|
106
|
-
- maintains at most one reconnect timer per stream;
|
|
107
|
-
- binds stream callbacks to the current runtime generation and stops stream ownership when the daemon session or healthy Core is unavailable;
|
|
108
|
-
- resets stale traffic rates/history when the current traffic stream disconnects without allowing an older socket's close event to clear a replacement stream;
|
|
109
|
-
- sends `X-Sash-Token` on HTTP requests after initialization.
|
|
63
|
+
## Development and verification
|
|
110
64
|
|
|
111
|
-
|
|
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
|
+
```
|
|
112
75
|
|
|
113
|
-
-
|
|
114
|
-
- `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.
|
|
115
77
|
|
|
116
|
-
|
|
78
|
+
For local source development:
|
|
117
79
|
|
|
118
|
-
|
|
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
|
+
```
|
|
119
86
|
|
|
120
|
-
|
|
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.
|
|
121
88
|
|
|
122
|
-
|
|
123
|
-
- Logs receive monotonic IDs before entering the capped 600-row buffer, providing stable Vue keys and an update sequence even when length remains constant.
|
|
124
|
-
- The global confirm service settles a previous pending Promise before opening another dialog; Escape, route changes and component unmount cancel the active confirmation.
|
|
125
|
-
- 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,227 +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
|
-
|
|
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.
|
|
85
17
|
|
|
86
|
-
|
|
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. |
|
|
18
|
+
## Commands
|
|
91
19
|
|
|
92
|
-
|
|
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. |
|
|
93
33
|
|
|
94
|
-
|
|
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.
|
|
95
35
|
|
|
96
|
-
|
|
36
|
+
## Browser access
|
|
97
37
|
|
|
98
|
-
|
|
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.
|
|
99
39
|
|
|
100
|
-
|
|
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.
|
|
101
41
|
|
|
102
|
-
|
|
103
|
-
| :--- | :--- |
|
|
104
|
-
| `sash web` | Open `http://127.0.0.1:19090/ui/`. |
|
|
105
|
-
| `sash web --no-open` | Print the dashboard URL without opening a browser. |
|
|
42
|
+
## Settings and profiles
|
|
106
43
|
|
|
107
|
-
|
|
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.
|
|
108
45
|
|
|
109
|
-
|
|
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.
|
|
110
47
|
|
|
111
|
-
|
|
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.
|
|
112
49
|
|
|
113
|
-
|
|
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).
|
|
114
51
|
|
|
115
|
-
|
|
52
|
+
## State format and data
|
|
116
53
|
|
|
117
|
-
|
|
118
|
-
| :--- | :--- |
|
|
119
|
-
| `sash update [--version V] [--force]` | Download and validate a replacement core, then swap it in transactionally. |
|
|
120
|
-
| `sash upgrade [--version V]` | Upgrade the Sash package through npm; requires `sashd` to be stopped first. |
|
|
121
|
-
| `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.
|
|
122
55
|
|
|
123
|
-
|
|
56
|
+
`sash.json` contains `{schemaVersion: 2, revision, settings, profiles}`. `profiles` contains the saved `activeId` and metadata list. The following keys live inside `settings`:
|
|
124
57
|
|
|
125
|
-
|
|
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 |
|
|
126
67
|
|
|
127
|
-
|
|
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.
|
|
128
69
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
| `mixedPort` | `7890` | Local HTTP/SOCKS5 mixed inbound port. The CLI key is `mixed-port`. |
|
|
135
|
-
| `controller` | `127.0.0.1:9090` | Internal controller listen address; only loopback hosts are accepted. |
|
|
136
|
-
| `daemonPort` | `19090` | Daemon API and WebUI port. |
|
|
137
|
-
| `secret` | *(random)* | Internal controller secret. It is never returned by the public status API. |
|
|
138
|
-
| `daemonSecret` | *(random)* | CLI bearer secret for state-changing daemon requests. |
|
|
139
|
-
| `systemProxy` | `false` | Desired OS-level system proxy state. |
|
|
140
|
-
| `tun` | `false` | Enable the TUN inbound; requires elevated privileges. |
|
|
141
|
-
| `allowLan` | `false` | Accept proxy traffic from other devices. |
|
|
142
|
-
|
|
143
|
-
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` |
|
|
144
75
|
|
|
145
|
-
|
|
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
|
+
```
|
|
146
91
|
|
|
147
|
-
|
|
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`.
|
|
148
93
|
|
|
149
|
-
|
|
94
|
+
## Updates
|
|
150
95
|
|
|
151
|
-
|
|
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.
|
|
152
97
|
|
|
153
|
-
|
|
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.
|
|
154
99
|
|
|
155
|
-
|
|
100
|
+
Update Sash itself through npm:
|
|
156
101
|
|
|
157
102
|
```sh
|
|
158
103
|
sash stop
|
|
159
|
-
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
On Windows, open PowerShell as Administrator:
|
|
163
|
-
|
|
164
|
-
```powershell
|
|
104
|
+
npm install -g @astralyn/sash
|
|
165
105
|
sash start
|
|
106
|
+
sash web
|
|
166
107
|
```
|
|
167
108
|
|
|
168
|
-
|
|
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.
|
|
169
110
|
|
|
170
|
-
|
|
111
|
+
## Status and troubleshooting
|
|
171
112
|
|
|
172
|
-
|
|
173
|
-
sash status # note the printed data root
|
|
174
|
-
sudo env SASH_HOME='<data root printed above>' "$(command -v sash)" start
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
While that elevated daemon is running on macOS or Linux, use the same `sudo` and `SASH_HOME` prefix for later lifecycle commands such as `status` or `stop`, so they target the same runtime and can read its protected state.
|
|
178
|
-
|
|
179
|
-
If TUN was already saved as on, just start Sash elevated. Because a full `sash restart` replaces the daemon itself, running it from the elevated shell is equivalent to `sash stop` + `sash start` here; restarting only the Core from the dashboard does not elevate `sashd`.
|
|
180
|
-
|
|
181
|
-
Sash distinguishes the desired setting from the Core's actual runtime state: `sash status` reports `on (active)`, `on (inactive)`, `on (unverified)` or `on (runtime unknown)`, and `sash status --json` reports `tun.desired` separately from `tun.active` (`true`, `false` or `null`). Privilege guidance is shown only after a responsive, healthy running Core explicitly reports inactive or unverified TUN state.
|
|
182
|
-
|
|
183
|
-
When a running Core is switched from TUN off to on, Sash reads back `tun.enable` from the Core before committing the setting. If the Core remains inactive or cannot be verified, the settings/config transaction and prior runtime are restored. An inactive result includes the platform-appropriate elevated `sash restart` instructions above. A TUN setting saved while the Core is stopped can only be verified on the next start; startup leaves the ordinary proxy Core available and reports any inactive or unverified TUN state explicitly. If an elevated start still leaves TUN inactive, inspect the Core error log.
|
|
184
|
-
|
|
185
|
-
Do not enable TUN in automated smoke tests.
|
|
186
|
-
|
|
187
|
-
---
|
|
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.
|
|
188
114
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
- `state/sashd.pid`, `state/sash.pid`: atomic daemon/Core discovery records.
|
|
205
|
-
- `state/system-proxy.json`: pre-takeover proxy snapshot and ownership phase.
|
|
206
|
-
- `state/install.json`: canonical installed Core version record.
|
|
207
|
-
- `state/core-install-transaction.json`: first-install publication journal; interrupted publishing rolls back, while a committed marker is only cleared.
|
|
208
|
-
- `state/core-update-transaction.json`: previous/target install records and update phase; retained with `.bak` until managed runtime health and restoration succeed.
|
|
209
|
-
- `state/*.lock`: daemon, runtime, mutation, settings and proxy ownership leases.
|
|
210
|
-
- `logs/`: core and daemon stdout/stderr logs.
|
|
211
|
-
- `ui/` *(optional)*: custom dashboard override.
|
|
212
|
-
|
|
213
|
-
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.
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## 6. Troubleshooting
|
|
218
|
-
|
|
219
|
-
- **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.
|
|
220
|
-
- **Daemon/Core ownership is corrupt:** inspect `state/*.lock` and PID records. Sash intentionally fails closed instead of deleting uncertain ownership records.
|
|
221
|
-
- **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.
|
|
222
|
-
- **Corrupt settings/profile index:** repair the JSON file or move it aside; Sash intentionally does not overwrite corrupt state.
|
|
223
|
-
- **Daemon errors:** `sash logs --daemon --errors`.
|
|
224
|
-
- **Core errors:** `sash logs --errors`. Log tails and follow-mode reads use bounded chunks, so large logs do not require one whole-file allocation.
|
|
225
|
-
- **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`.
|
|
226
|
-
- **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.
|
|
227
|
-
- **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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astralyn/sash",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|
|
7
7
|
"type": "module",
|
|
@@ -47,8 +47,7 @@
|
|
|
47
47
|
"network",
|
|
48
48
|
"toolbox",
|
|
49
49
|
"rules",
|
|
50
|
-
"dashboard"
|
|
51
|
-
"tun"
|
|
50
|
+
"dashboard"
|
|
52
51
|
],
|
|
53
52
|
"publishConfig": {
|
|
54
53
|
"access": "public"
|
|
@@ -56,23 +55,28 @@
|
|
|
56
55
|
"dependencies": {
|
|
57
56
|
"adm-zip": "^0.6.0",
|
|
58
57
|
"commander": "^13.1.0",
|
|
59
|
-
"cross-spawn": "^7.0.6",
|
|
60
58
|
"undici": "^7.29.0",
|
|
61
59
|
"yaml": "^2.7.0"
|
|
62
60
|
},
|
|
61
|
+
"overrides": {
|
|
62
|
+
"cn-font-split": {
|
|
63
|
+
"koffi": "2.16.3"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
63
66
|
"devDependencies": {
|
|
64
67
|
"@biomejs/biome": "^2.5.5",
|
|
65
68
|
"@codemirror/legacy-modes": "^6.5.4",
|
|
66
69
|
"@codemirror/theme-one-dark": "^6.1.3",
|
|
67
70
|
"@remixicon/vue": "^4.9.0",
|
|
68
71
|
"@types/adm-zip": "^0.5.7",
|
|
69
|
-
"@types/cross-spawn": "^6.0.6",
|
|
70
72
|
"@types/node": "^24.0.0",
|
|
73
|
+
"@types/sortablejs": "1.15.9",
|
|
71
74
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
72
75
|
"cn-font-split": "^7.4.3",
|
|
73
76
|
"codemirror": "^6.0.2",
|
|
74
77
|
"happy-dom": "20.13.0",
|
|
75
78
|
"playwright": "^1.62.1",
|
|
79
|
+
"sortablejs": "1.15.7",
|
|
76
80
|
"tsx": "^4.19.3",
|
|
77
81
|
"typescript": "^5.8.2",
|
|
78
82
|
"vite": "^6.4.3",
|