ownexit 0.3.0__tar.gz

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.
ownexit-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ownexit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ownexit-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,189 @@
1
+ Metadata-Version: 2.4
2
+ Name: ownexit
3
+ Version: 0.3.0
4
+ Summary: Turn a VPS you rent into your own fixed exit IP: direct or via a relay, set up from your laptop over SSH.
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/jakoes-wu/ownexit
7
+ Project-URL: Changelog, https://github.com/jakoes-wu/ownexit/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/jakoes-wu/ownexit/issues
9
+ Keywords: vps,proxy,sing-box,vless,reality,relay,ssh,bash
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Unix Shell
17
+ Classifier: Topic :: Internet :: Proxy Servers
18
+ Classifier: Topic :: System :: Networking
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # ownexit
25
+
26
+ **English** | [简体中文](README.zh-CN.md)
27
+
28
+ [![Release](https://img.shields.io/github/v/release/jakoes-wu/ownexit)](https://github.com/jakoes-wu/ownexit/releases)
29
+ [![CI](https://github.com/jakoes-wu/ownexit/actions/workflows/ci.yml/badge.svg)](https://github.com/jakoes-wu/ownexit/actions/workflows/ci.yml)
30
+ [![PyPI](https://img.shields.io/pypi/v/ownexit)](https://pypi.org/project/ownexit/)
31
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
32
+ ![bash](https://img.shields.io/badge/bash-3.2%2B-blue)
33
+ ![platform](https://img.shields.io/badge/control-macOS%20%7C%20Linux-lightgrey)
34
+
35
+ Turn a VPS you rent into your own fixed exit IP — connect directly, or through a relay — set up from your laptop with one command.
36
+
37
+ ```sh
38
+ pipx install ownexit
39
+ ownexit direct --host 203.0.113.7 # asks for the VPS root password once
40
+ ```
41
+
42
+ When it finishes, paste the printed subscription URL into Clash Verge or Shadowrocket. That's it.
43
+
44
+ ![ownexit demo: set up a relay + exit chain with two IPs](https://raw.githubusercontent.com/jakoes-wu/ownexit/main/docs/assets/demo.gif)
45
+
46
+ <sub>The IPs in the demo are examples.</sub>
47
+
48
+ - **One command, from your laptop.** No logging into the server to type commands.
49
+ - **A fixed exit that is yours.** Your traffic leaves from your own VPS; the IP is not shared with strangers.
50
+ - **Still works when the IP gets blocked.** Add a relay server in front; the exit IP and your client settings stay the same.
51
+ - **Undoable.** The relay setup rolls back both servers to how they were, and the relay never holds any keys.
52
+
53
+ ## How it works
54
+
55
+ **Direct** — your devices connect straight to your VPS:
56
+
57
+ ```text
58
+ phone / laptop ──VLESS-Reality──▶ your VPS (sing-box) ──▶ websites see your VPS's IP
59
+ ```
60
+
61
+ **Relay** — for when the VPS IP is blocked from where you are. The relay only forwards TCP bytes; it cannot read your traffic and stores no keys:
62
+
63
+ ```text
64
+ phone / laptop ──VLESS-Reality──▶ relay (systemd-socket-proxyd) ──▶ exit VPS (sing-box) ──▶ websites see the exit's IP
65
+ ```
66
+
67
+ Everything runs on your laptop and talks to the servers over SSH. Configuration, keys and state stay on your laptop, outside this repository.
68
+
69
+ ## Install
70
+
71
+ ```sh
72
+ pipx install ownexit # or: pip install --user ownexit
73
+ ownexit --help
74
+ ```
75
+
76
+ `ownexit` is a thin wrapper around the bundled bash scripts, so you can also run them straight from a clone — the commands map one to one:
77
+
78
+ | `ownexit …` | script in a clone |
79
+ | ---- | ---- |
80
+ | `ownexit direct` | `direct/setup_direct.sh` |
81
+ | `ownexit subctl` | `direct/subctl` |
82
+ | `ownexit connect` | `direct/connect_to.sh` |
83
+ | `ownexit chain` | `chain/setup_chain.sh` |
84
+ | `ownexit multi` | `chain/multi_chain_client.sh` |
85
+
86
+ ```sh
87
+ git clone https://github.com/jakoes-wu/ownexit && cd ownexit
88
+ ./direct/setup_direct.sh --host 203.0.113.7
89
+ ```
90
+
91
+ When run from a clone, the chain scripts additionally refuse configuration files that live inside the clone, so real IPs and keys cannot be committed by accident.
92
+
93
+ ## What you need
94
+
95
+ | | Direct | Relay |
96
+ | ---- | ---- | ---- |
97
+ | Your computer | macOS (Linux untested) | macOS or Linux (WSL counts as Linux) |
98
+ | Servers | 1 × Debian / Ubuntu VPS | 2 × Linux, both amd64 or both arm64 (relay + exit) |
99
+ | Login | root password over SSH, used once | same, for each server |
100
+ | Tools | `pipx` (or `git` for a clone), `ssh`, `curl`, `openssl`, `expect` (`brew install expect`) | same |
101
+
102
+ The first run asks for each server's root password (not echoed); after that everything uses a dedicated SSH key in `~/.ssh/ownexit/`.
103
+
104
+ ## Quick start: direct
105
+
106
+ 1. **Deploy**
107
+
108
+ ```sh
109
+ ownexit direct --host 203.0.113.7 # add --port 2222 if SSH is not on 22
110
+ ```
111
+
112
+ It sets up key login, checks the system, enables BBR, installs sing-box (by running the third-party installer [233boy/sing-box](https://github.com/233boy/sing-box) interactively — choose **VLESS-REALITY** and press Enter for the rest), renders subscriptions, uploads them and verifies every layer.
113
+
114
+ 2. **Import on your devices** — the script prints three URLs:
115
+
116
+ | URL | For |
117
+ | ---- | ---- |
118
+ | `…/clash.yaml` | Clash Verge, mihomo, Clash Meta for Android |
119
+ | `…/shadowrocket.txt` | Shadowrocket on iPhone |
120
+ | `…/node.txt` | the plain `vless://` link, for anything else |
121
+
122
+ 3. **Check and close up** — open `https://ipinfo.io` on a device: it should show your VPS's IP. Then turn the subscription endpoint off until you need it again:
123
+
124
+ ```sh
125
+ ownexit subctl stop
126
+ ```
127
+
128
+ The VPS is remembered, so later runs need no arguments: `ownexit direct` to redeploy, `ownexit subctl status|start|stop`. Step-by-step guide: [docs/manual/direct.md](docs/manual/direct.md) (Chinese).
129
+
130
+ ## Quick start: relay
131
+
132
+ 1. **Give it two IPs**
133
+
134
+ ```sh
135
+ ownexit chain init --relay 203.0.113.10 --exit 203.0.113.20
136
+ ```
137
+
138
+ It sets up key login on both servers (one password prompt each), detects the exit IP and asks you to confirm it, checks whether the relay already runs sing-box, and writes `~/.config/ownexit/chains/main.env`. Nothing on the servers is changed yet. By default deploy will also add an nftables rule on the exit so that only the relay can reach its Reality port (`--exit-source-filter managed`); use `provider` if your provider's security group already does that, or `none` to skip it.
139
+
140
+ 2. **Deploy**
141
+
142
+ ```sh
143
+ ownexit chain --id main deploy
144
+ ```
145
+
146
+ Both servers download the pinned sing-box release from GitHub themselves (falling back to an upload from your computer). It deploys the exit first, then the relay, as one transaction, and verifies the exit IP three different ways. If anything fails, it cleans up; if your network drops halfway, run `deploy` or `rollback` again and it converges.
147
+
148
+ 3. **Import** the node from `~/.local/state/ownexit/chains/main/client/node.txt`, or run `ownexit multi --chains main render` for QR codes and a Clash snippet.
149
+
150
+ Day to day: `ownexit chain --id main status | verify | conns | rollback`. Relay blocked? Deploy a second relay with `init --id backup …` and combine both with `multi_chain_client.sh` — clients switch automatically. Full reference: [chain/README.md](chain/README.md); guide: [docs/manual/chain.md](docs/manual/chain.md) (both in Chinese).
151
+
152
+ ## Supported platforms
153
+
154
+ | | Direct | Relay |
155
+ | ---- | ---- | ---- |
156
+ | Control machine | macOS (tested); Linux (untested); Windows not supported — try WSL at your own risk | macOS on Apple silicon (tested), macOS on Intel (untested), Linux amd64 (tested on Ubuntu 20.04), Linux arm64 and WSL (untested) |
157
+ | Server OS | Debian, Ubuntu | Linux with systemd; the relay needs `systemd-socket-proxyd`; no nftables tables other than ownexit's own, UFW inactive |
158
+ | Server CPU | whatever 233boy/sing-box supports (amd64, arm64) | amd64 (tested) or arm64 (untested); relay and exit must match |
159
+ | Clients | Clash Verge, mihomo, Shadowrocket tested; any VLESS-Reality client via `vless://` | same |
160
+
161
+ If your computer runs a proxy in TUN mode (Clash and similar), SSH to the servers may be cut off halfway through a deploy. Turn TUN off, or route the relay and exit IPs directly, while running chain commands.
162
+
163
+ ## Security notes
164
+
165
+ - No real IP, password or key ever goes into this repository. There is no "edit the IP at the top of the script" step and no `--password` option. Passwords are typed interactively (or passed via `OWNEXIT_SSH_PASSWORD` for non-interactive use), submitted once per try, and never written to disk.
166
+ - A wrong password is retried at most 3 times, and each try is submitted to the server only once, so you are unlikely to trip fail2ban. Failures end with `reason=bad-password`, `reason=password-disabled` or `reason=unreachable`.
167
+ - The direct subscription endpoint is plain HTTP protected by a random path. Keep it stopped (`ownexit subctl stop`) except while importing, and use `ownexit direct --rotate-token` if a URL leaks.
168
+ - The relay only runs `systemd-socket-proxyd`; the Reality private key lives only on the exit server, in a mode-600 file. By default the exit's Reality port only accepts connections from the relay (an nftables table that starts and stops with the exit service).
169
+ - The direct setup installs sing-box through the third-party script 233boy/sing-box. The relay setup downloads a pinned official sing-box release on each server and checks the SHA-256 of both the archive and the binary.
170
+
171
+ See [SECURITY.md](SECURITY.md) for how to report a vulnerability.
172
+
173
+ ## FAQ
174
+
175
+ **Can I change the SSH port or user?** Direct: `--port`, `--user`. Relay: `--relay-port`, `--exit-port`; the relay setup requires root.
176
+
177
+ **I manage several VPSes.** Pass `--host` to pick one. Without it, `ownexit direct` and `ownexit subctl` list the remembered servers and exit.
178
+
179
+ **How do I undo it?** Relay: `ownexit chain --id main rollback`. Direct (0.1.0 has no uninstall command yet): on the VPS, `systemctl disable --now ownexit-subscription`, remove `/opt/ownexit-subscription` and `/etc/systemd/system/ownexit-subscription.service`, and remove sing-box with the installer's own `sb` tool.
180
+
181
+ **Where are my files?** Keys: `~/.ssh/ownexit/`. Configuration: `~/.config/ownexit/`. State and subscriptions: `~/.local/state/ownexit/`.
182
+
183
+ ## Contributing
184
+
185
+ Issues and pull requests are welcome; please read [CONTRIBUTING.md](CONTRIBUTING.md) first. The project follows the [Contributor Covenant](CODE_OF_CONDUCT.md).
186
+
187
+ ## License
188
+
189
+ [MIT](LICENSE). Use this software in accordance with the laws where you live and the terms of your server provider.
@@ -0,0 +1,166 @@
1
+ # ownexit
2
+
3
+ **English** | [简体中文](README.zh-CN.md)
4
+
5
+ [![Release](https://img.shields.io/github/v/release/jakoes-wu/ownexit)](https://github.com/jakoes-wu/ownexit/releases)
6
+ [![CI](https://github.com/jakoes-wu/ownexit/actions/workflows/ci.yml/badge.svg)](https://github.com/jakoes-wu/ownexit/actions/workflows/ci.yml)
7
+ [![PyPI](https://img.shields.io/pypi/v/ownexit)](https://pypi.org/project/ownexit/)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
9
+ ![bash](https://img.shields.io/badge/bash-3.2%2B-blue)
10
+ ![platform](https://img.shields.io/badge/control-macOS%20%7C%20Linux-lightgrey)
11
+
12
+ Turn a VPS you rent into your own fixed exit IP — connect directly, or through a relay — set up from your laptop with one command.
13
+
14
+ ```sh
15
+ pipx install ownexit
16
+ ownexit direct --host 203.0.113.7 # asks for the VPS root password once
17
+ ```
18
+
19
+ When it finishes, paste the printed subscription URL into Clash Verge or Shadowrocket. That's it.
20
+
21
+ ![ownexit demo: set up a relay + exit chain with two IPs](https://raw.githubusercontent.com/jakoes-wu/ownexit/main/docs/assets/demo.gif)
22
+
23
+ <sub>The IPs in the demo are examples.</sub>
24
+
25
+ - **One command, from your laptop.** No logging into the server to type commands.
26
+ - **A fixed exit that is yours.** Your traffic leaves from your own VPS; the IP is not shared with strangers.
27
+ - **Still works when the IP gets blocked.** Add a relay server in front; the exit IP and your client settings stay the same.
28
+ - **Undoable.** The relay setup rolls back both servers to how they were, and the relay never holds any keys.
29
+
30
+ ## How it works
31
+
32
+ **Direct** — your devices connect straight to your VPS:
33
+
34
+ ```text
35
+ phone / laptop ──VLESS-Reality──▶ your VPS (sing-box) ──▶ websites see your VPS's IP
36
+ ```
37
+
38
+ **Relay** — for when the VPS IP is blocked from where you are. The relay only forwards TCP bytes; it cannot read your traffic and stores no keys:
39
+
40
+ ```text
41
+ phone / laptop ──VLESS-Reality──▶ relay (systemd-socket-proxyd) ──▶ exit VPS (sing-box) ──▶ websites see the exit's IP
42
+ ```
43
+
44
+ Everything runs on your laptop and talks to the servers over SSH. Configuration, keys and state stay on your laptop, outside this repository.
45
+
46
+ ## Install
47
+
48
+ ```sh
49
+ pipx install ownexit # or: pip install --user ownexit
50
+ ownexit --help
51
+ ```
52
+
53
+ `ownexit` is a thin wrapper around the bundled bash scripts, so you can also run them straight from a clone — the commands map one to one:
54
+
55
+ | `ownexit …` | script in a clone |
56
+ | ---- | ---- |
57
+ | `ownexit direct` | `direct/setup_direct.sh` |
58
+ | `ownexit subctl` | `direct/subctl` |
59
+ | `ownexit connect` | `direct/connect_to.sh` |
60
+ | `ownexit chain` | `chain/setup_chain.sh` |
61
+ | `ownexit multi` | `chain/multi_chain_client.sh` |
62
+
63
+ ```sh
64
+ git clone https://github.com/jakoes-wu/ownexit && cd ownexit
65
+ ./direct/setup_direct.sh --host 203.0.113.7
66
+ ```
67
+
68
+ When run from a clone, the chain scripts additionally refuse configuration files that live inside the clone, so real IPs and keys cannot be committed by accident.
69
+
70
+ ## What you need
71
+
72
+ | | Direct | Relay |
73
+ | ---- | ---- | ---- |
74
+ | Your computer | macOS (Linux untested) | macOS or Linux (WSL counts as Linux) |
75
+ | Servers | 1 × Debian / Ubuntu VPS | 2 × Linux, both amd64 or both arm64 (relay + exit) |
76
+ | Login | root password over SSH, used once | same, for each server |
77
+ | Tools | `pipx` (or `git` for a clone), `ssh`, `curl`, `openssl`, `expect` (`brew install expect`) | same |
78
+
79
+ The first run asks for each server's root password (not echoed); after that everything uses a dedicated SSH key in `~/.ssh/ownexit/`.
80
+
81
+ ## Quick start: direct
82
+
83
+ 1. **Deploy**
84
+
85
+ ```sh
86
+ ownexit direct --host 203.0.113.7 # add --port 2222 if SSH is not on 22
87
+ ```
88
+
89
+ It sets up key login, checks the system, enables BBR, installs sing-box (by running the third-party installer [233boy/sing-box](https://github.com/233boy/sing-box) interactively — choose **VLESS-REALITY** and press Enter for the rest), renders subscriptions, uploads them and verifies every layer.
90
+
91
+ 2. **Import on your devices** — the script prints three URLs:
92
+
93
+ | URL | For |
94
+ | ---- | ---- |
95
+ | `…/clash.yaml` | Clash Verge, mihomo, Clash Meta for Android |
96
+ | `…/shadowrocket.txt` | Shadowrocket on iPhone |
97
+ | `…/node.txt` | the plain `vless://` link, for anything else |
98
+
99
+ 3. **Check and close up** — open `https://ipinfo.io` on a device: it should show your VPS's IP. Then turn the subscription endpoint off until you need it again:
100
+
101
+ ```sh
102
+ ownexit subctl stop
103
+ ```
104
+
105
+ The VPS is remembered, so later runs need no arguments: `ownexit direct` to redeploy, `ownexit subctl status|start|stop`. Step-by-step guide: [docs/manual/direct.md](docs/manual/direct.md) (Chinese).
106
+
107
+ ## Quick start: relay
108
+
109
+ 1. **Give it two IPs**
110
+
111
+ ```sh
112
+ ownexit chain init --relay 203.0.113.10 --exit 203.0.113.20
113
+ ```
114
+
115
+ It sets up key login on both servers (one password prompt each), detects the exit IP and asks you to confirm it, checks whether the relay already runs sing-box, and writes `~/.config/ownexit/chains/main.env`. Nothing on the servers is changed yet. By default deploy will also add an nftables rule on the exit so that only the relay can reach its Reality port (`--exit-source-filter managed`); use `provider` if your provider's security group already does that, or `none` to skip it.
116
+
117
+ 2. **Deploy**
118
+
119
+ ```sh
120
+ ownexit chain --id main deploy
121
+ ```
122
+
123
+ Both servers download the pinned sing-box release from GitHub themselves (falling back to an upload from your computer). It deploys the exit first, then the relay, as one transaction, and verifies the exit IP three different ways. If anything fails, it cleans up; if your network drops halfway, run `deploy` or `rollback` again and it converges.
124
+
125
+ 3. **Import** the node from `~/.local/state/ownexit/chains/main/client/node.txt`, or run `ownexit multi --chains main render` for QR codes and a Clash snippet.
126
+
127
+ Day to day: `ownexit chain --id main status | verify | conns | rollback`. Relay blocked? Deploy a second relay with `init --id backup …` and combine both with `multi_chain_client.sh` — clients switch automatically. Full reference: [chain/README.md](chain/README.md); guide: [docs/manual/chain.md](docs/manual/chain.md) (both in Chinese).
128
+
129
+ ## Supported platforms
130
+
131
+ | | Direct | Relay |
132
+ | ---- | ---- | ---- |
133
+ | Control machine | macOS (tested); Linux (untested); Windows not supported — try WSL at your own risk | macOS on Apple silicon (tested), macOS on Intel (untested), Linux amd64 (tested on Ubuntu 20.04), Linux arm64 and WSL (untested) |
134
+ | Server OS | Debian, Ubuntu | Linux with systemd; the relay needs `systemd-socket-proxyd`; no nftables tables other than ownexit's own, UFW inactive |
135
+ | Server CPU | whatever 233boy/sing-box supports (amd64, arm64) | amd64 (tested) or arm64 (untested); relay and exit must match |
136
+ | Clients | Clash Verge, mihomo, Shadowrocket tested; any VLESS-Reality client via `vless://` | same |
137
+
138
+ If your computer runs a proxy in TUN mode (Clash and similar), SSH to the servers may be cut off halfway through a deploy. Turn TUN off, or route the relay and exit IPs directly, while running chain commands.
139
+
140
+ ## Security notes
141
+
142
+ - No real IP, password or key ever goes into this repository. There is no "edit the IP at the top of the script" step and no `--password` option. Passwords are typed interactively (or passed via `OWNEXIT_SSH_PASSWORD` for non-interactive use), submitted once per try, and never written to disk.
143
+ - A wrong password is retried at most 3 times, and each try is submitted to the server only once, so you are unlikely to trip fail2ban. Failures end with `reason=bad-password`, `reason=password-disabled` or `reason=unreachable`.
144
+ - The direct subscription endpoint is plain HTTP protected by a random path. Keep it stopped (`ownexit subctl stop`) except while importing, and use `ownexit direct --rotate-token` if a URL leaks.
145
+ - The relay only runs `systemd-socket-proxyd`; the Reality private key lives only on the exit server, in a mode-600 file. By default the exit's Reality port only accepts connections from the relay (an nftables table that starts and stops with the exit service).
146
+ - The direct setup installs sing-box through the third-party script 233boy/sing-box. The relay setup downloads a pinned official sing-box release on each server and checks the SHA-256 of both the archive and the binary.
147
+
148
+ See [SECURITY.md](SECURITY.md) for how to report a vulnerability.
149
+
150
+ ## FAQ
151
+
152
+ **Can I change the SSH port or user?** Direct: `--port`, `--user`. Relay: `--relay-port`, `--exit-port`; the relay setup requires root.
153
+
154
+ **I manage several VPSes.** Pass `--host` to pick one. Without it, `ownexit direct` and `ownexit subctl` list the remembered servers and exit.
155
+
156
+ **How do I undo it?** Relay: `ownexit chain --id main rollback`. Direct (0.1.0 has no uninstall command yet): on the VPS, `systemctl disable --now ownexit-subscription`, remove `/opt/ownexit-subscription` and `/etc/systemd/system/ownexit-subscription.service`, and remove sing-box with the installer's own `sb` tool.
157
+
158
+ **Where are my files?** Keys: `~/.ssh/ownexit/`. Configuration: `~/.config/ownexit/`. State and subscriptions: `~/.local/state/ownexit/`.
159
+
160
+ ## Contributing
161
+
162
+ Issues and pull requests are welcome; please read [CONTRIBUTING.md](CONTRIBUTING.md) first. The project follows the [Contributor Covenant](CODE_OF_CONDUCT.md).
163
+
164
+ ## License
165
+
166
+ [MIT](LICENSE). Use this software in accordance with the laws where you live and the terms of your server provider.
@@ -0,0 +1,235 @@
1
+ # 链式代理:中转机 + 出口机
2
+
3
+ 客户端连接中转机的 TCP 端口,中转机用 `systemd-socket-proxyd` 把字节原样转发到出口机,VLESS-Reality 只在出口机终止。出口机的 IP 被墙时,换一台中转机即可,出口 IP 和客户端凭据都不变。
4
+
5
+ ```text
6
+ 电脑 / 手机
7
+ │ VLESS-Reality
8
+ ▼
9
+ 中转机
10
+ │ systemd-socket-proxyd,纯 TCP 字节转发(不解密、不存密钥)
11
+ ▼
12
+ 出口机
13
+ │ sing-box VLESS-Reality + direct
14
+ ▼
15
+ 目标网站看到出口机的 IP
16
+ ```
17
+
18
+ ## 快速上手
19
+
20
+ 在本仓库根目录运行:
21
+
22
+ ```bash
23
+ # 1. 只问两个 IP:给两台机器配免密(各问一次 root 密码),探测出口 IP 与中转现状,生成配置
24
+ chain/setup_chain.sh init --relay 203.0.113.10 --exit 203.0.113.20
25
+
26
+ # 2. 部署(先只读预检、再按“出口机 → 中转机”顺序事务部署,最后完整验证)
27
+ chain/setup_chain.sh --id main deploy
28
+
29
+ # 3. 客户端节点链接在这里(含凭据,权限 600):
30
+ cat ~/.local/state/ownexit/chains/main/client/node.txt
31
+ ```
32
+
33
+ 想先看看能不能部署、不改动远端:`chain/setup_chain.sh --id main preflight`。
34
+
35
+ `init` 生成的配置写在 `~/.config/ownexit/chains/<名字>.env`(默认名字 `main`),之后 `deploy`、`verify`、`rollback` 等只读这个文件,不接受主机参数覆盖:部署状态绑定了配置文件的哈希,改了配置会和状态对不上。要部署第二条链,用 `init --id <新名字>`。
36
+
37
+ ## 前置条件
38
+
39
+ 1. 控制端是 macOS(Apple 芯片 / Intel,系统自带 `/bin/bash` 3.2 即可)或 Linux(amd64 / arm64,含 WSL);必须在本仓库的 git 工作区里运行(`git` 用来确认真实配置不在仓库内)。配置与状态目录的每一级上级目录都不能被同组或其他用户写入,否则会被安全检查拒绝。
40
+ 2. 两台 Linux 服务器,同为 amd64 或同为 arm64(不支持两端架构不同),root 能用密码 SSH 登录(只在 `init` 时用一次)。
41
+ 3. 出口机的云厂商安全组 / 防火墙允许中转机访问;脚本不调用任何云厂商 API。“只允许中转机连出口机的 Reality 端口”有三种做法,由 `EXIT_SOURCE_FILTER` 决定:默认 `managed`,部署时由本项目在出口机加一张只放行中转机出站地址的 nft 表;`provider`,由服务商在机器外的安全组负责;`none`,不限制(没有凭据仍无法使用)。`managed` 与 `provider` 部署时都会严格检查本机直连连不上。
42
+ 4. 两台机器除本项目的 `table inet ownexit_*` 白名单表外没有任何 nft 表;装了 UFW 的话须为 inactive;legacy iptables 不得有活动规则。
43
+ 5. 中转机已安装 `systemd-socket-proxyd`;两端具备 `preflight` 列出的系统工具。全新机器还要确认 `/etc/systemd/system/sockets.target.wants` 存在(root:root 755),缺失时 `preflight` 会给出创建命令,脚本不代建 systemd 标准目录。
44
+ 6. 中转机上已经在跑 sing-box 也可以:`init` 会自动识别并填 `RELAY_COHOSTS_SINGBOX=yes`,部署时保护既有 sing-box 不受影响;状态不完整(只有配置没有进程之类)时 `init` 会拒绝。
45
+
46
+ ## 安全边界
47
+
48
+ - 两端 SSH 用户固定为 `root`,要求免密 key 和已受信任的 ed25519 host key(`init` 会配好)。脚本生成隔离的 SSH config,不继承用户自己的 ProxyCommand、端口转发或远端命令;出口机的管理连接经中转机 `ProxyJump`。
49
+ - host-key 指纹取自各自 SSH 会话的 `ssh -vv -E` 日志,不读取远端的公钥文件,也不会把 ProxyJump 的中转指纹当成出口机指纹。
50
+ - 脚本不修改防火墙、云厂商安全组和既有 sing-box 配置。
51
+ - 本链专属的远端路径一旦已存在即拒绝覆盖;固定版本的共享 binary 只在 owner、mode、版本、哈希完全一致时复用。
52
+ - Reality 私钥只存在于出口机权限 600 的配置里;日志不打印 UUID、密钥、short id 或完整节点链接。
53
+ - 只验收 TCP + `socks5h`,不承诺 UDP、QUIC/HTTP3、ICMP 或客户端 DNS 零泄漏。
54
+
55
+ ## 配置(手工编辑属于进阶用法)
56
+
57
+ `init` 生成的文件格式与 `chain/chain.example.env` 相同。解析器不执行 shell,只接受空行、井号注释和严格的 `KEY=VALUE`,必须且只能包含下面 13 个键:
58
+
59
+ | 键 | 说明 | `init` 怎么得到 |
60
+ | ---- | ---- | ---- |
61
+ | `CHAIN_ID` | `[a-z0-9][a-z0-9-]{0,31}`,对应本地状态目录和远端 unit 名 | `--id`,默认 `main` |
62
+ | `RELAY_HOST` / `RELAY_SSH_PORT` | 中转机 IPv4 与 SSH 端口 | `--relay`、`--relay-port`(默认 22) |
63
+ | `RELAY_SSH_USER` | 固定为 `root` | 固定 |
64
+ | `RELAY_SSH_KEY` | 本机上中转机私钥的绝对路径 | `~/.ssh/ownexit/id_ed25519_root_<ip>_<端口>` |
65
+ | `EXIT_HOST` / `EXIT_SSH_PORT` | 出口机 IPv4 与 SSH 端口 | `--exit`、`--exit-port`(默认 22) |
66
+ | `EXIT_SSH_USER` | 固定为 `root` | 固定 |
67
+ | `EXIT_SSH_KEY` | 本机上出口机私钥的绝对路径,不会复制到中转机 | 同上规则 |
68
+ | `EXPECTED_EXIT_IPV4` | 出口验证时唯一允许返回的 IPv4 | 在出口机上探测,终端里请你确认 |
69
+ | `REALITY_SERVER_NAME` | Reality 伪装域名(ASCII FQDN),没有自动 fallback | `--sni`,默认 `www.amazon.com` |
70
+ | `RELAY_COHOSTS_SINGBOX` | `yes` 保护中转机上既有的 sing-box;`no` 要求中转机上没有 sing-box | 登录中转机自动识别 |
71
+ | `EXIT_SOURCE_FILTER` | `managed`:本项目在出口机加 nft 白名单;`provider`:服务商安全组只放行中转来源;两者部署 / verify 时本机能直连出口机 Reality 端口即失败。`none`:不限制,能直连只记 WARN | `--exit-source-filter`,默认 `managed` |
72
+
73
+ 配置文件必须由当前用户拥有、权限 600、不是符号链接,并且位于 git 工作区之外;私钥要求 group / other 没有任何权限。
74
+
75
+ ## 命令
76
+
77
+ ```bash
78
+ chain/setup_chain.sh init --relay <ip> --exit <ip> [--id <名字>] [--relay-port N] [--exit-port N] [--sni <域名>] [--exit-source-filter managed|provider|none]
79
+ chain/setup_chain.sh --id main preflight
80
+ chain/setup_chain.sh --id main deploy
81
+ chain/setup_chain.sh --id main verify
82
+ chain/setup_chain.sh --id main verify --with-fail-closed
83
+ chain/setup_chain.sh --id main status
84
+ chain/setup_chain.sh --id main rollback
85
+
86
+ # 中转连接治理(链已 deploy 后可用)
87
+ chain/setup_chain.sh --id main conns # 各来源 IP 的连接数 / 空闲秒数 / 是否拉黑,以及 proxyd fd 用量
88
+ chain/setup_chain.sh --id main kick 203.0.113.7 # 用 ss -K 断开该来源的已建连接
89
+ chain/setup_chain.sh --id main ban 203.0.113.7 # 加入黑名单并立即生效(顺带 kick)
90
+ chain/setup_chain.sh --id main ban 198.51.100.0/24
91
+ chain/setup_chain.sh --id main unban 203.0.113.7
92
+ chain/setup_chain.sh --id main banlist # 对照本地黑名单与中转两个 unit 的 IPAddressDeny 回读值
93
+
94
+ # 出口机同一台机器换了公网 IP(先改配置里的 EXIT_HOST / EXPECTED_EXIT_IPV4)
95
+ chain/setup_chain.sh --id main rehost-exit
96
+ ```
97
+
98
+ `--id <名字>` 是 `--config ~/.config/ownexit/chains/<名字>.env` 的简写,两者二选一。
99
+
100
+ 黑名单写在中转机两个专属 unit 的受管 drop-in(`<unit>.d/50-ownexit-chain-blacklist.conf`,内容是 `IPAddressDeny=`,由 systemd cgroup BPF 生效,不是防火墙),`daemon-reload` 后立即生效,不重启服务、不影响其它来源。本地权威副本是 `${XDG_STATE_HOME:-~/.local/state}/ownexit/chains/<id>/blacklist.txt`;`verify` / `status` 只放行这一个 drop-in,并要求远端 `IPAddressDeny` 回读值与本地列表逐字一致,列表为空时远端必须没有 drop-in。`kick` 依赖中转内核支持 `ss -K`,`ban` 依赖 cgroup v2,两者在 Debian 12 / systemd 252 上实测过。中转端口是无鉴权的四层转发,`conns` 里出现陌生来源时先 `ban`,再考虑 rollback 后换端口重新部署。
101
+
102
+ `preflight` 只在操作临时目录准备资产,不写持久 cache、远端 unit 或权威状态。`deploy` 取得锁后会重新执行全部检查,不复用之前 preflight 的结果。
103
+
104
+ `verify --with-fail-closed` 会短暂停止本链的中转 socket,确认新连接拿不到任何有效出口 IP,再恢复 socket 并重跑完整的出口验证。远端 90 秒的 timer 是控制进程崩溃时的第二道恢复保险。
105
+
106
+ 隔离 SSH 配置固定 `ConnectTimeout=12`、`ServerAliveInterval=15`、`ServerAliveCountMax=2`;持锁后的每个 SSH / scp 另有控制端 600 秒总超时,超时按“远端不可达”分类。`status` 通常只读,但在状态、身份、平台、基线和残留都通过后,如果 socket active 而 relay service inactive,会执行一次 `systemctl start` 并复查;它不会写远端文件。
107
+
108
+ ## 状态与退出码
109
+
110
+ `status` 输出以下状态之一:
111
+
112
+ | 状态 | 含义 | 退出码 |
113
+ | ---- | ---- | ---- |
114
+ | `deployed/healthy` | 状态、两端资源、进程、监听与既有服务基线一致 | 0 |
115
+ | `not_deployed` | 没有活动状态、事务、专属资源或本链的暂存目录 | 0 |
116
+ | `busy` | 同一条链有身份有效的活动锁 | 5 |
117
+ | `stale_lock` | 锁身份已失效;下一条修改类命令会归档 | 5 |
118
+ | `incomplete` | 存在待恢复的 deploy / rollback 事务 | 5 |
119
+ | `unreachable` | 至少一台远端无法经受控 SSH 探针核证 | 5 |
120
+ | `orphaned` | 没有状态,但存在专属对象或本链的暂存目录 | 5 |
121
+ | `drifted` | 有状态,但哈希、权限、unit、监听或基线不一致 | 5 |
122
+
123
+ 不健康时输出会带脱敏的 `reason`(适用时)和 `next=<安全动作>`;`unreachable` 还会给出 `role=relay|exit`,例如 `status=unreachable role=exit reason=hostkey-probe next=retry-status`。
124
+
125
+ 退出码:参数错误返回 2;预检 / 检查失败返回 3(`init` 配免密或探测失败也是 3);路径碰撞返回 4;状态或 verify 不健康返回 5;rollback 预校验失败返回 6。
126
+
127
+ ## 验证模型
128
+
129
+ 部署与 `verify` 分三层验证,不能互相替代:
130
+
131
+ 1. 出口机到 Reality 伪装站点的 TLS 1.3 / 证书探针。
132
+ 2. 中转机上临时起一个 sing-box 直连出口机,证明放行、SNI、凭据和出口机直连出口都正常。
133
+ 3. 中转机经 relay 的完整链路,以及本机用 Darwin 版 sing-box 连接公网 relay。
134
+
135
+ 出口请求固定用 `api.ipify.org`、`icanhazip.com`、`ifconfig.me/ip`:至少两个成功,且所有有效响应都必须等于 `EXPECTED_EXIT_IPV4`;没有 direct fallback。
136
+
137
+ 当 `RELAY_COHOSTS_SINGBOX=yes` 时,零回归基线以正在运行的 `sing-box.service` 的 MainPID 为准:解析 `/proc/<pid>/cmdline` 的 `-c/-C` 和 `/proc/<pid>/cwd` 得到实际配置,记录 cmdline、cwd、ExecStart、unit 与 drop-in、可执行文件元数据和该进程的监听端口;不假定配置一定在 `/etc/sing-box`。`no` 时写四份 `none` 占位。
138
+
139
+ ## 本机上的文件
140
+
141
+ | 路径 | 内容 |
142
+ | ---- | ---- |
143
+ | `${XDG_CONFIG_HOME:-$HOME/.config}/ownexit/chains/<id>.env` | 本链配置(`init` 生成或手工编写) |
144
+ | `${XDG_STATE_HOME:-$HOME/.local/state}/ownexit/chains/<id>/state.env` | 带内嵌校验和的权威部署状态 |
145
+ | `.../transaction.env` | 事务日志;存在即表示有未完成的事务 |
146
+ | `.../baseline/` | 中转机既有 sing-box 的四份只读基线,或 `none` 占位 |
147
+ | `.../client/node.txt` | 唯一持久的客户端产物,权限 600 |
148
+ | `.../audit/` | 完整的 deploy / rollback 与过期锁审计记录 |
149
+ | `${XDG_CACHE_HOME:-$HOME/.cache}/ownexit/chains/<id>/downloads/` | 可删除、可重新下载的固定版本官方资产 |
150
+ | `${XDG_STATE_HOME:-$HOME/.local/state}/ownexit/multi-chain-client/<name>/` | `multi_chain_client.sh render` 的多链聚合产物,与 `chains/<id>/` 互不重叠 |
151
+ | `~/.ssh/ownexit/` | `init`(经 `direct/connect_to.sh`)为每台机器生成的专用密钥 |
152
+
153
+ ## 远端下载与本机验证
154
+
155
+ - 远端的固定版本 sing-box 由服务器自己从 GitHub 下载并核对 SHA256(4 个平台包的归档与 binary 哈希写死在脚本顶部);远端下载失败才在本机下载后上传。日志里 `binary 来源=remote-download|local-upload` 说明走了哪条路。
156
+ - 本机只准备本机平台的官方包,用来做“本机层出口 smoke”。本机平台没有官方包、或缓存缺失且下载失败时,跳过这一层并 WARN,不影响部署;`multi_chain_client.sh verify` 则必须有本机包。
157
+ - 状态文件沿用 v0.1.0 的字段,v0.1.0 部署的链可以直接用新版本管理。
158
+
159
+ ## 远端资源
160
+
161
+ 中转机专属资源:
162
+
163
+ ```text
164
+ /etc/ownexit-chain/<id>.owner.env
165
+ /etc/systemd/system/ownexit-chain-relay-<id>.socket
166
+ /etc/systemd/system/ownexit-chain-relay-<id>.service
167
+ /etc/systemd/system/sockets.target.wants/ownexit-chain-relay-<id>.socket
168
+ # 仅黑名单非空时存在(ban 的产物,rollback 一并删除):
169
+ /etc/systemd/system/ownexit-chain-relay-<id>.socket.d/50-ownexit-chain-blacklist.conf
170
+ /etc/systemd/system/ownexit-chain-relay-<id>.service.d/50-ownexit-chain-blacklist.conf
171
+ ```
172
+
173
+ 出口机专属资源(`managed` 时还有一张随 `ownexit-chain-exit-<id>.service` 起停的 nft 表 `table inet ownexit_<id,- 换成 _>`,规则写在该 unit 的 `ExecStartPre` / `ExecStopPost` 里):
174
+
175
+ ```text
176
+ /etc/ownexit-chain/<id>.owner.env
177
+ /etc/ownexit-chain/<id>.exit.json
178
+ /etc/systemd/system/ownexit-chain-exit-<id>.service
179
+ /etc/systemd/system/multi-user.target.wants/ownexit-chain-exit-<id>.service
180
+ ```
181
+
182
+ 两端共享、rollback 后预期保留:
183
+
184
+ ```text
185
+ /etc/ownexit-chain
186
+ /opt/ownexit-chain
187
+ /opt/ownexit-chain/bin
188
+ /opt/ownexit-chain/bin/sing-box-1.13.14
189
+ ```
190
+
191
+ 官方 Linux 包里的 `libcronet.so` 只用于核对压缩包布局,不会发布到共享目录。
192
+
193
+ ## 回滚与故障恢复
194
+
195
+ `rollback` 在停止服务之前,先核验全部 owner、哈希、符号链接、systemd 加载路径、本地产物、主机与密钥指纹和既有 sing-box 基线。通过后按“中转机 → 出口机”顺序拆除专属资源;停止后只接受 `inactive` / `failed` 明确终态,service 必须 `MainPID=0`,删除后还要复核 `LoadState=not-found`、没有 fragment 和 drop-in、没有监听。状态、基线、节点和最终事务全部归档为带 `COMPLETE` 标记的 `audit/rolledback.*` 之后,才删除活动状态。
196
+
197
+ 控制端被 `kill -9` 或断电时,下一条修改类命令会读取 `transaction.env`:只有全量验证完成、且状态与远端一致时才补齐提交;其它 deploy 逆序清理,rollback 从最后完成的步骤继续。共享目录和固定 binary 不在单条链的回滚清单里。
198
+
199
+ 换一台中转机时,用新的 `--id` 跑 `init` 再部署,确认新链可用后再 rollback 旧链。
200
+
201
+ ## 出口机换 IP(同一台机器)
202
+
203
+ 服务商给出口机换了公网 IP、机器本身没换(ed25519 主机指纹不变)时,用 `rehost-exit` 原地迁移,不要 rollback 加 deploy:rollback 要连旧 IP,deploy 会重新生成凭据,所有客户端都得重新导入。
204
+
205
+ ```bash
206
+ # 1. 配置里把 EXIT_HOST(出口 IP 也变了就连同 EXPECTED_EXIT_IPV4)改成新值,其余键不动
207
+ # 2. known_hosts 补新 IP 的 ed25519 条目(同一台机器,主机公钥不变);补完先核对指纹
208
+ grep '^<旧IP> ssh-ed25519 ' ~/.ssh/known_hosts | sed 's/^<旧IP> /<新IP> /' >> ~/.ssh/known_hosts
209
+ ssh-keygen -F <新IP> | tail -1 | ssh-keygen -lf -
210
+ # 3. 迁移(共用这台出口机的每条链各跑一次)
211
+ chain/setup_chain.sh --id main rehost-exit
212
+ ```
213
+
214
+ 只允许配置里 `EXIT_HOST` / `EXPECTED_EXIT_IPV4` 两个键与状态不同,其余键不一致退出 2。经中转机登录新 IP 后,协商到的主机指纹必须等于状态里记录的值,否则退出 3(说明换成了另一台机器)。迁移顺序:出口机 owner → 中转机 owner 与 relay service 的 `ExecStart` 目标(改完 `daemon-reload`;正在运行的 relay 若仍指向旧目标就重启一次,在途连接会断开,客户端自动重连)→ 本地状态(旧状态归档到 `audit/rehosted.<部署ID>.<操作ID>/state.env`)→ 自动跑与 `verify` 相同的完整核验。UUID、Reality 密钥、端口和 `client/node.txt` 都不变,客户端不用重新导入。
215
+
216
+ 这个命令不走事务:每个远端步骤都用“整文件哈希守门 + 单行替换”,同时接受旧形态和已迁移形态,中途失败直接重跑同一条命令即可收敛;状态已经绑定新配置时输出 `rehost=noop` 并返回 0。退出码:0 成功或 noop;2 参数错误或其它配置键不一致;3 新 IP 不可达、缺 known_hosts 条目或不是同一台机器;5 锁、状态损坏、有未完成事务或收尾 verify 失败;1 远端迁移或本地提交失败(信息里带远端码 171–177 及含义)。
217
+
218
+ 注意:本机开着 Clash 一类的 TUN 模式时,发往中转机的 SSH 也可能被代理接管,部署过程中任何一次 SSH 断开都会让命令以退出码 3 停下(只读阶段)或留下待恢复的事务(之后由下一条 deploy / rollback 按事务记录收敛)。relay 重启或客户端在链之间切换的瞬间,控制端 SSH 会被切断,收尾 verify 可能报 drift 或残留。此时状态已经提交,直接再跑一次 `verify` 即可;想避免的话,执行前关闭 TUN,或让中转机 IP 走直连。
219
+
220
+ ## 多链客户端聚合(`multi_chain_client.sh`)
221
+
222
+ 中转机的 IP 最容易被墙。可以给每台中转机各部署一条链(各自的 `--id`,共用同一台出口机),再用 `multi_chain_client.sh` 把各链的 `node.txt` 聚合成客户端产物:Clash Verge / mihomo 用 `fallback` 自动组在链之间自动切换,iPhone 逐链扫码后手动切换。脚本只读本机的 `chains/<id>.env` 与 `chains/<id>/client/node.txt`,不连中转机和出口机,也不改链的状态。
223
+
224
+ ```bash
225
+ # 逐链从本机做真实 Reality 握手 + 出口三端点仲裁;本机 TUN 开着时对应链标 skipped(全部 skipped 退出 5)
226
+ chain/multi_chain_client.sh --chains main,backup verify
227
+ # 聚合产物:nodes.txt(每链一行 vless)、clash-snippet.yaml(proxies + fallback 自动组,供 Clash Verge 手工合并)、
228
+ # 每链一张二维码(iPhone Shadowrocket,扫完删除目录)
229
+ chain/multi_chain_client.sh --chains main,backup render
230
+ chain/multi_chain_client.sh --chains main,backup render --group url-test --no-qr
231
+ ```
232
+
233
+ `--chains` 的顺序就是自动组的优先级;`--name` 决定产物目录 `${XDG_STATE_HOME:-~/.local/state}/ownexit/multi-chain-client/<name>/`(默认 `all`)。节点名是各链自己的 `Exit-via-Relay-<id>`(合并进 Clash Verge 前先删掉同名的旧单节点),自动组名是 `Exit-Relay-auto`。各链的 `EXPECTED_EXIT_IPV4` 必须相同。退出码:0 成功;2 参数 / 配置 / `node.txt` 校验错误或出口 IP 跨链不一致;5 `verify` 有链不健康或全部 skipped;1 运行时失败。
234
+
235
+ 某台中转机换 IP 或被墙后:用新 `--id` 部署一条新链,把它加进 `--chains`,旧链 rollback 后从列表去掉,重新 `render` 并导入。
@@ -0,0 +1,16 @@
1
+ # 复制到仓库外的绝对路径后填写真实值,并执行 chmod 600。
2
+ # 不允许引号、变量展开、前后空白、重复键或未知键。
3
+ CHAIN_ID=example-chain
4
+ RELAY_HOST=REPLACE_WITH_RELAY_IPV4
5
+ RELAY_SSH_PORT=22
6
+ RELAY_SSH_USER=root
7
+ RELAY_SSH_KEY=/ABSOLUTE/PATH/TO/RELAY_PRIVATE_KEY
8
+ EXIT_HOST=REPLACE_WITH_EXIT_IPV4
9
+ EXIT_SSH_PORT=22
10
+ EXIT_SSH_USER=root
11
+ EXIT_SSH_KEY=/ABSOLUTE/PATH/TO/EXIT_PRIVATE_KEY
12
+ EXPECTED_EXIT_IPV4=REPLACE_WITH_EXPECTED_EXIT_IPV4
13
+ REALITY_SERVER_NAME=www.amazon.com
14
+ RELAY_COHOSTS_SINGBOX=yes
15
+ # managed:部署时在出口机加 nft 白名单只放行中转;provider:服务商安全组只放行中转;none:不限制
16
+ EXIT_SOURCE_FILTER=managed