#!/bin/sh
# perlistream-server grant FOLDER [--write]
#     Let the PerliStream Server service read (or, with --write, also write)
#     FOLDER and everything under it. Uses POSIX ACLs, so the folder's owner
#     and permissions are unchanged, and files added later are covered too.
#     The folders above FOLDER only get "traverse" (x) for the service user,
#     which lets it pass through but not list them.
#
# perlistream-server grant --revoke FOLDER
#     Undo a grant.
#
# perlistream-server run-as USER
#     Run the service as USER instead of the "perlistream" system user. For
#     drives that cannot hold ACLs (NTFS, exFAT, FAT) mounted as your user.
#     The data directory is handed to USER.
# perlistream-server run-as --default
#     Back to the "perlistream" user.
#
# Must run as root (sudo).
set -eu

UNIT=perlistream-server.service
DROPIN_DIR=/etc/systemd/system/$UNIT.d
GRANT_CONF=$DROPIN_DIR/perlistream-grant.conf
RUNAS_CONF=$DROPIN_DIR/perlistream-run-as.conf
DATA_DIR="${PERLISTREAM_DATA_DIR:-/var/lib/perlistream}"

die() { echo "perlistream-server: $*" >&2; exit 1; }
say() { echo "perlistream-server: $*"; }

[ "$(id -u)" -eq 0 ] || die "this needs root: sudo perlistream-server $*"

have_systemd() { [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; }

svc_user() {
    _u=""
    if have_systemd; then
        _u="$(systemctl show -p User --value "$UNIT" 2>/dev/null || true)"
    fi
    echo "${_u:-perlistream}"
}

as_svc() {  # run a test as the service user
    if command -v runuser >/dev/null 2>&1; then
        runuser -u "$SVC_USER" -- "$@"
    else
        setpriv --reuid="$SVC_USER" --regid="$(id -g "$SVC_USER")" --init-groups "$@"
    fi
}

restart_if_running() {
    if have_systemd; then
        systemctl daemon-reload
        if systemctl is-active --quiet "$UNIT"; then
            say "restarting the service"
            systemctl restart "$UNIT"
        fi
    fi
}

fs_type() { stat -f -c %T "$1" 2>/dev/null || echo unknown; }

no_acl_fs() {
    case "$(fs_type "$1")" in
        msdos|vfat|exfat|fuseblk|ntfs|ntfs3|fuse*|smb2|cifs|nfs|nfs4|9p) return 0 ;;
    esac
    return 1
}

