centralize

This commit is contained in:
root 2021-04-10 04:21:07 -04:00
parent 9f1a934066
commit 0ffb5804db
14 changed files with 137 additions and 452 deletions

26
.drone.yml Normal file
View File

@ -0,0 +1,26 @@
kind: pipeline
type: docker
name: Docker image build
trigger:
branch:
- master
steps:
- name: config
image: alpine
commands:
- echo -n "$VERSION,latest" > .tags
- sed -i -E "s/var_FRP_VERSION/$FRP_VERSION/" Dockerfile
- apk add openssl
- ./gen_uploads.sh
- name: build
image: plugins/docker
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: quackerd/rainloop

30
Dockerfile Normal file
View File

@ -0,0 +1,30 @@
FROM alpine:latest
ENV VERSION=var_VERSION
ENV URL https://github.com/XTLS/Xray-core/releases/download/v${VERSION}/Xray-linux-64.zip
COPY ./run.sh /opt/run.sh
RUN set -xe && \
apk add --no-cache unzip wget nginx certbot openssl && \
wget ${URL} && \
mkdir -p /opt/xray && \
unzip Xray-linux-64.zip -d /opt/xray && \
rm Xray-linux-64.zip && \
mkdir -p /opt/config && \
mkdir -p /opt/config/logs && \
mkdir -p /opt/config/certs && \
mkdir -p /opt/config/logs/nginx && \
mkdir -p /opt/config/logs/xray && \
mkdir -p /opt/config/logs/crond && \
chmod +x /opt/run.sh && \
apk del unzip wget
COPY ./nginx.conf /opt/nginx.conf
COPY ./crontab /var/spool/cron/crontabs/root
EXPOSE 80
EXPOSE 443
CMD ["/opt/run.sh"]

19
LICENSE
View File

@ -1,19 +0,0 @@
MIT License Copyright (c) 2021 quackerd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,118 +0,0 @@
{
"dns": {
"servers": [
"223.5.5.5",
"114.114.114.114",
{
"address": "8.8.8.8",
"port": 53,
"domains": [
"geosite:geolocation-!cn"
]
},
{
"address": "1.1.1.1",
"port": 53,
"domains": [
"geosite:geolocation-!cn"
]
}
]
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"outboundTag": "direct",
"ip": [
"223.5.5.5",
"114.114.114.114"
]
},
{
"type": "field",
"outboundTag": "proxy",
"ip": [
"8.8.8.8",
"1.1.1.1"
]
},
{
"type": "field",
"outboundTag": "direct",
"ip": [
"geoip:cn",
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "direct",
"domain": ["geosite:cn"]
},
{
"type": "field",
"outboundTag": "proxy",
"network": "udp,tcp"
}
]
},
"inbounds": [
{
"port": {{ port }},
"listen": "127.0.0.1",
"protocol": "socks",
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
},
"settings": {
"auth": "noauth",
"udp": false
}
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "{{ subdomain }}.{{ domain }}",
"port": 443,
"users": [
{
"id": "{{ id }}",
"encryption": "none",
"level": 0
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "xtls",
"xtlsSettings": {
"serverName": "{{ subdomain }}.{{ domain }}",
"allowInsecure": false,
"alpn": ["h2","http/1.1"]
}
}
},
{
"protocol": "freedom",
"settings": {},
"tag": "direct"
},
{
"protocol": "blackhole",
"settings": {},
"tag": "block"
}
]
}

View File

@ -1,5 +0,0 @@
{
"id": "{{ id }}",
"flow": "{{ flow }}",
"level": 0
}

View File

@ -1,43 +0,0 @@
server:
# domain: the domain name, e.g. google.com
domain: domain.tld
# subdomain: the subdomain name, e.g. write vps for vps.google.com. leave empty for naked domain.
subdomain: example
# loglevel: the log level of xray
# default: warning
loglevel:
# email: your email for the registered SSL cert. leave empty for a dummy email.
# default: dummy@dummy.org
email:
# uid/gid: the user/group to run the docker-compose stack.
# default: the current user
# comment: you can also manually set, e.g. uid: 1000 gid: 1000
uid:
gid:
# watchtower: enable watchtower docker image auto-update
# default: False
watchtower: False
clients:
# name: the name of the client, cannot be null
- name: example_user1
# id: the password(uuid) of each user, this can either be a string or an UUID. Please read the "comment" and "IMPORTANT" sections.
# default: auto-generated random string
# comment: for a managed environment we recommend hardcoding the id
# the generated id does NOT currently back-propagate to this file
# you WILL lose existing users if you run configure.py multiple times with blank ids as they will be regenerated with random ids
#
# ! IMPORTANT !: id DOES NOT have to be UUID, can be any string of length 1-30. This is supported by xray but not by some v2ray clients
# if the script detects that the id is not a valid UUID, it will also output the equivalent UUID of that string
id:
# flow: the flow parameter
# default: xtls-rprx-direct
flow:
# port: local socks5 proxy port on clients' machines
# default: 1080
port:
# you can also set each field manually like below:
# - name: example_user2
# id: example_passwd2
# flow: xtls-rprx-direct
# port: 6666

