diff --git a/manage.py b/manage.py index 18540d8..14db16f 100755 --- a/manage.py +++ b/manage.py @@ -14,15 +14,14 @@ def backupProj(project): def getImageId (image): - return subprocess.run(["podman", "image", "inspect", "--format", "{{.Id}}", image], capture_output=True, text=True).stdout.strip() + return runPodman("image", ["inspect", "--format", "{{.Id}}", image]).stdout.strip() def getUid(service): if service in env['users'].keys(): - user = env['users'][service] + env['uid_shift'] + return env['users'][service] + env['uid_shift'] else: - user = 1000 - return user + return env['podman_uid'] def pullProj(project): @@ -34,7 +33,7 @@ def pullProj(project): pulledImages = [] for image in images: currentId = getImageId(image) - subprocess.run(["podman", "pull", image]) + runPodman("pull", image) pulledId = getImageId(image) if currentId != pulledId: pulledImages += image @@ -57,17 +56,50 @@ def renderFile(templateFile): outputFile.close() +def runPodman(cmd, args): + if isinstance(args, str): + args = args.split(' ') + + if cmd == 'compose': + runArgs = ["podman-compose"] + args + else: + runArgs = ["podman", cmd] + args + + if env['rootless']: + return subprocess.run(runArgs, capture_output=True, text=True) + else: + return runSudo(runArgs) + + +def runSudo(args): + if isinstance(args, str): + args = args.split(' ') + + 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()): + child.communicate(input=input().encode())[0] + + child.wait() + + stderr = child.stderr.read().strip() + if stderr != '': + print(stderr, file=sys.stderr) + + return child + + def setCertPerms(service): - for path in ["/etc/letsencrypt", "/etc/letsencrypt/live", "/etc/letsencrypt/archive"]: - setOwner(path, 0, 0) - setPerms(path, 751) + dirs = ["/etc/letsencrypt", "/etc/letsencrypt/live", "/etc/letsencrypt/archive"] pkeyFile = env['certs'][service]['pkey'] - + domain_dir = re.search('.+(?=\\/.+$)', pkeyFile).group(0) - for path in [domain_dir, re.sub('live', 'archive', domain_dir)]: - setOwner(path, env['host_uid'], getUid(service)) - setPerms(path, 550) + dirs += [domain_dir, re.sub('live', 'archive', domain_dir)] + + for path in dirs: + setOwner(path, 0, env['podman_uid']) + setPerms(path, 711) setOwner(pkeyFile, 0, getUid(service)) setPerms(pkeyFile, 640) @@ -77,8 +109,8 @@ def setNftables(): renderFile("nftables.conf.mako") if not filecmp.cmp("nftables.conf.rendered", "/etc/nftables.conf"): print("nftables.conf changed, copying new version.") - sudoRun(["cp", "nftables.conf.rendered", "/etc/nftables.conf"]) - sudoRun(["systemctl", "restart", "nftables"]) + runSudo(["cp", "nftables.conf.rendered", "/etc/nftables.conf"]) + runSudo(["systemctl", "restart", "nftables"]) else: print("nftables.conf unchanged.") @@ -92,7 +124,7 @@ def setOwner(path, uid=None, gid=None): if stat.st_uid != uid or stat.st_gid != gid: print(f"Changing ownership of {path} to {uid}:{gid} from {stat.st_uid}:{stat.st_gid}.") - sudoRun(["chown", f"{uid}:{gid}", path]) + runSudo(["chown", f"{uid}:{gid}", path]) else: print(f"Ownership of {path} already set to {uid}:{gid}.") @@ -103,10 +135,10 @@ def setPerms(path, mode): curMode = oct(stat.st_mode)[-3:] if mode != curMode: print(f"Changing permissions of {path} to {mode} from {curMode}.") - if stat.st_uid == env['host_uid']: + if stat.st_uid == os.getuid(): subprocess.run(["chmod", mode, path]) else: - subprocess.run(["sudo", "chmod", mode, path]) + runSudo(["chmod", mode, path]) else: print(f"Permissions of {path} already set to {mode}.") @@ -116,6 +148,15 @@ def setupProj(project): backupProj(project) + if project == 'diun': + if not os.path.isfile(env['socket']): + if env['rootless']: + subprocess.run(["systemctl", "--user", "restart", "podman.socket"]) + else: + runSudo("systemctl restart podman.socket") + setPerms(env['socket'], 640) + setOwner(env['socket'], env['podman_uid'], env['users']['diun']) + if project in env['certs'].keys(): setCertPerms(project) @@ -123,22 +164,18 @@ def setupProj(project): renderFile(templateFile) renderedFilename = re.sub('\\.mako$', '.rendered', templateFile) setPerms(renderedFilename, 640) - setOwner(renderedFilename, env['host_uid'], getUid(project)) + setOwner(renderedFilename, os.getuid(), getUid(project)) upProj(project) -def sudoRun(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()): - child.communicate(input=input().encode())[0] - - def upProj(project): + if runPodman("container", ["exists", project]).returncode == 0: + print(f"Tearing down stack for project {project}.") + runPodman("compose", ["-f", f"projects/{project}/compose.yaml.rendered", "down"]) + print(f"Creating & starting stack for project {project}.") - if subprocess.run(["podman", "container", "exists", project]).returncode == 0: - subprocess.run(["podman-compose", "-f", f"projects/{project}/compose.yaml.rendered", "down"]) - subprocess.run(["podman-compose", "-f", f"projects/{project}/compose.yaml.rendered", "up", "-d"]) + runPodman("compose", ["-f", f"projects/{project}/compose.yaml.rendered", "up", "-d"]) def updateProj(project): @@ -160,13 +197,14 @@ def main(): with open(envFile, 'r') as envfile, open(secretsFile, 'r') as secretsfile: global env, secrets - env = yaml.safe_load(envfile) - env = yaml.safe_load(Template(filename=envFile).render(env=env)) + env = yaml.safe_load(Template(filename=envFile).render()) secrets = yaml.safe_load(secretsfile) - - setOwner(secretsFile, env['host_uid'], env['host_uid']) + + setOwner(secretsFile, os.getuid(), os.getgid()) setPerms(secretsFile, 600) + print("\nUsing socket " + env['socket'] + ".") + print("\nChoose action:") print("[1/S] Setup project") print("[2/U] Update project") diff --git a/projects/coturn/compose.yaml.mako b/projects/coturn/compose.yaml.mako index 2ec15fe..485d04b 100644 --- a/projects/coturn/compose.yaml.mako +++ b/projects/coturn/compose.yaml.mako @@ -2,7 +2,6 @@ services: coturn: container_name: coturn image: docker.io/coturn/coturn:4-alpine - network_mode: pasta restart: always user: ${env['users']['coturn']}:${env['users']['coturn']} ports: @@ -18,3 +17,4 @@ services: - ${env['certs']['coturn']['cert']}:/etc/coturn/cert.pem:ro - ${env['certs']['coturn']['pkey']}:/etc/coturn/pkey.pem:ro +${env['networks_attr']} diff --git a/projects/diun/compose.yaml.mako b/projects/diun/compose.yaml.mako index f2ea99f..fe27375 100644 --- a/projects/diun/compose.yaml.mako +++ b/projects/diun/compose.yaml.mako @@ -2,8 +2,8 @@ services: diun: container_name: diun image: docker.io/crazymax/diun:4 - network_mode: pasta restart: always + user: ${env['users']['diun']}:${env['users']['diun']} command: serve env_file: .env.rendered volumes: @@ -11,5 +11,7 @@ services: - ./images.yml:/etc/diun/images.yml:ro - data:/data +${env['networks_attr']} + volumes: data: diff --git a/projects/homepage/compose.yaml.mako b/projects/homepage/compose.yaml.mako index a694ee4..0d8167c 100644 --- a/projects/homepage/compose.yaml.mako +++ b/projects/homepage/compose.yaml.mako @@ -2,7 +2,6 @@ services: homepage: container_name: homepage image: git.ahur.ac/viyurz/homepage:latest - network_mode: pasta restart: always user: ${env['users']['homepage']}:${env['users']['homepage']} ports: diff --git a/pyenv.yml b/pyenv.yml index fe8f70d..40c1640 100644 --- a/pyenv.yml +++ b/pyenv.yml @@ -1,10 +1,23 @@ domain: viyurz.fr timezone: "Europe/Paris" -host_uid: 1000 -socket: "/run/user/${env['host_uid']}/podman/podman.sock" -# UID shift for mapping between host & containers -uid_shift: 99999 +<%! + import os, subprocess + + uid = os.getuid() + rootless = os.path.exists(f"/run/user/{uid}/podman/podman.sock") +%> +% if rootless: +rootless: true +podman_uid: ${uid} +uid_shift: ${int(subprocess.run(['sh', '-c', "grep " + os.getlogin() + " /etc/subuid | cut -d ':' -f 2"], capture_output=True, text=True).stdout.strip()) - 1} +socket: "/run/user/${uid}/podman/podman.sock" +% else: +rootless: false +podman_uid: 0 +uid_shift: 0 +socket: "/run/podman/podman.sock" +% endif # cifs_credentials is undefined when we run the backup playbook @@ -43,17 +56,23 @@ cifs_mounts: dir_mode: 750 -borg_repodir: "${env['cifs_mounts']['backups']['path']}/borg" +borg_repodir: "{env['cifs_mounts']['backups']['path']}/borg" borg_passphrase_file: /etc/borg-passphrase.txt certs: coturn: - cert: "/etc/letsencrypt/live/turn.${env['domain']}/fullchain.pem" - pkey: "/etc/letsencrypt/live/turn.${env['domain']}/privkey.pem" + cert: "/etc/letsencrypt/live/turn.viyurz.fr/fullchain.pem" + pkey: "/etc/letsencrypt/live/turn.viyurz.fr/privkey.pem" mailserver: - cert: "/etc/letsencrypt/live/mail.${env['domain']}/fullchain.pem" - pkey: "/etc/letsencrypt/live/mail.${env['domain']}/privkey.pem" + cert: "/etc/letsencrypt/live/mail.viyurz.fr/fullchain.pem" + pkey: "/etc/letsencrypt/live/mail.viyurz.fr/privkey.pem" + + +networks_attr: | + networks: + default: + enable_ipv6: true # Ports exposed to host @@ -89,6 +108,7 @@ ports: # UID in containers users: coturn: 666 + diun: 1011 etebase: 373 fireshare: 1007 hedgedoc: 1004 @@ -118,6 +138,6 @@ volumes: stump_configdir: /mnt/stump/config stump_datadir: /mnt/stump/data synapse_datadir: /mnt/synapsedata - syncthing_datadir: "${env['cifs_mounts']['syncthing']['path']}" + syncthing_datadir: "{env['cifs_mounts']['syncthing']['path']}" uptime_kuma_datadir: /mnt/uptimekumadata vaultwarden_datadir: /mnt/vwdata diff --git a/setup.sh b/setup.sh index 17789a8..18bcef5 100755 --- a/setup.sh +++ b/setup.sh @@ -6,15 +6,27 @@ if [[ $(whoami) == "root" ]]; then fi -sudo apt install -y aardvark-dns dbus-user-session nftables passt podman podman-compose python3-mako uidmap - - -for unit in podman.service podman.socket podman-auto-update.service podman-auto-update.timer podman-clean-transient.service podman-restart.service; do - sudo systemctl disable --now "$unit" +while ! [[ "$podman_mode" =~ ^(rootful|rootless)$ ]]; do + read -rp "Rootful or rootless Podman? " podman_mode done -sudo loginctl enable-linger $USER +declare -a podman_units=(podman.service podman.socket podman-auto-update.service podman-auto-update.timer podman-clean-transient.service podman-restart.service) + + +if [[ "$podman_mode" == "rootless" ]]; then + sudo apt install -y aardvark-dns dbus-user-session nftables passt podman podman-compose python3-mako 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 nftables podman podman-compose python3-mako + + systemctl --user disable --now "${podman_units[@]}" + sudo systemctl enable --now "${podman_units[@]}" +fi declare -A sysctl_vars=( @@ -27,12 +39,8 @@ echo -n "" | sudo tee /etc/sysctl.d/podman.conf for key in "${!sysctl_vars[@]}"; do value="${sysctl_vars[$key]}" echo "$key = $value" | sudo tee -a /etc/sysctl.d/podman.conf - sudo sysctl -p done +sudo sysctl -p -for unit in podman.service podman.socket podman-restart.service; do - systemctl --user enable --now "$unit" -done - sudo systemctl enable --now nftables