Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LXC Container support #53

Open
wants to merge 21 commits into
base: current-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
582e4db
feat: fix menu function and add platform type question
casesolved-co-uk Jun 5, 2022
6592708
feat: move hardware and container functionality. Add question for fir…
casesolved-co-uk Jun 5, 2022
74c94c2
fix: platform checks and info
casesolved-co-uk Jun 5, 2022
76e48f6
feat: add additional info for LXC container installs
casesolved-co-uk Jun 10, 2022
510987c
feat: add more LXC info
casesolved-co-uk Jun 10, 2022
c8fa394
fix: remove dss/dsa host key requirement, no longer in sshd_config de…
casesolved-co-uk Jun 10, 2022
f46a5f7
fix: bug overwriting PRIMARY_HOSTNAME with zone name if zone name als…
casesolved-co-uk Jun 10, 2022
3cf045f
feat: issue letsencrypt certificates instead of self-signed on initia…
casesolved-co-uk Jun 10, 2022
6e44d1b
fix: only issue certificate for primary hostname
casesolved-co-uk Jun 10, 2022
9cea7ed
fix: fix redirects to allow nginx to sit behind port forwarding
casesolved-co-uk Jun 16, 2022
13c943b
feat: regenerate nginx config from the commandline
casesolved-co-uk Jun 16, 2022
db2780c
feat: platform autodetection, configurable HTTPS port, rephrase quest…
casesolved-co-uk Jun 16, 2022
9d4e0c5
fix: add basic tool packages
casesolved-co-uk Jun 16, 2022
cf9a85e
fix: default https port for fresh installs
casesolved-co-uk Jun 16, 2022
8c35650
fix: basic package requirements
casesolved-co-uk Jun 16, 2022
d1ca987
fix: route not actually required
casesolved-co-uk Jun 16, 2022
257b93b
fix: HTTP -> HTTPS port number
casesolved-co-uk Jun 16, 2022
bbaae76
fix: also update the ios profile HTTPS port
casesolved-co-uk Jun 17, 2022
3817a86
fix: make system status checks less scary for container owners
casesolved-co-uk Jun 25, 2022
9de97bb
fix: remove more hard-coded HTTPS ports
casesolved-co-uk Jun 25, 2022
003c96c
feat: failsafe issue certificate for local host
casesolved-co-uk Dec 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions conf/ios-profile.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<key>CalDAVHostName</key>
<string>PRIMARY_HOSTNAME</string>
<key>CalDAVPort</key>
<real>443</real>
<integer>HTTPS_PORT</integer>
<key>CalDAVUseSSL</key>
<true/>
<key>PayloadDescription</key>
Expand Down Expand Up @@ -85,7 +85,7 @@
<key>CardDAVHostName</key>
<string>PRIMARY_HOSTNAME</string>
<key>CardDAVPort</key>
<integer>443</integer>
<integer>HTTPS_PORT</integer>
<key>CardDAVPrincipalURL</key>
<string>/cloud/remote.php/carddav/addressbooks/</string>
<key>CardDAVUseSSL</key>
Expand Down
20 changes: 10 additions & 10 deletions conf/nginx-primaryonly.conf
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# ADDITIONAL DIRECTIVES HERE

# Redirect all sites to the directory preserving port number
location ~ ^/(admin|admin/munin|mail|cloud)$ {
return 301 $scheme://$http_host/$1/;
}

# Control Panel
# Proxy /admin to our Python based control panel daemon. It is
# listening on IPv4 only so use an IP address and not 'localhost'.
location /admin/assets {
alias /usr/local/lib/mailinabox/vendor/assets;
}
rewrite ^/admin$ /admin/;
rewrite ^/admin/munin$ /admin/munin/ redirect;
location /admin/ {
proxy_pass http://127.0.0.1:10222/;
proxy_set_header X-Forwarded-For $remote_addr;
Expand All @@ -17,8 +20,6 @@
}

# Roundcube Webmail configuration.
rewrite ^/mail$ /mail/ redirect;
rewrite ^/mail/$ /mail/index.php;
location /mail/ {
index index.php;
alias /usr/local/lib/roundcubemail/;
Expand All @@ -41,11 +42,10 @@
}