View File

@ -1,188 +0,0 @@
import getopt
import sys
import uuid
import pwd
import jinja2
import random
import os
import string
import yaml
OUTPUT_DIR = "build"
CLIENT_OUTPUT_DIR = "clients"
DEFAULT_EMAIL = "dummy@dummy.org"
CONF_FILE = "config.yml"
SERVER_IN = "server.in"
SERVER_PATH = "xray"
SERVER_FN = "config.json"
NGINX_IN = "nginx.in"
NGINX_PATH = "nginx/nginx/site-confs"
NGINX_FN = "default"
CLIENT_OBJ_IN = "client_obj.in"
CLIENT_CONF_IN = "client_conf.in"
DOCKER_IN = "docker-compose.in"
WATCHTOWER_IN = "watchtower.in"
DEFAULT_WATCHTOWER_ENABLE = False
DEFAULT_CLIENT_PORT = 1080
DEFAULT_USER_FLOW = "xtls-rprx-direct"
DEFAULT_LOGLEVEL= "warning"
UUID_NAMESPACE = uuid.UUID('00000000-0000-0000-0000-000000000000')
def calc_uuid5(val):
return str(uuid.uuid5(UUID_NAMESPACE, val))
def is_valid_uuid(val):
try:
uuid.UUID(str(val))
return True
except ValueError:
return False
def yaml_key_exists_else(mapping : [], name : str, other_val = None, nullable = True):
if (name in mapping) and (mapping[name] != None):
return mapping[name]
else:
if not nullable:
raise Exception("Key " + name + " must not be null.")
else:
return other_val
def random_string(stringLength=16):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(stringLength))
class Client:
def __init__(self, conf_obj):
self.name = str(yaml_key_exists_else(conf_obj, 'name', nullable=False))
self.id = str(yaml_key_exists_else(conf_obj, 'id', other_val=random_string()))
self.flow = str(yaml_key_exists_else(conf_obj, 'flow', other_val=DEFAULT_USER_FLOW))
self.port = int(yaml_key_exists_else(conf_obj, 'port', other_val=DEFAULT_CLIENT_PORT))
def print(self, ident):
pre = ""
for i in range(ident):
pre += " "
print(pre + "{")
print(pre + " id: " + self.id)
print(pre + " uuid: " + (self.id if is_valid_uuid(self.id) else calc_uuid5(self.id)))
print(pre + " flow: " + self.flow)
print(pre + " port: " + str(self.port))
print(pre + "}")
class Config:
def print(self):
print("Server configuration:")
print(" domain: " + self.domain)
print(" email: " + self.email)
print(" subdomain: " + self.subdomain)
print(" uid: " + str(self.uid))
print(" gid: " + str(self.gid))
print(" loglevel: " + self.loglevel)
print(" clients:")
for i in range(len(self.clients)):
self.clients[i].print(8)
def __init__(self, f):
conf = yaml.safe_load(f)
conf_srv = conf['server']
self.domain = str(yaml_key_exists_else(conf_srv, 'domain', nullable=False))
self.email = str(yaml_key_exists_else(conf_srv, 'email', other_val=DEFAULT_EMAIL))
self.subdomain = str(yaml_key_exists_else(conf_srv, 'subdomain', other_val=""))
self.subdomain_only = len(self.subdomain) > 0
self.uid = int(yaml_key_exists_else(conf_srv, 'uid', other_val=os.getuid()))
self.gid = int(yaml_key_exists_else(conf_srv, 'gid', other_val=os.getgid()))
self.loglevel = str(yaml_key_exists_else(conf_srv, 'loglevel', other_val=DEFAULT_LOGLEVEL))
self.watchtower = bool(yaml_key_exists_else(conf_srv, 'watchtower', other_val=DEFAULT_WATCHTOWER_ENABLE))
self.clients = []
conf_clients = conf['clients']
for i in range(len(conf_clients)):
self.clients.append(Client(conf_clients[i]))
def main():
with open(CONF_FILE, 'r') as f:
conf = Config(f)
conf.print()
template_dict = {}
template_dict['uid'] = conf.uid
template_dict['gid'] = conf.gid
template_dict['subdomain'] = conf.subdomain
template_dict['subdomain_only'] = str(conf.subdomain_only).lower()
template_dict['domain'] = conf.domain
template_dict['email'] = conf.email
template_dict['loglevel'] = conf.loglevel
if conf.watchtower:
with open(WATCHTOWER_IN, "r") as f:
template_dict['watchtower'] = f.read()
else:
template_dict['watchtower'] = ""
clients = ""
with open(CLIENT_OBJ_IN, "r") as f:
client_obj = f.read()
for i in range(len(conf.clients)):
if i > 0:
clients += ",\n"
clients += jinja2.Template(client_obj).render(id = conf.clients[i].id,
flow = conf.clients[i].flow)
template_dict['clients'] = clients
print("Generating files...")
# create output dir
os.makedirs(OUTPUT_DIR, exist_ok=True)
# generate docker-compose.yml
path = os.path.join(OUTPUT_DIR, "docker-compose.yml")
with open(DOCKER_IN, "r") as f:
template = jinja2.Template(f.read())
with open(path, "w") as f:
f.write(template.render(template_dict))
# generate NGINX conf
with open(NGINX_IN, "r") as f:
template = jinja2.Template(f.read())
path = os.path.join(OUTPUT_DIR, NGINX_PATH)
os.makedirs(path, exist_ok=True)
path = os.path.join(path, NGINX_FN)
with open(path, "w") as f:
f.write(template.render(template_dict))
# generate xray conf
with open(SERVER_IN, "r") as f:
template = jinja2.Template(f.read())
path = os.path.join(OUTPUT_DIR, SERVER_PATH)
os.makedirs(path, exist_ok=True)
path = os.path.join(path, SERVER_FN)
with open(path, "w") as f:
f.write(template.render(template_dict))
# generate client confs
path = os.path.join(OUTPUT_DIR, CLIENT_OUTPUT_DIR)
os.makedirs(path, exist_ok=True)
with open(CLIENT_CONF_IN, "r") as f:
client_conf_temp = jinja2.Template(f.read())
for i in range(len(conf.clients)):
template_dict['id'] = conf.clients[i].id
template_dict['port'] = conf.clients[i].port
template_dict['flow'] = conf.clients[i].flow
epath = os.path.join(path, conf.clients[i].name)
os.makedirs(epath, exist_ok=True)
with open(os.path.join(epath, SERVER_FN), "w") as f:
f.write(client_conf_temp.render(template_dict))
# chown
os.chown(OUTPUT_DIR, conf.uid, conf.gid)
for dirpath, dirnames, filenames in os.walk(OUTPUT_DIR):
os.chown(dirpath, conf.uid, conf.gid)
for fname in filenames:
os.chown(os.path.join(dirpath, fname), conf.uid, conf.gid)
print("Please find the generated files in the build directory. To start the stack, run docker-compose up -d in the build directory.")
main()

