#!/bin/bash

# AppArmor profile shim for the rootless Docker + pasta combo.
#
# Debian's stock `passt` profile (shipped by the `passt` package, used for the
# pasta network driver) is enforce-mode and denies the accesses pasta needs to
# wire up rootlesskit's net namespace:
#
#   - ptrace(read) into the rootlesskit child to enter its user namespace
#   - read /run/user/<uid>/dockerd-rootless/netns
#   - read /proc/<pid>/net/{tcp,tcp6,udp,udp6} for implicit port-forwarding
#
# Without these the daemon fails to start with
#   error: failed to setup network: pasta failed with exit code 1:
#   Couldn't open user namespace /proc/<pid>/ns/user: Permission denied
#
# We extend the profile via the standard local-override pattern:
#   /etc/apparmor.d/local/usr.bin.passt          — our rules
#   /etc/apparmor.d/usr.bin.passt                — include if exists <local/...>
#
# Idempotent — safe to call on every rootless install. The local file survives
# `apt upgrade passt` (it's outside the package's managed paths). The one-line
# include statement could theoretically get reverted by an apt upgrade; we
# re-add it on every run as a belt-and-braces guard.

installRootlessApparmorForPasta()
{
    # Bail silently when apparmor isn't on this system at all — Fedora/RHEL,
    # ungrouped Debian minimals, etc. Nothing to patch.
    if [[ ! -d /etc/apparmor.d ]]; then
        return 0
    fi
    if [[ ! -f /etc/apparmor.d/usr.bin.passt ]]; then
        isNotice "AppArmor present but no /etc/apparmor.d/usr.bin.passt — passt may be too old or not installed under apparmor."
        return 0
    fi

    isHeader "AppArmor: enabling pasta for rootless Docker"

    # 1. Add `include if exists <local/usr.bin.passt>` to the main profile if
    # it isn't already there. The line lives just before the closing `}` of
    # the profile block.
    if ! sudo grep -q "include if exists <local/usr.bin.passt>" /etc/apparmor.d/usr.bin.passt; then
        # Insert right before the final `}` of the file. We don't try to be
        # surgical about which `}` — passt's profile has exactly one top-level
        # close, and any nested `{` `}` (e.g. inside abstractions) are
        # behind `include` lines, not literal in this file.
        sudo awk '
            /^}/ && !done { print "  include if exists <local/usr.bin.passt>"; done=1 }
            { print }
        ' /etc/apparmor.d/usr.bin.passt | sudo tee /etc/apparmor.d/usr.bin.passt.new > /dev/null
        sudo mv /etc/apparmor.d/usr.bin.passt.new /etc/apparmor.d/usr.bin.passt
        checkSuccess "Added include-local line to /etc/apparmor.d/usr.bin.passt"
    else
        isSuccessful "Main passt profile already sources /etc/apparmor.d/local/usr.bin.passt"
    fi

    # 2. Write (or overwrite — same content every time) the local rules file.
    sudo mkdir -p /etc/apparmor.d/local
    sudo tee /etc/apparmor.d/local/usr.bin.passt > /dev/null <<'EOF'
# Managed by LibrePortal (installRootlessApparmorForPasta). Edits here are
# fine but will be overwritten on the next rootless reinstall — put your
# customisations in a sibling file (e.g. local/usr.bin.passt.local) and
# include them yourself if you need to extend this further.
#
# These rules permit rootless Docker's pasta network driver to:
#   - ptrace_read the rootlesskit child to enter its user namespace
#   - read the netns file rootlesskit writes
#   - read per-process socket tables for implicit port-forwarding

  ptrace (read) peer=unconfined,
  /run/user/[0-9]*/dockerd-rootless/netns r,
  /proc/*/net/tcp r,
  /proc/*/net/tcp6 r,
  /proc/*/net/udp r,
  /proc/*/net/udp6 r,
EOF
    checkSuccess "Wrote /etc/apparmor.d/local/usr.bin.passt"

    # 3. Reload the profile. apparmor_parser -r updates an in-kernel profile
    # in place, so already-running pasta processes see the new rules on the
    # next syscall — no daemon restart strictly needed, but the caller
    # restarts docker anyway when override.conf changes.
    if sudo apparmor_parser -r /etc/apparmor.d/usr.bin.passt 2>/dev/null; then
        isSuccessful "Reloaded passt AppArmor profile"
    else
        isError "apparmor_parser -r failed — passt may still deny pasta. Check /var/log/syslog or journalctl for the error."
        return 1
    fi
}
