dsh-remote-panel 0.0.0-stage → 0.1.0
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/LICENSE +202 -0
- package/README.md +132 -2
- package/cordis.patch.yml +8 -0
- package/deploy/templates/Caddyfile.tmpl +50 -0
- package/deploy/templates/dsh-proxy.service.tmpl +15 -0
- package/deploy/templates/easytier.env.tmpl +7 -0
- package/deploy/templates/easytier.service.tmpl +24 -0
- package/deploy/templates/install.sh.tmpl +185 -0
- package/lib/allowlist.js +101 -0
- package/lib/api.js +107 -0
- package/lib/client.js +1091 -0
- package/lib/deploy/assets.js +28 -0
- package/lib/deploy/render.js +217 -0
- package/lib/doctor.js +356 -0
- package/lib/index.js +259 -0
- package/lib/logs.js +56 -0
- package/lib/netcheck.js +42 -0
- package/lib/pair.js +109 -0
- package/lib/probe.js +105 -0
- package/lib/state.js +153 -0
- package/lib/token.js +46 -0
- package/package.json +61 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,133 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-remote-panel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
让**手机/平板**安全地打开你这台机器上的 DeepSeek Harness —— 同一份会话、同一批工具、同一台机器。
|
|
4
|
+
|
|
5
|
+
这个包只做**控制面**:部署向导、状态面板、一键配对。
|
|
6
|
+
真正转发流量的 Caddy 与组网的 EasyTier 是独立进程,插件不监听任何端口。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 快速开始(5 分钟)
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
dsh plugin --profile web add dsh-remote-panel
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
1. 重启 DSH,打开 **设置 → 远程访问**。
|
|
17
|
+
2. 填三个字段:中继地址、网络名、网络密钥(其余用默认值)。
|
|
18
|
+
3. 点「生成部署脚本」,复制整块任务,交给目标机器的 AI 执行。
|
|
19
|
+
4. 脚本最后会打印 `DSH_REMOTE_OK ...`,贴回面板。
|
|
20
|
+
5. 面板出二维码:手机先扫**门禁**,再扫**令牌**,然后直接开地址。
|
|
21
|
+
|
|
22
|
+
之后手机重开浏览器无需再登录:门禁 Cookie 一年,DSH Cookie 30 天。
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 架构
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
手机 / 平板 ──EasyTier── 10.x.x.x:8710 ──► Caddy ──► 127.0.0.1:3080 (DSH)
|
|
30
|
+
│ 来源白名单 + 口令门禁 │ 只监听回环,不对外
|
|
31
|
+
└ 用户级服务,无 root └ 通过 --trusted-host 接受该 authority
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
三个部件、一条边界:
|
|
35
|
+
|
|
36
|
+
| 部件 | 职责 | 权限 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| **Caddy**(用户级 systemd) | 白名单、口令门禁、转发、WS、压缩 | 无 root |
|
|
39
|
+
| **EasyTier**(system systemd) | 虚拟组网,唯一需要 TUN 的部件 | root |
|
|
40
|
+
| **本插件** | 检测、生成脚本、配对、状态 | 与应用同权限 |
|
|
41
|
+
|
|
42
|
+
### 为什么不做成一个插件
|
|
43
|
+
|
|
44
|
+
- EasyTier 要建 TUN、改路由,**必须** root,进不了用户态插件。
|
|
45
|
+
- 插件生命周期等于应用生命周期 —— 而你在应用崩了/重启时**最需要**远程访问。
|
|
46
|
+
- 若把反代也塞进进程,就得自己实现 TLS/WS/门禁,等于把成熟的 Caddy 换成几百行自研代码。
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 安全模型
|
|
51
|
+
|
|
52
|
+
三层,从外到内:
|
|
53
|
+
|
|
54
|
+
1. **虚拟网**:只有入网设备可达。
|
|
55
|
+
2. **来源白名单**:Caddyfile 里的 `@lan remote_ip`,其余一律 `403 source not allowed`。
|
|
56
|
+
3. **口令门禁**:`?k=` 换一张一年期 Cookie,缺了返回 `401 access key required`。
|
|
57
|
+
|
|
58
|
+
再往里才是 DSH 自己的令牌与 30 天签名 Cookie。
|
|
59
|
+
|
|
60
|
+
> ⚠️ 会话默认 `danger-full-access` 且不弹审批:**拿到入口就等于能在这台机器上执行任意命令**。口令不要外发。
|
|
61
|
+
|
|
62
|
+
面板自身的 API 另有一道独立令牌:每进程随机生成,随 index 注入,请求必须带 `x-dsh-remote-panel` 头。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 故障速查
|
|
67
|
+
|
|
68
|
+
四段状态码,四个方向:
|
|
69
|
+
|
|
70
|
+
| 现象 | 含义 | 处置 |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| 连接被拒绝 | 网络层或 Caddy 没起 | `systemctl --user status dsh-proxy`、`easytier-cli peer` |
|
|
73
|
+
| `403 source not allowed` | 来源不在白名单 | 面板「来源白名单」加设备,或在家连 Wi-Fi |
|
|
74
|
+
| `401 access key required` | 缺门禁 Cookie | 用 `?k=` 那条链接重开一次 |
|
|
75
|
+
| `401 dsh web authentication required` | 已到 DSH,缺令牌 | 用 `?token=` 那条链接重开一次 |
|
|
76
|
+
| `/healthz` 返回 200 | 链路本身健康 | 问题在认证层,不在网络层 |
|
|
77
|
+
| 手机「一直连接中」 | 系统浏览器会周期性切断 WebSocket | 换 Chrome |
|
|
78
|
+
| 能 ping 通但页面转圈 | 代理软件(如 Karing)抢走了 EasyTier 流量 | 给中继地址加「直连」规则 |
|
|
79
|
+
| 面板报 `no route: GET /doctor` | Host 侧代码在内存里,加路由后没重启 | `systemctl --user restart dsh-web` |
|
|
80
|
+
| 面板报「面板令牌不匹配」 | 页面是重启前打开的(令牌已持久化,理论上不该再出现) | 硬刷新;仍不行就删 `~/.dsh/remote-panel-token` 让两边都换新 |
|
|
81
|
+
|
|
82
|
+
面板「自检」里的**布局 canary** 用来发现 DSH 升级导致的选择器失配;「复制诊断包」导出的 JSON 已剔除口令与密钥。
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 兼容性
|
|
87
|
+
|
|
88
|
+
| 项 | 范围 |
|
|
89
|
+
|---|---|
|
|
90
|
+
| DSH | `>= 0.2.1-rc.3`(`engines.dsh`) |
|
|
91
|
+
| 系统 | Linux + systemd(v1 只做这一种) |
|
|
92
|
+
| 架构 | x86_64 / aarch64 |
|
|
93
|
+
| 反代端口 | 非特权端口(默认 8710),因此 Caddy 无需 root |
|
|
94
|
+
| EasyTier | 固定 `2.6.4`,`--tcp-whitelist` 走运行时探测 |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 卸载
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# 数据面
|
|
102
|
+
sudo bash dsh-remote-panel.sh --uninstall
|
|
103
|
+
# 控制面
|
|
104
|
+
dsh plugin --profile web remove dsh-remote-panel
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`--uninstall` 会停并删两个单元与配置,**不删**二进制(可能被别的用途复用)。
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## v1 不做
|
|
112
|
+
|
|
113
|
+
Windows / macOS 服务管理 · 从零搭建 EasyTier 共享节点 · 多机集中管理 · TLS/PWA · 消息推送。
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 开发
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm test # node --test,81 条(80 通过 + 1 条未设置 CADDY_BIN 时跳过)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
提交约定:一个功能点一个 commit,清单见 [docs/checklist.md](docs/checklist.md)。
|
|
124
|
+
|
|
125
|
+
浏览器里才能确认的部分(移动端外壳、三层门禁)见 [docs/verify.md](docs/verify.md)。
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
CADDY_BIN=/path/to/caddy npm test # 额外跑真实 Caddy 的门禁语义测试
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 许可
|
|
132
|
+
|
|
133
|
+
Apache-2.0
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# dsh-remote-panel: 反代与门禁(用户级 Caddy,独立配置,不碰系统 Caddy)
|
|
2
|
+
# 生成者:dsh-remote-panel @@VERSION@@
|
|
3
|
+
{
|
|
4
|
+
# admin API 保持默认(仅本机 2019):白名单热重载依赖 `caddy reload`
|
|
5
|
+
auto_https off
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
:@@PORT@@ {
|
|
9
|
+
# 访问日志进 journal,供面板「最近拒绝」使用
|
|
10
|
+
log {
|
|
11
|
+
output stderr
|
|
12
|
+
format json
|
|
13
|
+
level INFO
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# 第一层:来源白名单
|
|
17
|
+
@lan remote_ip 127.0.0.1 ::1 @@LAN_CIDR@@ @@VIRTUAL_IP@@
|
|
18
|
+
handle @lan {
|
|
19
|
+
# 诊断用:不经过任何认证
|
|
20
|
+
handle /healthz {
|
|
21
|
+
respond "ok" 200
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# 第二层:口令门禁
|
|
25
|
+
@key query k=@@ACCESS_KEY@@
|
|
26
|
+
handle @key {
|
|
27
|
+
# Cookie 值就是口令本身(24 位随机),而不是常量:常量能被手工伪造
|
|
28
|
+
header Set-Cookie "dsh_gate=@@ACCESS_KEY@@; Max-Age=31536000; Path=/; HttpOnly; SameSite=Lax"
|
|
29
|
+
# 显式写 * 匹配器:否则开头的 / 会被当成路径匹配器,
|
|
30
|
+
# 变成 Location: 303 的怪响应
|
|
31
|
+
redir * / 303
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Caddy 默认没有 cookie 匹配器,用 Header 通配匹配替代
|
|
35
|
+
@paired header Cookie "*dsh_gate=@@ACCESS_KEY@@*"
|
|
36
|
+
handle @paired {
|
|
37
|
+
# 关键:不重写 Host/Origin。DSH 通过 --trusted-host 接受这个 authority,
|
|
38
|
+
# Cookie 因此天然同源,不需要任何注入或伪造。
|
|
39
|
+
reverse_proxy 127.0.0.1:@@UPSTREAM_PORT@@
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
handle {
|
|
43
|
+
respond "dsh-remote: access key required" 401
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
handle {
|
|
48
|
+
respond "dsh-remote: source not allowed" 403
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# dsh-remote-panel: 反代(用户级 systemd 服务,无 root)
|
|
2
|
+
# 生成者:dsh-remote-panel @@VERSION@@
|
|
3
|
+
[Unit]
|
|
4
|
+
Description=DSH remote proxy (Caddy, user)
|
|
5
|
+
After=network-online.target
|
|
6
|
+
|
|
7
|
+
[Service]
|
|
8
|
+
Type=simple
|
|
9
|
+
ExecStart=/usr/bin/caddy run --config %h/.config/caddy/Caddyfile --adapter caddyfile
|
|
10
|
+
ExecReload=/usr/bin/caddy reload --config %h/.config/caddy/Caddyfile --adapter caddyfile
|
|
11
|
+
Restart=always
|
|
12
|
+
RestartSec=2
|
|
13
|
+
|
|
14
|
+
[Install]
|
|
15
|
+
WantedBy=default.target
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@ Unit: EasyTier 节点(system 级,唯一需要 root 的部件)
|
|
2
|
+
@ 生成者:dsh-remote-panel @@VERSION@@
|
|
3
|
+
[Unit]
|
|
4
|
+
Description=EasyTier node for dsh-remote-panel
|
|
5
|
+
After=network-online.target
|
|
6
|
+
Wants=network-online.target
|
|
7
|
+
|
|
8
|
+
[Service]
|
|
9
|
+
Type=simple
|
|
10
|
+
EnvironmentFile=/etc/easytier/dsh-remote.env
|
|
11
|
+
ExecStart=/usr/local/bin/easytier-core \
|
|
12
|
+
--network-name "${ET_NETWORK}" \
|
|
13
|
+
--network-secret "${ET_SECRET}" \
|
|
14
|
+
--ipv4 "${ET_IPV4}" \
|
|
15
|
+
--dev-name "${ET_DEV_NAME}" \
|
|
16
|
+
--hostname "${ET_HOSTNAME}" \
|
|
17
|
+
@@PEERS_FLAGS@@ \
|
|
18
|
+
@@TCP_WHITELIST@@
|
|
19
|
+
Restart=always
|
|
20
|
+
RestartSec=3
|
|
21
|
+
LimitNOFILE=1048576
|
|
22
|
+
|
|
23
|
+
[Install]
|
|
24
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dsh-remote-panel 部署脚本(由插件渲染,版本 @@VERSION@@)
|
|
3
|
+
#
|
|
4
|
+
# 幂等:重复执行不会重复写入,也不会覆盖已有的 Caddy 站点配置。
|
|
5
|
+
# 卸载:本脚本 --uninstall
|
|
6
|
+
#
|
|
7
|
+
# 只做两件事:装一个用户级 Caddy(数据面),装一个 system 级 EasyTier(网络面)。
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
TARGET_USER='@@USER@@'
|
|
11
|
+
TARGET_HOME='@@HOME@@'
|
|
12
|
+
PROXY_PORT='@@PORT@@'
|
|
13
|
+
UPSTREAM_PORT='@@UPSTREAM_PORT@@'
|
|
14
|
+
VIRTUAL_IP='@@VIRTUAL_IP@@'
|
|
15
|
+
RELAY_HOST='@@RELAY_HOST@@'
|
|
16
|
+
ET_VERSION='@@ET_VERSION@@'
|
|
17
|
+
PANEL_VERSION='@@VERSION@@'
|
|
18
|
+
ACCESS_KEY='@@ACCESS_KEY@@'
|
|
19
|
+
ET_SHA_X86_64='@@ET_SHA_X86_64@@'
|
|
20
|
+
ET_SHA_AARCH64='@@ET_SHA_AARCH64@@'
|
|
21
|
+
|
|
22
|
+
# 路径可覆盖:便于在临时目录里做端到端演练,生产环境用默认值
|
|
23
|
+
ETC_DIR="${ETC_DIR:-/etc}"
|
|
24
|
+
UNIT_DIR="${UNIT_DIR:-/etc/systemd/system}"
|
|
25
|
+
BIN_DIR="${BIN_DIR:-/usr/local/bin}"
|
|
26
|
+
|
|
27
|
+
ET_UNIT='easytier-dsh-remote'
|
|
28
|
+
USER_UNIT='dsh-proxy'
|
|
29
|
+
ENV_FILE="$ETC_DIR/easytier/dsh-remote.env"
|
|
30
|
+
UNIT_FILE="$UNIT_DIR/$ET_UNIT.service"
|
|
31
|
+
CADDY_DIR="$TARGET_HOME/.config/caddy"
|
|
32
|
+
USER_UNIT_DIR="$TARGET_HOME/.config/systemd/user"
|
|
33
|
+
|
|
34
|
+
log() { printf '\033[1;34m[deploy]\033[0m %s\n' "$*"; }
|
|
35
|
+
die() { printf '\033[1;31m[deploy]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
36
|
+
|
|
37
|
+
[ "$(id -u)" -eq 0 ] || die "请用 sudo 运行本脚本"
|
|
38
|
+
|
|
39
|
+
if [ "${1:-}" = "--uninstall" ]; then
|
|
40
|
+
log "停止并移除服务"
|
|
41
|
+
systemctl disable --now "$ET_UNIT" 2>/dev/null || true
|
|
42
|
+
rm -f "$UNIT_FILE"
|
|
43
|
+
systemctl daemon-reload
|
|
44
|
+
rm -f "$ENV_FILE"
|
|
45
|
+
runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$(id -u "$TARGET_USER")" \
|
|
46
|
+
systemctl --user disable --now "$USER_UNIT" 2>/dev/null || true
|
|
47
|
+
rm -f "$USER_UNIT_DIR/$USER_UNIT.service"
|
|
48
|
+
rm -rf "$CADDY_DIR"
|
|
49
|
+
log "已移除。二进制未删除(可能被其他用途复用)。"
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
case "$(uname -m)" in
|
|
54
|
+
x86_64) ET_ARCH=x86_64; ET_SHA="$ET_SHA_X86_64" ;;
|
|
55
|
+
aarch64|arm64) ET_ARCH=aarch64; ET_SHA="$ET_SHA_AARCH64" ;;
|
|
56
|
+
*) die "不支持的架构:$(uname -m)(v1 仅提供 x86_64 / aarch64)" ;;
|
|
57
|
+
esac
|
|
58
|
+
command -v systemctl >/dev/null || die "未找到 systemd"
|
|
59
|
+
command -v curl >/dev/null || die "缺少 curl:apt install curl"
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------- EasyTier
|
|
62
|
+
if command -v easytier-core >/dev/null; then
|
|
63
|
+
log "easytier-core 已存在:$(easytier-core --version 2>/dev/null | head -1)"
|
|
64
|
+
else
|
|
65
|
+
log "下载 EasyTier ${ET_VERSION} (${ET_ARCH})"
|
|
66
|
+
tmp="$(mktemp -d)"
|
|
67
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
68
|
+
url="https://github.com/EasyTier/EasyTier/releases/download/v${ET_VERSION}/easytier-linux-${ET_ARCH}-v${ET_VERSION}.zip"
|
|
69
|
+
curl -fsSL "$url" -o "$tmp/et.zip" || die "下载失败:$url(请检查网络或代理)"
|
|
70
|
+
if [ -n "$ET_SHA" ]; then
|
|
71
|
+
echo "$ET_SHA $tmp/et.zip" | sha256sum -c - >/dev/null 2>&1 \
|
|
72
|
+
|| die "sha256 校验失败:$url(下载被篡改或版本不匹配)"
|
|
73
|
+
log "sha256 校验通过"
|
|
74
|
+
else
|
|
75
|
+
log "警告:这个版本没有收录 sha256,跳过校验"
|
|
76
|
+
fi
|
|
77
|
+
( cd "$tmp" && unzip -q et.zip ) || die "解压失败:$tmp/et.zip"
|
|
78
|
+
core="$(find "$tmp" -name easytier-core -type f | head -1)"
|
|
79
|
+
cli="$(find "$tmp" -name easytier-cli -type f | head -1)"
|
|
80
|
+
[ -n "$core" ] || die "压缩包内未找到 easytier-core"
|
|
81
|
+
install -d -m 755 "$BIN_DIR"
|
|
82
|
+
install -m 755 "$core" "$BIN_DIR/easytier-core"
|
|
83
|
+
[ -n "$cli" ] && install -m 755 "$cli" "$BIN_DIR/easytier-cli"
|
|
84
|
+
log "安装完成:$("$BIN_DIR/easytier-core" --version 2>/dev/null | head -1)"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# tcp-whitelist 在旧版本不存在,探测后再决定是否加
|
|
88
|
+
TCP_WL=""
|
|
89
|
+
if easytier-core --help 2>&1 | grep -q 'tcp-whitelist'; then
|
|
90
|
+
TCP_WL="--tcp-whitelist ${PROXY_PORT}"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
install -d -m 755 "$ETC_DIR/easytier" "$UNIT_DIR"
|
|
94
|
+
umask 077
|
|
95
|
+
cat > "$ENV_FILE" <<'DSH_ENV_EOF'
|
|
96
|
+
@@ENV_BODY@@
|
|
97
|
+
DSH_ENV_EOF
|
|
98
|
+
chmod 600 "$ENV_FILE"
|
|
99
|
+
|
|
100
|
+
cat > "$UNIT_FILE" <<'DSH_UNIT_EOF'
|
|
101
|
+
@@UNIT_BODY@@
|
|
102
|
+
DSH_UNIT_EOF
|
|
103
|
+
sed -i "s#@@TCP_WHITELIST@@#${TCP_WL}#" "$UNIT_FILE"
|
|
104
|
+
chmod 644 "$UNIT_FILE"
|
|
105
|
+
systemctl daemon-reload
|
|
106
|
+
systemctl enable --now "$ET_UNIT"
|
|
107
|
+
log "EasyTier 已启动"
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------- Caddy
|
|
110
|
+
if ! command -v caddy >/dev/null; then
|
|
111
|
+
if command -v apt-get >/dev/null; then
|
|
112
|
+
log "安装 Caddy(apt)"
|
|
113
|
+
apt-get update -qq
|
|
114
|
+
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq caddy >/dev/null
|
|
115
|
+
elif command -v dnf >/dev/null; then
|
|
116
|
+
log "安装 Caddy(dnf)"
|
|
117
|
+
dnf install -y -q 'dnf-command(copr)' >/dev/null 2>&1 || true
|
|
118
|
+
dnf copr enable -y '@caddy/caddy' >/dev/null 2>&1 || true
|
|
119
|
+
dnf install -y -q caddy >/dev/null
|
|
120
|
+
else
|
|
121
|
+
die "无法自动安装 Caddy:请手动安装后重跑(https://caddyserver.com/docs/install)"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# apt/dnf 的 caddy 包会顺手起一个系统级实例,占住 127.0.0.1:2019 管理端口,
|
|
125
|
+
# 导致我们的用户级实例无限重启。这里把它停掉——本项目固定用用户级实例。
|
|
126
|
+
if systemctl list-unit-files caddy.service >/dev/null 2>&1; then
|
|
127
|
+
if systemctl is-enabled --quiet caddy 2>/dev/null || systemctl is-active --quiet caddy 2>/dev/null; then
|
|
128
|
+
log "停用系统级 caddy.service(改用用户级实例,避免 2019 端口冲突)"
|
|
129
|
+
systemctl disable --now caddy >/dev/null 2>&1 || true
|
|
130
|
+
fi
|
|
131
|
+
fi
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
install -d -m 700 -o "$TARGET_USER" -g "$(id -gn "$TARGET_USER")" "$CADDY_DIR"
|
|
135
|
+
install -d -m 700 -o "$TARGET_USER" -g "$(id -gn "$TARGET_USER")" "$USER_UNIT_DIR"
|
|
136
|
+
|
|
137
|
+
cat > "$CADDY_DIR/Caddyfile" <<'DSH_CADDY_EOF'
|
|
138
|
+
@@CADDY_BODY@@
|
|
139
|
+
DSH_CADDY_EOF
|
|
140
|
+
cat > "$USER_UNIT_DIR/$USER_UNIT.service" <<'DSH_PROXY_EOF'
|
|
141
|
+
@@PROXY_UNIT_BODY@@
|
|
142
|
+
DSH_PROXY_EOF
|
|
143
|
+
chown "$TARGET_USER":"$(id -gn "$TARGET_USER")" \
|
|
144
|
+
"$CADDY_DIR/Caddyfile" \
|
|
145
|
+
"$USER_UNIT_DIR/$USER_UNIT.service"
|
|
146
|
+
chmod 644 "$CADDY_DIR/Caddyfile" "$USER_UNIT_DIR/$USER_UNIT.service"
|
|
147
|
+
|
|
148
|
+
loginctl enable-linger "$TARGET_USER" >/dev/null 2>&1 || true
|
|
149
|
+
runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$(id -u "$TARGET_USER")" \
|
|
150
|
+
systemctl --user daemon-reload
|
|
151
|
+
runuser -u "$TARGET_USER" -- env XDG_RUNTIME_DIR="/run/user/$(id -u "$TARGET_USER")" \
|
|
152
|
+
systemctl --user enable --now "$USER_UNIT"
|
|
153
|
+
log "反代已启动(用户级服务,无 root 权限)"
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------- 验收
|
|
156
|
+
log "等待虚拟 IP ${VIRTUAL_IP}"
|
|
157
|
+
for _ in $(seq 1 60); do
|
|
158
|
+
ip -4 -o addr show 2>/dev/null | grep -q " ${VIRTUAL_IP}/" && break
|
|
159
|
+
sleep 1
|
|
160
|
+
done
|
|
161
|
+
ip -4 -o addr show 2>/dev/null | grep -q " ${VIRTUAL_IP}/" \
|
|
162
|
+
|| die "虚拟 IP 未就绪:检查 $ENV_FILE 与中继 ${RELAY_HOST}"
|
|
163
|
+
|
|
164
|
+
for _ in $(seq 1 30); do
|
|
165
|
+
ss -ltn 2>/dev/null | grep -q ":${PROXY_PORT} " && break
|
|
166
|
+
sleep 1
|
|
167
|
+
done
|
|
168
|
+
ss -ltn 2>/dev/null | grep -q ":${PROXY_PORT} " || die "反代未监听 :${PROXY_PORT}"
|
|
169
|
+
|
|
170
|
+
health="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${PROXY_PORT}/healthz" || true)"
|
|
171
|
+
[ "$health" = "200" ] || die "healthz 未返回 200(实际 ${health})"
|
|
172
|
+
|
|
173
|
+
PAYLOAD="$(printf '{"version":"%s","port":%s,"ip":"%s","upstream":%s,"health":"%s","key":"%s"}' \
|
|
174
|
+
"$PANEL_VERSION" "$PROXY_PORT" "$VIRTUAL_IP" "$UPSTREAM_PORT" "$health" "$ACCESS_KEY" | base64 -w0)"
|
|
175
|
+
|
|
176
|
+
cat <<EOF
|
|
177
|
+
|
|
178
|
+
部署完成。
|
|
179
|
+
|
|
180
|
+
手机 / 平板: http://${VIRTUAL_IP}:${PROXY_PORT}/
|
|
181
|
+
家里局域网: http://<本机局域网IP>:${PROXY_PORT}/
|
|
182
|
+
|
|
183
|
+
回填码(复制到「远程访问」面板):
|
|
184
|
+
DSH_REMOTE_OK ${PAYLOAD}
|
|
185
|
+
EOF
|