cmd_grant() {
    WRITE=0; REVOKE=0; TARGET=""
    while [ $# -gt 0 ]; do
        case "$1" in
            --write|-w) WRITE=1 ;;
            --revoke) REVOKE=1 ;;
            --) shift; [ $# -gt 0 ] && TARGET="$1"; break ;;
            -*) die "unknown option $1" ;;
            *) TARGET="$1" ;;
        esac
        shift
    done
    [ -n "$TARGET" ] || die "usage: perlistream-server grant FOLDER [--write]"
    [ -d "$TARGET" ] || die "not a folder: $TARGET"
    TARGET="$(readlink -f -- "$TARGET")"
    case "$TARGET" in
        /|/etc|/etc/*|/root|/root/*|/boot|/boot/*|/proc|/proc/*|/sys|/sys/*|/dev|/dev/*|/usr|/bin|/sbin|/lib|/lib64|/var/lib/perlistream|/var/lib/perlistream/*)
            die "refusing to grant $TARGET" ;;
    esac
    command -v setfacl >/dev/null 2>&1 || die "setfacl not found — install the 'acl' package"
    SVC_USER="$(svc_user)"
    id "$SVC_USER" >/dev/null 2>&1 || die "service user $SVC_USER does not exist"

    if [ "$REVOKE" = 1 ]; then
        setfacl -R -x "u:$SVC_USER" -- "$TARGET" 2>/dev/null || true
        setfacl -R -d -x "u:$SVC_USER" -- "$TARGET" 2>/dev/null || true
        if [ -f "$GRANT_CONF" ]; then
            _esc="$(printf '%s' "$TARGET" | sed 's/[][\\.*^$/]/\\&/g')"
            sed -i "\\|^ReadWritePaths=-$_esc\$|d" "$GRANT_CONF"
            grep -q '^ReadWritePaths=' "$GRANT_CONF" || rm -f "$GRANT_CONF"
            restart_if_running
        fi
        say "removed $SVC_USER's access to $TARGET"
        return 0
    fi

    if no_acl_fs "$TARGET"; then
        _owner="$(stat -c %U -- "$TARGET")"
        die "$TARGET is on a $(fs_type "$TARGET") drive, which has no per-user permissions.
  Either mount it so the server can read it (e.g. add uid=$SVC_USER or umask=022 to its mount options),
  or run the server as the user who owns the drive:
      sudo perlistream-server run-as $_owner"
    fi

    # Traverse: every folder above TARGET the service user cannot pass through.
    _d="$(dirname -- "$TARGET")"
    while [ "$_d" != "/" ]; do
        if ! as_svc test -x "$_d" 2>/dev/null; then
            setfacl -m "u:$SVC_USER:x" -- "$_d" || die "could not open $_d for $SVC_USER"
            say "  + pass-through on $_d"
        fi
        _d="$(dirname -- "$_d")"
    done

    if [ "$WRITE" = 1 ]; then PERM=rwX; else PERM=rX; fi
    setfacl -R -m "u:$SVC_USER:$PERM" -- "$TARGET" || die "setfacl failed on $TARGET (does this filesystem support ACLs?)"
    # Default ACL on folders: files added later inherit the grant.
    find "$TARGET" -type d -exec setfacl -d -m "u:$SVC_USER:$PERM" -- {} + \
        || die "setfacl (default) failed on $TARGET"

    if [ "$WRITE" = 1 ] && have_systemd; then
        # ProtectSystem=strict makes everything outside ReadWritePaths
        # read-only for the service, whatever the file permissions say.
        mkdir -p "$DROPIN_DIR"
        if [ ! -f "$GRANT_CONF" ]; then
            printf '# Written by perlistream-server grant --write\n[Service]\n' > "$GRANT_CONF"
        fi
        if ! grep -qxF "ReadWritePaths=-$TARGET" "$GRANT_CONF"; then
            printf 'ReadWritePaths=-%s\n' "$TARGET" >> "$GRANT_CONF"
        fi
        restart_if_running
    fi

    if as_svc test -r "$TARGET" && as_svc test -x "$TARGET"; then
        if [ "$WRITE" = 1 ]; then
            say "$SVC_USER can now read and write $TARGET"
        else
            say "$SVC_USER can now read $TARGET"
        fi
        say "go back to the setup page and press Try again"
    else
        die "granted, but $SVC_USER still cannot open $TARGET (check the mount options)"
    fi
}

cmd_run_as() {
    [ $# -ge 1 ] || die "usage: perlistream-server run-as USER|--default"
    have_systemd || die "run-as needs systemd"
    if [ "$1" = "--default" ]; then
        rm -f "$RUNAS_CONF"
        NEW_USER=perlistream
    else
        NEW_USER="$1"
        id "$NEW_USER" >/dev/null 2>&1 || die "no such user: $NEW_USER"
        [ "$(id -u "$NEW_USER")" -ne 0 ] || die "refusing to run the server as root"
        mkdir -p "$DROPIN_DIR"
        {
            echo "# Written by perlistream-server run-as $NEW_USER"
            echo "[Service]"
            echo "User=$NEW_USER"
            echo "Group=$(id -gn "$NEW_USER")"
            # ProtectHome=read-only would hide nothing from this user but keeps
            # $HOME read-only; the data dir stays in /var/lib.
        } > "$RUNAS_CONF"
    fi
    if [ -d "$DATA_DIR" ]; then
        chown -R "$NEW_USER:$(id -gn "$NEW_USER")" "$DATA_DIR"
    fi
    systemctl daemon-reload
    systemctl restart "$UNIT"
    say "the service now runs as $NEW_USER"
}

case "${1:-}" in
    grant) shift; cmd_grant "$@" ;;
    run-as) shift; cmd_run_as "$@" ;;
    *) die "usage: perlistream-server grant FOLDER [--write] | grant --revoke FOLDER | run-as USER|--default" ;;
esac
