clean new images
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
quackerd 2022-12-02 13:52:11 +01:00
parent c2148456ac
commit 206a920bf8
31 changed files with 251 additions and 403 deletions

View File

@ -7,11 +7,6 @@ trigger:
- master - master
steps: steps:
- name: prep
image: alpine
commands:
- sh ./ci_prep.sh
- name: build - name: build
image: plugins/docker image: plugins/docker
settings: settings:
@ -19,9 +14,4 @@ steps:
from_secret: docker_username from_secret: docker_username
password: password:
from_secret: docker_password from_secret: docker_password
repo: quackerd/d2ray repo: quackerd/d2ray
- name: reload
image: alpine
commands:
- sh ./ci_reload.sh

View File

@ -1,18 +1,33 @@
FROM alpine:latest FROM alpine:latest
COPY image/ /opt/ ENV VER_XRAY 1.6.1
ENV VER_SO 2.5.20
ENV VER_NG 1.7.20
# install packages # install packages
RUN set -xe && apk add --no-cache unzip wget nginx certbot openssl RUN set -xe && apk add --no-cache zip unzip wget nginx certbot openssl python3 py3-jinja2 supervisor apache2-utils bash
# setup core files COPY ./opt /opt/
RUN set -xe && mkdir -p /opt/xray && \
unzip /opt/Xray-linux-64.zip -d /opt/xray && \
rm /opt/Xray-linux-64.zip && \
chmod +x /opt/run.sh /opt/crypt.sh
# crond # download packages
# RUN set -xe && mv /opt/crontab /var/spool/cron/crontabs/root RUN set -xe && \
wget -P /opt/zip/windows/ https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-windows-64.zip && \
mkdir -p /opt/zip/linux && \
wget -P /opt/zip/linux/ https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-linux-64.zip && \
mkdir -p /opt/zip/chrome && \
wget -P /opt/zip/chrome/ https://github.com/FelisCatus/SwitchyOmega/releases/download/v$VER_SO/SwitchyOmega_Chromium.crx && \
wget -P /opt/zip/android/ https://github.com/2dust/v2rayNG/releases/download/$VER_NG/v2rayNG_"$VER_NG"_arm64-v8a.apk && \
wget -P /opt/zip/macos/ https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-macos-64.zip && \
wget -P /opt/zip/macos/ https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-macos-arm64-v8a.zip
# xray
RUN set -xe && unzip /opt/zip/linux/Xray-linux-64.zip -d /opt/xray
# create zip
RUN set -xe && \
zip -r /opt/d2ray.zip /opt/zip && \
mv /opt/d2ray.zip /opt/nginx/download/ && \
rm -r /opt/zip
# nginx # nginx
RUN set -xe && addgroup www && \ RUN set -xe && addgroup www && \
@ -20,8 +35,7 @@ RUN set -xe && addgroup www && \
chown -R www:www /opt/nginx chown -R www:www /opt/nginx
# remove packages # remove packages
RUN set -xe && apk del unzip wget RUN set -xe && apk del zip unzip wget
EXPOSE 80
EXPOSE 80 443 VOLUME /etc/letsencrypt
CMD ["sh", "/opt/init.sh"]
CMD ["/opt/run.sh"]

View File

