diff --git a/manage.py b/manage.py index 66d1259..efb590b 100755 --- a/manage.py +++ b/manage.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -import os, sys, re +import os, sys, re, shutil import filecmp from glob import glob from mako.template import Template @@ -115,6 +115,24 @@ def getUid(service): return env['podman_uid'] +def promptTargetProjects(): + projects = os.listdir("projects") + print(f"\nProjects list: {projects}") + if len(sys.argv) > 2: + target_projects = sys.argv[2] + else: + target_projects = input("Target compose project(s), space separated, leave empty to target all: ") + + if target_projects.strip() == '': + target_projects = projects + else: + target_projects = re.split(' ?, ?| ', target_projects.strip()) + + print(f"Target projects: {target_projects}") + + return target_projects + + def pullProj(project): print(f"Pulling images for project {project}.") @@ -138,16 +156,16 @@ def pullProj(project): return pulledImages -def renderFile(templateFile): - print(f"Rendering file {templateFile}.") +def renderFile(templateFile, renderedFile=None): + if renderedFile is None: + renderedFile = re.sub('\\.mako$', '.rendered', templateFile) - renderedFilename = re.sub('\\.mako$', '.rendered', templateFile) + print(f"Rendering file {templateFile} to {renderedFile}.") template = Template(filename=templateFile) - outputFile = open(renderedFilename, "w") - outputFile.write(template.render(env=env, secrets=secrets)) - outputFile.close() + with open(renderedFile, "w") as outputFile: + outputFile.write(template.render(env=env, secrets=secrets)) def runBorg(args, input=None): @@ -192,11 +210,19 @@ def runSudo(args): child = subprocess.Popen(["sudo"] + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - if re.search('^\\[sudo\\] password for .+', child.stdout.read().strip()): + stdout = child.stdout.read().strip() + if stdout != '': + print(stdout) + + if re.search('^\\[sudo\\] password for .+', stdout): child.communicate(input=input().encode())[0] child.wait() + stdout = child.stdout.read().strip() + if stdout != '': + print(stdout) + stderr = child.stderr.read().strip() if stderr != '': print(stderr, file=sys.stderr) @@ -259,6 +285,26 @@ def setPerms(path, mode): print(f"Permissions of {path} already set to {mode}.") +def setupNGINX(): + if os.getuid() != 0: + print("Current user is not root, rerunning program as root.") + runSudo([sys.executable, sys.argv[0], "nginx"]) + return + + for dir in ["sites-enabled", "snippets"]: + print(f"Recreating directory /etc/nginx/{dir}") + shutil.rmtree(f"/etc/nginx/{dir}") + os.mkdir(f"/etc/nginx/{dir}") + + for templateFile in glob("nginx/**/*.conf", recursive=True, include_hidden=True): + renderFile(templateFile, "/etc/" + templateFile) + + subprocess.run(["nginx", "-t"]) + + print("Reloading service nginx.") + runSudo("systemctl reload nginx") + + def setupProj(project): print(f"Running setup for project {project}.") @@ -324,8 +370,9 @@ def main(): env = yaml.safe_load(Template(filename=envFile).render()) secrets = yaml.safe_load(secretsfile) - setOwner(secretsFile, os.getuid(), os.getgid()) - setPerms(secretsFile, 600) + if os.getuid() != 0: + setOwner(secretsFile, os.getuid(), os.getgid()) + setPerms(secretsFile, 600) print("\nUsing socket " + env['socket'] + ".") @@ -338,28 +385,16 @@ def main(): print("[1/S] Setup project") print("[2/U] Update project") print("[3/B] Backup project") + print("[4/N] Setup NGINX") while action == '': action = input("Action: ") - projects = os.listdir("projects") - print(f"\nProjects list: {projects}") - if len(sys.argv) > 2: - target_projects = sys.argv[2] - else: - target_projects = input("Target compose project(s), space separated, leave empty to target all: ") - - if target_projects.strip() == '': - target_projects = projects - else: - target_projects = re.split(' ?, ?| ', target_projects.strip()) - - print(f"Target projects: {target_projects}") - - match action.casefold(): case '1' | 's' | 'setup': + target_projects = promptTargetProjects() + setNftables() for project in target_projects: @@ -371,6 +406,8 @@ def main(): print(f"Failed to setup project {project}.", file=sys.stderr) case '2' | 'u' | 'update': + target_projects = promptTargetProjects() + for project in target_projects: try: print() @@ -380,6 +417,8 @@ def main(): print(f"Failed to update project {project}.", file=sys.stderr) case '3' | 'b' | 'backup': + target_projects = promptTargetProjects() + print() if not os.path.exists(env['borg_repo']): print(f"Creating borg repository {env['borg_repo']}.") @@ -397,6 +436,9 @@ def main(): runBorg(["compact", env['borg_repo']]) + case '4' | 'n' | 'nginx': + setupNGINX() + if __name__ == "__main__": main() diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..6db0562 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,38 @@ +user www-data; +worker_processes auto; +worker_rlimit_nofile 1024; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 512; + multi_accept off; +} + +http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + gzip off; + server_tokens off; + keepalive_timeout 30; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + include /etc/nginx/mime.types; + + # Needed to support websocket connections + map $http_upgrade $connection_upgrade { + default upgrade; + '' ""; + } + + include /etc/nginx/conf.d/*.conf; + + include /etc/nginx/snippets/proxy.conf; + include /etc/nginx/snippets/ssl.conf; + include /etc/nginx/snippets/ssl-headers.conf; + + include /etc/nginx/sites-enabled/*; +} diff --git a/nginx/sites-enabled/clips.conf b/nginx/sites-enabled/clips.conf new file mode 100644 index 0000000..5d381fa --- /dev/null +++ b/nginx/sites-enabled/clips.conf @@ -0,0 +1,12 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name clips.${env['domain']}; + + location / { + proxy_pass http://127.0.0.1:${env['ports']['fireshare']}; + + client_max_body_size 500M; + } +} diff --git a/nginx/sites-enabled/default.conf b/nginx/sites-enabled/default.conf new file mode 100644 index 0000000..81e58c1 --- /dev/null +++ b/nginx/sites-enabled/default.conf @@ -0,0 +1,17 @@ +# Redirect HTTP to HTTPS +server { + listen 80 default_server; + listen [::]:80 default_server; + + return 308 https://$host$request_uri; +} + +# Default HTTPS server +server { + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + + http2 on; + + return 404; +} diff --git a/nginx/sites-enabled/downloads.conf b/nginx/sites-enabled/downloads.conf new file mode 100644 index 0000000..e9ff084 --- /dev/null +++ b/nginx/sites-enabled/downloads.conf @@ -0,0 +1,9 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name dl.${env['domain']}; + + root /var/www/html; + autoindex on; +} diff --git a/nginx/sites-enabled/etebase.conf b/nginx/sites-enabled/etebase.conf new file mode 100644 index 0000000..8a7d50b --- /dev/null +++ b/nginx/sites-enabled/etebase.conf @@ -0,0 +1,10 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name etebase.${env['domain']}; + + location / { + proxy_pass http://127.0.0.1:${env['ports']['etebase']}; + } +} diff --git a/nginx/sites-enabled/hedgedoc.conf b/nginx/sites-enabled/hedgedoc.conf new file mode 100644 index 0000000..b2158af --- /dev/null +++ b/nginx/sites-enabled/hedgedoc.conf @@ -0,0 +1,17 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name hedgedoc.${env['domain']}; + + location / { + proxy_pass http://127.0.0.1:${env['ports']['hedgedoc']}; + } + + location /socket.io/ { + proxy_pass http://127.0.0.1:${env['ports']['hedgedoc']}; + + include /etc/nginx/snippets/websocket.conf; + include /etc/nginx/snippets/proxy.conf; + } +} diff --git a/nginx/sites-enabled/homepage.conf b/nginx/sites-enabled/homepage.conf new file mode 100644 index 0000000..5bbf429 --- /dev/null +++ b/nginx/sites-enabled/homepage.conf @@ -0,0 +1,25 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name ${env['domain']}; + + location = /.well-known/matrix/server { + default_type application/json; + + return 200 '{ "m.server": "matrix.${env["domain"]}:443" }'; + } + + location = /.well-known/matrix/client { + default_type application/json; + + include /etc/nginx/snippets/ssl-headers.conf; + add_header Access-Control-Allow-Origin '*'; + + return 200 '{ "m.homeserver": { "base_url": "https://matrix.${env["domain"]}" } }'; + } + + location / { + proxy_pass http://127.0.0.1:${env['ports']['homepage']}; + } +} diff --git a/nginx/sites-enabled/keycloak.conf b/nginx/sites-enabled/keycloak.conf new file mode 100644 index 0000000..bd4f611 --- /dev/null +++ b/nginx/sites-enabled/keycloak.conf @@ -0,0 +1,13 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name kc.${env['domain']}; + + location / { + proxy_pass https://127.0.0.1:${env['ports']['keycloak']}; + + #include /etc/nginx/snippets/websocket.conf; + #include /etc/nginx/snippets/proxy.conf; + } +} diff --git a/nginx/sites-enabled/mail.conf b/nginx/sites-enabled/mail.conf new file mode 100644 index 0000000..ef030d6 --- /dev/null +++ b/nginx/sites-enabled/mail.conf @@ -0,0 +1,43 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name mail.${env['domain']}; + + location / { + proxy_pass https://127.0.0.1:${env['ports']['mailserver_https']}; + + include /etc/nginx/snippets/websocket.conf; + include /etc/nginx/snippets/proxy.conf; + } +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name autoconfig.${env['domain']}; + + location / { + return 404; + } + + location = /mail/config-v1.1.xml { + proxy_pass https://127.0.0.1:${env['ports']['mailserver_https']}; + } +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name mta-sts.${env['domain']}; + + location / { + return 404; + } + + location = /.well-known/mta-sts.txt { + proxy_pass https://127.0.0.1:${env['ports']['mailserver_https']}; + } +} diff --git a/nginx/sites-enabled/searxng.conf b/nginx/sites-enabled/searxng.conf new file mode 100644 index 0000000..a71fef2 --- /dev/null +++ b/nginx/sites-enabled/searxng.conf @@ -0,0 +1,13 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name searx.${env['domain']}; + + location / { + proxy_pass http://127.0.0.1:${env['ports']['searxng']}; + + include /etc/nginx/snippets/ssl-headers.conf; + add_header Content-Security-Policy "upgrade-insecure-requests; default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'self' https://github.com/searxng/searxng/issues/new; font-src 'self'; frame-ancestors 'self'; base-uri 'self'; connect-src 'self' https://overpass-api.de; img-src 'self' data: https://*.tile.openstreetmap.org; frame-src https://www.youtube-nocookie.com https://player.vimeo.com https://www.dailymotion.com https://www.deezer.com https://www.mixcloud.com https://w.soundcloud.com https://embed.spotify.com"; + } +} diff --git a/nginx/sites-enabled/sex.conf b/nginx/sites-enabled/sex.conf new file mode 100644 index 0000000..64582fd --- /dev/null +++ b/nginx/sites-enabled/sex.conf @@ -0,0 +1,12 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name sex.${env['domain']}; + + root /var/www/sex; + + location / { + random_index on; + } +} diff --git a/nginx/sites-enabled/stdiscosrv.conf b/nginx/sites-enabled/stdiscosrv.conf new file mode 100644 index 0000000..690fbcf --- /dev/null +++ b/nginx/sites-enabled/stdiscosrv.conf @@ -0,0 +1,17 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name stdisco.${env['domain']}; + + ssl_verify_client optional_no_ca; + + location / { + proxy_pass http://127.0.0.1:${env['ports']['syncthing_discosrv']}; + + proxy_set_header X-Client-Port $remote_port; + proxy_set_header X-SSL-Cert $ssl_client_cert; + include /etc/nginx/snippets/websocket.conf; + include /etc/nginx/snippets/proxy.conf; + } +} diff --git a/nginx/sites-enabled/stump.conf b/nginx/sites-enabled/stump.conf new file mode 100644 index 0000000..4751a41 --- /dev/null +++ b/nginx/sites-enabled/stump.conf @@ -0,0 +1,13 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name stump.${env['domain']}; + + location / { + proxy_pass http://127.0.0.1:${env['ports']['stump']}; + + include /etc/nginx/snippets/websocket.conf; + include /etc/nginx/snippets/proxy.conf; + } +} diff --git a/nginx/sites-enabled/synapse.conf b/nginx/sites-enabled/synapse.conf new file mode 100644 index 0000000..3637e1c --- /dev/null +++ b/nginx/sites-enabled/synapse.conf @@ -0,0 +1,12 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name matrix.${env['domain']}; + + location / { + proxy_pass http://127.0.0.1:${env['ports']['synapse']}; + + client_max_body_size 50M; + } +} diff --git a/nginx/sites-enabled/uptime.conf b/nginx/sites-enabled/uptime.conf new file mode 100644 index 0000000..ba96226 --- /dev/null +++ b/nginx/sites-enabled/uptime.conf @@ -0,0 +1,13 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name status.${env['domain']}; + + location / { + proxy_pass http://127.0.0.1:${env['ports']['uptime']}; + + include /etc/nginx/snippets/websocket.conf; + include /etc/nginx/snippets/proxy.conf; + } +} diff --git a/nginx/sites-enabled/vaultwarden.conf b/nginx/sites-enabled/vaultwarden.conf new file mode 100644 index 0000000..2b22255 --- /dev/null +++ b/nginx/sites-enabled/vaultwarden.conf @@ -0,0 +1,19 @@ +upstream vaultwarden { + zone vaultwarden 64k; + server 127.0.0.1:${env['ports']['vaultwarden']}; + keepalive 2; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name vw.${env['domain']}; + + location / { + proxy_pass http://vaultwarden; + + include /etc/nginx/snippets/websocket.conf; + include /etc/nginx/snippets/proxy.conf; + } +} diff --git a/nginx/snippets/proxy.conf b/nginx/snippets/proxy.conf new file mode 100644 index 0000000..e54c7ed --- /dev/null +++ b/nginx/snippets/proxy.conf @@ -0,0 +1,10 @@ +proxy_http_version 1.1; +proxy_set_header Host $host; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Host $http_host; +proxy_set_header X-Forwarded-Port $server_port; +proxy_set_header X-Forwarded-Proto $scheme; +proxy_set_header X-Forwarded-Scheme $scheme; +proxy_set_header X-Forwarded-URI $request_uri; +proxy_set_header X-Original-URL $scheme://$http_host$request_uri; +proxy_set_header X-Real-IP $remote_addr; diff --git a/nginx/snippets/ssl-headers.conf b/nginx/snippets/ssl-headers.conf new file mode 100644 index 0000000..a84a1ba --- /dev/null +++ b/nginx/snippets/ssl-headers.conf @@ -0,0 +1,3 @@ +add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; +# add_header X-Robots-Tag "noindex, nofollow" always; +add_header Set-Cookie "Path=/; HttpOnly; Secure"; diff --git a/nginx/snippets/ssl.conf b/nginx/snippets/ssl.conf new file mode 100644 index 0000000..fb6f3f4 --- /dev/null +++ b/nginx/snippets/ssl.conf @@ -0,0 +1,18 @@ +ssl_certificate /etc/letsencrypt/live/${env['domain']}/fullchain.pem; +ssl_certificate_key /etc/letsencrypt/live/${env['domain']}/privkey.pem; +ssl_trusted_certificate /etc/letsencrypt/live/${env['domain']}/chain.pem; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; + +# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam +ssl_dhparam /etc/nginx/dhparam.txt; + +ssl_prefer_server_ciphers on; + +ssl_session_timeout 1d; +ssl_session_cache shared:MozSSL:10m; +ssl_session_tickets off; + +ssl_stapling on; +ssl_stapling_verify on; diff --git a/nginx/snippets/websocket.conf b/nginx/snippets/websocket.conf new file mode 100644 index 0000000..f75eb91 --- /dev/null +++ b/nginx/snippets/websocket.conf @@ -0,0 +1,2 @@ +proxy_set_header Upgrade $http_upgrade; +proxy_set_header Connection $connection_upgrade; diff --git a/setup.sh b/setup.sh index a0c54f0..4de5848 100755 --- a/setup.sh +++ b/setup.sh @@ -15,14 +15,14 @@ declare -a podman_units=(podman.service podman.socket podman-auto-update.service if [[ "$podman_mode" == "rootless" ]]; then - sudo apt install -y aardvark-dns borgbackup cifs-utils dbus-user-session nftables passt podman podman-compose python3-mako slirp4netns uidmap + sudo apt install -y aardvark-dns borgbackup cifs-utils curl dbus-user-session nftables nginx passt podman podman-compose python3-mako slirp4netns uidmap sudo loginctl enable-linger "$USER" sudo systemctl disable --now "${podman_units[@]}" systemctl --user enable --now "${podman_units[@]}" else - sudo apt install -y aardvark-dns borgbackup cifs-utils nftables podman podman-compose python3-mako + sudo apt install -y aardvark-dns borgbackup cifs-utils curl nftables nginx podman podman-compose python3-mako systemctl --user disable --now "${podman_units[@]}" sudo systemctl enable --now "${podman_units[@]}" @@ -43,4 +43,7 @@ done sudo sysctl -p /etc/sysctl.d/podman.conf -sudo systemctl enable --now nftables +sudo curl -o /etc/nginx/dhparam.txt https://ssl-config.mozilla.org/ffdhe2048.txt + + +sudo systemctl enable --now nftables nginx