diff options
Diffstat (limited to 'roles')
60 files changed, 1667 insertions, 0 deletions
diff --git a/roles/base/handlers/main.yaml b/roles/base/handlers/main.yaml new file mode 100644 index 00000000..f229852c --- /dev/null +++ b/roles/base/handlers/main.yaml @@ -0,0 +1,11 @@ +--- +- name: restart_notification + ansible.builtin.debug: + msg: "Restart your box for sysctl.conf and syslog.conf to take effect" + +- name: syslogd_restart + ansible.builtin.service: + name: syslogd + state: restarted + enabled: true + diff --git a/roles/base/tasks/add_pubkeys.yaml b/roles/base/tasks/add_pubkeys.yaml new file mode 100644 index 00000000..9f0e7c59 --- /dev/null +++ b/roles/base/tasks/add_pubkeys.yaml @@ -0,0 +1,9 @@ +--- +- name: "add pubkeys to root user" + ansible.builtin.lineinfile: + path: /root/.ssh/authorized_keys + create: true + line: "{{ item | trim }}" + search_string: "{{ (item | trim | split(' '))[2:-1]|join(' ') }}" + when: "root_keys is defined" + loop: "{{ root_keys }}" diff --git a/roles/base/tasks/base_setup.yaml b/roles/base/tasks/base_setup.yaml new file mode 100644 index 00000000..f1ed062a --- /dev/null +++ b/roles/base/tasks/base_setup.yaml @@ -0,0 +1,58 @@ +--- +- name: "Create vpns user" + ansible.builtin.user: + name: _vpn + create_home: no + comment: Project VPN user + state: present + shell: /sbin/nologin + notify: restart_notification + +- name: "Create root directory of vpn services" + ansible.builtin.file: + path: /var/reactance/ + state: directory + owner: _vpn + group: _vpn + mode: 0755 + +- name: "templating out ip forwarding rules in sysctl.conf" + ansible.builtin.blockinfile: + path: /etc/sysctl.conf + create: true + backup: true + marker: "### REACTANCE - IP Forwarding - {mark} ###" + insertafter: "EOF" + block: | + net.inet.ip.forwarding=1 + net.inet6.ip6.forwarding=1 + +- name: "templating out sysctl.conf" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: '0644' + notify: syslogd_restart + loop: + - src: syslog.conf.j2 + dest: /etc/syslog.conf + - src: newsyslog.conf.j2 + dest: /etc/newsyslog.conf + + +# openbsd_pkg cant be run parallely otherwise there could be package locks and pipeline would fail +- name: "install necessary utils" + community.general.openbsd_pkg: + name: + - unzip-- + - curl-- + - rsync-- + - jq-- + state: present + when: inventory_hostname in (groups['vless']|default([]) + groups['vmess']|default([]) + groups['trojan']|default([]) + groups['all_vpns']|default([])) + +- name: "tune unbound performance" + community.general.openbsd_pkg: + name: ripgrep + state: present + when: not disable_dns|default(False) diff --git a/roles/base/tasks/main.yaml b/roles/base/tasks/main.yaml new file mode 100644 index 00000000..5ab9bf45 --- /dev/null +++ b/roles/base/tasks/main.yaml @@ -0,0 +1,12 @@ +--- +- name: "add root pubkeys" + ansible.builtin.include_tasks: add_pubkeys.yaml + +- name: "run pre execution checks" + ansible.builtin.include_tasks: pre_execution_checks.yaml + +- name: "run base setup" + ansible.builtin.include_tasks: base_setup.yaml + +- name: "template out user expiration script and cronjob" + ansible.builtin.include_tasks: setup_user_expiration.yaml diff --git a/roles/base/tasks/pre_execution_checks.yaml b/roles/base/tasks/pre_execution_checks.yaml new file mode 100644 index 00000000..f6a969a7 --- /dev/null +++ b/roles/base/tasks/pre_execution_checks.yaml @@ -0,0 +1,11 @@ +--- +- name: "pre execution test : check os" + ansible.builtin.fail: + msg: "Reactance can only be ran on OpenBSD" + when: ansible_facts["os_family"]|lower != "openbsd" + +- name: "pre execution test : looking for invalid usernames" + ansible.builtin.fail: + msg: "Username cannot be 'server' or 'ca'" + when: item.user in ["server", "ca"] + loop: "{{ all_users|default([]) + ocserv_users|default([]) + vless_users|default([]) + vmess_users|default([]) + trojan_users|default([]) + sshvpn_users|default([]) + hysteria_users|default([]) }}" diff --git a/roles/base/tasks/setup_user_expiration.yaml b/roles/base/tasks/setup_user_expiration.yaml new file mode 100644 index 00000000..977977f9 --- /dev/null +++ b/roles/base/tasks/setup_user_expiration.yaml @@ -0,0 +1,19 @@ +--- +- name: "Template out user expiration script" + ansible.builtin.template: + src: user_expiration_control.py.j2 + dest: /root/.user_expiration_control.py + mode: "0400" + owner: root + group: nogroup + +- name: "write user expiration information to file" + user_expiration: + users: "{{ all_users|default([]) + ocserv_users|default([]) + vless_users|default([]) + vmess_users|default([]) + trojan_users|default([]) + sshvpn_users|default([]) + hysteria_users|default([]) }}" + +- name: "setup daily user expiration cronjob" + ansible.builtin.cron: + name: "daily run user expiration script" + user: root + job: "python3 /root/.user_expiration_control.py" + special_time: daily diff --git a/roles/base/templates/newsyslog.conf.j2 b/roles/base/templates/newsyslog.conf.j2 new file mode 100644 index 00000000..a8f348d6 --- /dev/null +++ b/roles/base/templates/newsyslog.conf.j2 @@ -0,0 +1,31 @@ +# $OpenBSD: newsyslog.conf - fsa generated +# +# NOTE: SIGHUP has to be sent to syslogd for services whose logs are being written by syslogd +# +# logfile_name owner:group mode count size when flags +/var/cron/log root:wheel 600 3 10 * Z +/var/log/authlog root:wheel 640 7 * 168 Z +/var/log/daemon 640 5 300 * Z +/var/log/lpd-errs 640 7 10 * Z +/var/log/maillog 640 7 * 24 Z +/var/log/messages 644 5 300 * Z +/var/log/secure 600 7 * 168 Z +/var/log/wtmp 644 7 * $M1D4 B "" +/var/log/xferlog 640 7 250 * Z +/var/log/pflog 600 3 250 * ZB "pkill -HUP -u root -U root -t - -x pflogd" +/var/www/logs/access.log 644 4 * $W0 Z "pkill -USR1 -u root -U root -x httpd" +/var/www/logs/error.log 644 7 250 * Z "pkill -USR1 -u root -U root -x httpd" +{# for services whose logs are written by syslogd, we need to restart syslogd instead #} +{% if inventory_hostname in (groups['xray']|default([])) + (groups['all_vpns']|default([])) %} +/var/reactance/xray/logs/xray-access.log 640 4 512 * Z "pkill -HUP -u root -U root -x xray" +/var/reactance/xray/logs/xray-error.log 640 4 512 * Z "pkill -HUP -u root -U root -x xray" +{% endif %} +{% if not disable_dns|default(true) %} +/var/log/unbound.log 600 5 * $W0 Z "pkill -HUP -u root -U root -x syslogd" +{% endif %} +{% if inventory_hostname in (groups['ocserv']|default([])) + (groups['all_vpns']|default([])) %} +/var/log/ocserv.log 600 5 128000 * Z "pkill -HUP -u root -U root -x syslogd" +{% endif %} +{% if inventory_hostname in (groups['trojan']|default([])) + (groups['vless']|default([])) + (groups['vmess']|default([])) + (groups['all_vpns']|default([])) %} +/var/log/relayd.log 600 5 512 * Z "pkill -HUP -u root -U root -x syslogd" +{% endif %} diff --git a/roles/base/templates/syslog.conf.j2 b/roles/base/templates/syslog.conf.j2 new file mode 100644 index 00000000..261b6d88 --- /dev/null +++ b/roles/base/templates/syslog.conf.j2 @@ -0,0 +1,21 @@ +# $OpenBSD: syslog.conf,v 1.20 2016/12/27 13:38:14 jca Exp $ + +*.notice;auth,authpriv,cron,ftp,kern,lpr,mail,user.none /var/log/messages +kern.debug;syslog,user.info /var/log/messages +auth.info /var/log/authlog +authpriv.debug /var/log/secure +cron.info /var/cron/log +{% if inventory_hostname in (groups['ocserv']|default([])) + (groups['all_vpns']|default([])) %} +!!ocserv +daemon.* /var/log/ocserv.log +!* +{% endif %} +{% if inventory_hostname in (groups['hysteria']|default([])) + (groups['all_vpns']|default([])) %} +!!hysteria +daemon.* /var/log/hysteria.log +!* +{% endif %} +daemon.info /var/log/daemon +ftp.info /var/log/xferlog +lpr.debug /var/log/lpd-errs +mail.info /var/log/maillog diff --git a/roles/base/templates/user_expiration_control.py.j2 b/roles/base/templates/user_expiration_control.py.j2 new file mode 100644 index 00000000..36551617 --- /dev/null +++ b/roles/base/templates/user_expiration_control.py.j2 @@ -0,0 +1,99 @@ +#!/usr/local/bin/python3 + +import json, os, subprocess, shutil +from datetime import datetime + +EXPIRE_USER_JSON_PATH = "/var/reactance/.user_expiration.json" +EXPIRE_WEB_JSON_PATH = "/var/reactance/.web_expiration.json" +OCSERV_CONFIG_PATH = "/var/reactance/ocserv/etc/ocserv.passwd" +HYSTERIA_CONFIG_FILE = "/var/reactance/hysteria/etc/config.json" +XRAY_CONFIG_PATH = "/var/reactance/xray/etc/config.json" +SSH_ROOT = "/var/reactance/sshvpn/.ssh" +AUTHORIZED_KEYS = os.path.join(SSH_ROOT, "authorized_keys") + +def ocserv_get_users(): + ocserv_config_dict = {} + if os.path.isfile(OCSERV_CONFIG_PATH): + with open(OCSERV_CONFIG_PATH, "r") as f: + ocserv_content = f.read() + ocserv_config_dict = dict(map(lambda x: x.split(':*:'), list(filter(lambda x: x != '', ocserv_content.split("\n"))))) + return ocserv_config_dict + +def xray_get_users(): + with open(XRAY_CONFIG_PATH, "r") as f: + xray_config_dict = json.loads(f.read()) + return xray_config_dict + +def hysteria_get_users(): + with open(HYSTERIA_CONFIG_FILE, "r") as f: + hysteria_config_dict = json.loads(f.read()) + return hysteria_config_dict + +def sshvpn_get_users(): + previous_users = [".".join(i.split('.')[:-1]) for i in os.listdir(SSH_ROOT) if i.endswith(".pub")] + return previous_users + +def ocserv_user_purge(users_to_remove): + for user in users_to_remove: + subprocess.run(f"ocpasswd -d {user} -c {OCSERV_CONFIG_PATH}", shell=True) + +def xray_user_purge(users_to_remove): + xray_config_dict = xray_get_users() + for i, inbound in enumerate(xray_config_dict['inbounds']): + previous_users_list = inbound['settings']['clients'] + new_users_list = previous_users_list.copy() + for user in previous_users_list: + if user['email'] in users_to_remove: + new_users_list.remove(user) + xray_config_dict['inbounds'][i]['settings']['clients'] = new_users_list + with open(XRAY_CONFIG_PATH, "w") as f: + f.write(json.dumps(xray_config_dict, indent=1)) + +def hysteria_user_purge(users_to_remove): + hysteria_config_dict = hysteria_get_users() + previous_users_dict = hysteria_config_dict["auth"]["userpass"] + new_users_dict = {} + for user in previous_users_dict.keys(): + if user not in users_to_remove: + new_users_dict[user] = previous_users_dict[user] + hysteria_config_dict["auth"]["userpass"] = new_users_dict + with open(HYSTERIA_CONFIG_FILE, "w") as f: + f.write(json.dumps(hysteria_config_dict, indent=1)) + +def sshvpn_user_purge(users_to_remove): + previous_users_list = sshvpn_get_users() + for user in previous_users_list: + if user in users_to_remove: + os.remove(f"{SSH_ROOT}/{user}.pub") + os.remove(f"{SSH_ROOT}/{user}") + + # Overwrite existing authorized_key file + users_pubkey_files = [os.path.join(SSH_ROOT, i) for i in os.listdir(SSH_ROOT) if i.endswith(".pub")] + with open(AUTHORIZED_KEYS, "w") as f: + for pubkey_file in users_pubkey_files: + with open(pubkey_file, "r") as pkey: + f.write(pkey.read()) + +def main(): + current_unix_time = datetime.now().timestamp() + + with open(EXPIRE_USER_JSON_PATH, "r") as f: + expire_user_dict = json.loads(f.read()) + for exp in expire_user_dict.keys(): + if float(exp) <= current_unix_time: + users = expire_user_dict[exp] + + xray_user_purge(users) + sshvpn_user_purge(users) + ocserv_user_purge(users) + hysteria_user_purge(users) + with open(EXPIRE_USER_JSON_PATH, "w") as f: + f.write(json.dumps(expire_user_dict, indent=1)) + + with open(EXPIRE_WEB_JSON_PATH, "r") as f: + expire_web_dict = json.loads(f.read()) + for exp in expire_web_dict.keys(): + shutil.rmtree(f"/var/www/reactance/{exp}") + +if __name__ == "__main__": + main() diff --git a/roles/dns/handlers/main.yaml b/roles/dns/handlers/main.yaml new file mode 100644 index 00000000..cb476298 --- /dev/null +++ b/roles/dns/handlers/main.yaml @@ -0,0 +1,6 @@ +--- +- name: restart_unbound + ansible.builtin.service: + name: unbound + state: restarted + enabled: true diff --git a/roles/dns/tasks/check_dns_setup.yaml b/roles/dns/tasks/check_dns_setup.yaml new file mode 100644 index 00000000..b1fdb500 --- /dev/null +++ b/roles/dns/tasks/check_dns_setup.yaml @@ -0,0 +1,13 @@ +--- +- name: "check if adblock.rpz exists" + ansible.builtin.stat: + path: /var/unbound/db/adblock.rpz + register: adblock_rpz + +- name: "setup dns resolver (unbound)" + ansible.builtin.include_tasks: setup_unbound.yaml + when: not adblock_rpz.stat.exists + +- name: "setup adblocking" + ansible.builtin.include_tasks: setup_adblock.yaml + when: not adblock_rpz.stat.exists diff --git a/roles/dns/tasks/main.yaml b/roles/dns/tasks/main.yaml new file mode 100644 index 00000000..27978e73 --- /dev/null +++ b/roles/dns/tasks/main.yaml @@ -0,0 +1,3 @@ +--- +- name: "check dns resolver setup" + ansible.builtin.include_tasks: check_dns_setup.yaml diff --git a/roles/dns/tasks/setup_adblock.yaml b/roles/dns/tasks/setup_adblock.yaml new file mode 100644 index 00000000..edf09a7e --- /dev/null +++ b/roles/dns/tasks/setup_adblock.yaml @@ -0,0 +1,66 @@ +--- +- name: "activate unbound control" + ansible.builtin.command: unbound-control-setup + changed_when: false + +- name: "fetch unbound filter script" + ansible.builtin.get_url: + url: https://geoghegan.ca/pub/unbound-adblock/latest/unbound-adblock.sh + dest: /usr/local/bin/unbound-adblock + group: bin + mode: 755 + register: adblock_changed + # DL fails from time to time, so we retry a couple times + until: adblock_changed.state == "file" + retries: 10 + delay: 2 + ignore_errors: yes + notify: + - restart_unbound + +- name: "create adblock user" + ansible.builtin.user: + name: _adblock + shell: nologin + home: /var/empty + create_home: false + +- name: "add _adblock doas privileges" + ansible.builtin.blockinfile: + path: /etc/doas.conf + create: true + backup: true + marker: "### REACTANCE - Unbound Adblock - {mark} ###" + insertafter: "EOF" + block: | + permit nopass root + permit nopass _adblock cmd /usr/sbin/unbound-control args -q status + permit nopass _adblock cmd /usr/sbin/unbound-control args -q flush_zone unbound-adblock + permit nopass _adblock cmd /usr/sbin/unbound-control args -q auth_zone_reload unbound-adblock + +- name: "create binaries for adblock" + ansible.builtin.command: "{{ item }}" + loop: + - install -m 644 -o _adblock -g wheel /dev/null /var/unbound/db/adblock.rpz + - install -d -o root -g wheel -m 755 /var/log/unbound-adblock + - install -o _adblock -g wheel -m 640 /dev/null /var/log/unbound-adblock/unbound-adblock.log + - install -o _adblock -g wheel -m 640 /dev/null /var/log/unbound-adblock/unbound-adblock.log.0.gz + changed_when: false + notify: restart_unbound + +- name: "restarting adblock (as separate task otherwise cant create rule)" + ansible.builtin.service: + name: unbound + state: restarted + enabled: true + +- name: "create first ruleset" + ansible.builtin.shell: "cd /var/unbound/db && doas -u _adblock /usr/local/bin/unbound-adblock -O openbsd" + changed_when: false + +- name: "setup daily cronjob" + ansible.builtin.cron: + name: "update dns blocklist" + user: root + job: "cd /var/unbound/db && doas -u _adblock /usr/local/bin/unbound-adblock -O openbsd 1> /dev/null" + special_time: daily diff --git a/roles/dns/tasks/setup_unbound.yaml b/roles/dns/tasks/setup_unbound.yaml new file mode 100644 index 00000000..ec6f123e --- /dev/null +++ b/roles/dns/tasks/setup_unbound.yaml @@ -0,0 +1,44 @@ +--- +- name: "template out configs" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + loop: + - src: unbound.conf.j2 + dest: /var/unbound/etc/unbound.conf + - src: resolv.conf.j2 + dest: /etc/resolv.conf.j2 + - src: hostname.vether0.j2 + dest: /etc/hostname.vether0 + # we need a separate virtual network interface for binding the dns resolver to since ocserv doesn't creates tunnel interface for each separate connected client, it does not create a primary interface + + # unbound will fail if there's nonexisting interface in config +- name: "create vether0 interface" + ansible.builtin.shell: "sh /etc/netstart vether0" + when: inventory_hostname in (groups['ocserv']|default([])) + (groups['all_vpns']|default([])) + +- name: "setup log file" + ansible.builtin.file: + path: /var/log/unbound.log + state: touch + mode: "0600" + + # pure convenience +- name: "obsd : dns : symlink it to /etc" + ansible.builtin.file: + src: /var/unbound/etc/unbound.conf + dest: /etc/unbound.conf + state: link + +- name: "obsd : dns : not exists. generate..." + ansible.builtin.command: /usr/sbin/unbound-anchor -a /var/unbound/db/root.key + args: + creates: /var/unbound/db/root.key + failed_when: false + +- name: "obsd : dns : get root hints" + ansible.builtin.command: ftp -o /var/unbound/etc/root.hints https://www.internic.net/domain/named.root + args: + creates: /var/unbound/db/root.key + notify: + - restart_unbound diff --git a/roles/dns/templates/hostname.vether0.j2 b/roles/dns/templates/hostname.vether0.j2 new file mode 100644 index 00000000..e7ac2c71 --- /dev/null +++ b/roles/dns/templates/hostname.vether0.j2 @@ -0,0 +1 @@ +inet {{ (ocserv_network|default("172.16.16.0/24"))|ansible.utils.nthhost(2) }}/{{ (ocserv_network|default("172.16.16.0/24")|ipaddr('prefix')) }} diff --git a/roles/dns/templates/resolv.conf.j2 b/roles/dns/templates/resolv.conf.j2 new file mode 100644 index 00000000..7a0de7f4 --- /dev/null +++ b/roles/dns/templates/resolv.conf.j2 @@ -0,0 +1,6 @@ +{% if not disable_dns|default(false) %} +nameserver 127.0.0.1 +{% endif %} +nameserver 9.9.9.9 +nameserver 149.112.112.112 +lookup file bind diff --git a/roles/dns/templates/unbound.conf.j2 b/roles/dns/templates/unbound.conf.j2 new file mode 100644 index 00000000..b02df37e --- /dev/null +++ b/roles/dns/templates/unbound.conf.j2 @@ -0,0 +1,41 @@ +server: + interface: 127.0.0.1 +{% if inventory_hostname in (groups['ocserv']|default([])) + (groups['all_vpns']|default([])) %} + interface: {{ (ocserv_network|default("172.16.16.0/24"))|ansible.utils.nthhost(2) }} +{% endif %} + do-ip6: no + + access-control: 0.0.0.0/0 refuse + access-control: 127.0.0.0/8 allow +{% if inventory_hostname in (groups['ocserv']|default([])) + (groups['all_vpns']|default([])) %} + access-control: {{ ocserv_network|default("172.16.16.0/24") }} allow +{% endif %} + + hide-identity: yes + hide-version: yes + + auto-trust-anchor-file: "/var/unbound/db/root.key" + val-log-level: 2 + qname-minimisation: yes + + aggressive-nsec: yes + verbosity: 1 + log-queries: no + use-caps-for-id: yes + + cache-min-ttl: 3600 + cache-max-ttl: 86400 + prefetch: yes + unwanted-reply-threshold: 10000 + do-not-query-localhost: yes + val-clean-additional: yes + module-config: "respip validator iterator" + +remote-control: + control-enable: yes + +rpz: + name: "unbound-adblock" + zonefile: "/var/unbound/db/adblock.rpz" + rpz-log: no + rpz-log-name: "unbound-adblock" diff --git a/roles/hysteria/handlers/main.yaml b/roles/hysteria/handlers/main.yaml new file mode 100644 index 00000000..d4227c5e --- /dev/null +++ b/roles/hysteria/handlers/main.yaml @@ -0,0 +1,11 @@ +--- +- name: restart_hysteria + ansible.builtin.service: + name: hysteria + state: restarted + enabled: true + +- name: remove_hysteria_tempdir + ansible.builtin.file: + path: "{{ hysteria_tempdir.path }}" + state: absent diff --git a/roles/hysteria/tasks/check_hysteria_exists.yaml b/roles/hysteria/tasks/check_hysteria_exists.yaml new file mode 100644 index 00000000..3c8f85c7 --- /dev/null +++ b/roles/hysteria/tasks/check_hysteria_exists.yaml @@ -0,0 +1,21 @@ +--- +- name: "Check if hysteria is already installed" + ansible.builtin.stat: + path: /var/reactance/hysteria + register: hysteria_directory + +- name: "Check if hysteria is configured" + ansible.builtin.stat: + path: /var/reactance/hysteria/etc/config.json + register: hysteria_config + +- name: "Install hysteria if directory doesn't exist" + ansible.builtin.include_tasks: install_hysteria.yaml + when: hysteria_directory.stat.exists == false + +- name: "Configure hysteria" + ansible.builtin.include_tasks: configure_hysteria.yaml + when: hysteria_config.stat.exists == false + +- name: "Create hysteria users" + ansible.builtin.include_tasks: create_users_hysteria.yaml diff --git a/roles/hysteria/tasks/configure_hysteria.yaml b/roles/hysteria/tasks/configure_hysteria.yaml new file mode 100644 index 00000000..3495f336 --- /dev/null +++ b/roles/hysteria/tasks/configure_hysteria.yaml @@ -0,0 +1,51 @@ +--- +# check if salamander password file exists +- name: "check if obfuscation password file exists " + ansible.builtin.stat: + path: "/var/reactance/hysteria/salamander_password" + register: salamander_password_file + +# generate salamander password +- name: "generate salamander password for obfuscation " + ansible.builtin.shell: "openssl rand -hex 32" + register: random_string + when: salamander_password_file.stat.exists != true + +# retrieve password from file, if exists +- name: "use previous password, if it exists" + ansible.builtin.set_fact: + salamander_password: "{{ lookup('file', '/var/reactance/hysteria/salamander_password') }}" + when: salamander_password_file.stat.exists + +- name: "set salamander password as var" + ansible.builtin.set_fact: + salamander_password: "{{ random_string.stdout }}" + when: salamander_password_file.stat.exists != true + +- name: "write obfuscation password to file" + ansible.builtin.copy: + content: "{{ salamander_password }}" + dest: "/var/reactance/hysteria/salamander_password" + + when: salamander_password_file.stat.exists != true + +- name: "template out configs" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + loop: + - src: config.json.j2 + dest: "/var/reactance/hysteria/etc/config.json" + - src: ca.tmpl.j2 + dest: /var/reactance/hysteria/certs/ca.tmpl + - src: server.tmpl.j2 + dest: /var/reactance/hysteria/certs/server.tmpl + +# generate ca, server certs, crl file +- name: "generate ca, server certs" + ansible.builtin.shell: "{{ item }}" + loop: + - "certtool --generate-privkey --outfile /var/reactance/hysteria/certs/ca-key.pem" +R - "certtool --generate-self-signed --load-privkey /var/reactance/hysteria/certs/ca-key.pem --template /var/reactance/hysteria/certs/ca.tmpl --outfile /var/reactance/hysteria/certs/ca-cert.pem" + - "certtool --generate-privkey --outfile /var/reactance/hysteria/certs/server-key.pem" + - "certtool --generate-certificate --load-privkey /var/reactance/hysteria/certs/server-key.pem --load-ca-certificate /var/reactance/hysteria/certs/ca-cert.pem --load-ca-privkey /var/reactance/hysteria/certs/ca-key.pem --template /var/reactance/hysteria/certs/server.tmpl --outfile /var/reactance/hysteria/certs/server-cert.pem" diff --git a/roles/hysteria/tasks/create_users_hysteria.yaml b/roles/hysteria/tasks/create_users_hysteria.yaml new file mode 100644 index 00000000..a6881413 --- /dev/null +++ b/roles/hysteria/tasks/create_users_hysteria.yaml @@ -0,0 +1,13 @@ +--- +- name: "hysteria user management" + hysteria: + users: "{{ all_users|default([]) + hysteria_users|default([]) }}" + register: hysteria_user_pass_dict + no_log: true + notify: + - restart_hysteria + +- name: "add hysteria user password pair to dict" + set_fact: + user_pass_dict: "{{ user_pass_dict|default({}) | combine(hysteria_user_pass_dict['msg'], recursive=true, list_merge='append') }}" + no_log: true diff --git a/roles/hysteria/tasks/install_hysteria.yaml b/roles/hysteria/tasks/install_hysteria.yaml new file mode 100644 index 00000000..59dcd7b9 --- /dev/null +++ b/roles/hysteria/tasks/install_hysteria.yaml @@ -0,0 +1,64 @@ +--- +- name: "create directory" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: _vpn + group: _vpn + mode: 0700 + loop: + - "/var/reactance/hysteria" + - "/var/reactance/hysteria/bin" + - "/var/reactance/hysteria/etc" + - "/var/reactance/hysteria/certs" + +- name: "setup log file" + ansible.builtin.file: + path: /var/log/hysteria.log + state: touch + mode: "0600" + changed_when: false + +# this can remain here while hysteria is not being used +- name: "install necessary utils" + community.general.openbsd_pkg: + name: + - git-- + - go-- + - rsync-- + state: present + +- name: "create temporary directory" + ansible.builtin.tempfile: + state: directory + suffix: temp + register: hysteria_tempdir + notify: + - remove_hysteria_tempdir + +- name: "clone hysteria" + ansible.builtin.shell: "git clone https://github.com/apernet/hysteria.git" + args: + chdir: "{{ hysteria_tempdir.path }}" + +- name: "build hysteria" + ansible.builtin.shell: "python3 hyperbole.py build" + args: + chdir: "{{ hysteria_tempdir.path }}/hysteria" + +- name: "install hysteria" + ansible.builtin.shell: "{{ item }}" + loop: + - "install -m 750 -o _vpn -g bin {{ hysteria_tempdir.path }}/hysteria/build/hysteria-openbsd-* /var/reactance/hysteria/bin/hysteria" + +# Find the list of dependences through ldd and copy them over +- name: "copy chroot dependencies" + ansible.builtin.shell: "deps=$(ldd /var/reactance/hysteria/bin/hysteria | awk 'FNR > 3 {print $7}'); for dep in $deps; do rsync -av --relative $dep /var/reactance/hysteria; done" + +- name: "template out init script" + ansible.builtin.template: + src: hysteria.rc.j2 + dest: "{{ hysteria_tempdir.path }}/hysteria.rc" + +- name: "install init script" + ansible.builtin.shell: "install -m 755 -g bin {{ hysteria_tempdir.path }}/hysteria.rc /etc/rc.d/hysteria" diff --git a/roles/hysteria/tasks/main.yaml b/roles/hysteria/tasks/main.yaml new file mode 100644 index 00000000..258c01d7 --- /dev/null +++ b/roles/hysteria/tasks/main.yaml @@ -0,0 +1,7 @@ +--- +- name: "ignore hysteria" + ansible.builtin.debug: + msg: "Hysteria2 sing-box clients do not support any system for verifying server identity and thus Hysteria2 is prone to MITM attacks thus should not be used. The role may not be fully developed. Check README.md for more info." + +# - name: "setup hysteria" +# ansible.builtin.include_tasks: check_hysteria_exists.yaml diff --git a/roles/hysteria/tasks/setup_hysteria.yaml b/roles/hysteria/tasks/setup_hysteria.yaml new file mode 100644 index 00000000..64161b43 --- /dev/null +++ b/roles/hysteria/tasks/setup_hysteria.yaml @@ -0,0 +1,17 @@ +--- +- name: "create certificate dir" + ansible.builtin.file: + path: /var/reactance/ocserv/certs/ + state: directory + owner: _vpn + group: _vpn + +- name: "generate server certs and key" + ansible.builtin.shell: "openssl req -x509 -newkey rsa:4096 -keyout /var/reactance/hysteria/certs/server-key.pem -out /var/reactance/hysteria/certs/server-cert.pem -sha256 -days 3650 -nodes -subj '/CN=JohnDane'" + +- name: "template out ocserv config" + ansible.builtin.template: + src: ocserv.conf.j2 + dest: /var/reactance/ocserv/ocserv.conf + notify: + - hysteria_start diff --git a/roles/hysteria/templates/config.json.j2 b/roles/hysteria/templates/config.json.j2 new file mode 100644 index 00000000..40dde2e4 --- /dev/null +++ b/roles/hysteria/templates/config.json.j2 @@ -0,0 +1,52 @@ +{ + "listen": "0.0.0.0:{{ hysteria_port | default('4435') }}", + "tls": { + "cert": "/certs/server-cert.pem", + "key": "/certs/server-key.pem" + }, + "disableUDP": false, + "bandwidth": { + "up": "1 gbps", + "down": "1 gbps" + }, + "auth": { + "type": "userpass", + "userpass": {} + }, + "obfs": { + "type": "salamander", + "salamander": { + "password": "{{ salamander_password }}" + } + }, + "resolver": { + "type": "udp", + "udp": { +{% if not lookup('vars', 'disable_dns', default=false) %} + "addr": "{{ ansible_all_ipv4_addresses[0] }}" +{% else %} + "addr": "9.9.9.9" +{% endif %} + } + }, + "outbounds": [{ + "name": "direct_out", + "type": "direct", + "direct": { + "mode": "auto" + }}], + "trafficStats": { + "listen": "127.0.0.1:9999", + "secret": "secret" + }, + "masquerade": { + "type": "string", + "string": { + "content": "Hello World", + "headers": { + "content-type": "text/plain" + }, + "statusCode": 200 + } + } +} diff --git a/roles/hysteria/templates/hysteria.rc.j2 b/roles/hysteria/templates/hysteria.rc.j2 new file mode 100644 index 00000000..c0aeed29 --- /dev/null +++ b/roles/hysteria/templates/hysteria.rc.j2 @@ -0,0 +1,22 @@ +#!/bin/ksh +# +# $OpenBSD: hysteria + +chroot_dir=/var/reactance/hysteria +chroot_user=_vpn +daemon_class=daemon +daemon=/bin/hysteria +daemon_flags="server -c /etc/config.json" +daemon_user=root +daemon_logger=daemon.info + +. /etc/rc.d/rc.subr + +rc_start() { + rc_exec "chroot -u $chroot_user $chroot_dir $daemon $daemon_flags" +} + +rc_bg=YES +rc_reload=NO + +rc_cmd $1 diff --git a/roles/ocserv/handlers/main.yml b/roles/ocserv/handlers/main.yml new file mode 100644 index 00000000..dcc18f2c --- /dev/null +++ b/roles/ocserv/handlers/main.yml @@ -0,0 +1,11 @@ +--- +- name: restart_ocserv + ansible.builtin.service: + name: ocserv + state: restarted + enabled: true + +- name: remove_ocserv_tempdir + ansible.builtin.file: + path: "{{ ocserv_tempdir.path }}" + state: absent diff --git a/roles/ocserv/tasks/check_ocserv_exists.yaml b/roles/ocserv/tasks/check_ocserv_exists.yaml new file mode 100644 index 00000000..bb4734ce --- /dev/null +++ b/roles/ocserv/tasks/check_ocserv_exists.yaml @@ -0,0 +1,16 @@ +--- +- name: "Check if ocserv is already installed" + ansible.builtin.stat: + path: /var/reactance/ocserv + register: ocserv_directory + +- name: "Install ocserv if directory doesn't exist" + ansible.builtin.include_tasks: install_ocserv.yaml + when: ocserv_directory.stat.exists == false + +- name: "Configure ocserv" + ansible.builtin.include_tasks: configure_ocserv.yaml + when: ocserv_directory.stat.exists == false + +- name: "Create ocserv users" + ansible.builtin.include_tasks: create_users_ocserv.yaml diff --git a/roles/ocserv/tasks/configure_ocserv.yaml b/roles/ocserv/tasks/configure_ocserv.yaml new file mode 100644 index 00000000..53e771c0 --- /dev/null +++ b/roles/ocserv/tasks/configure_ocserv.yaml @@ -0,0 +1,40 @@ +--- +- name: "template out config" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + loop: + - src: ocserv.conf.j2 + dest: /var/reactance/ocserv/ocserv.conf + - src: ca.tmpl.j2 + dest: /var/reactance/ocserv/certs/ca.tmpl + - src: server.tmpl.j2 + dest: /var/reactance/ocserv/certs/server.tmpl + - src: crl.tmpl.j2 + dest: /var/reactance/ocserv/certs/crl.tmpl + +# generate ca, server certs, crl file +- name: "generate ca, server certs" + ansible.builtin.shell: "{{ item }}" + loop: + - "certtool --generate-privkey --outfile /var/reactance/ocserv/certs/ca-key.pem" + - "certtool --generate-self-signed --load-privkey /var/reactance/ocserv/certs/ca-key.pem --template /var/reactance/ocserv/certs/ca.tmpl --outfile /var/reactance/ocserv/certs/ca-cert.pem" + - "certtool --generate-privkey --outfile /var/reactance/ocserv/certs/server-key.pem" + - "certtool --generate-certificate --load-privkey /var/reactance/ocserv/certs/server-key.pem --load-ca-certificate /var/reactance/ocserv/certs/ca-cert.pem --load-ca-privkey /var/reactance/ocserv/certs/ca-key.pem --template /var/reactance/ocserv/certs/server.tmpl --outfile /var/reactance/ocserv/certs/server-cert.pem" + - "certtool --generate-crl --load-ca-privkey /var/reactance/ocserv/certs/ca-key.pem --load-ca-certificate /var/reactance/ocserv/certs/ca-cert.pem --template /var/reactance/ocserv/certs/crl.tmpl --outfile /var/reactance/ocserv/certs/crl.pem" + no_log: true + +- name: "template out nat rules in pf.conf" + ansible.builtin.blockinfile: + path: /etc/pf.conf + create: true + backup: true + marker: "### REACTANCE - Ocserv NAT - {mark} ###" + insertafter: "EOF" + block: | + match out on {{ ansible_default_ipv4.interface|default(ansible_all_ipv4_addresses[0]) }} from {{ ocserv_network | default("172.16.16.0/24") }} to any nat-to ({{ ansible_default_ipv4.interface|default(ansible_all_ipv4_addresses[0]) }}) + match in on {{ ansible_default_ipv4.interface|default(ansible_all_ipv4_addresses[0]) }} from any to {{ ocserv_network | default("172.16.16.0/24") }} nat-to ({{ ansible_default_ipv4.interface|default(ansible_all_ipv4_addresses[0]) }}) +# default(ansible_all_ipv4_addresses[0]) is added, in case a default route doesn't exist + +- name: "generate public, private key pair" + ansible.builtin.shell: "openssl req -x509 -newkey rsa:4096 -keyout /var/reactance/ocserv/certs/server-key.pem -out /var/reactance/ocserv/certs/server-cert.pem -sha256 -days 3650 -nodes -subj /CN=example &>/dev/null" diff --git a/roles/ocserv/tasks/create_users_ocserv.yaml b/roles/ocserv/tasks/create_users_ocserv.yaml new file mode 100644 index 00000000..12748e7a --- /dev/null +++ b/roles/ocserv/tasks/create_users_ocserv.yaml @@ -0,0 +1,18 @@ +--- +- name: "ocserv user management" + ocserv: + users: "{{ all_users|default([]) + ocserv_users|default([]) }}" + notify: + - restart_ocserv +# no_log: true + register: ocserv_user_pass_dict + +- name: "make temp dir" + ansible.builtin.file: + path: /var/reactance/.temp/ + state: directory + +- name: "add ocserv user password pair to dict" + ansible.builtin.copy: + content: "{{ ocserv_user_pass_dict['msg']|default({}) | to_json }}" + dest: /var/reactance/.temp/ocserv_user_pass_dict diff --git a/roles/ocserv/tasks/install_ocserv.yaml b/roles/ocserv/tasks/install_ocserv.yaml new file mode 100644 index 00000000..a31a2ee5 --- /dev/null +++ b/roles/ocserv/tasks/install_ocserv.yaml @@ -0,0 +1,63 @@ +--- +# ocserv has built-in chroot functionality + +# it's fine here, other roles won't be running any pkg_add +- name: "install ocserv" + community.general.openbsd_pkg: + name: ocserv-- + state: present + +- name: "create directory" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: _vpn + group: _vpn + mode: 0700 + loop: + - /var/reactance/ocserv + - /var/reactance/ocserv/run + - /var/reactance/ocserv/certs + +- name: "create log file" + ansible.builtin.file: + path: /var/log/ocserv.log + state: touch + mode: "0600" + changed_when: false + +- name: "create temporary directory" + ansible.builtin.tempfile: + state: directory + suffix: temp + register: ocserv_tempdir + notify: + - remove_ocserv_tempdir + +- name: "template out config" + ansible.builtin.template: + src: ocserv.conf.j2 + dest: /var/reactance/ocserv/ocserv.conf + +- name: "template out init script" + ansible.builtin.template: + src: ocserv.rc.j2 + dest: "{{ ocserv_tempdir.path }}/ocserv.rc" + +# will fail without it +- name: "copy ocserv-worker" + ansible.builtin.copy: + owner: _vpn + group: _vpn + remote_src: true + src: /usr/local/sbin/ocserv-worker + dest: /var/reactance/ocserv/ocserv-worker + mode: 0770 + +- name: "install init script" + ansible.builtin.shell: "install -m 755 -g bin {{ ocserv_tempdir.path }}/ocserv.rc /etc/rc.d/ocserv && rm -rf /var/reactance/ocserv/ocserv.rc" + +- name: "remove /etc/ocserv (we are using /var/reactance/ocserv)" + ansible.builtin.file: + path: /etc/ocserv + state: absent diff --git a/roles/ocserv/tasks/main.yaml b/roles/ocserv/tasks/main.yaml new file mode 100644 index 00000000..4ed25a48 --- /dev/null +++ b/roles/ocserv/tasks/main.yaml @@ -0,0 +1,3 @@ +--- +- name: "setup ocserv" + include_tasks: check_ocserv_exists.yaml diff --git a/roles/ocserv/tasks/setup_ocserv.yaml b/roles/ocserv/tasks/setup_ocserv.yaml new file mode 100644 index 00000000..d2a9bb59 --- /dev/null +++ b/roles/ocserv/tasks/setup_ocserv.yaml @@ -0,0 +1,8 @@ +--- +- name: "generate server cert and key" + ansible.builtin.shell: "openssl req -x509 -newkey rsa:4096 -keyout /var/reactance/ocserv/certs/server-key.pem -out /var/reactance/ocserv/certs/server-cert.pem -sha256 -days 3650 -nodes -subj '/CN=JohnDane'" + +- name: "template out ocserv config" + ansible.builtin.template: + src: ocserv.conf.j2 + dest: /var/reactance/ocserv/ocserv.conf diff --git a/roles/ocserv/templates/ca.tmpl.j2 b/roles/ocserv/templates/ca.tmpl.j2 new file mode 100644 index 00000000..c595f0c3 --- /dev/null +++ b/roles/ocserv/templates/ca.tmpl.j2 @@ -0,0 +1,8 @@ +cn = "VPN CA" +organization = "Big Corp" +serial = 1 +expiration_days = -1 +ca +signing_key +cert_signing_key +crl_signing_key diff --git a/roles/ocserv/templates/crl.tmpl.j2 b/roles/ocserv/templates/crl.tmpl.j2 new file mode 100644 index 00000000..b70745fd --- /dev/null +++ b/roles/ocserv/templates/crl.tmpl.j2 @@ -0,0 +1,2 @@ +crl_next_update = 365 +crl_number = 1 diff --git a/roles/ocserv/templates/ocserv.conf.j2 b/roles/ocserv/templates/ocserv.conf.j2 new file mode 100644 index 00000000..4f722487 --- /dev/null +++ b/roles/ocserv/templates/ocserv.conf.j2 @@ -0,0 +1,48 @@ +chroot-dir = /var/reactance/ocserv +auth = "certificate" +tcp-port = {{ ocserv_port | default("4430") }} +run-as-user = _vpn +run-as-group = _vpn + +socket-file = run/ocserv-socket +server-cert = /var/reactance/ocserv/certs/server-cert.pem +server-key = /var/reactance/ocserv/certs/server-key.pem +ca-cert = /var/reactance/ocserv/certs/ca-cert.pem +crl = /var/reactance/ocserv/certs/crl.pem + +max-clients = 10000 +max-same-clients = 2 +rate-limit-ms = 100 +server-stats-reset-time = 604800 +keepalive = 32400 +dpd = 90 +mobile-dpd = 1800 +switch-to-tcp-timeout = 25 +try-mtu-discovery = false +cert-user-oid = 0.9.2342.19200300.100.1.1 +compression = true +tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1" +auth-timeout = 240 +min-reauth-time = 300 +max-ban-score = 80 +ban-reset-time = 1200 +cookie-timeout = 300 +deny-roaming = false +rekey-time = 172800 +rekey-method = ssl +use-occtl = true +pid-file = /var/reactance/ocserv/run/ocserv.pid +log-level = 3 +device = vpns +predictable-ips = true +ipv4-network = {{ ocserv_network | default("172.16.16.0/24") }} +tunnel-all-dns = true +{% if not lookup('vars', 'disable_dns', default=false) %} +dns = {{ (ocserv_network|default("172.16.16.0/24"))|ansible.utils.nthhost(2) }} +{% else %} +dns = 9.9.9.9 +{% endif %} +ping-leases = false +route = default +cisco-client-compat = true +max-ban-score = 20 diff --git a/roles/ocserv/templates/ocserv.rc.j2 b/roles/ocserv/templates/ocserv.rc.j2 new file mode 100644 index 00000000..f68a06ff --- /dev/null +++ b/roles/ocserv/templates/ocserv.rc.j2 @@ -0,0 +1,14 @@ +#!/bin/ksh +# $OpenBSD: ocserv +daemon="/usr/local/sbin/ocserv" +daemon_flags="-c /var/reactance/ocserv/ocserv.conf" + +. /etc/rc.d/rc.subr + +pexp="ocserv: ocserv-main" + +rc_pre() { + /usr/bin/install -d -o _vpn /var/reactance/ocserv/run/ +} + +rc_cmd $1 diff --git a/roles/ocserv/templates/server.tmpl.j2 b/roles/ocserv/templates/server.tmpl.j2 new file mode 100644 index 00000000..f5eb7b66 --- /dev/null +++ b/roles/ocserv/templates/server.tmpl.j2 @@ -0,0 +1,7 @@ +cn = "VPN server" +ip_address = "{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}" +organization = "MyCompany" +expiration_days = -1 +signing_key +encryption_key +tls_www_server diff --git a/roles/sshvpn/handlers/main.yaml b/roles/sshvpn/handlers/main.yaml new file mode 100644 index 00000000..cd12bec6 --- /dev/null +++ b/roles/sshvpn/handlers/main.yaml @@ -0,0 +1,6 @@ +--- +- name: restart_ssh + ansible.builtin.service: + name: sshd + state: restarted + enabled: yes diff --git a/roles/sshvpn/tasks/check_sshvpn_exists.yaml b/roles/sshvpn/tasks/check_sshvpn_exists.yaml new file mode 100644 index 00000000..3939f2d8 --- /dev/null +++ b/roles/sshvpn/tasks/check_sshvpn_exists.yaml @@ -0,0 +1,12 @@ +--- +- name: "Check if sshvpn is already installed" + ansible.builtin.stat: + path: /home/sshvpn + register: sshvpn_directory + +- name: "Setup sshvpn if directory doesn't exist" + ansible.builtin.include_tasks: setup_sshvpn.yaml + when: sshvpn_directory.stat.exists == false + +- name: "Create sshvpn users" + ansible.builtin.include_tasks: create_users_sshvpn.yaml diff --git a/roles/sshvpn/tasks/create_users_sshvpn.yaml b/roles/sshvpn/tasks/create_users_sshvpn.yaml new file mode 100644 index 00000000..f4b0ed68 --- /dev/null +++ b/roles/sshvpn/tasks/create_users_sshvpn.yaml @@ -0,0 +1,16 @@ +--- +- name: "sshvpn user management" + sshvpn: + users: "{{ all_users|default([]) + sshvpn_users|default([]) }}" + register: sshvpn_user_pass_dict + #no_log: true + +- name: "make temp dir" + ansible.builtin.file: + path: /var/reactance/.temp/ + state: directory + +- name: "add sshvpn user password pair to dict" + ansible.builtin.copy: + content: "{{ sshvpn_user_pass_dict['msg']|default({}) | to_json }}" + dest: /var/reactance/.temp/sshvpn_user_pass_dict diff --git a/roles/sshvpn/tasks/main.yaml b/roles/sshvpn/tasks/main.yaml new file mode 100644 index 00000000..78a241af --- /dev/null +++ b/roles/sshvpn/tasks/main.yaml @@ -0,0 +1,3 @@ +--- +- name: "Setup sshvpn" + ansible.builtin.include_tasks: check_sshvpn_exists.yaml diff --git a/roles/sshvpn/tasks/setup_sshvpn.yaml b/roles/sshvpn/tasks/setup_sshvpn.yaml new file mode 100644 index 00000000..20504e57 --- /dev/null +++ b/roles/sshvpn/tasks/setup_sshvpn.yaml @@ -0,0 +1,16 @@ +--- +- name: "Create sshvpns user" + ansible.builtin.user: + name: sshvpn + shell: /sbin/nologin + home: /var/reactance/sshvpn + +- name: "Create .ssh directory (if not exists)" + ansible.builtin.file: + path: /var/reactance/sshvpn/.ssh + owner: sshvpn + group: sshvpn + mode: 0700 + +- name: "Run user management script" + include_tasks: create_users_sshvpn.yaml diff --git a/roles/web/handlers/main.yaml b/roles/web/handlers/main.yaml new file mode 100644 index 00000000..85096548 --- /dev/null +++ b/roles/web/handlers/main.yaml @@ -0,0 +1,22 @@ +--- +- name: restart_httpd + ansible.builtin.service: + name: httpd + state: restarted + enabled: true + +- name: notification_restart + ansible.builtin.debug: + msg: "Restart your box once reactance run is complete" + +- name: show_links + ansible.builtin.debug: + msg: "{{ }}" +- name: show_htpasswd_passwords + ansible.builtin.debug: + msg: "{{ htpasswd_passwords | format_userpass_output(ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0])) }}" + +- name: cleanup_temp_dir + ansible.builtin.file: + path: /var/reactance/.temp + state: absent diff --git a/roles/web/tasks/.setup_sites.yaml.swp b/roles/web/tasks/.setup_sites.yaml.swp Binary files differnew file mode 100644 index 00000000..0534a7f0 --- /dev/null +++ b/roles/web/tasks/.setup_sites.yaml.swp diff --git a/roles/web/tasks/build_hugo_sites.yaml b/roles/web/tasks/build_hugo_sites.yaml new file mode 100644 index 00000000..f3852133 --- /dev/null +++ b/roles/web/tasks/build_hugo_sites.yaml @@ -0,0 +1,66 @@ +--- +- name: "make build directory" + delegate_to: localhost + ansible.builtin.file: + path: "{{ item }}" + state: directory + loop: + - "{{ inventory_dir }}/.hugo_sites_build" + - "{{ inventory_dir }}/.built_sites" + +- name: "copy specific folders to hugo_tmp for each user" + delegate_to: localhost + ansible.builtin.shell: "rsync -avz {{ inventory_dir }}/web/ {{ inventory_dir }}/.hugo_sites_build/{{ item }} {{ '--exclude=anyconnect.md' if 'ocserv' not in user_pass_dict[item] }} {{'--exclude=openconnect.md' if 'ocserv' not in user_pass_dict[item] }} {{'--exclude=nekobox.md' if ['trojan','vmess','vless']|intersect(user_pass_dict[item]) == {} }} {{'--exclude=nekoray.md' if ['trojan','vmess','vless']|intersect(user_pass_dict[item]) == {} }} {{'--exclude=nekossh.md' if 'sshvpn' not in user_pass_dict[item] }}" + loop: "{{ user_pass_dict.keys() }}" + +- name: "template out hugo.toml" + delegate_to: localhost + ansible.builtin.template: + src: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/hugo.toml.j2" + dest: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/hugo.toml" + loop: "{{ user_pass_dict.keys() }}" + +- name: "template out vars" + ansible.builtin.include_tasks: template_vars.yaml + loop: + - content.en + - content.fa + loop_control: + loop_var: content_dir + +- name: "copy hugo_build.sh to temp dir" + delegate_to: localhost + ansible.builtin.copy: + src: "{{ inventory_dir }}/utils/hugo_build.sh" + dest: ".hugo_sites_build/hugo_build.sh" + +- name: "build hugo sites" + delegate_to: localhost + ansible.builtin.shell: "sh {{ inventory_dir }}/.hugo_sites_build/hugo_build.sh" + +- name: "copy sites" + ansible.posix.synchronize: + src: "{{ inventory_dir }}/.built_sites/" # this will only copy contents + dest: /var/www/reactance + +- name: "copy images" + ansible.posix.synchronize: + src: "{{ inventory_dir }}/web/static/images/" + dest: /var/www/reactance/images + +- name: "charge dir permissions" + ansible.builtin.file: + dest: /var/www/reactance + owner: www + group: daemon + mode: 0755 + recurse: yes + +- name: "remove build directory" + delegate_to: localhost + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - "{{ inventory_dir }}/.hugo_sites_build" + - "{{ inventory_dir }}/.built_sites" diff --git a/roles/web/tasks/copy_certs.yaml b/roles/web/tasks/copy_certs.yaml new file mode 100644 index 00000000..ad670ff3 --- /dev/null +++ b/roles/web/tasks/copy_certs.yaml @@ -0,0 +1,28 @@ +--- +- name: "copy ocserv cert" + ansible.builtin.copy: + remote_src: true + src: "/var/reactance/ocserv/certs/{{ item }}-cert.pem" + dest: "/var/www/reactance/{{ item }}/{{ item }}-User-Certificate.pem" + owner: www + group: daemon + loop: "{{ (ocserv_user_pass_dict_contents.content|default('e30K')|b64decode|from_json).keys()}}" + +- name: "copy ocserv key" + ansible.builtin.copy: + remote_src: true + src: "/var/reactance/ocserv/certs/{{ item }}-key.pem" + dest: "/var/www/reactance/{{ item }}/{{ item }}-User-Key.pem" + owner: www + group: daemon + loop: "{{ (ocserv_user_pass_dict_contents.content|default('e30K')|b64decode|from_json).keys()}}" + +- name: "copy ocserv p12 cert" + ansible.builtin.copy: + remote_src: true + src: "/var/reactance/ocserv/certs/{{ item }}.p12" + dest: "/var/www/reactance/{{ item }}/{{ item }}-Certificate-Android.p12" + owner: www + group: daemon + loop: "{{ (ocserv_user_pass_dict_contents.content|default('e30K')|b64decode|from_json).keys()}}" + diff --git a/roles/web/tasks/main.yaml b/roles/web/tasks/main.yaml new file mode 100644 index 00000000..e541df09 --- /dev/null +++ b/roles/web/tasks/main.yaml @@ -0,0 +1,78 @@ +--- + +- name: "check if files exist" + ansible.builtin.stat: + path: "{{ item }}" + register: check_pass_stats + loop: + - /var/reactance/.temp/ocserv_user_pass_dict + - /var/reactance/.temp/xray_user_pass_dict + - /var/reactance/.temp/sshvpn_user_pass_dict + +- name: "slurp ocserv creds" + ansible.builtin.slurp: + src: /var/reactance/.temp/ocserv_user_pass_dict + register: ocserv_user_pass_dict_contents + when: check_pass_stats.results[0].stat.exists + +- name: "slurp xray creds" + ansible.builtin.slurp: + src: /var/reactance/.temp/xray_user_pass_dict + register: xray_user_pass_dict_contents + when: check_pass_stats.results[1].stat.exists + +- name: "slurp sshvpn creds" + ansible.builtin.slurp: + src: /var/reactance/.temp/sshvpn_user_pass_dict + register: sshvpn_user_pass_dict_contents + when: check_pass_stats.results[2].stat.exists + +- name: "combine dicts" + ansible.builtin.set_fact: + user_pass_dict: "{{ (ocserv_user_pass_dict_contents.content|default('e30K')|b64decode|from_json)|combine(xray_user_pass_dict_contents.content|default('e30K')|b64decode|from_json, sshvpn_user_pass_dict_contents.content|default('e30K')|b64decode|from_json, recursive=true, list_merge='append') }}" + notify: + - cleanup_temp_dir + +- name: "get salamander public key" + ansible.builtin.slurp: + path: "/var/reactance/xray/xray_public_key" + register: xray_pub_key_b64e + when: check_pass_stats.results[1].stat.exists + +- name: "register salamander public key" + ansible.builtin.set_fact: + xray_public_key: "{{ xray_pub_key_b64e.content|b64decode }}" + when: check_pass_stats.results[1].stat.exists + +- name: "build and copy sites" + ansible.builtin.include_tasks: build_hugo_sites.yaml + +- name: "copy certificates and keys" + ansible.builtin.include_tasks: copy_certs.yaml + +- name: "setup httpd" + ansible.builtin.include_tasks: setup_httpd.yaml + +- name: "setup htpasswd auth" + ansible.builtin.include_tasks: setup_auth.yaml + +- name: "store web expiration date" + ansible.builtin.set_fact: + web_exp_dict: "{{ web_exp_dict|default({}) | combine({item: ansible_facts.date_time.epoch|int + 86400 }) }}" + loop: "{{ user_pass_dict.keys() }}" + +- name: "check if web_expiration.json exists" + ansible.builtin.stat: + path: /var/reactance/.web_expiration.json + register: web_exp_stat + +- name: "slurp previous web_expiration.json contents" + ansible.builtin.slurp: + path: /var/reactance/.web_expiration.json + when: web_exp_stat.stat.exists + register: web_exp_e64 + +- name: "write it to file" + ansible.builtin.copy: + content: "{{ web_exp_dict|default({})|combine(web_exp_e64.content|default('e30K')|b64decode|from_json) | to_json }}" + dest: /var/reactance/.web_expiration.json diff --git a/roles/web/tasks/setup_auth.yaml b/roles/web/tasks/setup_auth.yaml new file mode 100644 index 00000000..edcd83ef --- /dev/null +++ b/roles/web/tasks/setup_auth.yaml @@ -0,0 +1,12 @@ +--- +- name: "generate arbitrary passwords for htpasswd" + ansible.builtin.set_fact: + htpasswd_passwords: "{{ htpasswd_passwords|default({}) | combine({ item: lookup('community.general.random_string', length=14, base64=true) }) }}" + loop: "{{ user_pass_dict.keys() }}" + +- name: "generate htpasswd" + ansible.builtin.shell: " echo {{item}}:{{htpasswd_passwords[item]}} | htpasswd -I /var/www/reactance/{{ item }}/.htpasswd && chown www /var/www/reactance/{{ item }}/.htpasswd && chmod 700 /var/www/reactance/{{ item }}/.htpasswd" + loop: "{{ user_pass_dict.keys() }}" + notify: + - show_htpasswd_passwords + - restart_httpd diff --git a/roles/web/tasks/setup_httpd.yaml b/roles/web/tasks/setup_httpd.yaml new file mode 100644 index 00000000..2bb9e880 --- /dev/null +++ b/roles/web/tasks/setup_httpd.yaml @@ -0,0 +1,8 @@ +--- +- name: "template out httpd.conf" + ansible.builtin.template: + src: httpd.conf.j2 + dest: /etc/httpd.conf + notify: + - restart_httpd + diff --git a/roles/web/tasks/template_vars.yaml b/roles/web/tasks/template_vars.yaml new file mode 100644 index 00000000..ba4423b4 --- /dev/null +++ b/roles/web/tasks/template_vars.yaml @@ -0,0 +1,41 @@ +--- +- name: "template out anyconnect.md" + delegate_to: localhost + ansible.builtin.template: + src: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/android/anyconnect.md.j2" + dest: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/android/anyconnect.md" + loop: "{{ (ocserv_user_pass_dict_contents.content|default('e30K')|b64decode|from_json).keys()}}" + +- name: "template out openconnect.md" + delegate_to: localhost + ansible.builtin.template: + src: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/windows/openconnect.md.j2" + dest: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/windows/openconnect.md" + loop: "{{ (ocserv_user_pass_dict_contents.content|default('e30K')|b64decode|from_json).keys() }}" + +- name: "template out nekobox.md" + delegate_to: localhost + ansible.builtin.template: + src: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/android/nekobox.md.j2" + dest: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/android/nekobox.md" + loop: "{{ users.keys() }}" + vars: + users: "{{ xray_user_pass_dict_contents.content|default('e30K')|b64decode|from_json}}" + +- name: "template out nekoray.md" + delegate_to: localhost + ansible.builtin.template: + src: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/windows/nekoray.md.j2" + dest: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/windows/nekoray.md" + loop: "{{ users.keys() }}" + vars: + users: "{{ xray_user_pass_dict_contents.content|default('e30K')|b64decode|from_json}}" + +- name: "template out nekossh.md" + delegate_to: localhost + ansible.builtin.template: + src: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/android/nekossh.md.j2" + dest: "{{ inventory_dir }}/.hugo_sites_build/{{ item }}/{{ content_dir }}/docs/android/nekossh.md" + loop: "{{ users.keys() }}" + vars: + users: "{{ sshvpn_user_pass_dict_contents.content|default('e30K')|b64decode|from_json}}" diff --git a/roles/web/templates/httpd.conf.j2 b/roles/web/templates/httpd.conf.j2 new file mode 100644 index 00000000..ab26722c --- /dev/null +++ b/roles/web/templates/httpd.conf.j2 @@ -0,0 +1,19 @@ +server "default" { + listen on * port 80 + location "/" { + block drop + } + location "/images/*" { + root "/reactance/images" + request strip 1 + } + +{% for uname in user_pass_dict.keys() %} + location "/{{ uname }}/*" { + root "/reactance/{{ uname }}" + request strip 1 + directory auto index + authenticate with "/reactance/{{ uname }}/.htpasswd" + } +{% endfor %} +} diff --git a/roles/xray/handlers/main.yaml b/roles/xray/handlers/main.yaml new file mode 100644 index 00000000..63ee84c8 --- /dev/null +++ b/roles/xray/handlers/main.yaml @@ -0,0 +1,11 @@ +--- +- name: restart_xray + ansible.builtin.service: + name: xray + state: restarted + enabled: true + +- name: remove_xray_tempdir + ansible.builtin.file: + path: "{{ xray_tempdir.path }}" + state: absent diff --git a/roles/xray/tasks/check_xray_exists.yaml b/roles/xray/tasks/check_xray_exists.yaml new file mode 100644 index 00000000..c0fdc525 --- /dev/null +++ b/roles/xray/tasks/check_xray_exists.yaml @@ -0,0 +1,21 @@ +--- +- name: "Check if xray is already installed" + ansible.builtin.stat: + path: /var/reactance/xray + register: xray_directory + +- name: "Check if xray is configured" + ansible.builtin.stat: + path: /var/reactance/xray/etc/config.json + register: xray_config + +- name: "Install xray if directory doesn't exist" + ansible.builtin.include_tasks: install_xray.yaml + when: xray_directory.stat.exists == false + +- name: "Configure xray" + ansible.builtin.include_tasks: configure_xray.yaml + when: xray_config.stat.exists == false + +- name: "Create xray users" + ansible.builtin.include_tasks: create_users_xray.yaml diff --git a/roles/xray/tasks/configure_xray.yaml b/roles/xray/tasks/configure_xray.yaml new file mode 100644 index 00000000..4f8cf5e6 --- /dev/null +++ b/roles/xray/tasks/configure_xray.yaml @@ -0,0 +1,50 @@ +--- +# generate keypair, needed for config +- name: "generate private, public keypair for xtls-reality" + ansible.builtin.shell: "/var/reactance/xray/bin/xray x25519 | awk '{ print $3 }' | tr '\n' ','" + register: keypair + +- name: "set private key as var" + ansible.builtin.set_fact: + xray_private_key: "{{ (keypair.stdout | split(',')).0 }}" + xray_public_key: "{{ (keypair.stdout | split(',')).1 }}" + +- name: "write public key to file" + ansible.builtin.copy: + content: "{{ xray_public_key }}" + dest: "/var/reactance/xray/xray_public_key" + +- name: "write private key to file" + ansible.builtin.copy: + content: "{{ xray_private_key }}" + dest: "/var/reactance/xray/xray_private_key" + +- name: "template out config and init script" + ansible.builtin.template: + src: config.json.j2 + dest: "/var/reactance/xray/etc/config.json" + +# xray is chrooted and has their own mechanism for logging, which is why it needs to be separatly linked later +- name: "touch xray log files" + ansible.builtin.file: + path: "{{ item }}" + state: touch + mode: "0700" + owner: _vpn + group: _vpn + loop: + - "/var/reactance/xray/logs/xray-access.log" + - "/var/reactance/xray/logs/xray-error.log" + +# purely for convenience +- name: "link log files to /var/log/xray" + ansible.builtin.file: + src: "/var/reactance/xray/logs/{{ item }}" + dest: "/var/log/xray/{{ item }}" + state: link + mode: "0700" + owner: _vpn + group: _vpn + loop: + - xray-access.log + - xray-error.log diff --git a/roles/xray/tasks/create_users_xray.yaml b/roles/xray/tasks/create_users_xray.yaml new file mode 100644 index 00000000..b56cb0fa --- /dev/null +++ b/roles/xray/tasks/create_users_xray.yaml @@ -0,0 +1,55 @@ +--- + +- name: "get salamaner public key" + ansible.builtin.slurp: + path: "/var/reactance/xray/xray_public_key" + register: xray_pub_key_b64e + +- name: "vless user management" + xray: + users: "{{ all_users|default([]) + vless_users|default([]) }}" + protocol: vless + address: "{{ ansible_default_ipv4.interface|default(ansible_all_ipv4_addresses[0]) }}" + service_port: "{{ vless_port|default(4437) }}" + public_key: "{{ xray_pub_key_b64e.content|b64decode }}" + when: inventory_hostname in (groups['vless']|default([])) + (groups['all_vpns']|default([])) + register: vless_user_pass_dict + # no_log: true + notify: + - restart_xray + +- name: "vmess user management" + xray: + users: "{{ all_users|default([]) + vmess_users|default([]) }}" + protocol: vmess + address: "{{ ansible_default_ipv4.interface|default(ansible_all_ipv4_addresses[0]) }}" + service_port: "{{ vless_port|default(4437) }}" + public_key: "{{ xray_pub_key_b64e.content|b64decode }}" + when: inventory_hostname in (groups['vmess']|default([])) + (groups['all_vpns']|default([])) + register: vmess_user_pass_dict + no_log: true + notify: + - restart_xray + +- name: "trojan user management" + xray: + users: "{{ all_users|default([]) + trojan_users|default([]) }}" + protocol: trojan + address: "{{ ansible_default_ipv4.interface|default(ansible_all_ipv4_addresses[0]) }}" + service_port: "{{ vless_port|default(4437) }}" + public_key: "{{ xray_pub_key_b64e.content|b64decode }}" + when: inventory_hostname in (groups['trojan']|default([])) + (groups['all_vpns']|default([])) + register: trojan_user_pass_dict + no_log: true + notify: + - restart_xray + +- name: "make temp dir" + ansible.builtin.file: + path: /var/reactance/.temp/ + state: directory + +- name: "add ocserv user password pair to dict" + ansible.builtin.copy: + content: "{{ (trojan_user_pass_dict['msg']|default({}) | combine(vmess_user_pass_dict['msg']|default({}), vless_user_pass_dict['msg']|default({}), recursive=true, list_merge='append')) | to_json }}" + dest: /var/reactance/.temp/xray_user_pass_dict diff --git a/roles/xray/tasks/install_xray.yaml b/roles/xray/tasks/install_xray.yaml new file mode 100644 index 00000000..d4889531 --- /dev/null +++ b/roles/xray/tasks/install_xray.yaml @@ -0,0 +1,100 @@ +--- +- name: "create directory" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: _vpn + group: _vpn + mode: 0700 + loop: + - "/var/reactance/xray" + - "/var/reactance/xray/bin" + - "/var/reactance/xray/etc" + - "/var/reactance/xray/logs" + - "/var/log/xray" + +- name: "install golang to build xray" + community.general.openbsd_pkg: + name: go-- + state: present + register: installed_go + +- name: "log var" + ansible.builtin.debug: + msg: "{{ is_installed }}" + +- name: "create temporary directory" + ansible.builtin.tempfile: + state: directory + suffix: temp + register: xray_tempdir + notify: + - remove_xray_tempdir + +- name: "get latest version" + ansible.builtin.shell: 'curl --silent "https://api.github.com/repos/XTLS/Xray-core/releases/latest" | jq -r .tag_name' + register: xray_latest_version + +- name: "download latest source" + ansible.builtin.get_url: + url: "https://github.com/XTLS/Xray-core/releases/download/{{ xray_latest_version.stdout }}/{{ xray_latest_version.stdout }}.zip" + dest: "{{ xray_tempdir.path }}/source.zip" + +- name: "additionally download latest version (for geoip.dat, geosite.dat)" + ansible.builtin.get_url: + url: "https://github.com/XTLS/Xray-core/releases/download/{{ xray_latest_version.stdout }}/Xray-openbsd-64.zip" + dest: "{{ xray_tempdir.path }}/xray.zip" + +- name: "unzip xray" + ansible.builtin.unarchive: + src: "{{ xray_tempdir.path }}/xray.zip" + dest: "{{ xray_tempdir.path }}" + remote_src: yes + +- name: "unzip xray source" + ansible.builtin.unarchive: + src: "{{ xray_tempdir.path }}/xray.source" + dest: "{{ xray_tempdir.path }}/source" + remote_src: yes + +- name: "build xray from source" + ansible.builtin.shell: "CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags=\"-s -w -buildid= -X 'xray.buf.readv='\" ./main" + args: + chdir: "{{ xray_tempdir.path }}/source" + +- name: "template out init script" + ansible.builtin.template: + src: "{{ item.file_src }}" + dest: "{{ item.file_dest }}" + loop: + - file_src: xray.rc.j2 + file_dest: "{{ xray_tempdir.path }}/xray.rc" + +- name: "install xray" + ansible.builtin.shell: "{{ item }}" + loop: + - "install -m 700 -o _vpn -g bin {{ xray_tempdir.path }}/source/xray /var/reactance/xray/bin/xray" + - "install -m 755 -o _vpn -g bin {{ xray_tempdir.path }}/xray.rc /etc/rc.d/xray" + - "install -m 700 -o _vpn -g bin {{ xray_tempdir.path }}/geoip.dat /var/reactance/xray/bin/geoip.dat" + - "install -m 700 -o _vpn -g bin {{ xray_tempdir.path }}/geosite.dat /var/reactance/xray/bin/geosite.dat" + +- name: "copy chroot dependencies" + ansible.builtin.shell: "deps=$(ldd /var/reactance/xray/bin/xray | awk 'FNR > 3 {print $7}'); for dep in $deps; do rsync -av --relative $dep /var/reactance/xray; done" + +# xray will fail without these two files +- name: "copy hosts and resolv.conf" + ansible.builtin.copy: + remote_src: true + src: "{{ item.src }}" + dest: "{{ item.dest }}" + loop: + - src: /etc/hosts + dest: /var/reactance/xray/etc/hosts + - src: /etc/resolv.conf + dest: /var/reactance/xray/etc/resolv.conf + +# - name: "uninstall golang (if wasn't installed in past)" +# community.general.openbsd_pkg: +# name: go-- +# state: present + diff --git a/roles/xray/tasks/main.yaml b/roles/xray/tasks/main.yaml new file mode 100644 index 00000000..422b2bb5 --- /dev/null +++ b/roles/xray/tasks/main.yaml @@ -0,0 +1,3 @@ +--- +- name: "setup xray vpn" + ansible.builtin.include_tasks: check_xray_exists.yaml diff --git a/roles/xray/templates/config.json.j2 b/roles/xray/templates/config.json.j2 new file mode 100644 index 00000000..35e78b75 --- /dev/null +++ b/roles/xray/templates/config.json.j2 @@ -0,0 +1,131 @@ +{ + "log": { + "loglevel": "debug", + "access": "/logs/xray-access.log", + "error": "/logs/xray-error.log", + "dnsLog": false + }, + "stats": {}, + "api": { + "tag": "api", + "services": [ + "StatsService" + ] + }, + "policy": { + "levels": { + "0": { + "statsUserUplink": true, + "statsUserDownlink": true + } + } + }, + "inbounds": [ +{% if inventory_hostname in (groups['trojan']|default([])) + (groups['all_vpns']|default([])) %} + { + "listen": "0.0.0.0", + "port": {{ trojan_port|default(4436) }}, + "protocol": "trojan", + "settings": { + "clients": [] + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "show": false, + "dest": "behindthename.com:443", + "serverNames": [ + "behindthename.com", + "www.behindthename.com" + ], + "xver": 0, + "privateKey": "{{ xray_private_key }}", + "maxTimeDiff": 0, + "shortIds": [""] + } + } + }, +{% endif %} +{% if inventory_hostname in (groups['vless']|default([])) + (groups['all_vpns']|default([])) %} + { + "listen": "0.0.0.0", + "port": {{ vless_port|default(4437) }}, + "protocol": "vless", + "settings": { + "decryption": "none", + "clients": [] + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "show": false, + "dest": "behindthename.com:443", + "serverNames": [ + "behindthename.com", + "www.behindthename.com" + ], + "xver": 0, + "privateKey": "{{ xray_private_key }}", + "maxTimeDiff": 0, + "shortIds": [""] + } + } + }, +{% endif %} +{% if inventory_hostname in (groups['vmess']|default([])) + (groups['all_vpns']|default([])) %} + { + "listen": "0.0.0.0", + "port": {{ vmess_port|default(4438) }}, + "protocol": "vmess", + "settings": { + "clients": [] + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "show": false, + "dest": "behindthename.com:443", + "serverNames": [ + "behindthename.com", + "www.behindthename.com" + ], + "xver": 0, + "privateKey": "{{ xray_private_key }}", + "maxTimeDiff": 0, + "shortIds": [""] + } + } + }, +{% endif %} + { + "listen": "127.0.0.1", + "port": 10085, + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1" + }, + "tag": "api" + } + ], + "outbounds": [ + { + "protocol": "freedom", + "tag": "direct" + } + ], + "routing": { + "rules": [ + { + "inboundTag": [ + "api" + ], + "outboundTag": "api", + "type": "field" + } + ] + } +} + diff --git a/roles/xray/templates/xray.rc.j2 b/roles/xray/templates/xray.rc.j2 new file mode 100644 index 00000000..fe658fbc --- /dev/null +++ b/roles/xray/templates/xray.rc.j2 @@ -0,0 +1,22 @@ +#!/bin/ksh +# +# $OpenBSD: xray + +chroot_dir=/var/reactance/xray +chroot_user=_vpn +daemon_class=daemon +daemon=/bin/xray +daemon_flags="run -c /etc/config.json" +daemon_user=root + +. /etc/rc.d/rc.subr + +rc_start() { + rc_exec "chroot -u $chroot_user $chroot_dir $daemon $daemon_flags" +} + +rc_bg=YES +rc_reload=NO + +rc_cmd $1 + |