@ -1,62 +0,0 @@
#!/bin/sh
set -e
apk add openssh openssl wget unzip zip apache2-utils
source image/crypt.sh
chmod 600 ./id_root
# versions
VER_XRAY=1.6.1
VER_SO=2.5.20
VER_NG=1.7.20
# upload files
for filename in confs/*; do
basename=$(basename $filename)
hash_sha256 $basename $(cat ./key)
output=$crypt_ret
encrypt_file $filename $(cat ./key) $output
scp -P77 -o StrictHostKeychecking=no -i ./id_root $output root@parrot.quacker.org:/dat/apps/nginx/http_dl/root/pub
rm $output
done
# build zip
URL_SO=https://github.com/FelisCatus/SwitchyOmega/releases/download/v$VER_SO/SwitchyOmega_Chromium.crx
wget $URL_SO -O SwitchyOmega_Chromium.zip
mkdir zip/chrome
unzip ./SwitchyOmega_Chromium.zip -d zip/chrome || true
URL_NG=https://github.com/2dust/v2rayNG/releases/download/$VER_NG/v2rayNG_"$VER_NG"_arm64-v8a.apk
wget $URL_NG -P image/nginx/download/android/
URL_XRAY_WIN=https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-windows-64.zip
wget $URL_XRAY_WIN
unzip Xray-windows-64.zip -d zip/windows
URL_XRAY_MAC=https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-macos-64.zip
wget $URL_XRAY_MAC
unzip Xray-macos-64.zip -d zip/macos
URL_XRAY_LINUX=https://github.com/XTLS/Xray-core/releases/download/v$VER_XRAY/Xray-linux-64.zip
wget $URL_XRAY_LINUX -P image/
cd zip
zip -r -D ../windows_macos.zip .
cd ..
mv windows_macos.zip image/nginx/download/
# build htpassword
touch .htpasswd
htpasswd -b ./.htpasswd liangyifang liangyifang
htpasswd -b ./.htpasswd ruyuechun ruyuechun
htpasswd -b ./.htpasswd liuxiangdong liuxiangdong
htpasswd -b ./.htpasswd zhoubowen zhoubowen
htpasswd -b ./.htpasswd gaoyuchen gaoyuchen
htpasswd -b ./.htpasswd quackerd quackerd
htpasswd -b ./.htpasswd yushengde yushengde
htpasswd -b ./.htpasswd ivansun ivansun
encrypt_file ./.htpasswd "$(cat ./key)" image/htpasswd

View File

@ -1,32 +0,0 @@
#!/bin/sh
set -e
apk add openssh
key=$(cat ./key)
chmod 600 ./id_root
while read -r line
do
filename="confs/$line"
echo "Conf name: $filename"
addr=$(basename $filename)
echo "Refreshing $addr..."
ssh -p 77 -o StrictHostKeychecking=no -i ./id_root root@$addr -t "docker pull quackerd/d2ray:latest"
set +e
ssh -p 77 -o StrictHostKeychecking=no -i ./id_root root@$addr -t "docker stop d2ray && docker rm d2ray"
set -e
ssh -p 77 -o StrictHostKeychecking=no -i ./id_root root@$addr -t "docker run -d \
--restart unless-stopped \
-e KEY=$key \
-e FQDN=$addr \
-p 80:80 \
-p 8443:443 \
-v d2ray_volume:/opt/config \
--name d2ray \
quackerd/d2ray:latest"
ssh -p 77 -o StrictHostKeychecking=no -i ./id_root root@$addr -t "docker system prune -af"
done < ./reload_list
wait

View File

@ -1,50 +0,0 @@
{
"log": {
"loglevel": "debug",
"access": "/opt/config/logs/xray/access.log",
"error": "/opt/config/logs/xray/error.log"
},
"inbounds": [
{
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "quackerd",
"flow": "xtls-rprx-direct"
},
{
"id": "7-1803",
"flow": "xtls-rprx-direct"
}
],
"decryption": "none",
"fallbacks": [
{
"dest": "localhost:80"
}
]
},
"streamSettings": {
"network": "tcp",
"security": "xtls",
"xtlsSettings": {
"alpn": ["http/1.1", "h2"],
"certificates": [
{
"certificateFile": "/etc/letsencrypt/live/concerto.quacker.net/fullchain.pem",
"keyFile": "/etc/letsencrypt/live/concerto.quacker.net/privkey.pem"
}
]
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
}
]
}

View File

@ -1,46 +0,0 @@
{
"log": {
"loglevel": "warn",
"access": "/opt/config/logs/xray/access.log",
"error": "/opt/config/logs/xray/error.log"
},
"inbounds": [
{
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "ivansun",
"flow": "xtls-rprx-direct"
}
],
"decryption": "none",
"fallbacks": [
{
"dest": "localhost:80"
}
]
},
"streamSettings": {
"network": "tcp",
"security": "xtls",
"xtlsSettings": {
"alpn": ["http/1.1", "h2"],
"certificates": [
{
"certificateFile": "/etc/letsencrypt/live/ivans.quacker.net/fullchain.pem",
"keyFile": "/etc/letsencrypt/live/ivans.quacker.net/privkey.pem"
}
]
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
}
]
}

View File

@ -1,66 +0,0 @@
{
"log": {
"loglevel": "warn",
"access": "/opt/config/logs/xray/access.log",
"error": "/opt/config/logs/xray/error.log"
},
"inbounds": [
{
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "quackerd",
"flow": "xtls-rprx-direct"
},
{
"id": "e6569ab4-c0dd-4c29-9b29-5afef6a39a92",
"flow": "xtls-rprx-direct"
},
{
"id": "ruyuechun",
"flow": "xtls-rprx-direct"
},
{
"id": "liangyifang",
"flow": "xtls-rprx-direct"
},
{
"id": "bowen",
"flow": "xtls-rprx-direct"
},
{
"id": "gaoyuchen",
"flow": "xtls-rprx-direct"
}
],
"decryption": "none",
"fallbacks": [
{
"dest": "localhost:80"
}
]
},
"streamSettings": {
"network": "tcp",
"security": "xtls",
"xtlsSettings": {
"alpn": ["http/1.1", "h2"],
"certificates": [
{
"certificateFile": "/etc/letsencrypt/live/nocturne.quacker.net/fullchain.pem",
"keyFile": "/etc/letsencrypt/live/nocturne.quacker.net/privkey.pem"
}
]
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
}
]
}

24
docker-compose.yml Normal file
View File

@ -0,0 +1,24 @@
networks:
d2ray_br:
external: false
volumes:
d2ray_certs:
services:
d2ray:
image: quackerd/d2ray
container_name: d2ray
ports:
- ${PORT}:${PORT}
- 80:80
environment:
- PORT=${PORT}
- FQDN=${FQDN}
- USERS=${USERS}
restart: "no"
networks:
- d2ray_br
volumes:
- d2ray_certs:/etc/letsencrypt
- ${LOGDIR}:/etc/d2ray

View File

@ -1,7 +0,0 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACCZkiqqwNqxkOBmoaSiTqKfz6Vt8doQyFt8KhHRFAmXGAAAAKB/tAMff7QD
HwAAAAtzc2gtZWQyNTUxOQAAACCZkiqqwNqxkOBmoaSiTqKfz6Vt8doQyFt8KhHRFAmXGA
AAAECpU6mEunFZV2qLmgJHRlpj08fIR6b5Ndz23fde0Q9UN5mSKqrA2rGQ4GahpKJOop/P
pW3x2hDIW3wqEdEUCZcYAAAAHHF1YWNrZXJkQGJhbGxhZGUucXVhY2tlci5vcmcB
-----END OPENSSH PRIVATE KEY-----

View File

@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJmSKqrA2rGQ4GahpKJOop/PpW3x2hDIW3wqEdEUCZcY quackerd@ballade.quacker.org

View File

@ -1 +0,0 @@
# 0 */24 * * * certbot renew

