@qpjoy/tunnel-cli 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/README.md +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +220 -0
- package/package.json +32 -0
- package/resources/mihomo-client.sh +1037 -0
- package/scripts/prepare-package.mjs +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @qpjoy/tunnel-cli
|
|
2
|
+
|
|
3
|
+
Global CLI wrapper for the QPJoy Linux `mihomo-client` server script.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i -g @qpjoy/tunnel-cli
|
|
7
|
+
qp-tunnel-cli help
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The package ships the existing `scripts/mihomo-client.sh` file in the npm
|
|
11
|
+
tarball. It does not reimplement the Linux systemd, proxy, SSH, daemon, or TUN
|
|
12
|
+
orchestration in Node.
|
|
13
|
+
|
|
14
|
+
## Server Usage
|
|
15
|
+
|
|
16
|
+
Run commands directly through the bundled script:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
qp-tunnel-cli status
|
|
20
|
+
qp-tunnel-cli tun-on
|
|
21
|
+
qp-tunnel-cli tun-off
|
|
22
|
+
qp-tunnel-cli update-subscription
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For server commands, `qp-tunnel-cli` re-runs itself with `sudo` when root is needed.
|
|
26
|
+
|
|
27
|
+
Install the bundled script as a normal Linux command:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
sudo qp-tunnel-cli install-script
|
|
31
|
+
sudo mihomo-client status
|
|
32
|
+
sudo mihomo-client tun-on
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Use a custom target when needed:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
sudo qp-tunnel-cli install-script --target /opt/qpjoy/bin/mihomo-client
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Show the underlying script help:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
qp-tunnel-cli client-help
|
|
45
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const packageRoot = (0, node_path_1.resolve)(__dirname, '..');
|
|
10
|
+
const bundledClientScript = (0, node_path_1.resolve)(packageRoot, 'resources/mihomo-client.sh');
|
|
11
|
+
const repoClientScript = (0, node_path_1.resolve)(packageRoot, '../../../scripts/mihomo-client.sh');
|
|
12
|
+
const defaultInstallTarget = '/usr/local/bin/mihomo-client';
|
|
13
|
+
const clientCommands = new Set([
|
|
14
|
+
'setup',
|
|
15
|
+
'install',
|
|
16
|
+
'update-subscription',
|
|
17
|
+
'start',
|
|
18
|
+
'stop',
|
|
19
|
+
'restart',
|
|
20
|
+
'status',
|
|
21
|
+
'logs',
|
|
22
|
+
'enable',
|
|
23
|
+
'disable',
|
|
24
|
+
'proxy-on',
|
|
25
|
+
'proxy-off',
|
|
26
|
+
'tun-on',
|
|
27
|
+
'tun-off',
|
|
28
|
+
'ssh-proxy-on',
|
|
29
|
+
'ssh-proxy-off',
|
|
30
|
+
'daemon-proxy-on',
|
|
31
|
+
'daemon-proxy-off',
|
|
32
|
+
'docker-proxy-on',
|
|
33
|
+
'docker-proxy-off',
|
|
34
|
+
'run',
|
|
35
|
+
'test',
|
|
36
|
+
'print-env',
|
|
37
|
+
'uninstall',
|
|
38
|
+
]);
|
|
39
|
+
function help() {
|
|
40
|
+
process.stdout.write(`QPJoy Tunnel CLI
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
qp-tunnel-cli help
|
|
44
|
+
qp-tunnel-cli install-script [--target /usr/local/bin/mihomo-client]
|
|
45
|
+
qp-tunnel-cli script-path
|
|
46
|
+
qp-tunnel-cli client-help
|
|
47
|
+
qp-tunnel-cli <mihomo-client command> [options]
|
|
48
|
+
|
|
49
|
+
Common commands:
|
|
50
|
+
qp-tunnel-cli install --url http://IP:3434/peer_user01.mihomo.yaml --user download --password pass
|
|
51
|
+
qp-tunnel-cli status
|
|
52
|
+
qp-tunnel-cli start
|
|
53
|
+
qp-tunnel-cli tun-on
|
|
54
|
+
qp-tunnel-cli tun-off
|
|
55
|
+
qp-tunnel-cli update-subscription
|
|
56
|
+
qp-tunnel-cli uninstall --purge
|
|
57
|
+
|
|
58
|
+
The npm package is a thin distributor for the Linux mihomo-client script. Client
|
|
59
|
+
commands re-run through sudo when needed, then execute the bundled shell script.
|
|
60
|
+
|
|
61
|
+
Install the script as a normal server command:
|
|
62
|
+
sudo qp-tunnel-cli install-script
|
|
63
|
+
sudo mihomo-client status
|
|
64
|
+
`);
|
|
65
|
+
}
|
|
66
|
+
function clientHelp() {
|
|
67
|
+
runScriptWithoutSudo(['help']);
|
|
68
|
+
}
|
|
69
|
+
function version() {
|
|
70
|
+
try {
|
|
71
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(packageRoot, 'package.json'), 'utf8'));
|
|
72
|
+
return pkg.version ?? 'unknown';
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return 'unknown';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function isRoot() {
|
|
79
|
+
return typeof process.getuid === 'function' && process.getuid() === 0;
|
|
80
|
+
}
|
|
81
|
+
function resolveClientScript() {
|
|
82
|
+
if ((0, node_fs_1.existsSync)(bundledClientScript)) {
|
|
83
|
+
return bundledClientScript;
|
|
84
|
+
}
|
|
85
|
+
if ((0, node_fs_1.existsSync)(repoClientScript)) {
|
|
86
|
+
return repoClientScript;
|
|
87
|
+
}
|
|
88
|
+
process.stderr.write(`Could not find mihomo-client.sh. Expected ${bundledClientScript} in the npm package.\n`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
function exitFromSpawn(result) {
|
|
92
|
+
if (result.error) {
|
|
93
|
+
process.stderr.write(`${result.error.message}\n`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
if (result.signal) {
|
|
97
|
+
process.stderr.write(`Command terminated by signal ${result.signal}\n`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
process.exit(result.status ?? 0);
|
|
101
|
+
}
|
|
102
|
+
function sudoSelf(cliArgs) {
|
|
103
|
+
const result = (0, node_child_process_1.spawnSync)('sudo', ['-E', process.execPath, __filename, ...cliArgs], {
|
|
104
|
+
stdio: 'inherit',
|
|
105
|
+
env: process.env,
|
|
106
|
+
});
|
|
107
|
+
exitFromSpawn(result);
|
|
108
|
+
}
|
|
109
|
+
function runScriptWithoutSudo(scriptArgs) {
|
|
110
|
+
const result = (0, node_child_process_1.spawnSync)('bash', [resolveClientScript(), ...scriptArgs], {
|
|
111
|
+
stdio: 'inherit',
|
|
112
|
+
env: process.env,
|
|
113
|
+
});
|
|
114
|
+
exitFromSpawn(result);
|
|
115
|
+
}
|
|
116
|
+
function runClientCommand(scriptArgs) {
|
|
117
|
+
if (process.platform !== 'linux') {
|
|
118
|
+
process.stderr.write('mihomo-client commands target Linux systemd servers. Use this CLI on the server host.\n');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
if (!isRoot()) {
|
|
122
|
+
sudoSelf(scriptArgs);
|
|
123
|
+
}
|
|
124
|
+
runScriptWithoutSudo(scriptArgs);
|
|
125
|
+
}
|
|
126
|
+
function parseInstallScriptArgs(scriptArgs) {
|
|
127
|
+
let target = defaultInstallTarget;
|
|
128
|
+
for (let index = 0; index < scriptArgs.length; index += 1) {
|
|
129
|
+
const arg = scriptArgs[index];
|
|
130
|
+
if (arg === '--target') {
|
|
131
|
+
const value = scriptArgs[index + 1];
|
|
132
|
+
if (!value) {
|
|
133
|
+
process.stderr.write('Missing value for --target.\n');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
target = value;
|
|
137
|
+
index += 1;
|
|
138
|
+
}
|
|
139
|
+
else if (arg === '--help' || arg === '-h') {
|
|
140
|
+
process.stdout.write(`Usage:
|
|
141
|
+
qp-tunnel-cli install-script [--target /usr/local/bin/mihomo-client]
|
|
142
|
+
|
|
143
|
+
Copies the bundled mihomo-client.sh to the target path and chmods it 755.
|
|
144
|
+
`);
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
process.stderr.write(`Unknown install-script option: ${arg}\n`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return (0, node_path_1.resolve)(target);
|
|
153
|
+
}
|
|
154
|
+
function isPermissionError(error) {
|
|
155
|
+
return (typeof error === 'object' &&
|
|
156
|
+
error !== null &&
|
|
157
|
+
'code' in error &&
|
|
158
|
+
error.code === 'EACCES');
|
|
159
|
+
}
|
|
160
|
+
function installClientScript(scriptArgs) {
|
|
161
|
+
const target = parseInstallScriptArgs(scriptArgs);
|
|
162
|
+
const source = resolveClientScript();
|
|
163
|
+
try {
|
|
164
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(target), { recursive: true });
|
|
165
|
+
(0, node_fs_1.copyFileSync)(source, target);
|
|
166
|
+
(0, node_fs_1.chmodSync)(target, 0o755);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (!isRoot() && isPermissionError(error)) {
|
|
170
|
+
sudoSelf(['install-script', ...scriptArgs]);
|
|
171
|
+
}
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
process.stdout.write(`Installed mihomo-client launcher to ${target}\n`);
|
|
175
|
+
}
|
|
176
|
+
async function printScriptPath() {
|
|
177
|
+
const script = resolveClientScript();
|
|
178
|
+
try {
|
|
179
|
+
await (0, promises_1.access)(script, node_fs_1.constants.R_OK);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
process.stderr.write(`Script is not readable: ${script}\n`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
process.stdout.write(`${script}\n`);
|
|
186
|
+
}
|
|
187
|
+
async function main() {
|
|
188
|
+
const command = args[0] ?? 'help';
|
|
189
|
+
if (command === 'help' || command === '--help' || command === '-h') {
|
|
190
|
+
help();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (command === 'version' || command === '--version' || command === '-v') {
|
|
194
|
+
process.stdout.write(`${version()}\n`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (command === 'client-help') {
|
|
198
|
+
clientHelp();
|
|
199
|
+
}
|
|
200
|
+
if (command === 'script-path') {
|
|
201
|
+
await printScriptPath();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (command === 'install-script') {
|
|
205
|
+
installClientScript(args.slice(1));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const passthroughCommand = command === '--verbose' ? args[1] : command;
|
|
209
|
+
if (passthroughCommand && clientCommands.has(passthroughCommand)) {
|
|
210
|
+
runClientCommand(args);
|
|
211
|
+
}
|
|
212
|
+
process.stderr.write(`Unknown command: ${command}\n`);
|
|
213
|
+
help();
|
|
214
|
+
process.exitCode = 1;
|
|
215
|
+
}
|
|
216
|
+
main().catch((error) => {
|
|
217
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
218
|
+
process.stderr.write(`${message}\n`);
|
|
219
|
+
process.exitCode = 1;
|
|
220
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qpjoy/tunnel-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Global QPJoy Tunnel CLI for installing and running the Linux mihomo-client script.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"qp-tunnel-cli": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"resources",
|
|
15
|
+
"scripts",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.10.7"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"lint": "tsc -p tsconfig.json --noEmit"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,1037 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
MIHOMO_VERBOSE="${MIHOMO_VERBOSE:-false}"
|
|
6
|
+
|
|
7
|
+
MIHOMO_HOME="${MIHOMO_HOME:-/etc/mihomo-client}"
|
|
8
|
+
MIHOMO_ENV_FILE="${MIHOMO_ENV_FILE:-$MIHOMO_HOME/client.env}"
|
|
9
|
+
MIHOMO_SUBSCRIPTION_FILE="${MIHOMO_SUBSCRIPTION_FILE:-$MIHOMO_HOME/subscription.yaml}"
|
|
10
|
+
MIHOMO_CONFIG_FILE="${MIHOMO_CONFIG_FILE:-$MIHOMO_HOME/config.yaml}"
|
|
11
|
+
MIHOMO_TUN_OVERLAY_FILE="${MIHOMO_TUN_OVERLAY_FILE:-$MIHOMO_HOME/tun-overlay.yaml}"
|
|
12
|
+
MIHOMO_BIN="${MIHOMO_BIN:-/usr/local/bin/mihomo}"
|
|
13
|
+
MIHOMO_CLIENT_LAUNCHER="${MIHOMO_CLIENT_LAUNCHER:-/usr/local/bin/mihomo-client}"
|
|
14
|
+
MIHOMO_SERVICE_NAME="${MIHOMO_SERVICE_NAME:-mihomo-client.service}"
|
|
15
|
+
MIHOMO_SERVICE_FILE="${MIHOMO_SERVICE_FILE:-/etc/systemd/system/$MIHOMO_SERVICE_NAME}"
|
|
16
|
+
MIHOMO_PROFILE_PROXY_FILE="${MIHOMO_PROFILE_PROXY_FILE:-/etc/profile.d/mihomo-client-proxy.sh}"
|
|
17
|
+
MIHOMO_DAEMON_PROXY_SERVICES="${MIHOMO_DAEMON_PROXY_SERVICES:-docker.service containerd.service buildkit.service}"
|
|
18
|
+
MIHOMO_DAEMON_PROXY_DROPIN_NAME="${MIHOMO_DAEMON_PROXY_DROPIN_NAME:-mihomo-proxy.conf}"
|
|
19
|
+
MIHOMO_SSH_PROXY_HELPER="${MIHOMO_SSH_PROXY_HELPER:-/usr/local/bin/mihomo-ssh-proxy}"
|
|
20
|
+
MIHOMO_SSH_CONFIG_DIR="${MIHOMO_SSH_CONFIG_DIR:-/etc/ssh/ssh_config.d}"
|
|
21
|
+
MIHOMO_SSH_CONFIG_FILE="${MIHOMO_SSH_CONFIG_FILE:-$MIHOMO_SSH_CONFIG_DIR/99-mihomo-proxy.conf}"
|
|
22
|
+
MIHOMO_SSH_PROXY_HOSTS="${MIHOMO_SSH_PROXY_HOSTS:-github.com gitlab.com bitbucket.org ssh.dev.azure.com}"
|
|
23
|
+
GITHUB_API_ROOT="${GITHUB_API_ROOT:-https://api.github.com/repos/MetaCubeX/mihomo/releases}"
|
|
24
|
+
MIHOMO_GEOX_GEOIP_URL="${MIHOMO_GEOX_GEOIP_URL:-https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat}"
|
|
25
|
+
MIHOMO_GEOX_GEOSITE_URL="${MIHOMO_GEOX_GEOSITE_URL:-https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat}"
|
|
26
|
+
MIHOMO_GEOX_MMDB_URL="${MIHOMO_GEOX_MMDB_URL:-https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb}"
|
|
27
|
+
|
|
28
|
+
usage() {
|
|
29
|
+
cat <<'EOF'
|
|
30
|
+
Usage:
|
|
31
|
+
sudo bash ./scripts/mihomo-client.sh [--verbose] <command> [options]
|
|
32
|
+
|
|
33
|
+
Commands:
|
|
34
|
+
setup Interactive install wizard (same as install without flags)
|
|
35
|
+
install Install Mihomo core, fetch remote subscription, create+enable service
|
|
36
|
+
update-subscription Re-download remote YAML and restart service if it is running
|
|
37
|
+
start Start Mihomo client service
|
|
38
|
+
stop Stop Mihomo client service
|
|
39
|
+
restart Restart Mihomo client service
|
|
40
|
+
status Show service status and current local proxy info
|
|
41
|
+
logs Show recent service logs
|
|
42
|
+
enable Enable service on boot
|
|
43
|
+
disable Disable service on boot
|
|
44
|
+
proxy-on Write /etc/profile.d proxy exports for login shells
|
|
45
|
+
proxy-off Remove /etc/profile.d proxy exports
|
|
46
|
+
tun-on Enable Mihomo TUN mode and also turn proxy-on on
|
|
47
|
+
tun-off Disable Mihomo TUN mode and also turn proxy-on off
|
|
48
|
+
ssh-proxy-on Configure OpenSSH client to use local Mihomo SOCKS for common Git hosts
|
|
49
|
+
ssh-proxy-off Remove OpenSSH proxy override
|
|
50
|
+
daemon-proxy-on Configure common daemon services to use local Mihomo proxy
|
|
51
|
+
daemon-proxy-off Remove daemon proxy overrides
|
|
52
|
+
docker-proxy-on Backward-compatible alias for daemon-proxy-on
|
|
53
|
+
docker-proxy-off Backward-compatible alias for daemon-proxy-off
|
|
54
|
+
run Run one command with Mihomo proxy env injected
|
|
55
|
+
test Test outbound access through the local mixed-port
|
|
56
|
+
print-env Print proxy env exports for the current local mixed-port
|
|
57
|
+
uninstall Stop+disable service and optionally remove config/binary
|
|
58
|
+
help Show this help
|
|
59
|
+
|
|
60
|
+
Install options:
|
|
61
|
+
--url URL Subscription URL, e.g. http://IP:3434/peer_user01.mihomo.yaml
|
|
62
|
+
--user USER Basic Auth username for subscription
|
|
63
|
+
--password PASS Basic Auth password for subscription
|
|
64
|
+
--version TAG Mihomo version tag. Default: latest stable release
|
|
65
|
+
--binary-path PATH Use an existing Mihomo binary instead of downloading from GitHub
|
|
66
|
+
--no-start Install/update files but do not start the service
|
|
67
|
+
|
|
68
|
+
Update options:
|
|
69
|
+
--url URL Override saved subscription URL
|
|
70
|
+
--user USER Override saved subscription username
|
|
71
|
+
--password PASS Override saved subscription password
|
|
72
|
+
|
|
73
|
+
Uninstall options:
|
|
74
|
+
--purge Also remove config, env, downloaded YAML, and Mihomo binary
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
sudo bash ./scripts/mihomo-client.sh install \
|
|
78
|
+
--url http://IP:3434/peer_user01.mihomo.yaml \
|
|
79
|
+
--user download \
|
|
80
|
+
--password pass
|
|
81
|
+
|
|
82
|
+
sudo bash ./scripts/mihomo-client.sh install \
|
|
83
|
+
--url http://IP:3434/peer_user01.mihomo.yaml \
|
|
84
|
+
--binary-path /tmp/mihomo
|
|
85
|
+
|
|
86
|
+
sudo bash ./scripts/mihomo-client.sh update-subscription
|
|
87
|
+
sudo bash ./scripts/mihomo-client.sh start
|
|
88
|
+
sudo bash ./scripts/mihomo-client.sh proxy-on
|
|
89
|
+
sudo bash ./scripts/mihomo-client.sh tun-on
|
|
90
|
+
sudo bash ./scripts/mihomo-client.sh ssh-proxy-on
|
|
91
|
+
sudo bash ./scripts/mihomo-client.sh daemon-proxy-on
|
|
92
|
+
sudo bash ./scripts/mihomo-client.sh run curl -I https://www.google.com/generate_204
|
|
93
|
+
sudo bash ./scripts/mihomo-client.sh print-env
|
|
94
|
+
EOF
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
die() {
|
|
98
|
+
echo "Error: $*" >&2
|
|
99
|
+
exit 1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
log() {
|
|
103
|
+
echo "[mihomo-client] $*" >&2
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
enable_verbose() {
|
|
107
|
+
MIHOMO_VERBOSE="true"
|
|
108
|
+
set -x
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
require_root() {
|
|
112
|
+
[[ "${EUID:-0}" -eq 0 ]] || die "Please run this script as root."
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
require_cmd() {
|
|
116
|
+
command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
ensure_dirs() {
|
|
120
|
+
mkdir -p "$MIHOMO_HOME"
|
|
121
|
+
chmod 700 "$MIHOMO_HOME"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
set_env_value() {
|
|
125
|
+
local key="$1"
|
|
126
|
+
local value="$2"
|
|
127
|
+
local tmp escaped
|
|
128
|
+
|
|
129
|
+
escaped="$(printf "%s" "$value" | sed "s/'/'\\\\''/g")"
|
|
130
|
+
value="'$escaped'"
|
|
131
|
+
tmp="$(mktemp)"
|
|
132
|
+
|
|
133
|
+
if [[ -f "$MIHOMO_ENV_FILE" ]]; then
|
|
134
|
+
awk -v key="$key" -v value="$value" '
|
|
135
|
+
BEGIN { updated = 0 }
|
|
136
|
+
$0 ~ "^" key "=" {
|
|
137
|
+
print key "=" value
|
|
138
|
+
updated = 1
|
|
139
|
+
next
|
|
140
|
+
}
|
|
141
|
+
{ print }
|
|
142
|
+
END {
|
|
143
|
+
if (!updated) {
|
|
144
|
+
print key "=" value
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
' "$MIHOMO_ENV_FILE" > "$tmp"
|
|
148
|
+
else
|
|
149
|
+
printf "%s=%s\n" "$key" "$value" > "$tmp"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
mv "$tmp" "$MIHOMO_ENV_FILE"
|
|
153
|
+
chmod 600 "$MIHOMO_ENV_FILE"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
load_env() {
|
|
157
|
+
[[ -f "$MIHOMO_ENV_FILE" ]] || return 0
|
|
158
|
+
set -a
|
|
159
|
+
# shellcheck disable=SC1090
|
|
160
|
+
source "$MIHOMO_ENV_FILE"
|
|
161
|
+
set +a
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
prompt_default() {
|
|
165
|
+
local prompt="$1"
|
|
166
|
+
local default_value="${2:-}"
|
|
167
|
+
local result
|
|
168
|
+
|
|
169
|
+
if [[ -n "$default_value" ]]; then
|
|
170
|
+
read -r -p "$prompt [$default_value]: " result
|
|
171
|
+
echo "${result:-$default_value}"
|
|
172
|
+
else
|
|
173
|
+
read -r -p "$prompt: " result
|
|
174
|
+
echo "$result"
|
|
175
|
+
fi
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
prompt_password() {
|
|
179
|
+
local prompt="$1"
|
|
180
|
+
local value
|
|
181
|
+
read -r -s -p "$prompt: " value
|
|
182
|
+
echo
|
|
183
|
+
echo "$value"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
extract_auth_from_url() {
|
|
187
|
+
local raw_url="$1"
|
|
188
|
+
python3 - "$raw_url" <<'PY'
|
|
189
|
+
import sys
|
|
190
|
+
from urllib.parse import urlsplit, urlunsplit, unquote
|
|
191
|
+
|
|
192
|
+
raw = sys.argv[1]
|
|
193
|
+
parts = urlsplit(raw)
|
|
194
|
+
|
|
195
|
+
username = parts.username or ""
|
|
196
|
+
password = parts.password or ""
|
|
197
|
+
|
|
198
|
+
hostname = parts.hostname or ""
|
|
199
|
+
netloc = hostname
|
|
200
|
+
if parts.port:
|
|
201
|
+
netloc = f"{netloc}:{parts.port}"
|
|
202
|
+
|
|
203
|
+
sanitized = urlunsplit((parts.scheme, netloc, parts.path, parts.query, parts.fragment))
|
|
204
|
+
print(sanitized)
|
|
205
|
+
print(unquote(username))
|
|
206
|
+
print(unquote(password))
|
|
207
|
+
PY
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
normalize_subscription_inputs() {
|
|
211
|
+
local url="$1"
|
|
212
|
+
local username="$2"
|
|
213
|
+
local password="$3"
|
|
214
|
+
local sanitized_url parsed_user parsed_pass
|
|
215
|
+
local -a parsed=()
|
|
216
|
+
|
|
217
|
+
if [[ "$url" == *"@"* ]] && [[ "$url" == http://* || "$url" == https://* ]]; then
|
|
218
|
+
mapfile -t parsed < <(extract_auth_from_url "$url")
|
|
219
|
+
if [[ "${#parsed[@]}" -ge 3 ]]; then
|
|
220
|
+
sanitized_url="${parsed[0]}"
|
|
221
|
+
parsed_user="${parsed[1]}"
|
|
222
|
+
parsed_pass="${parsed[2]}"
|
|
223
|
+
url="$sanitized_url"
|
|
224
|
+
log "Detected Basic Auth inside subscription URL. Credentials will be stored separately."
|
|
225
|
+
if [[ -z "$username" && -n "$parsed_user" ]]; then
|
|
226
|
+
username="$parsed_user"
|
|
227
|
+
fi
|
|
228
|
+
if [[ -z "$password" && -n "$parsed_pass" ]]; then
|
|
229
|
+
password="$parsed_pass"
|
|
230
|
+
fi
|
|
231
|
+
fi
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
printf "%s\n%s\n%s\n" "$url" "$username" "$password"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
detect_asset_selector() {
|
|
238
|
+
case "$(uname -m)" in
|
|
239
|
+
x86_64|amd64) echo "linux-amd64-v1" ;;
|
|
240
|
+
aarch64|arm64) echo "linux-arm64" ;;
|
|
241
|
+
armv7l|armv7) echo "linux-armv7" ;;
|
|
242
|
+
armv6l|armv6) echo "linux-armv6" ;;
|
|
243
|
+
i386|i686) echo "linux-386" ;;
|
|
244
|
+
riscv64) echo "linux-riscv64" ;;
|
|
245
|
+
s390x) echo "linux-s390x" ;;
|
|
246
|
+
*) die "Unsupported CPU architecture: $(uname -m)" ;;
|
|
247
|
+
esac
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
resolve_release_asset() {
|
|
251
|
+
local version="${1:-latest}"
|
|
252
|
+
local selector="$2"
|
|
253
|
+
local api_url json_file
|
|
254
|
+
|
|
255
|
+
json_file="$(mktemp)"
|
|
256
|
+
if [[ "$version" == "latest" ]]; then
|
|
257
|
+
api_url="$GITHUB_API_ROOT"
|
|
258
|
+
log "Querying latest stable Mihomo release metadata for $selector"
|
|
259
|
+
else
|
|
260
|
+
api_url="$GITHUB_API_ROOT/tags/$version"
|
|
261
|
+
log "Querying Mihomo release metadata for tag $version ($selector)"
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
if [[ "$version" == "latest" ]]; then
|
|
265
|
+
curl -fsSL "$api_url" -o "$json_file"
|
|
266
|
+
python3 - "$json_file" "$selector" <<'PY'
|
|
267
|
+
import json, sys
|
|
268
|
+
from pathlib import Path
|
|
269
|
+
|
|
270
|
+
path = Path(sys.argv[1])
|
|
271
|
+
selector = sys.argv[2]
|
|
272
|
+
releases = json.loads(path.read_text())
|
|
273
|
+
|
|
274
|
+
for rel in releases:
|
|
275
|
+
if rel.get("prerelease"):
|
|
276
|
+
continue
|
|
277
|
+
assets = rel.get("assets", [])
|
|
278
|
+
for asset in assets:
|
|
279
|
+
name = asset.get("name", "")
|
|
280
|
+
if name.startswith(f"mihomo-{selector}-") and name.endswith(".gz"):
|
|
281
|
+
print(rel.get("tag_name", ""))
|
|
282
|
+
print(asset.get("browser_download_url", ""))
|
|
283
|
+
sys.exit(0)
|
|
284
|
+
raise SystemExit(1)
|
|
285
|
+
PY
|
|
286
|
+
else
|
|
287
|
+
curl -fsSL "$api_url" -o "$json_file"
|
|
288
|
+
python3 - "$json_file" "$selector" <<'PY'
|
|
289
|
+
import json, sys
|
|
290
|
+
from pathlib import Path
|
|
291
|
+
|
|
292
|
+
path = Path(sys.argv[1])
|
|
293
|
+
selector = sys.argv[2]
|
|
294
|
+
rel = json.loads(path.read_text())
|
|
295
|
+
assets = rel.get("assets", [])
|
|
296
|
+
|
|
297
|
+
for asset in assets:
|
|
298
|
+
name = asset.get("name", "")
|
|
299
|
+
if name.startswith(f"mihomo-{selector}-") and name.endswith(".gz"):
|
|
300
|
+
print(rel.get("tag_name", ""))
|
|
301
|
+
print(asset.get("browser_download_url", ""))
|
|
302
|
+
sys.exit(0)
|
|
303
|
+
raise SystemExit(1)
|
|
304
|
+
PY
|
|
305
|
+
fi
|
|
306
|
+
rm -f "$json_file"
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
install_binary() {
|
|
310
|
+
local version="${1:-latest}"
|
|
311
|
+
local selector release_info tag download_url tmpdir archive tmpbin
|
|
312
|
+
|
|
313
|
+
require_cmd curl
|
|
314
|
+
require_cmd python3
|
|
315
|
+
require_cmd gzip
|
|
316
|
+
|
|
317
|
+
selector="$(detect_asset_selector)"
|
|
318
|
+
mapfile -t release_info < <(resolve_release_asset "$version" "$selector") || die "Failed to resolve Mihomo release asset for $selector"
|
|
319
|
+
[[ "${#release_info[@]}" -ge 2 ]] || die "Unexpected release metadata returned from GitHub."
|
|
320
|
+
tag="${release_info[0]}"
|
|
321
|
+
download_url="${release_info[1]}"
|
|
322
|
+
|
|
323
|
+
tmpdir="$(mktemp -d)"
|
|
324
|
+
archive="$tmpdir/mihomo.gz"
|
|
325
|
+
tmpbin="$tmpdir/mihomo"
|
|
326
|
+
|
|
327
|
+
log "Downloading Mihomo $tag from GitHub"
|
|
328
|
+
curl -fsSL "$download_url" -o "$archive"
|
|
329
|
+
gzip -dc "$archive" > "$tmpbin"
|
|
330
|
+
chmod 755 "$tmpbin"
|
|
331
|
+
mv "$tmpbin" "$MIHOMO_BIN"
|
|
332
|
+
|
|
333
|
+
set_env_value MIHOMO_VERSION "$tag"
|
|
334
|
+
echo "Installed Mihomo $tag to $MIHOMO_BIN"
|
|
335
|
+
rm -rf "$tmpdir"
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
install_binary_from_path() {
|
|
339
|
+
local source_path="$1"
|
|
340
|
+
[[ -n "$source_path" ]] || die "Binary path is required."
|
|
341
|
+
[[ -f "$source_path" ]] || die "Mihomo binary not found: $source_path"
|
|
342
|
+
[[ -x "$source_path" ]] || die "Mihomo binary is not executable: $source_path"
|
|
343
|
+
cp "$source_path" "$MIHOMO_BIN"
|
|
344
|
+
chmod 755 "$MIHOMO_BIN"
|
|
345
|
+
set_env_value MIHOMO_VERSION "local"
|
|
346
|
+
echo "Installed Mihomo local binary to $MIHOMO_BIN"
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
install_client_launcher() {
|
|
350
|
+
local source_script
|
|
351
|
+
source_script="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")"
|
|
352
|
+
[[ -f "$source_script" ]] || die "Could not locate current mihomo-client script: $source_script"
|
|
353
|
+
cp "$source_script" "$MIHOMO_CLIENT_LAUNCHER"
|
|
354
|
+
chmod 755 "$MIHOMO_CLIENT_LAUNCHER"
|
|
355
|
+
log "Installed global launcher to $MIHOMO_CLIENT_LAUNCHER"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
write_service_file() {
|
|
359
|
+
cat > "$MIHOMO_SERVICE_FILE" <<EOF
|
|
360
|
+
[Unit]
|
|
361
|
+
Description=Mihomo Client
|
|
362
|
+
After=network-online.target
|
|
363
|
+
Wants=network-online.target
|
|
364
|
+
|
|
365
|
+
[Service]
|
|
366
|
+
Type=simple
|
|
367
|
+
WorkingDirectory=$MIHOMO_HOME
|
|
368
|
+
ExecStart=$MIHOMO_BIN -d $MIHOMO_HOME -f $MIHOMO_CONFIG_FILE
|
|
369
|
+
Restart=on-failure
|
|
370
|
+
RestartSec=5
|
|
371
|
+
LimitNOFILE=1048576
|
|
372
|
+
|
|
373
|
+
[Install]
|
|
374
|
+
WantedBy=multi-user.target
|
|
375
|
+
EOF
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
systemd_reload() {
|
|
379
|
+
systemctl daemon-reload
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
service_is_active() {
|
|
383
|
+
systemctl is-active --quiet "$MIHOMO_SERVICE_NAME"
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
ensure_subscription_source() {
|
|
387
|
+
if [[ ! -f "$MIHOMO_SUBSCRIPTION_FILE" && -f "$MIHOMO_CONFIG_FILE" ]]; then
|
|
388
|
+
cp "$MIHOMO_CONFIG_FILE" "$MIHOMO_SUBSCRIPTION_FILE"
|
|
389
|
+
chmod 600 "$MIHOMO_SUBSCRIPTION_FILE"
|
|
390
|
+
fi
|
|
391
|
+
[[ -f "$MIHOMO_SUBSCRIPTION_FILE" ]] || die "Subscription source not found. Please run install or update-subscription first."
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
write_tun_overlay() {
|
|
395
|
+
cat > "$MIHOMO_TUN_OVERLAY_FILE" <<'EOF'
|
|
396
|
+
tun:
|
|
397
|
+
enable: true
|
|
398
|
+
stack: system
|
|
399
|
+
auto-route: true
|
|
400
|
+
auto-redirect: true
|
|
401
|
+
auto-detect-interface: true
|
|
402
|
+
strict-route: true
|
|
403
|
+
dns-hijack:
|
|
404
|
+
- any:53
|
|
405
|
+
- tcp://any:53
|
|
406
|
+
|
|
407
|
+
dns:
|
|
408
|
+
enable: true
|
|
409
|
+
listen: 0.0.0.0:1053
|
|
410
|
+
ipv6: false
|
|
411
|
+
use-hosts: true
|
|
412
|
+
use-system-hosts: true
|
|
413
|
+
cache-algorithm: arc
|
|
414
|
+
enhanced-mode: fake-ip
|
|
415
|
+
fake-ip-range: 198.18.0.1/16
|
|
416
|
+
default-nameserver:
|
|
417
|
+
- 223.5.5.5
|
|
418
|
+
- 119.29.29.29
|
|
419
|
+
- 1.1.1.1
|
|
420
|
+
nameserver:
|
|
421
|
+
- https://dns.alidns.com/dns-query
|
|
422
|
+
- https://doh.pub/dns-query
|
|
423
|
+
fallback:
|
|
424
|
+
- tls://1.1.1.1
|
|
425
|
+
- tls://8.8.8.8
|
|
426
|
+
fallback-filter:
|
|
427
|
+
geoip: true
|
|
428
|
+
geoip-code: CN
|
|
429
|
+
geosite:
|
|
430
|
+
- gfw
|
|
431
|
+
EOF
|
|
432
|
+
chmod 600 "$MIHOMO_TUN_OVERLAY_FILE"
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
remove_tun_overlay() {
|
|
436
|
+
rm -f "$MIHOMO_TUN_OVERLAY_FILE"
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
tun_enabled() {
|
|
440
|
+
[[ -f "$MIHOMO_TUN_OVERLAY_FILE" ]]
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
config_uses_geodata() {
|
|
444
|
+
grep -Eq '^[[:space:]]*geodata-mode:[[:space:]]*true([[:space:]]|$)' "$MIHOMO_SUBSCRIPTION_FILE"
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
config_has_geox_url() {
|
|
448
|
+
grep -Eq '^[[:space:]]*geox-url:[[:space:]]*$' "$MIHOMO_SUBSCRIPTION_FILE"
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
append_geox_overlay_if_needed() {
|
|
452
|
+
if config_uses_geodata && ! config_has_geox_url; then
|
|
453
|
+
log "Injecting geox-url mirror overrides for geo data downloads"
|
|
454
|
+
cat >> "$MIHOMO_CONFIG_FILE" <<EOF
|
|
455
|
+
|
|
456
|
+
geox-url:
|
|
457
|
+
geoip: "$MIHOMO_GEOX_GEOIP_URL"
|
|
458
|
+
geosite: "$MIHOMO_GEOX_GEOSITE_URL"
|
|
459
|
+
mmdb: "$MIHOMO_GEOX_MMDB_URL"
|
|
460
|
+
EOF
|
|
461
|
+
fi
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
render_runtime_config() {
|
|
465
|
+
ensure_subscription_source
|
|
466
|
+
cat "$MIHOMO_SUBSCRIPTION_FILE" > "$MIHOMO_CONFIG_FILE"
|
|
467
|
+
append_geox_overlay_if_needed
|
|
468
|
+
if tun_enabled; then
|
|
469
|
+
printf "\n" >> "$MIHOMO_CONFIG_FILE"
|
|
470
|
+
cat "$MIHOMO_TUN_OVERLAY_FILE" >> "$MIHOMO_CONFIG_FILE"
|
|
471
|
+
fi
|
|
472
|
+
chmod 600 "$MIHOMO_CONFIG_FILE"
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
fetch_subscription() {
|
|
476
|
+
local url="$1"
|
|
477
|
+
local username="${2:-}"
|
|
478
|
+
local password="${3:-}"
|
|
479
|
+
local tmp_file
|
|
480
|
+
local -a curl_args=()
|
|
481
|
+
|
|
482
|
+
[[ -n "$url" ]] || die "Subscription URL is required."
|
|
483
|
+
|
|
484
|
+
tmp_file="$(mktemp)"
|
|
485
|
+
curl_args=(-fsSL)
|
|
486
|
+
if [[ -n "$username" || -n "$password" ]]; then
|
|
487
|
+
curl_args+=(-u "${username}:${password}")
|
|
488
|
+
fi
|
|
489
|
+
|
|
490
|
+
log "Fetching remote subscription: $url"
|
|
491
|
+
curl "${curl_args[@]}" "$url" -o "$tmp_file"
|
|
492
|
+
[[ -s "$tmp_file" ]] || die "Downloaded subscription is empty."
|
|
493
|
+
mv "$tmp_file" "$MIHOMO_SUBSCRIPTION_FILE"
|
|
494
|
+
chmod 600 "$MIHOMO_SUBSCRIPTION_FILE"
|
|
495
|
+
render_runtime_config
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
mixed_port_from_config() {
|
|
499
|
+
if [[ -f "$MIHOMO_CONFIG_FILE" ]]; then
|
|
500
|
+
awk -F: '/^[[:space:]]*mixed-port[[:space:]]*:/ {gsub(/[[:space:]]/, "", $2); print $2; exit}' "$MIHOMO_CONFIG_FILE"
|
|
501
|
+
fi
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
proxy_env_lines() {
|
|
505
|
+
local port="$1"
|
|
506
|
+
cat <<EOF
|
|
507
|
+
export http_proxy=http://127.0.0.1:$port
|
|
508
|
+
export https_proxy=http://127.0.0.1:$port
|
|
509
|
+
export HTTP_PROXY=http://127.0.0.1:$port
|
|
510
|
+
export HTTPS_PROXY=http://127.0.0.1:$port
|
|
511
|
+
export all_proxy=socks5://127.0.0.1:$port
|
|
512
|
+
export ALL_PROXY=socks5://127.0.0.1:$port
|
|
513
|
+
EOF
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
print_env_command() {
|
|
517
|
+
local port
|
|
518
|
+
port="$(mixed_port_from_config)"
|
|
519
|
+
[[ -n "$port" ]] || die "Could not detect mixed-port from $MIHOMO_CONFIG_FILE"
|
|
520
|
+
proxy_env_lines "$port"
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
docker_proxy_env_lines() {
|
|
524
|
+
local port="$1"
|
|
525
|
+
cat <<EOF
|
|
526
|
+
HTTP_PROXY=http://127.0.0.1:$port
|
|
527
|
+
HTTPS_PROXY=http://127.0.0.1:$port
|
|
528
|
+
NO_PROXY=localhost,127.0.0.1,::1
|
|
529
|
+
http_proxy=http://127.0.0.1:$port
|
|
530
|
+
https_proxy=http://127.0.0.1:$port
|
|
531
|
+
no_proxy=localhost,127.0.0.1,::1
|
|
532
|
+
EOF
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
service_dropin_dir() {
|
|
536
|
+
local service="$1"
|
|
537
|
+
echo "/etc/systemd/system/${service}.d"
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
service_dropin_file() {
|
|
541
|
+
local service="$1"
|
|
542
|
+
echo "$(service_dropin_dir "$service")/$MIHOMO_DAEMON_PROXY_DROPIN_NAME"
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
service_exists() {
|
|
546
|
+
local service="$1"
|
|
547
|
+
local state
|
|
548
|
+
state="$(systemctl show -p LoadState --value "$service" 2>/dev/null || true)"
|
|
549
|
+
[[ -n "$state" && "$state" != "not-found" ]]
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
managed_daemon_services() {
|
|
553
|
+
local service
|
|
554
|
+
for service in $MIHOMO_DAEMON_PROXY_SERVICES; do
|
|
555
|
+
if service_exists "$service"; then
|
|
556
|
+
echo "$service"
|
|
557
|
+
fi
|
|
558
|
+
done
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
managed_daemon_services_with_proxy() {
|
|
562
|
+
local service file
|
|
563
|
+
for service in $MIHOMO_DAEMON_PROXY_SERVICES; do
|
|
564
|
+
file="$(service_dropin_file "$service")"
|
|
565
|
+
if [[ -f "$file" ]]; then
|
|
566
|
+
echo "$service"
|
|
567
|
+
fi
|
|
568
|
+
done
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
ssh_proxy_enabled() {
|
|
572
|
+
[[ -f "$MIHOMO_SSH_CONFIG_FILE" ]]
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
status_command() {
|
|
576
|
+
local port version daemon_services
|
|
577
|
+
load_env
|
|
578
|
+
version="${MIHOMO_VERSION:-unknown}"
|
|
579
|
+
port="$(mixed_port_from_config || true)"
|
|
580
|
+
daemon_services="$(managed_daemon_services_with_proxy | paste -sd ',' - 2>/dev/null || true)"
|
|
581
|
+
|
|
582
|
+
echo "Mihomo binary: $MIHOMO_BIN"
|
|
583
|
+
echo "Mihomo version: $version"
|
|
584
|
+
echo "Config file: $MIHOMO_CONFIG_FILE"
|
|
585
|
+
echo "Subscription file: $MIHOMO_SUBSCRIPTION_FILE"
|
|
586
|
+
echo "Subscription URL: ${MIHOMO_SUBSCRIPTION_URL:-unset}"
|
|
587
|
+
echo "Mixed port: ${port:-unknown}"
|
|
588
|
+
echo "Shell proxy profile: $([[ -f "$MIHOMO_PROFILE_PROXY_FILE" ]] && echo enabled || echo disabled)"
|
|
589
|
+
echo "TUN mode: $([[ -f "$MIHOMO_TUN_OVERLAY_FILE" ]] && echo enabled || echo disabled)"
|
|
590
|
+
echo "SSH proxy config: $([[ -f "$MIHOMO_SSH_CONFIG_FILE" ]] && echo enabled || echo disabled)"
|
|
591
|
+
echo "Managed daemon proxy services: ${daemon_services:-none}"
|
|
592
|
+
echo
|
|
593
|
+
systemctl status "$MIHOMO_SERVICE_NAME" --no-pager || true
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
logs_command() {
|
|
597
|
+
journalctl -u "$MIHOMO_SERVICE_NAME" -n 100 --no-pager
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
update_subscription_command() {
|
|
601
|
+
local url="${1:-}"
|
|
602
|
+
local username="${2:-}"
|
|
603
|
+
local password="${3:-}"
|
|
604
|
+
local -a normalized=()
|
|
605
|
+
|
|
606
|
+
load_env
|
|
607
|
+
|
|
608
|
+
url="${url:-${MIHOMO_SUBSCRIPTION_URL:-}}"
|
|
609
|
+
username="${username:-${MIHOMO_SUBSCRIPTION_USER:-}}"
|
|
610
|
+
password="${password:-${MIHOMO_SUBSCRIPTION_PASSWORD:-}}"
|
|
611
|
+
mapfile -t normalized < <(normalize_subscription_inputs "$url" "$username" "$password")
|
|
612
|
+
url="${normalized[0]}"
|
|
613
|
+
username="${normalized[1]}"
|
|
614
|
+
password="${normalized[2]}"
|
|
615
|
+
|
|
616
|
+
[[ -n "$url" ]] || die "No subscription URL configured. Use install or pass --url."
|
|
617
|
+
|
|
618
|
+
fetch_subscription "$url" "$username" "$password"
|
|
619
|
+
set_env_value MIHOMO_SUBSCRIPTION_URL "$url"
|
|
620
|
+
set_env_value MIHOMO_SUBSCRIPTION_USER "$username"
|
|
621
|
+
set_env_value MIHOMO_SUBSCRIPTION_PASSWORD "$password"
|
|
622
|
+
|
|
623
|
+
if service_is_active; then
|
|
624
|
+
log "Restarting Mihomo service after subscription update"
|
|
625
|
+
systemctl restart "$MIHOMO_SERVICE_NAME"
|
|
626
|
+
echo "Subscription updated and service restarted."
|
|
627
|
+
else
|
|
628
|
+
echo "Subscription updated."
|
|
629
|
+
fi
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
proxy_on_command() {
|
|
633
|
+
local port
|
|
634
|
+
port="$(mixed_port_from_config)"
|
|
635
|
+
[[ -n "$port" ]] || die "Could not detect mixed-port from $MIHOMO_CONFIG_FILE"
|
|
636
|
+
cat > "$MIHOMO_PROFILE_PROXY_FILE" <<EOF
|
|
637
|
+
# Generated by scripts/mihomo-client.sh
|
|
638
|
+
$(proxy_env_lines "$port")
|
|
639
|
+
EOF
|
|
640
|
+
chmod 644 "$MIHOMO_PROFILE_PROXY_FILE"
|
|
641
|
+
echo "Shell proxy exports enabled at $MIHOMO_PROFILE_PROXY_FILE"
|
|
642
|
+
echo "Open a new shell or run: source $MIHOMO_PROFILE_PROXY_FILE"
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
proxy_off_command() {
|
|
646
|
+
rm -f "$MIHOMO_PROFILE_PROXY_FILE"
|
|
647
|
+
echo "Shell proxy exports removed."
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
write_ssh_proxy_helper() {
|
|
651
|
+
cat > "$MIHOMO_SSH_PROXY_HELPER" <<'EOF'
|
|
652
|
+
#!/bin/sh
|
|
653
|
+
set -eu
|
|
654
|
+
|
|
655
|
+
HOST="${1:?host required}"
|
|
656
|
+
PORT="${2:?port required}"
|
|
657
|
+
PROXY_ADDR="${MIHOMO_SSH_PROXY_ADDR:-127.0.0.1:7890}"
|
|
658
|
+
|
|
659
|
+
if command -v nc >/dev/null 2>&1; then
|
|
660
|
+
exec nc -x "$PROXY_ADDR" -X 5 "$HOST" "$PORT"
|
|
661
|
+
fi
|
|
662
|
+
|
|
663
|
+
if command -v ncat >/dev/null 2>&1; then
|
|
664
|
+
exec ncat --proxy "$PROXY_ADDR" --proxy-type socks5 "$HOST" "$PORT"
|
|
665
|
+
fi
|
|
666
|
+
|
|
667
|
+
if command -v connect-proxy >/dev/null 2>&1; then
|
|
668
|
+
exec connect-proxy -S "$PROXY_ADDR" "$HOST" "$PORT"
|
|
669
|
+
fi
|
|
670
|
+
|
|
671
|
+
echo "No SOCKS-capable helper found (need nc, ncat, or connect-proxy)." >&2
|
|
672
|
+
exit 1
|
|
673
|
+
EOF
|
|
674
|
+
chmod 755 "$MIHOMO_SSH_PROXY_HELPER"
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
ssh_proxy_on_command() {
|
|
678
|
+
local port
|
|
679
|
+
port="$(mixed_port_from_config)"
|
|
680
|
+
[[ -n "$port" ]] || die "Could not detect mixed-port from $MIHOMO_CONFIG_FILE"
|
|
681
|
+
mkdir -p "$MIHOMO_SSH_CONFIG_DIR"
|
|
682
|
+
write_ssh_proxy_helper
|
|
683
|
+
cat > "$MIHOMO_SSH_CONFIG_FILE" <<EOF
|
|
684
|
+
Host $MIHOMO_SSH_PROXY_HOSTS
|
|
685
|
+
ProxyCommand env MIHOMO_SSH_PROXY_ADDR=127.0.0.1:$port $MIHOMO_SSH_PROXY_HELPER %h %p
|
|
686
|
+
EOF
|
|
687
|
+
chmod 644 "$MIHOMO_SSH_CONFIG_FILE"
|
|
688
|
+
echo "OpenSSH proxy config enabled at $MIHOMO_SSH_CONFIG_FILE"
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
ssh_proxy_off_command() {
|
|
692
|
+
rm -f "$MIHOMO_SSH_CONFIG_FILE"
|
|
693
|
+
rm -f "$MIHOMO_SSH_PROXY_HELPER"
|
|
694
|
+
echo "OpenSSH proxy config removed."
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
daemon_proxy_on_command() {
|
|
698
|
+
local port
|
|
699
|
+
local service file dir
|
|
700
|
+
require_cmd systemctl
|
|
701
|
+
port="$(mixed_port_from_config)"
|
|
702
|
+
[[ -n "$port" ]] || die "Could not detect mixed-port from $MIHOMO_CONFIG_FILE"
|
|
703
|
+
if [[ -z "$(managed_daemon_services)" ]]; then
|
|
704
|
+
log "No known daemon services found for proxy integration."
|
|
705
|
+
fi
|
|
706
|
+
for service in $(managed_daemon_services); do
|
|
707
|
+
dir="$(service_dropin_dir "$service")"
|
|
708
|
+
file="$(service_dropin_file "$service")"
|
|
709
|
+
mkdir -p "$dir"
|
|
710
|
+
cat > "$file" <<EOF
|
|
711
|
+
[Service]
|
|
712
|
+
Environment="HTTP_PROXY=http://127.0.0.1:$port"
|
|
713
|
+
Environment="HTTPS_PROXY=http://127.0.0.1:$port"
|
|
714
|
+
Environment="NO_PROXY=localhost,127.0.0.1,::1"
|
|
715
|
+
Environment="http_proxy=http://127.0.0.1:$port"
|
|
716
|
+
Environment="https_proxy=http://127.0.0.1:$port"
|
|
717
|
+
Environment="no_proxy=localhost,127.0.0.1,::1"
|
|
718
|
+
EOF
|
|
719
|
+
done
|
|
720
|
+
systemd_reload
|
|
721
|
+
for service in $(managed_daemon_services); do
|
|
722
|
+
systemctl restart "$service"
|
|
723
|
+
done
|
|
724
|
+
echo "Daemon proxy enabled for: $(managed_daemon_services | paste -sd ',' - 2>/dev/null || true)"
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
daemon_proxy_off_command() {
|
|
728
|
+
local service dir file
|
|
729
|
+
require_cmd systemctl
|
|
730
|
+
for service in $MIHOMO_DAEMON_PROXY_SERVICES; do
|
|
731
|
+
file="$(service_dropin_file "$service")"
|
|
732
|
+
dir="$(service_dropin_dir "$service")"
|
|
733
|
+
rm -f "$file"
|
|
734
|
+
if [[ -d "$dir" ]] && [[ -z "$(ls -A "$dir" 2>/dev/null)" ]]; then
|
|
735
|
+
rmdir "$dir" 2>/dev/null || true
|
|
736
|
+
fi
|
|
737
|
+
done
|
|
738
|
+
systemd_reload
|
|
739
|
+
for service in $(managed_daemon_services); do
|
|
740
|
+
systemctl restart "$service"
|
|
741
|
+
done
|
|
742
|
+
echo "Daemon proxy disabled."
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
tun_on_command() {
|
|
746
|
+
write_tun_overlay
|
|
747
|
+
render_runtime_config
|
|
748
|
+
proxy_on_command
|
|
749
|
+
ssh_proxy_on_command
|
|
750
|
+
daemon_proxy_on_command
|
|
751
|
+
if service_is_active; then
|
|
752
|
+
systemctl restart "$MIHOMO_SERVICE_NAME"
|
|
753
|
+
echo "Mihomo TUN mode enabled and service restarted."
|
|
754
|
+
else
|
|
755
|
+
systemctl start "$MIHOMO_SERVICE_NAME"
|
|
756
|
+
echo "Mihomo TUN mode enabled and service started."
|
|
757
|
+
fi
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
tun_off_command() {
|
|
761
|
+
remove_tun_overlay
|
|
762
|
+
render_runtime_config
|
|
763
|
+
proxy_off_command
|
|
764
|
+
ssh_proxy_off_command
|
|
765
|
+
daemon_proxy_off_command
|
|
766
|
+
if service_is_active; then
|
|
767
|
+
systemctl restart "$MIHOMO_SERVICE_NAME"
|
|
768
|
+
echo "Mihomo TUN mode disabled and service restarted."
|
|
769
|
+
else
|
|
770
|
+
echo "Mihomo TUN mode disabled."
|
|
771
|
+
fi
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
run_command() {
|
|
775
|
+
local port
|
|
776
|
+
[[ $# -gt 0 ]] || die "Usage: sudo bash ./scripts/mihomo-client.sh run <command> [args...]"
|
|
777
|
+
port="$(mixed_port_from_config)"
|
|
778
|
+
[[ -n "$port" ]] || die "Could not detect mixed-port from $MIHOMO_CONFIG_FILE"
|
|
779
|
+
http_proxy="http://127.0.0.1:$port" \
|
|
780
|
+
https_proxy="http://127.0.0.1:$port" \
|
|
781
|
+
HTTP_PROXY="http://127.0.0.1:$port" \
|
|
782
|
+
HTTPS_PROXY="http://127.0.0.1:$port" \
|
|
783
|
+
all_proxy="socks5://127.0.0.1:$port" \
|
|
784
|
+
ALL_PROXY="socks5://127.0.0.1:$port" \
|
|
785
|
+
"$@"
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
test_command() {
|
|
789
|
+
local url="${1:-https://www.google.com/generate_204}"
|
|
790
|
+
local port
|
|
791
|
+
port="$(mixed_port_from_config)"
|
|
792
|
+
[[ -n "$port" ]] || die "Could not detect mixed-port from $MIHOMO_CONFIG_FILE"
|
|
793
|
+
require_cmd curl
|
|
794
|
+
echo "Testing through local mixed-port $port -> $url"
|
|
795
|
+
curl -I --proxy "http://127.0.0.1:$port" --max-time 15 "$url"
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
install_command() {
|
|
799
|
+
local url="${1:-}"
|
|
800
|
+
local username="${2:-}"
|
|
801
|
+
local password="${3:-}"
|
|
802
|
+
local version="${4:-latest}"
|
|
803
|
+
local autostart="${5:-true}"
|
|
804
|
+
local binary_path="${6:-}"
|
|
805
|
+
local -a normalized=()
|
|
806
|
+
|
|
807
|
+
ensure_dirs
|
|
808
|
+
load_env
|
|
809
|
+
log "Starting Mihomo client install/setup"
|
|
810
|
+
|
|
811
|
+
if [[ -z "$url" ]]; then
|
|
812
|
+
url="$(prompt_default "Subscription URL" "${MIHOMO_SUBSCRIPTION_URL:-}")"
|
|
813
|
+
fi
|
|
814
|
+
mapfile -t normalized < <(normalize_subscription_inputs "$url" "$username" "$password")
|
|
815
|
+
url="${normalized[0]}"
|
|
816
|
+
username="${normalized[1]}"
|
|
817
|
+
password="${normalized[2]}"
|
|
818
|
+
if [[ -z "$username" ]]; then
|
|
819
|
+
username="$(prompt_default "Subscription username (empty if none)" "${MIHOMO_SUBSCRIPTION_USER:-}")"
|
|
820
|
+
fi
|
|
821
|
+
if [[ -z "$password" ]]; then
|
|
822
|
+
password="$(prompt_password "Subscription password (empty if none)")"
|
|
823
|
+
fi
|
|
824
|
+
|
|
825
|
+
if [[ -n "$binary_path" ]]; then
|
|
826
|
+
log "Installing Mihomo core from local binary path"
|
|
827
|
+
install_binary_from_path "$binary_path"
|
|
828
|
+
else
|
|
829
|
+
log "Installing Mihomo core binary"
|
|
830
|
+
install_binary "$version"
|
|
831
|
+
fi
|
|
832
|
+
log "Installing global mihomo-client launcher"
|
|
833
|
+
install_client_launcher
|
|
834
|
+
log "Writing systemd service file"
|
|
835
|
+
write_service_file
|
|
836
|
+
systemd_reload
|
|
837
|
+
|
|
838
|
+
set_env_value MIHOMO_SUBSCRIPTION_URL "$url"
|
|
839
|
+
set_env_value MIHOMO_SUBSCRIPTION_USER "$username"
|
|
840
|
+
set_env_value MIHOMO_SUBSCRIPTION_PASSWORD "$password"
|
|
841
|
+
|
|
842
|
+
log "Downloading initial subscription and rendering runtime config"
|
|
843
|
+
update_subscription_command "$url" "$username" "$password"
|
|
844
|
+
|
|
845
|
+
systemctl enable "$MIHOMO_SERVICE_NAME" >/dev/null 2>&1 || true
|
|
846
|
+
if [[ "$autostart" == "true" ]]; then
|
|
847
|
+
log "Starting Mihomo client service"
|
|
848
|
+
systemctl restart "$MIHOMO_SERVICE_NAME"
|
|
849
|
+
echo "Mihomo client installed and started."
|
|
850
|
+
else
|
|
851
|
+
echo "Mihomo client installed. Service not started because --no-start was used."
|
|
852
|
+
fi
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
start_command() {
|
|
856
|
+
systemctl start "$MIHOMO_SERVICE_NAME"
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
stop_command() {
|
|
860
|
+
systemctl stop "$MIHOMO_SERVICE_NAME"
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
restart_command() {
|
|
864
|
+
systemctl restart "$MIHOMO_SERVICE_NAME"
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
enable_command() {
|
|
868
|
+
systemctl enable "$MIHOMO_SERVICE_NAME"
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
disable_command() {
|
|
872
|
+
systemctl disable "$MIHOMO_SERVICE_NAME"
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
uninstall_command() {
|
|
876
|
+
local purge="${1:-false}"
|
|
877
|
+
systemctl disable --now "$MIHOMO_SERVICE_NAME" >/dev/null 2>&1 || true
|
|
878
|
+
rm -f "$MIHOMO_SERVICE_FILE"
|
|
879
|
+
rm -f "$MIHOMO_PROFILE_PROXY_FILE"
|
|
880
|
+
rm -f "$MIHOMO_TUN_OVERLAY_FILE"
|
|
881
|
+
rm -f "$MIHOMO_SSH_CONFIG_FILE"
|
|
882
|
+
rm -f "$MIHOMO_SSH_PROXY_HELPER"
|
|
883
|
+
rm -f "$MIHOMO_CLIENT_LAUNCHER"
|
|
884
|
+
systemd_reload
|
|
885
|
+
|
|
886
|
+
if [[ "$purge" == "true" ]]; then
|
|
887
|
+
rm -f "$MIHOMO_BIN"
|
|
888
|
+
rm -rf "$MIHOMO_HOME"
|
|
889
|
+
fi
|
|
890
|
+
|
|
891
|
+
echo "Mihomo client service removed."
|
|
892
|
+
if [[ "$purge" == "true" ]]; then
|
|
893
|
+
echo "Binary and client state purged."
|
|
894
|
+
fi
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
main() {
|
|
898
|
+
local command="${1:-help}"
|
|
899
|
+
|
|
900
|
+
if [[ "${1:-}" == "--verbose" ]]; then
|
|
901
|
+
enable_verbose
|
|
902
|
+
shift
|
|
903
|
+
command="${1:-help}"
|
|
904
|
+
fi
|
|
905
|
+
|
|
906
|
+
shift || true
|
|
907
|
+
|
|
908
|
+
if [[ "$command" == "help" || "$command" == "-h" || "$command" == "--help" ]]; then
|
|
909
|
+
usage
|
|
910
|
+
exit 0
|
|
911
|
+
fi
|
|
912
|
+
|
|
913
|
+
require_root
|
|
914
|
+
require_cmd systemctl
|
|
915
|
+
|
|
916
|
+
case "$command" in
|
|
917
|
+
setup)
|
|
918
|
+
install_command "" "" "" "latest" "true"
|
|
919
|
+
;;
|
|
920
|
+
install)
|
|
921
|
+
local url=""
|
|
922
|
+
local username=""
|
|
923
|
+
local password=""
|
|
924
|
+
local version="latest"
|
|
925
|
+
local autostart="true"
|
|
926
|
+
local binary_path=""
|
|
927
|
+
while [[ $# -gt 0 ]]; do
|
|
928
|
+
case "$1" in
|
|
929
|
+
--url) url="$2"; shift 2 ;;
|
|
930
|
+
--user) username="$2"; shift 2 ;;
|
|
931
|
+
--password) password="$2"; shift 2 ;;
|
|
932
|
+
--version) version="$2"; shift 2 ;;
|
|
933
|
+
--binary-path) binary_path="$2"; shift 2 ;;
|
|
934
|
+
--no-start) autostart="false"; shift ;;
|
|
935
|
+
*) die "Unknown install option: $1" ;;
|
|
936
|
+
esac
|
|
937
|
+
done
|
|
938
|
+
install_command "$url" "$username" "$password" "$version" "$autostart" "$binary_path"
|
|
939
|
+
;;
|
|
940
|
+
update-subscription)
|
|
941
|
+
local url=""
|
|
942
|
+
local username=""
|
|
943
|
+
local password=""
|
|
944
|
+
while [[ $# -gt 0 ]]; do
|
|
945
|
+
case "$1" in
|
|
946
|
+
--url) url="$2"; shift 2 ;;
|
|
947
|
+
--user) username="$2"; shift 2 ;;
|
|
948
|
+
--password) password="$2"; shift 2 ;;
|
|
949
|
+
*) die "Unknown update-subscription option: $1" ;;
|
|
950
|
+
esac
|
|
951
|
+
done
|
|
952
|
+
update_subscription_command "$url" "$username" "$password"
|
|
953
|
+
;;
|
|
954
|
+
start)
|
|
955
|
+
start_command
|
|
956
|
+
;;
|
|
957
|
+
stop)
|
|
958
|
+
stop_command
|
|
959
|
+
;;
|
|
960
|
+
restart)
|
|
961
|
+
restart_command
|
|
962
|
+
;;
|
|
963
|
+
status)
|
|
964
|
+
status_command
|
|
965
|
+
;;
|
|
966
|
+
logs)
|
|
967
|
+
logs_command
|
|
968
|
+
;;
|
|
969
|
+
enable)
|
|
970
|
+
enable_command
|
|
971
|
+
;;
|
|
972
|
+
disable)
|
|
973
|
+
disable_command
|
|
974
|
+
;;
|
|
975
|
+
proxy-on)
|
|
976
|
+
proxy_on_command
|
|
977
|
+
;;
|
|
978
|
+
proxy-off)
|
|
979
|
+
proxy_off_command
|
|
980
|
+
;;
|
|
981
|
+
tun-on)
|
|
982
|
+
tun_on_command
|
|
983
|
+
;;
|
|
984
|
+
tun-off)
|
|
985
|
+
tun_off_command
|
|
986
|
+
;;
|
|
987
|
+
ssh-proxy-on)
|
|
988
|
+
ssh_proxy_on_command
|
|
989
|
+
;;
|
|
990
|
+
ssh-proxy-off)
|
|
991
|
+
ssh_proxy_off_command
|
|
992
|
+
;;
|
|
993
|
+
daemon-proxy-on)
|
|
994
|
+
daemon_proxy_on_command
|
|
995
|
+
;;
|
|
996
|
+
daemon-proxy-off)
|
|
997
|
+
daemon_proxy_off_command
|
|
998
|
+
;;
|
|
999
|
+
docker-proxy-on)
|
|
1000
|
+
daemon_proxy_on_command
|
|
1001
|
+
;;
|
|
1002
|
+
docker-proxy-off)
|
|
1003
|
+
daemon_proxy_off_command
|
|
1004
|
+
;;
|
|
1005
|
+
run)
|
|
1006
|
+
run_command "$@"
|
|
1007
|
+
;;
|
|
1008
|
+
test)
|
|
1009
|
+
local url="https://www.google.com/generate_204"
|
|
1010
|
+
while [[ $# -gt 0 ]]; do
|
|
1011
|
+
case "$1" in
|
|
1012
|
+
--url) url="$2"; shift 2 ;;
|
|
1013
|
+
*) die "Unknown test option: $1" ;;
|
|
1014
|
+
esac
|
|
1015
|
+
done
|
|
1016
|
+
test_command "$url"
|
|
1017
|
+
;;
|
|
1018
|
+
print-env)
|
|
1019
|
+
print_env_command
|
|
1020
|
+
;;
|
|
1021
|
+
uninstall)
|
|
1022
|
+
local purge="false"
|
|
1023
|
+
while [[ $# -gt 0 ]]; do
|
|
1024
|
+
case "$1" in
|
|
1025
|
+
--purge) purge="true"; shift ;;
|
|
1026
|
+
*) die "Unknown uninstall option: $1" ;;
|
|
1027
|
+
esac
|
|
1028
|
+
done
|
|
1029
|
+
uninstall_command "$purge"
|
|
1030
|
+
;;
|
|
1031
|
+
*)
|
|
1032
|
+
die "Unknown command: $command"
|
|
1033
|
+
;;
|
|
1034
|
+
esac
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
main "$@"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const repoRoot = resolve(packageRoot, '../../..');
|
|
9
|
+
const source = resolve(repoRoot, 'scripts/mihomo-client.sh');
|
|
10
|
+
const target = resolve(packageRoot, 'resources/mihomo-client.sh');
|
|
11
|
+
|
|
12
|
+
if (!existsSync(source)) {
|
|
13
|
+
if (existsSync(target)) {
|
|
14
|
+
console.log(`Using existing packaged script at ${target}`);
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
throw new Error(`Cannot package @qpjoy/tunnel-cli; missing ${source}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
21
|
+
copyFileSync(source, target);
|
|
22
|
+
chmodSync(target, 0o755);
|
|
23
|
+
|
|
24
|
+
console.log(`Copied ${source} -> ${target}`);
|