# Nextcloud configuration.
rewrite ^/cloud$ /cloud/ redirect;
rewrite ^/cloud/$ /cloud/index.php;
rewrite ^/cloud/(contacts|calendar|files)$ /cloud/index.php/apps/$1/ redirect;
rewrite ^/cloud/(contacts|calendar|files)$ $scheme://$http_host/cloud/index.php/apps/$1/;
rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
rewrite ^(/cloud/oc[sm]-provider)/$ $1/index.php redirect;
rewrite ^(/cloud/oc[sm]-provider)/$ $scheme://$http_host$1/index.php;
location /cloud/ {
alias /usr/local/lib/owncloud/;
location ~ ^/cloud/(build|tests|config|lib|3rdparty|templates|data|README)/ {
Expand Down Expand Up @@ -92,9 +92,9 @@
location ~ ^/((caldav|carddav|webdav).*)$ {
# Z-Push doesn't like getting a redirect, and a plain rewrite didn't work either.
# Properly proxying like this seems to work fine.
proxy_pass https://127.0.0.1/cloud/remote.php/$1;
proxy_pass $scheme://127.0.0.1/cloud/remote.php/$1;
}
rewrite ^/.well-known/host-meta /cloud/public.php?service=host-meta last;
rewrite ^/.well-known/host-meta.json /cloud/public.php?service=host-meta-json last;
rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;
rewrite ^/.well-known/carddav $scheme://$http_host/cloud/remote.php/carddav/;
rewrite ^/.well-known/caldav $scheme://$http_host/cloud/remote.php/caldav/;
6 changes: 3 additions & 3 deletions conf/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ server {
# Redirect using the 'return' directive and the built-in
# variable '$request_uri' to avoid any capturing, matching
# or evaluation of regular expressions.
return 301 https://$HOSTNAME$request_uri;
return 301 https://$host:$HTTPS_PORT$request_uri;
}

location /.well-known/acme-challenge/ {
Expand All @@ -31,8 +31,8 @@ server {

# The secure HTTPS server.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen $HTTPS_PORT ssl http2;
listen [::]:$HTTPS_PORT ssl http2;

server_name $HOSTNAME;

Expand Down
2 changes: 1 addition & 1 deletion conf/zpush/backend_caldav.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

define('CALDAV_PROTOCOL', 'https');
define('CALDAV_SERVER', '127.0.0.1');
define('CALDAV_PORT', '443');
define('CALDAV_PORT', 'HTTPS_PORT');
define('CALDAV_PATH', '/caldav/calendars/%u/');
define('CALDAV_PERSONAL', 'PRINCIPAL');
define('CALDAV_SUPPORTS_SYNC', false);
Expand Down
2 changes: 1 addition & 1 deletion conf/zpush/backend_carddav.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

define('CARDDAV_PROTOCOL', 'https'); /* http or https */
define('CARDDAV_SERVER', '127.0.0.1');
define('CARDDAV_PORT', '443');
define('CARDDAV_PORT', 'HTTPS_PORT');
define('CARDDAV_PATH', '/carddav/addressbooks/%u/');
define('CARDDAV_DEFAULT_PATH', '/carddav/addressbooks/%u/contacts/'); /* subdirectory of the main path */
define('CARDDAV_GAL_PATH', ''); /* readonly, searchable, not syncd */
Expand Down
7 changes: 3 additions & 4 deletions management/dns_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def build_zone(domain,
None))

# Add a DANE TLSA record for HTTPS, which some browser extensions might make use of.
records.append(("_443._tcp", "TLSA", build_tlsa_record(
records.append(("_" + env["HTTPS_PORT"] + "._tcp", "TLSA", build_tlsa_record(
env
), "Optional. When DNSSEC is enabled, provides out-of-band HTTPS certificate validation for a few web clients that support it.",
None))
Expand Down Expand Up @@ -469,7 +469,7 @@ def has_rec(qname, rtype, prefix=None):
if not has_rec(qname, "SRV"):
records.append((
qname, "SRV",
"0 0 443 " + env["PRIMARY_HOSTNAME"] + ".",
"0 0 " + env["HTTPS_PORT"] + " " + env["PRIMARY_HOSTNAME"] + ".",
"Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain.",
None))

Expand Down Expand Up @@ -660,7 +660,6 @@ def build_sshfp_records():

algorithm_number = {
"ssh-rsa": 1,
"ssh-dss": 2,
nameduser0 marked this conversation as resolved.
Show resolved Hide resolved
"ecdsa-sha2-nistp256": 3,
"ssh-ed25519": 4,
}
Expand Down Expand Up @@ -690,7 +689,7 @@ def build_sshfp_records():
return []

keys = shell("check_output", [
"ssh-keyscan", "-t", "rsa,dsa,ecdsa,ed25519", "-p",
"ssh-keyscan", "-t", "rsa,ecdsa,ed25519", "-p",
str(port), "localhost"
])
keys = sorted(keys.split("\n"))
Expand Down
28 changes: 17 additions & 11 deletions management/ssl_certificates.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#!/usr/local/lib/mailinabox/env/bin/python
# Utilities for installing and selecting SSL certificates.

import sys
import os
import os.path
import re
import shutil
import subprocess
import tempfile

from exclusiveprocess import Lock
from utils import shell, safe_domain_name, sort_domains
import idna

Expand Down Expand Up @@ -300,7 +302,7 @@ def provision_certificates(env, limit_domains):
for domain in sort_domains(domains, env):
# Does the domain end with any domain we've seen so far.
for parent in certs.keys():
if domain.endswith("." + parent):
nameduser0 marked this conversation as resolved.
Show resolved Hide resolved
if domain.endswith(parent):
# Add this to the parent's list of domains.
# Start a new group if the list already has
# 100 items.
Expand Down Expand Up @@ -426,15 +428,8 @@ def provision_certificates(env, limit_domains):
return ret


def provision_certificates_cmdline():
import sys
from exclusiveprocess import Lock

from utils import load_environment

Lock(die=True).forever()
env = load_environment()

@Lock(die=True)
def provision_certificates_cmdline(env):
quiet = False
domains = []

Expand Down Expand Up @@ -781,5 +776,16 @@ def idna_decode_dns_name(dns_name):


if __name__ == "__main__":
from utils import load_environment
env = load_environment()

# Provision certificates.
provision_certificates_cmdline()
provision_certificates_cmdline(env)

# Failsafe
args = sys.argv[0:1]
if "-q" in sys.argv:
args.append('-q')
args.append(env['PRIMARY_HOSTNAME'])
sys.argv = args
provision_certificates_cmdline(env)
20 changes: 13 additions & 7 deletions management/status_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from utils import shell, sort_domains, load_env_vars_from_file, load_settings


def get_services():
def get_services(env):
return [
{
"name": "Local DNS (bind9)",
Expand Down Expand Up @@ -116,7 +116,7 @@ def get_services():
},
{
"name": "HTTPS Web (nginx)",
"port": 443,
"port": int(env["HTTPS_PORT"]),
"public": True,
},
]
Expand Down Expand Up @@ -174,9 +174,13 @@ def run_services_checks(env, output, pool):
# Check that system services are running.
all_running = True
fatal = False

if env["PLATFORM_TYPE"] == "LXC":
output.print_warning("The following checks try to connect to each service on your public IP, which may not work on your platform type.")

ret = pool.starmap(check_service,
((i, service, env)
for i, service in enumerate(get_services())),
for i, service in enumerate(get_services(env))),
chunksize=1)
for i, running, fatal2, output2 in sorted(ret):
if output2 is None:
Expand Down Expand Up @@ -228,15 +232,17 @@ def try_connect(ip):

# IPv4 ok but IPv6 failed. Try the PRIVATE_IPV6 address to see if the service is bound to the interface.
elif service["port"] != 53 and try_connect(env["PRIVATE_IPV6"]):
output.print_error("%s is running (and available over IPv4 and the local IPv6 address), but it is not publicly accessible at %s:%d." % (service['name'], env['PUBLIC_IPV6'], service['port']))
output.print_warning(
"%s is running (and available over IPv4 and the local IPv6 address), but it is not publicly accessible at %s:%d."
% (service['name'], env['PUBLIC_IP'], service['port']))
else:
output.print_error(
output.print_warning(
"%s is running and available over IPv4 but is not accessible over IPv6 at %s port %d."
% (service['name'], env['PUBLIC_IPV6'], service['port']))

# IPv4 failed. Try the private IP to see if the service is running but not accessible (except DNS because a different service runs on the private IP).
elif service["port"] != 53 and try_connect("127.0.0.1"):
output.print_error(
output.print_warning(
"%s is running but is not publicly accessible at %s:%d." %
(service['name'], env['PUBLIC_IP'], service['port']))
else:
Expand Down Expand Up @@ -294,7 +300,7 @@ def check_ufw(env, output):
ufw = ufw.splitlines()
if ufw[0] == "Status: active":
not_allowed_ports = 0
for service in get_services():
for service in get_services(env):
if service["public"] and not is_port_allowed(ufw, service["port"]):
not_allowed_ports += 1
output.print_error(
Expand Down
11 changes: 9 additions & 2 deletions management/web_update.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/usr/local/lib/mailinabox/env/bin/python3
# Creates an nginx configuration file so we serve HTTP/HTTPS on all
# domains for which a mail account has been set up.
########################################################################
Expand Down Expand Up @@ -331,10 +332,10 @@ def hashfile(filepath):
# Replace substitution strings in the template & return.
nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
nginx_conf = nginx_conf.replace("$HTTPS_PORT", env['HTTPS_PORT'])
nginx_conf = nginx_conf.replace("$ROOT", root)
nginx_conf = nginx_conf.replace("$SSL_KEY", tls_cert["private-key"])
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE",
tls_cert["certificate"])
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", tls_cert["certificate"])
nginx_conf = nginx_conf.replace(
"$REDIRECT_DOMAIN",
re.sub(r"^www\.", "",
Expand Down Expand Up @@ -397,3 +398,9 @@ def check_cert(domain):
"static_enabled":
domain not in (www_redirects | has_root_proxy_or_redirect),
} for domain in get_web_domains(env)]

if __name__ == '__main__':
from web_update import do_web_update
from utils import load_environment
env = load_environment()
print(do_web_update(env))
Empty file modified setup/bootstrap.sh
100644 → 100755
Empty file.
Empty file modified setup/firstuser.sh
100644 → 100755
Empty file.
18 changes: 17 additions & 1 deletion setup/functions.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ function apt_install {
apt_get_quiet install "$@"
}

function apt_remove {
# Remove unnecessary packages
DEBIAN_FRONTEND=noninteractive hide_output apt-get -y purge "$@"
}

function get_default_hostname {
# Guess the machine's hostname. It should be a fully qualified
# domain name suitable for DNS. None of these calls may provide
Expand Down Expand Up @@ -172,15 +177,26 @@ function input_menu {
# input_menu "title" "prompt" "tag item tag item" VARIABLE
# The user's input will be stored in the variable VARIABLE.
# The exit code from dialog will be stored in VARIABLE_EXITCODE.
eval menu_opts=($3)
declare -n result=$4
declare -n result_code=$4_EXITCODE
local IFS=^$'\n'
set +e
result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 $3)
result=$(dialog --stdout --title "$1" --menu "$2" 0 0 0 ${menu_opts[@]})
result_code=$?
set -e
}

function input_yesno {
# input_yesno "title" "prompt" VARIABLE
# If the user enters yes the variable will be set, otherwise not set
declare -n result=$3
set +e
dialog --title "$1" --yesno "$2" 0 0
result=$?
set -e
}

function wget_verify {
# Downloads a file from the web and checks that it matches
# a provided hash. If the comparison fails, exit immediately.
Expand Down
6 changes: 6 additions & 0 deletions setup/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ def migration_15(env):
db = os.path.join(env["STORAGE_ROOT"], 'mail/users.sqlite')
shell("check_call", ["sqlite3", db, "CREATE TABLE auto_aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL UNIQUE, destination TEXT NOT NULL, permitted_senders TEXT);"])

def migration_16(env):
# Add in extra environment parameters from LXC support
with open('/etc/mailinabox.conf', 'a') as fd:
fd.write('PLATFORM_TYPE=HW\n')
fd.write('HTTPS_PORT=443\n')

###########################################################

def get_current_migration():
Expand Down
Empty file modified setup/network-checks.sh
100644 → 100755
Empty file.
Loading