View File

@ -1,37 +0,0 @@
#!/bin/sh
decrypt()
{
input=$1
key=$2
crypt_ret=$(echo $input | openssl enc -d -salt -aes-256-cbc -a -A -md sha512 -pbkdf2 -pass pass:$key)
}
decrypt_file()
{
input=$1
key=$2
output=$3
openssl enc -d -salt -aes-256-cbc -a -md sha512 -pbkdf2 -pass pass:$key -in $input -out $output
}
encrypt_file()
{
input=$1
key=$2
output=$3
openssl enc -e -salt -aes-256-cbc -a -md sha512 -pbkdf2 -pass pass:$key -in $input -out $output
}
encrypt()
{
input=$1
key=$2
crypt_ret=$(echo $input | openssl enc -e -salt -aes-256-cbc -a -A -md sha512 -pbkdf2 -pass pass:$key)
}
hash_sha256()
{
input=$1$2
crypt_ret=$(echo $input | openssl dgst -sha256 | sed -E "s/\(stdin\)= (.*)/\1/g")
}

View File

@ -1,59 +0,0 @@
#!/bin/sh
set -e
source /opt/crypt.sh
mkdir -p /opt/config
mkdir -p /opt/config/logs
mkdir -p /opt/config/logs/nginx
mkdir -p /opt/config/logs/xray
mkdir -p /opt/config/logs/crond
URL='U2FsdGVkX19/qz4kcbpQpJKz/iebXKih1BK3Cp1wGSoEyhLtoyAi0wewP5Tr++FbRLt/EG2f8zDF9cIEuoTLEA=='
echo ""
echo "===== Checking Environment Variables ====="
if [ -z "$FQDN" ]; then
echo "FQDN must be set"
exit 1
fi
if [ -z "$KEY" ]; then
echo "KEY must be set"
exit 1
fi
echo ""
echo "===== Checking Certificates ===="
if [ ! -d "/etc/letsencrypt/live/$FQDN" ]; then
echo "Generating new certificates..."
certbot certonly -n --standalone -m dummy@dummy.com --agree-tos --no-eff-email -d $FQDN
fi
echo ""
echo "===== Fetching Configuration ====="
decrypt $URL $KEY
URL=$crypt_ret
echo "Fetching from $URL..."
hash_sha256 $FQDN $KEY
URL=$URL/$crypt_ret
wget $URL -O /opt/$FQDN
echo "Decrypting..."
decrypt_file /opt/$FQDN $KEY /opt/config.json
decrypt_file /opt/htpasswd $KEY /opt/nginx/.htpasswd
echo ""
echo "===== Starting cron ====="
crond -L /opt/config/logs/crond/log.txt
echo ""
echo "===== Starting Nginx ====="
nginx -c /opt/nginx/nginx.conf
echo ""
echo "===== Starting xray ====="
exec /opt/xray/xray -c /opt/config.json