View File

@ -1,8 +1,8 @@
{ {
"log": { "log": {
"loglevel": "{{ loglevel }}", "loglevel": "debug",
"access": "/etc/xray/access.log", "access": "/opt/config/xray/access.log",
"error": "/etc/xray/error.log" "error": "/opt/config/xray/error.log"
}, },
"inbounds": [ "inbounds": [
{ {
@ -10,12 +10,15 @@
"protocol": "vless", "protocol": "vless",
"settings": { "settings": {
"clients": [ "clients": [
{{ clients }} {
"id": "zsy",
"flow": "xtls-rprx-direct"
}
], ],
"decryption": "none", "decryption": "none",
"fallbacks": [ "fallbacks": [
{ {
"dest": "d2ray_nginx:80" "dest": "localhost:80"
} }
] ]
}, },
@ -26,8 +29,8 @@
"alpn": ["http/1.1", "h2"], "alpn": ["http/1.1", "h2"],
"certificates": [ "certificates": [
{ {
"certificateFile": "/le-etc/letsencrypt/live/{{ subdomain }}.{{ domain }}/fullchain.pem", "certificateFile": "/etc/letsencrypt/live/concerto.quacker.net/fullchain.pem",
"keyFile": "/le-etc/letsencrypt/live/{{ subdomain }}.{{ domain }}/privkey.pem" "keyFile": "/etc/letsencrypt/live/concerto.quacker.net/privkey.pem"
} }
] ]
} }

1
crontab Normal file
View File

@ -0,0 +1 @@
0 1 * * * certbot renew

View File

@ -1,60 +0,0 @@
version: '3.0'
networks:
br-d2ray:
external: false
driver: bridge
driver_opts:
com.docker.network.bridge.name: br-d2ray
enable_ipv6: false
ipam:
driver: default
config:
- subnet: 172.127.127.0/24
services:
d2ray_nginx:
container_name: d2ray_nginx
image: linuxserver/swag
restart: unless-stopped
cap_add:
- NET_ADMIN
networks:
- br-d2ray
environment:
- PUID={{ uid }}
- PGID={{ gid }}
- TZ=US/Eastern
- URL={{ domain }}
- SUBDOMAINS={{ subdomain }}
- VALIDATION=http
- EMAIL=dummy@dummy.com
- DHLEVEL=2048
- ONLY_SUBDOMAINS={{ subdomain_only }}
- STAGING=false
ports:
- 80:80
volumes:
- ./nginx:/config
healthcheck:
test: ["CMD", "curl", "-f", "http://{{ subdomain }}.{{ domain }}:80"]
interval: 5s
timeout: 3s
retries: 30
d2ray_xray:
container_name: d2ray_xray
image: teddysun/xray
restart: unless-stopped
depends_on:
d2ray_nginx:
condition: service_healthy
networks:
- br-d2ray
ports:
- 443:443
volumes:
- ./nginx/etc:/le-etc
- ./xray:/etc/xray
{{ watchtower }}

11
gen_upload.sh Normal file
View File

@ -0,0 +1,11 @@
#!/bin/sh
set +e
mkdir -p ./uploads
for filename in ./confs/*; do
fname=$(basename $filename)
fhash=$(echo -n '$fname' | openssl dgst -md5 | sed -E 's/\(stdin\)= (.*)/\1/')
openssl aes-256-cbc -md sha512 -pbkdf2 -in $filename -out ./uploads/$fhash.conf -k "sergeygorbunov"
done

View File

@ -1,17 +1,17 @@
geo $external { geo $external {
default 1; default 1;
172.127.127.0/24 0; localhost 0;
} }
server { server {
listen 80 default_server; listen 80 http http2 default_server;
server_name {{subdomain}}.{{domain}}; server_name {{subdomain}}.{{domain}};
if ($external) { if ($external) {
return 301 https://$host$request_uri; return 301 https://$host$request_uri;
} }
root /config/www; root /var/lib/nginx/html;
index index.html index.htm index.php; index index.html;
} }

55
run.sh Normal file
View File

@ -0,0 +1,55 @@
#!/bin/sh
set +e
BUCKET_NAME="config.quacker.net"
echo "===== Checking Environment Variables ====="
if [ -z "$FQDN" ]; then
echo "FQDN must be set"
exit 1
else
echo "FQDN = $FQDN"
fi
if [ -z "$SALT" ]; then
echo "SALT must be set"
exit 1
else
echo "SALT = $SALT"
fi
if [ -z "$KEY" ]; then
echo "KEY must be set"
exit 1
else
echo "KEY = $KEY"
fi
BUCKET_HASH=$(echo -n "$BUCKET_NAME" | openssl dgst -md5 | sed -E 's/\(stdin\)= (.*)/\1/')
echo "BUCKET_HASH= $BUCKET_HASH"
echo "===== Setting Up Environment ======"
ln -s /opt/config/certs /etc/letsencrypt
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"
else
echo "Certificate exists. Checking renewal..."
certbot renew
fi
echo "===== Downloading configuration file ====="
hash=$(echo -n "$FQDN.$SALT" | openssl dgst -sha256 | sed -E 's/\(stdin\)= (.*)/\1/')
echo "Host hash is $hash"
wget http://$BUCKET_HASH.s3-website-us-west-1.amazonaws.com/config/$hash.conf -P /opt/
openssl aes-256-cbc -d -md sha512 -pbkdf2 -in /opt/$hash.conf -out /opt/$FQDN.conf -k $KEY
echo "===== Starting services ====="
crond -L /opt/config/logs/crond/log.txt
nginx -c /opt/nginx.conf
echo "===== Starting xray ====="
/opt/xray/xray -c /opt/$FQDN.conf

View File

@ -1,8 +0,0 @@
watchtower:
container_name: d2ray_watchtower
image: containrrr/watchtower
restart: unless-stopped
networks:
- br-d2ray
volumes:
- /var/run/docker.sock:/var/run/docker.sock