1
key
View File

@ -1 +0,0 @@
K336yS5BAoQabLyxLvcnwrRxt5Vv

1
opt/crontabs/root Normal file
View File

@ -0,0 +1 @@
* * * * * certbot renew --post-hook "supervisorctl xray restart"

75
opt/init.py Normal file
View File

@ -0,0 +1,75 @@
import os
import getopt
import sys
import subprocess
import jinja2
import random
import string
def parse_comma_str(users : str) -> list[str]:
return users.split(",")
def build_users_json(users: list[str]) -> str:
ret : str= ""
for i in range(len(users)):
if (i > 0):
ret = ret + ","
u = users[i]
ret = ret + "{ \"id\": \"" + u + "\", \"flow\": \"xtls-rprx-direct\"}"
return ret
try:
opts, _ = getopt.getopt(sys.argv[1:], "u:p:f:")
except getopt.GetoptError as err:
# print help information and exit:
print(err, flush=True) # will print something like "option -a not recognized"
exit(1)
port : int = 443
users : list[str] = [''.join(random.choices(string.ascii_uppercase + string.digits, k=24))]
fqdn : str = "example.com"
for o, a in opts:
if o == "-u":
users = parse_comma_str(a)
elif o == "-p":
port = int(a)
elif o == "-f":
fqdn = a
else:
print(f"Unknown option {o}, ignoring...", flush=True)
exit(1)
print("====== init.py ======", flush=True)
print("Configuration:\n" + \
f" port = {port}\n" + \
f" fqdn = {fqdn}\n" + \
f" users = {str(users)}", flush=True)
print(f"Checking certs for {fqdn}...", flush=True)
if (os.path.exists(f"/etc/letsencrypt/live/{fqdn}")):
print("Found existing certs, trying to renew...", flush=True)
subprocess.check_call(f"certbot renew", shell=True)
else:
print("Unable to locate certs, generating...", flush=True)
subprocess.check_call(f"certbot certonly -n --standalone -m dummy@dummy.com --agree-tos --no-eff-email -d {fqdn}", shell=True)
jinja_dict : dict[str,str] = dict()
jinja_dict["USERS"] = build_users_json(users)
jinja_dict["PORT"] = str(port)
jinja_dict["FQDN"] = str(fqdn)
print(f"Processing Xray config files...", flush=True)
with open("/opt/xray/d2ray.json.in", "r") as f:
with open("/opt/xray/d2ray.json", "w") as d:
template : jinja2.Template = jinja2.Template(f.read())
d.write(template.render(**jinja_dict))
print(f"Processing Nginx config files...", flush=True)
with open("/opt/nginx/nginx.conf.in", "r") as f:
with open("/opt/nginx/nginx.conf", "w") as d:
template : jinja2.Template = jinja2.Template(f.read())
d.write(template.render(**jinja_dict))
for u in users:
subprocess.check_call(f"htpasswd -b /opt/nginx/.htpasswd {u} {u}", shell=True)
exit(0)

13
opt/init.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/sh
# create log directories
mkdir -p /etc/d2ray/logs/cron
mkdir -p /etc/d2ray/logs/xray
mkdir -p /etc/d2ray/logs/nginx
mkdir -p /etc/d2ray/logs/supervisor
python3 /opt/init.py -p $PORT -u $USERS -f $FQDN
retval=$?
if [ $retval -ne 0 ]; then
exit $retval
fi
exec /usr/bin/supervisord -c /opt/supervisord.conf

0
opt/nginx/.htpasswd Normal file
View File

View File

@ -0,0 +1,3 @@
Android用户请看 android 文件夹
iOS用户请看 ios 文件夹
其他系统用户请下载 d2ray.zip

View File

@ -1,7 +1,8 @@
user www www; user www www;
worker_processes auto; worker_processes auto;
error_log /opt/config/logs/nginx/error.log; daemon off;
pid /tmp/nginx.pid; pid /tmp/nginx.pid;
worker_rlimit_nofile 8192;
events { events {
worker_connections 4096; ## Default: 1024 worker_connections 4096; ## Default: 1024
@ -22,13 +23,16 @@ http {
} }
charset utf-8; charset utf-8;
access_log /etc/d2ray/logs/nginx/access.log;
error_log /etc/d2ray/logs/nginx/error.log;
include /etc/nginx/mime.types;
server { server {
listen 80 default_server; listen 80 default_server;
server_name _; server_name _;
access_log /opt/config/logs/nginx/access.log;
if ($external) { if ($external) {
return 301 https://$host$request_uri:8443; return 301 https://$host:{{ PORT }}$request_uri;
} }
location / { location / {
@ -39,7 +43,7 @@ http {
location /download { location /download {
root /opt/nginx/; root /opt/nginx/;
autoindex on; autoindex on;
auth_basic "Provide credentials to access downloads"; auth_basic "Please provide credentials to access this server";
auth_basic_user_file "/opt/nginx/.htpasswd"; auth_basic_user_file "/opt/nginx/.htpasswd";
} }
} }

42
opt/supervisord.conf Normal file
View File

@ -0,0 +1,42 @@
[unix_http_server]
file=/var/run/supervisord.sock
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisord]
nodaemon=true
loglevel=warn
logfile=/etc/d2ray/logs/supervisor/supervisord.log
logfile_maxbytes=0
[supervisorctl]
serverurl=unix:///var/run/supervisord.sock
[program:nginx]
command=nginx -c /opt/nginx/nginx.conf
autostart=true
autorestart=false
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:cron]
command=crond -f -L /etc/d2ray/logs/cron/crond.log -c /opt/crontabs
autostart=true
autorestart=false
stdout_logfile=/etc/d2ray/logs/cron/crond.log
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:xray]
command=/opt/xray/xray -c /opt/xray/d2ray.json
autostart=true
autorestart=false
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
[eventlistener:exit]
command=bash -c "printf 'READY\n' && while read line; do kill -SIGQUIT $PPID; done < /dev/stdin"
events=PROCESS_STATE_FATAL,PROCESS_STATE_STOPPED,PROCESS_STATE_EXITED

43
opt/xray/d2ray.json.in Normal file
View File

@ -0,0 +1,43 @@
{
"log": {
"loglevel": "warn",
"access": "/etc/d2ray/logs/xray/access.log",
"error": "/etc/d2ray/logs/xray/error.log"
},
"inbounds": [
{
"port": {{ PORT }},
"protocol": "vless",
"settings": {
"clients": [
{{ USERS }}
],
"decryption": "none",
"fallbacks": [
{
"dest": "localhost:80"
}
]
},
"streamSettings": {
"network": "tcp",
"security": "xtls",
"xtlsSettings": {
"alpn": ["http/1.1", "h2"],
"certificates": [
{
"certificateFile": "/etc/letsencrypt/live/{{ FQDN }}/fullchain.pem",
"keyFile": "/etc/letsencrypt/live/{{ FQDN }}/privkey.pem"
}
]
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
}
]
}

View File

@ -11,21 +11,25 @@ VPN陪置教程
**** 1. 配置 VPN程序 **** **** 1. 配置 VPN程序 ****
Windows系统请看这里 Windows系统请看这里
- 进入windows目录 - 进入当前文件夹 windows 目录
- 用文本编辑器打开config.json搜索并替换 *所有* (划重点*所有* - 解压 Xray-windows-64.zip
- 用文本编辑器打开 config.json搜索并替换 *所有* (划重点*所有*
%PASSWORD% => 你的密码. 比如密码是12345的话 文件中所有的 "%PASSWORD%" 应该替换成 "12345" 以下同理 %PASSWORD% => 你的密码. 比如密码是12345的话 文件中所有的 "%PASSWORD%" 应该替换成 "12345" 以下同理
%SERVER% => 你的服务器地址这个是你下载zip文件的地址比如你从https://xxx.quacker.net/download下载你的服务器地址就是xxx.quacker.net %SERVER% => 你的服务器地址这个是你下载zip文件的地址比如你从https://xxx.quacker.net/download下载你的服务器地址就是xxx.quacker.net
- 保存config.json - 保存 config.json
- 双击run.bat运行VPN程序 - 拷贝 config.json 和 run.bat 进入解压后的文件夹
- 进入解压后文件夹双击 run.bat 运行VPN程序
MacOS系统请看这里 MacOS系统请看这里
- 进入macos目录 - 进入当前文件夹 macos 目录
- 用文本编辑器打开config.json搜索并替换 *所有* (划重点*所有* - 解压 Xray-macos-64.zip 或者 Xray-macos-arm64-v8a.zip (Apple M系芯片请用arm64)
- 用文本编辑器打开 config.json搜索并替换 *所有* (划重点*所有*
%PASSWORD% => 你的密码(和你的网站下载密码相同). 比如密码是12345的话 文件中所有的 "%PASSWORD%" 应该替换成 "12345" 以下同理 %PASSWORD% => 你的密码(和你的网站下载密码相同). 比如密码是12345的话 文件中所有的 "%PASSWORD%" 应该替换成 "12345" 以下同理
%SERVER% => 你的服务器地址这个是你下载zip文件的地址比如你从https://xxx.quacker.net/download下载你的服务器地址就是xxx.quacker.net %SERVER% => 你的服务器地址这个是你下载zip文件的地址比如你从https://xxx.quacker.net/download下载你的服务器地址就是xxx.quacker.net
- 保存config.json - 保存 config.json
- 右键run.sh => 用应用程序打开(Open With) => 其他(Other) => 在新打开的窗口下方选择框里选"所有应用程序"(All Applications) => 找到 终端"Terminal" 并勾选 永久以程序打开(Always Open With) => 确定 - 右键 run.sh => 用应用程序打开(Open With) => 其他(Other) => 在新打开的窗口下方选择框里选"所有应用程序"(All Applications) => 找到 终端"Terminal" 并勾选 永久以程序打开(Always Open With) => 确定
- 上述设置后直接双击run.sh就可以打开VPN程序了 - 拷贝 config.json 和 run.sh 进入解压后的文件夹
- 进入解压后文件夹双击 run.sh 运行VPN程序
**** 2. 配置 浏览器 **** **** 2. 配置 浏览器 ****

View File

@ -1,3 +0,0 @@
ivans.quacker.net
concerto.quacker.net
nocturne.quacker.net