You are not logged in.

#1 2026-03-12 10:24:49

7thCore
Member
Registered: 2018-06-09
Posts: 67

PKGBUILD Review: psono-ee-combo

Hello,

I have created packages for my personal use for maybe two years now but never submitted them to the AUR. Now I would like to submit two of them (not yet submitted). I've got permission from the developer of Psono to add his software to the AUR under the condition that it needs to include a disclaimer that the package is not officially supported and the developer is not responsible for any issues with it. I've read the Arch Linux package guidelines cover to cover and also made both packages REUSE compliant. Both PKGBUILD files are almost identical (apart from the software edition) so i will show only one here. The PKGBUILD downloads a docker image from docker hub and extracts the needed files from it for use. The docker fetch  function was downloaded from GitHub. This is mentioned and linked in a commented out line in the PKGBUILD. I do have a few questions regarding the Python environment it uses at the bottom of the post.

Contents of PKGBUILD:

# Maintainer: 7thCore <redacted-for-now>
# Disclaimer: This AUR package is maintained by the community and is not
# officially supported by the software developers or the package maintainer.
# Use this package at your own risk. The developers of the original software
# and the package maintainer are not responsible for any issues, bugs, or
# damages resulting from its use. For installation or usage questions, please
# do not contact the original developers. Instead, seek assistance from the
# package maintainer, the Arch Linux community or relevant forums.

pkgname=psono-ee-combo
pkgver=1.0
pkgrel=1
pkgdesc='Enterprise Edition of the open source and self hosted password manager. Includes the server, file server, web client and web admin portal.'
arch=('x86_64' 'aarch64')
url='https://psono.com'
license=('Apache-2.0')
depends=('bash'
        'coreutils'
        'gcc'
        'git'
        'glibc'
        'haveged'
        'libb2'
        'libffi'
        'libldap'
        'libpqxx'
        'libsasl'
        'libyaml'
        'nginx'
        'openssl'
        'postgresql'
        'python=3.14'
        'util-linux')
optdepends=('logrotate: rotates system logs automatically'
            'fail2ban: bans IPs after too many failed authentication attempts')
makedepends=('curl'
            'jq'
            'patchelf'
            'sed'
            'tar'
            'wget')
provides=('psono-server'
          'psono-fileserver'
          'psono-web-client'
          'psono-admin-client')
conflicts=('psono-server'
          'psono-fileserver'
          'psono-web-client'
          'psono-admin-client')
backup=('etc/fail2ban/filter.d/psono-ee.conf'
        'etc/fail2ban/jail.d/psono-ee.local'
        'etc/logrotate.d/psono'
        'etc/nginx/sites-available/psono-ee-combo.conf'
        'usr/share/webapps/psono-webclient/config.json'
        'usr/share/webapps/psono-webclient/portal/config.json'
        'var/lib/psono/.psono_fileserver/settings.yaml'
        'var/lib/psono/.psono_server/settings.yaml')
options=('!strip' '!debug')
install='psono-script.install'
source=('conf_psono.logrotate'
        'conf_psono-fail2ban-filter.conf'
        'conf_psono-fail2ban-jail.local'
        'conf_psono-fileserver-settings.yaml'
        'conf_psono-nginx.conf'
        'conf_psono-python-pip.conf'
        'conf_psono-server-settings.yaml'
        'pacman_05-psono-ee-pre.hook'
        'pacman_98-psono-ee-glibc.hook'
        'pacman_99-psono-ee-post.hook'
        'psono-ee-server-requirements.patch'
        'psono-script.install'
        'script_psono-ee-pacman-hooks.sh'
        'systemd_psono-fileserver-cleanup.service'
        'systemd_psono-fileserver-cleanup.timer'
        'systemd_psono-fileserver-ping.service'
        'systemd_psono-fileserver-ping.timer'
        'systemd_psono-fileserver.service'
        'systemd_psono-server-ee-cleartoken.service'
        'systemd_psono-server-ee-cleartoken.timer'
        'systemd_psono-server-ee.service')
sha256sums=('bd391b1824d182c3d5813ca71535d5e47c2404883f1bc58f76b677fde3d6e70f'
            '66ac600efad0aa1af7ddfeca5452f369ac3fe8f0e8c9ad90ae1bf73be18460bc'
            '5d422995f77d17c65d7827e0af27afb0ba6500e91e17f66fcb758aa322d8108c'
            '7d658724229b49a4e69e45e0a57b536a42ec02ad1c3343539b21f8fa505c1002'
            '2f815769b7dd5c672b491138e59e1709ea30676d66c7562ad54c7c2ce14dc55c'
            '45d4604c640b220af6625dbfa6b9fa939ebb00ae2d24bf0e9e1ce8643087a5cc'
            '6316939b8c5a36b1b3c379a50d5a86a2d4c153a4e38ec7b2b2e18da7ad7db34d'
            'b3cdc9d315b628f0970459a4af08bbe4b5c08e1b8a0dff8da7f5a0c6875c76f9'
            '5f461e6094fd241057c1cea99b1b432a0a197316efa2f7ab4a87969ec20c24d3'
            'd9f9810503879afd9774e399d1efe6d1f3dfe3aaa017f0ce16010ff8549a858d'
            '4a171e8c0ee529bad90ad37e6633f8dc7cfe40e02fb4cf24f6e95f8084b4584a'
            'f3a91cff4234ddfb5c2aa3c16626c6b0315e186ccc3453d72fd44b6188762d76'
            'b8f1149d028e30f43ce7db4942015084262965dc21d23cb47111da02b86a80d4'
            'af7985b5a1ebf8664888959f8dd9f4d24ada00702548e4e07253eacddacc8403'
            'ac7d656c02632ca81fb3683a65764b076e25a5ae39bdd2a4ef9d5827b984e1fe'
            '4009d186b42e1c79bebf340899a649a15f2019945dca1d81bcd8ec9455852e97'
            'e013719b0a8fd62e4c1d0fc3e83fc1903fc7189205bb58899b7e5fd4a7ae6b26'
            'e61be50b496a39b12e40f66bb665eaaff9d3edd3c09db6f39c6116527e3049f7'
            'dcd96456d173063200f2ef0a62364b89fb799228870f566c6c84da9d368f5e5b'
            'ebc9d9ad66ff7cfdbf4698b2ce24392b1a52ff6a0294f72288e14be1ede6219b'
            '9f7ff36e2dc7db569d46a84b1232737b5a3ed77190fb01f524a478941b5d36c5')

pkgver() {
  # The version number is combined by the developer on docker. First numbers are the server version,
  # second are the client portal version and third are the admin portal version. To keep up with this
  # version form, add the file server version to the version number.
  PSONO_EE_COMBO_VERSION=$(curl -s "https://hub.docker.com/v2/repositories/psono/psono-combo-enterprise/tags?page_size=1" | jq -r '.results[0].name' | sed 's/-/_/g')
  PSONO_FILESERVER_VERSION=$(curl -s "https://hub.docker.com/v2/repositories/psono/psono-fileserver/tags?page_size=1" | jq -r '.results[0].name')
  printf "$PSONO_EE_COMBO_VERSION"_"$PSONO_FILESERVER_VERSION"
}

prepare() {
  # Docker fetch function modified from https://github.com/jjlin/docker-image-extract
  docker_fetch() {
    PLATFORM="$2"
    OUT_DIR="${srcdir}/psono-src-docker"

    have_curl() {
        command -v curl >/dev/null
    }

    have_wget() {
        command -v wget >/dev/null
    }

    image_spec="$1"
    image="${image_spec%%:*}"
    if [ "${image#*/}" = "${image}" ]; then
        # Docker official images are in the 'library' namespace.
        image="library/${image}"
    fi
    ref="${image_spec#*:}"

    # Split platform (OS/arch/variant) into separate variables.
    # A platform specifier doesn't always include the `variant` component.
    OLD_IFS="${IFS}"
    IFS=/ read -r OS ARCH VARIANT <<-EOF
    ${PLATFORM}
    EOF
    IFS="${OLD_IFS}"

    # Given a JSON input on stdin, extract the string value associated with the
    # specified key. This avoids an extra dependency on a tool like `jq`.
    extract() {
        local key="$1"
        # Extract "<key>":"<val>" (assumes key/val won't contain double quotes).
        # The colon may have whitespace on either side.
        grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]\+\"" |
        # Extract just <val> by deleting the last '"', and then greedily deleting
        # everything up to '"'.
        sed -e 's/"$//' -e 's/.*"//'
    }

    # Fetch a URL to stdout. Up to two header arguments may be specified:
    #
    #   fetch <url> [name1: value1] [name2: value2]
    #
    fetch() {
        if have_curl; then
            if [ $# -eq 2 ]; then
                set -- -H "$2" "$1"
            elif [ $# -eq 3 ]; then
                set -- -H "$2" -H "$3" "$1"
            fi
            curl -sSL "$@"
        else
            if [ $# -eq 2 ]; then
                set -- --header "$2" "$1"
            elif [ $# -eq 3 ]; then
                set -- --header "$2" --header "$3" "$1"
            fi
            wget -qO- "$@"
        fi
    }

    # https://docs.docker.com/docker-hub/api/latest/#tag/repositories
    manifest_list_url="https://hub.docker.com/v2/repositories/${image}/tags/${ref}"

    # If the ref is already a SHA-256 image digest, then we don't need to look up anything.
    if [ -z "${ref##sha256:*}" ]; then
        digest="${ref}"
    else
        echo "Getting multi-arch manifest list..."
        NL='
    '
        digest=$(fetch "${manifest_list_url}" |
            # Break up the single-line JSON output into separate lines by adding
            # newlines before and after the chars '[', ']', '{', and '}'.
            # This uses the \${NL} syntax because some BSD variants of sed don't
            # support \n syntax in the replacement string, but instead require
            # a literal newline preceded by a backslash.
            sed -e 's/\([][{}]\)/\'"${NL}"'\1\'"${NL}"'/g' |
            # Extract the "images":[...] list.
            sed -n '/"images":/,/]/ p' |
            # Each image's details are now on a separate line, e.g.
            # "architecture":"arm64","features":"","variant":"v8","digest":"sha256:054c85801c4cb41511b176eb0bf13a2c4bbd41611ddd70594ec3315e88813524","os":"linux","os_features":"","os_version":null,"size":828724,"status":"active","last_pulled":"2022-09-02T22:46:48.240632Z","last_pushed":"2022-09-02T00:42:45.69226Z"
            # The image details are interspersed with lines of stray punctuation,
            # so grep for an arbitrary string that must be in these lines.
            grep architecture |
            # Search for an image that matches the platform.
            while read -r image; do
                # Arch is probably most likely to be unique, so check that first.
                arch="$(echo ${image} | extract 'architecture')"
                if [ "${arch}" != "${ARCH}" ]; then continue; fi

                os="$(echo ${image} | extract 'os')"
                if [ "${os}" != "${OS}" ]; then continue; fi

                variant="$(echo ${image} | extract 'variant')"
                if [ "${variant}" = "${VARIANT}" ]; then
                    echo ${image} | extract 'digest'
                    break
                fi
            done)

        if [ -n "${digest}" ]; then
            echo "Platform ${PLATFORM} resolved to '${digest}'..."
        else
            echo "No image digest found. Verify that the image, ref, and platform are valid."
            exit 1
        fi
    fi

    # https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate
    api_token_url="https://auth.docker.io/token?service=registry.docker.io&scope=repository:$image:pull"

    # https://github.com/docker/distribution/blob/master/docs/spec/api.md#pulling-an-image-manifest
    manifest_url="https://registry-1.docker.io/v2/${image}/manifests/${digest}"

    # https://github.com/docker/distribution/blob/master/docs/spec/api.md#pulling-a-layer
    blobs_base_url="https://registry-1.docker.io/v2/${image}/blobs"

    echo "Getting API token..."
    token=$(fetch "${api_token_url}" | extract 'token')
    auth_header="Authorization: Bearer $token"

    # https://github.com/distribution/distribution/blob/main/docs/spec/manifest-v2-2.md
    docker_manifest_v2="application/vnd.docker.distribution.manifest.v2+json"

    # https://github.com/opencontainers/image-spec/blob/main/manifest.md
    oci_manifest_v1="application/vnd.oci.image.manifest.v1+json"

    # Docker Hub can return either type of manifest format. Most images seem to
    # use the Docker format for now, but the OCI format will likely become more
    # common as features that require that format become enabled by default
    # (e.g., https://github.com/docker/build-push-action/releases/tag/v3.3.0).
    accept_header="Accept: ${docker_manifest_v2},${oci_manifest_v1}"

    echo "Getting image manifest for $image:$ref..."
    layers=$(fetch "${manifest_url}" "${auth_header}" "${accept_header}" |
                # Extract `digest` values only after the `layers` section appears.
                sed -n '/"layers":/,$ p' |
                extract 'digest')

    if [ -z "${layers}" ]; then
        echo "No layers returned. Verify that the image and ref are valid."
        exit 1
    fi

    for layer in $layers; do
        hash="${layer#sha256:}"
        echo "Fetching and extracting layer ${hash}..."
        fetch "${blobs_base_url}/${layer}" "${auth_header}" | gzip -d | tar -C "${OUT_DIR}" -xf -
        # Ref: https://github.com/moby/moby/blob/master/image/spec/v1.2.md#creating-an-image-filesystem-changeset
        #      https://github.com/moby/moby/blob/master/pkg/archive/whiteouts.go
        # Search for "whiteout" files to indicate files deleted in this layer.
        OLD_IFS="${IFS}"
        find "${OUT_DIR}" -name '.wh.*' | while IFS= read -r f; do
            dir="${f%/*}"
            wh_file="${f##*/}"
            file="${wh_file#.wh.}"
            # Delete both the whiteout file and the whited-out file.
            rm -rf "${dir}/${wh_file}" "${dir}/${file}"
        done
        IFS="${OLD_IFS}"
    done

    echo "Image contents extracted into ${OUT_DIR}."
  }

  printf "\e[1;31mDisclaimer: This AUR package is maintained by the community and is not\e[0m\n"
  printf "\e[1;31mofficially supported by the software developers or the package maintainer.\e[0m\n"
  printf "\e[1;31mUse this package at your own risk. The developers of the original software\e[0m\n"
  printf "\e[1;31mand the package maintainer are not responsible for any issues, bugs, or\e[0m\n"
  printf "\e[1;31mdamages resulting from its use. For installation or usage questions, please\e[0m\n"
  printf "\e[1;31mdo not contact the original developers. Instead, seek assistance from the\e[0m\n"
  printf "\e[1;31mpackage maintainer, the Arch Linux community or relevant forums.\e[0m\n"

  if [ "$(uname -m)" = "x86_64" ]; then
    PLATFORM_DEFAULT="linux/amd64"
  elif [ "$(uname -m)" = "aarch64" ]; then
    PLATFORM_DEFAULT="linux/arm64"
  fi

  [ -d "${srcdir}/psono-ee-src" ] && rm -rf "${srcdir}/psono-ee-src"
  [ -d "${srcdir}/psono-fs-src" ] && rm -rf "${srcdir}/psono-fs-src"
  [ -d "${srcdir}/psono-web" ] && rm -rf "${srcdir}/psono-web"
  [ -d "${srcdir}/psono-web-admin" ] && rm -rf "${srcdir}/psono-web-admin"
  [ -d "${srcdir}/psono-src-docker" ] && rm -rf "${srcdir}/psono-src-docker"

  mkdir -p "${srcdir}/psono-ee-src"
  mkdir -p "${srcdir}/psono-fs-src"
  mkdir -p "${srcdir}/psono-web"
  mkdir -p "${srcdir}/psono-web-admin"
  mkdir -p "${srcdir}/psono-src-docker"

  printf "\e[32mFetching Psono Server Enterprise Edition Docker image\e[0m\n"
  docker_fetch psono/psono-combo-enterprise:latest "$PLATFORM_DEFAULT"

  printf "\e[32mExtracting Psono Server Enterprise Edition from Docker image\e[0m\n"
  cp -rf ${srcdir}/psono-src-docker ${srcdir}/test
  mv ${srcdir}/psono-src-docker/root/* ${srcdir}/psono-ee-src/

  printf "\e[32mExtracting Psono Admin Portal from Docker image\e[0m\n"
  mv ${srcdir}/psono-src-docker/usr/share/nginx/html/portal/* "${srcdir}/psono-web-admin/"

  printf "\e[32mExtracting Psono Web Portal from Docker image\e[0m\n"
  mv ${srcdir}/psono-src-docker/usr/share/nginx/html/* "${srcdir}/psono-web/"

  printf "\e[32mRemoving unused files of Psono Server Enterprise Edition Docker image\e[0m\n"
  rm -rf ${srcdir}/psono-src-docker/*

  printf "\e[32mFetching Psono File Server Docker image\e[0m\n"
  docker_fetch psono/psono-fileserver:latest "$PLATFORM_DEFAULT"

  printf "\e[32mExtracting Psono File Server from Docker image\e[0m\n"
  mv ${srcdir}/psono-src-docker/root/* ${srcdir}/psono-fs-src/

  printf "\e[32mRemoving unused files of Psono File Server Docker image\e[0m\n"
  rm -rf ${srcdir}/psono-src-docker/*

  printf "\e[32mPatching Psono Server Enterprise Edition requirements.txt\e[0m\n"
  cd "${srcdir}/psono-ee-src" || exit 1
  patch -Np1 -i "${srcdir}/psono-ee-server-requirements.patch"

  # Find the latest pyarmor_runtime.so folder, cd to it and patch pyarmor_runtime.so
  printf "\e[32mPatching pyarmor_runtime.so ELF by replacing libc.so with libc.so.6 to avoid 'invalid ELF header' errors\e[0m\n"
  cd "${srcdir}/psono-ce-src/psono" || exit 1
  PYARMOR_RUNTIME_DIR=$(ls -d pyarmor_runtime_* | sort -V | tail -n 1)
  if [[ -d "$PYARMOR_RUNTIME_DIR" ]]; then
    cd "$PYARMOR_RUNTIME_DIR" || exit 1
    patchelf --replace-needed libc.so libc.so.6 pyarmor_runtime.so
  fi
}

package() {
  # Fail2ban
  install -dm755 "${pkgdir}/etc/fail2ban/jail.d"
  install -dm755 "${pkgdir}/etc/fail2ban/filter.d"
  install -Dm644 "${srcdir}/conf_psono-fail2ban-filter.conf" "${pkgdir}/etc/fail2ban/filter.d/psono-ee.conf"
  install -Dm644 "${srcdir}/conf_psono-fail2ban-jail.local" "${pkgdir}/etc/fail2ban/jail.d/psono-ee.local"

  # Logrotate
  install -dm755 "${pkgdir}/etc/logrotate.d"
  install -Dm644 "${srcdir}/conf_psono.logrotate" "${pkgdir}/etc/logrotate.d/psono"

  # Nginx
  install -dm755 "${pkgdir}/etc/nginx/sites-available"
  install -dm755 "${pkgdir}/etc/nginx/sites-enabled"
  install -Dm644 "${srcdir}/conf_psono-nginx.conf" "${pkgdir}/etc/nginx/sites-available/psono-ee-combo.conf"

  # Pacman
  install -Dm644 "${srcdir}/pacman_05-psono-ee-pre.hook" "${pkgdir}/usr/share/libalpm/hooks/05-psono-ee-pre.hook"
  install -Dm644 "${srcdir}/pacman_99-psono-ee-post.hook" "${pkgdir}/usr/share/libalpm/hooks/99-psono-ee-post.hook"
  install -Dm755 "${srcdir}/script_psono-ee-pacman-hooks.sh" "${pkgdir}/usr/share/libalpm/scripts/psono-ee-pacman-hooks.sh"

  # Psono
  install -dm755 "${pkgdir}/var/lib/psono/psono-backups"
  install -dm755 "${pkgdir}/var/lib/psono/psono-fileserver"
  install -dm755 "${pkgdir}/var/lib/psono/psono-server-ee"
  install -dm755 "${pkgdir}/var/lib/psono/psono-shard"
  install -dm755 "${pkgdir}/var/lib/psono/.pip"
  install -dm755 "${pkgdir}/var/lib/psono/.psono_fileserver"
  install -dm755 "${pkgdir}/var/lib/psono/.psono_server"
  install -dm755 "${pkgdir}/var/lib/psono/.python_env/psono-fileserver"
  install -dm755 "${pkgdir}/var/lib/psono/.python_env/psono-server-ee"
  install -dm755 "${pkgdir}/var/log/psono"
  install -dm755 "${pkgdir}/usr/share/webapps/psono-webclient/portal"

  install -Dm644 "${srcdir}/conf_psono-fileserver-settings.yaml" "${pkgdir}/var/lib/psono/.psono_fileserver/settings.yaml"
  install -Dm644 "${srcdir}/conf_psono-python-pip.conf" "${pkgdir}/var/lib/psono/.pip/pip.conf"
  install -Dm644 "${srcdir}/conf_psono-server-settings.yaml" "${pkgdir}/var/lib/psono/.psono_server/settings.yaml"
  install -Dm644 "${srcdir}/systemd_psono-fileserver-cleanup.service" "${pkgdir}/usr/lib/systemd/system/psono-fileserver-cleanup.service"
  install -Dm644 "${srcdir}/systemd_psono-fileserver-cleanup.timer" "${pkgdir}/usr/lib/systemd/system/psono-fileserver-cleanup.timer"
  install -Dm644 "${srcdir}/systemd_psono-fileserver-ping.service" "${pkgdir}/usr/lib/systemd/system/psono-fileserver-ping.service"
  install -Dm644 "${srcdir}/systemd_psono-fileserver-ping.timer" "${pkgdir}/usr/lib/systemd/system/psono-fileserver-ping.timer"
  install -Dm644 "${srcdir}/systemd_psono-fileserver.service" "${pkgdir}/usr/lib/systemd/system/psono-fileserver.service"
  install -Dm644 "${srcdir}/systemd_psono-server-ee-cleartoken.service" "${pkgdir}/usr/lib/systemd/system/psono-server-ee-cleartoken.service"
  install -Dm644 "${srcdir}/systemd_psono-server-ee-cleartoken.timer" "${pkgdir}/usr/lib/systemd/system/psono-server-ee-cleartoken.timer"
  install -Dm644 "${srcdir}/systemd_psono-server-ee.service" "${pkgdir}/usr/lib/systemd/system/psono-server-ee.service"

  cp -dr --no-preserve=ownership "${srcdir}/psono-ee-src"/* "${pkgdir}/var/lib/psono/psono-server-ee/"
  cp -dr --no-preserve=ownership "${srcdir}/psono-fs-src"/* "${pkgdir}/var/lib/psono/psono-fileserver/"
  cp -dr --no-preserve=ownership "${srcdir}/psono-web"/* "${pkgdir}/usr/share/webapps/psono-webclient/"
  cp -dr --no-preserve=ownership "${srcdir}/psono-web-admin"/* "${pkgdir}/usr/share/webapps/psono-webclient/portal/"
}

Source files (I added prefixes for easy managment):

LICENSES
LICENSE
PKGBUILD
README.md
REUSE.toml
conf_psono-fail2ban-filter.conf
conf_psono-fail2ban-jail.local
conf_psono-fileserver-settings.yaml
conf_psono-nginx.conf
conf_psono-python-pip.conf
conf_psono-server-settings.yaml
conf_psono.logrotate
pacman_05-psono-ee-pre.hook
pacman_99-psono-ee-post.hook
psono-ee-server-requirements.patch
psono-script.install
script_psono-ee-pacman-hooks.sh
systemd_psono-fileserver-cleanup.service
systemd_psono-fileserver-cleanup.timer
systemd_psono-fileserver-ping.service
systemd_psono-fileserver-ping.timer
systemd_psono-fileserver.service
systemd_psono-server-ee-cleartoken.service
systemd_psono-server-ee-cleartoken.timer
systemd_psono-server-ee.service

REUSE.toml:

version = 1

[[annotations]]
path = [
    "PKGBUILD",
    "README.md",
    "keys/**",
    ".SRCINFO",
    ".gitignore",
    ".nvchecker.toml",
    "*.install",
    "*.sysusers",
    "*sysusers.conf",
    "*.tmpfiles",
    "*tmpfiles.conf",
    "*.logrotate",
    "*.pam",
    "*.service",
    "*.socket",
    "*.timer",
    "*.desktop",
    "*.hook",
    "conf_psono-fail2ban-filter.conf",
    "conf_psono-fail2ban-jail.local",
    "conf_psono-nginx.conf",
    "psono-ee-server-requirements.patch",
    "script_psono-ee-pacman-hooks.sh",
]
SPDX-FileCopyrightText = "Arch Linux contributors"
SPDX-License-Identifier = "0BSD"

[[annotations]]
path = [
    "conf_psono-fileserver-settings.yaml",
    "conf_psono-python-pip.conf",
    "conf_psono-server-settings.yaml",
]
SPDX-FileCopyrightText = "psono-ee-combo contributors"
SPDX-License-Identifier = "Apache-2.0"

psono-script.install

pre_install() {
  getent group psono >/dev/null || groupadd --system psono
  getent passwd psono >/dev/null || useradd --system -g psono -d /var/lib/psono -s /usr/bin/nologin psono
}

post_install() {
  printf "\e[1;31mDisclaimer: This AUR package is maintained by the community and is not\e[0m\n"
  printf "\e[1;31mofficially supported by the software developers or the package maintainer.\e[0m\n"
  printf "\e[1;31mUse this package at your own risk. The developers of the original software\e[0m\n"
  printf "\e[1;31mand the package maintainer are not responsible for any issues, bugs, or\e[0m\n"
  printf "\e[1;31mdamages resulting from its use. For installation or usage questions, please\e[0m\n"
  printf "\e[1;31mdo not contact the original developers. Instead, seek assistance from the\e[0m\n"
  printf "\e[1;31mpackage maintainer, the Arch Linux community or relevant forums.\e[0m\n"

  chown -R psono:psono /var/lib/psono

  printf "\e[32mInstalling Python packages from requirements.txt for Psono Server\e[0m\n"
  runuser -l psono -s /bin/bash -c "/usr/bin/python3.14 -m venv /var/lib/psono/.python_env/psono-server-ee-3.14"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-server-ee-3.14/bin/python3.14 -m pip install --upgrade pip"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-server-ee-3.14/bin/pip3.14 install -r /var/lib/psono/psono-server-ee/requirements.txt"

  if [ "$(uname -m)" = "x86_64" ]; then
    runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-server-ee-3.14/bin/pip3.14 install -r /var/lib/psono/psono-server-ee/requirements-amd64.txt"
  fi

  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-server-ee-3.14/bin/pip3.14 install gunicorn"

  printf "\e[32mInstalling Python packages from requirements.txt for Psono Fileserver\e[0m\n"
  runuser -l psono -s /bin/bash -c "/usr/bin/python3.14 -m venv /var/lib/psono/.python_env/psono-fileserver-3.14"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-fileserver-3.14/bin/python3.14 -m pip install --upgrade pip"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-fileserver-3.14/bin/pip3.14 install -r /var/lib/psono/psono-fileserver/requirements.txt"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-fileserver-3.14/bin/pip3.14 install gunicorn"

  printf "\e[32mDon't forget to setup your Psono configuration and execute the following command to complete the Psono installation before starting the services:\e[0m\n"
  printf "\e[32m'runuser -l psono -s /bin/bash -c \"/var/lib/psono/.python_env/psono-server-ee-3.14/bin/python3.14 /var/lib/psono/psono-server-ee/psono/manage.py migrate\"'\e[0m\n"
}

post_upgrade() {
  printf "\e[1;31mDisclaimer: This AUR package is maintained by the community and is not\e[0m\n"
  printf "\e[1;31mofficially supported by the software developers or the package maintainer.\e[0m\n"
  printf "\e[1;31mUse this package at your own risk. The developers of the original software\e[0m\n"
  printf "\e[1;31mand the package maintainer are not responsible for any issues, bugs, or\e[0m\n"
  printf "\e[1;31mdamages resulting from its use. For installation or usage questions, please\e[0m\n"
  printf "\e[1;31mdo not contact the original developers. Instead, seek assistance from the\e[0m\n"
  printf "\e[1;31mpackage maintainer, the Arch Linux community or relevant forums.\e[0m\n"

  chown -R psono:psono /var/lib/psono/

  printf "\e[32mUpdating Python packages from requirements.txt for Psono Server\e[0m\n"
  runuser -l psono -s /bin/bash -c "/usr/bin/python3.14 -m venv /var/lib/psono/.python_env/psono-server-ee-3.14"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-server-ee-3.14/bin/python3.14 -m pip install --upgrade pip"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-server-ee-3.14/bin/pip3.14 install -r /var/lib/psono/psono-server-ee/requirements.txt"

  if [ "$(uname -m)" = "x86_64" ]; then
    runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-server-ee-3.14/bin/pip3.14 install -r /var/lib/psono/psono-server-ee/requirements-amd64.txt"
  fi

  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-server-ee-3.14/bin/pip3.14 install gunicorn"

  printf "\e[32mUpdating Python packages from requirements.txt for Psono Fileserver\e[0m\n"
  runuser -l psono -s /bin/bash -c "/usr/bin/python3.14 -m venv /var/lib/psono/.python_env/psono-fileserver-3.14"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-fileserver-3.14/bin/python3.14 -m pip install --upgrade pip"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-fileserver-3.14/bin/pip3.14 install -r /var/lib/psono/psono-fileserver/requirements.txt"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-fileserver-3.14/bin/pip3.14 install gunicorn"
}

pre_remove() {
  printf "\e[32mBacking up Psono configuration and shards to /opt/backups/psono-backup\e[0m\n"
  mkdir -p /opt/backups/psono-backup
  cp /etc/nginx/sites-available/psono-ee-combo.conf /opt/backups/psono-backup/psono-ee-combo-nginx.conf
  cp /var/lib/psono/.psono_server/settings.yaml /opt/backups/psono-backup/server_settings.yaml
  cp /var/lib/psono/.psono_fileserver/settings.yaml /opt/backups/psono-backup/fileserver_settings.yaml
  cp -rf /var/lib/psono/psono-shard /opt/backups/psono-backup/
}

post_remove() {
  userdel --remove psono
  rm /etc/nginx/sites-available/psono-ee-combo.conf
  rm /etc/nginx/sites-enabled/psono-ee-combo.conf
  rm -rf /usr/share/webapps/psono-webclient
}

script_psono-ee-pacman-hooks.sh

#!/bin/sh

#Stop all psono services
psono_stop() {
  /usr/bin/systemctl stop psono-server-ee.service psono-server-ee-cleartoken.timer psono-fileserver.service psono-fileserver-cleanup.timer psono-fileserver-ping.timer
}

#Execute migration and start psono services
psono_migrate_start() {
  cd "/var/lib/psono/psono-server-ee" || exit
  sudo -u psono /var/lib/psono/.python_env/psono-server-ee-3.14/bin/python3.14 /var/lib/psono/psono-server-ee/psono/manage.py migrate

  # The hook runs after 30-systemd-daemon-reload.hook so another systemctl daemon-reload is not necessary.
  /usr/bin/systemctl start psono-server-ee.service psono-server-ee-cleartoken.timer psono-fileserver.service psono-fileserver-cleanup.timer psono-fileserver-ping.timer
}

case "$1" in
  stop)
    psono_stop
    ;;
  migrate)
    psono_migrate_start
    ;;
  *)
  exit 1
  ;;
esac

Questions:
- the original software requires a python environment, currently it's being installed by the package install script. The install script however uses sudo and I'm not sure what are the guidelines regarding it so if someone could explain, I'd apprechiate it See Edit 2 below
- are there any other ways python environments are installed with packages?
- problem with the libc.so library, apparently it contains "links" to libc.so.6 and is causing problems with python, resulting in "Invalid ELF header" errors. This has been the case for about a year and a half now. Creating a backup of libc.so and symlinking libc.so.6 to libc.so fixes the issue and I created a pacman hook for this process. However from my common sense perspective this is a big no no because libc.so is not part of my package and I shouldn't manipulate it. Am i wrong? Any recommendations? See Edit 4 below
- any other issues i am missing regarding my source files?

Feedback apprechiated.

Regards,
7thCore

Edit: Added url to PKGBUILD and renamed some config files.
Edit 2: Removed sudo as dependancy and replaced sudo with runuser.
Edit 3: Changed the pre_remove() backup location to /opt/backups/psono-backup
Edit 4: Removed pacman libc.so patching and added patchelf to build process to replace libc.so with libc.so.6 in pyarmor_runtime.so ELF in the Psono software to solve the glibc package tampering. Tested in live environment and confirmed working.

Last edited by 7thCore (Today 10:38:42)


The Linux philosophy is 'Laugh in the face of danger'. Oops. Wrong One. 'Do it yourself'. Yes, that's it.

Offline

#2 Yesterday 12:09:07

Lone_Wolf
Administrator
From: Netherlands, Europe
Registered: 2005-10-04
Posts: 14,837

Re: PKGBUILD Review: psono-ee-combo

- the original software requires a python environment, currently it's being installed by the package install script. The install script however uses sudo and I'm not sure what are the guidelines regarding it so if someone could explain, I'd apprechiate it
- are there any other ways python environments are installed with packages?
- problem with the libc.so library, apparently it contains "links" to libc.so.6 and is causing problems with python, resulting in "Invalid ELF header" errors. This has been the case for about a year and a half now. Creating a backup of libc.so and symlinking libc.so.6 to libc.so fixes the issue and I created a pacman hook for this process. However from my common sense perspective this is a big no no because libc.so is not part of my package and I shouldn't manipulate it. Am i wrong? Any recommendations?

That suggests the sw needs a specific python setup and relies on libraries provided by them instead of system libraries .
I guess this is proprietary software ?
the url= link indicates it's open source.

psono-script.install looks like it's intended to be run as user with elevated rights for certain parts .
Such scripts are not suitable for packages and are only useful to see what things need to be done.
pip is only useful for user environments and is blocked from systemwide stuff, updating systemwide packages is the job of pacman.

To package this software for archlinux you'd probably have to disable the auto-update feature and create packages for psono-server-ee and psono-fileserver .

The pre_remove() function makes changes to /root/ user settings and archlinux packages are forbidden to touch user stuff .

The services and timer files should not be included in the packages, but put somewhere in a vcs (git) repository so they can be maintained and updated when needed.

It might be possible to use a custom DL-agent for the docker part.

I didn't investigate in-depth, but sofar I haven't seen anything that blocks creating archlinux packages for this.

NOTE: it won't be easy, a lot of work and may need changes by upstream.


Disliking systemd intensely, but not satisfied with alternatives so focusing on taming systemd.

clean chroot building not flexible enough ?
Try clean chroot manager by graysky

Offline

#3 Yesterday 17:42:13

7thCore
Member
Registered: 2018-06-09
Posts: 67

Re: PKGBUILD Review: psono-ee-combo

Lone_Wolf wrote:

That suggests the sw needs a specific python setup and relies on libraries provided by them instead of system libraries .
I guess this is proprietary software ?
the url= link indicates it's open source.

Yes, it uses a python virtual environment that's being installed by the psono-script.install script used by pacman when installing or upgrading the package.
Edit: removed pacman libc.so patching and added patchelf to build process to replace libc.so with libc.so.6 in pyarmor_runtime.so ELF in the Psono software to solve the glibc package tampering. Tested in live environment and confirmed working.

Lone_Wolf wrote:

psono-script.install looks like it's intended to be run as user with elevated rights for certain parts .
Such scripts are not suitable for packages and are only useful to see what things need to be done.
pip is only useful for user environments and is blocked from systemwide stuff, updating systemwide packages is the job of pacman.

Some of it yes:
- pre_install() create a new system user and system group called psono with it's home folder located at /var/lib/psono where the python application and the virtual environments for it are stored. This user has it's login disabled
- post_install() uses sudo (I think I'll switch this to the runuser command and get rid of the sudo dependancy) to execute commands as the psono user, the commands create a python virtual environment, upgrades pip in that python virtual environment (not the system package) and installs packages designated in psono's requirements.txt file. It does not install, update, modify system packages in any way, shape or form. The python virtual environment is in the psono user's home folder.

Lone_Wolf wrote:

To package this software for archlinux you'd probably have to disable the auto-update feature and create packages for psono-server-ee and psono-fileserver .

Split them up you mean? That can be done with ease. I'll have to remove the userdel from pre_remove() then. If both packages are installed they'll both use the same user and a manual cleanup may be required. I could also have a package each for the web client and the web admin created. Also what auto update feature? The migrate command you mean?

Lone_Wolf wrote:

The pre_remove() function makes changes to /root/ user settings and archlinux packages are forbidden to touch user stuff .

Understood, I'll pick a more appropriate location for a configuration backup and a backup of the encrypted files from the fileserver. The configuration files have secrets for the database so I believe it's appropriate to create a backup of them just in case. Without those same secrets the postgresql database and the encrypted files are useless and can't be reused if that is the intention.
Edit: moved the location to /opt/backups/psono-backup

Lone_Wolf wrote:

The services and timer files should not be included in the packages, but put somewhere in a vcs (git) repository so they can be maintained and updated when needed.

Can be done no problem. I just used this on my personal GitLab server so I could update my personal repository with it. I can upload it to GitHub and link it here if you wish to take a look at it.

Lone_Wolf wrote:

It might be possible to use a custom DL-agent for the docker part.

It's already part of the PKGBUILD file. You might have missed it. Search for the docker_fetch() function. Every time the package is built a command in prepare() calls the docker_fetch() function and grabs the latest release of the server and fileserver from docker's hub.

The reason I'm creating a new user is so the systemd services run the software with the psono user's user permision. No elevated access, so it can only run what it needs to. I'm basically doing what for example postgresql does when it creates the postgres user and runs the service as that user. Here is the Psono server service file for example:

# /usr/lib/systemd/system/psono-server-ee.service
[Unit]
Description=Psono Password Manager - Enterprise Edition Service
After=network.target network-online.target
Wants=network-online.target

[Service]
Type=notify
TimeoutSec=120

# Psono user specific settings
User=psono
Group=psono
LogsDirectory=psono
StateDirectory=psono
SyslogIdentifier=psono
WorkingDirectory=/var/lib/psono/psono-server-ee/psono
Environment=HOME=/var/lib/psono

# Executable settings
ExecStart=/var/lib/psono/.python_env/psono-server-ee-3.14/bin/gunicorn --bind 0.0.0.0:10100 wsgi
KillMode=mixed
KillSignal=SIGINT
RestartForceExitStatus=100
TimeoutSec=15min

# Additional security-related features
PrivateTmp=true
ProtectHome=true
ProtectSystem=full
NoNewPrivileges=true
ProtectControlGroups=true
ProtectKernelModules=true
ProtectKernelTunables=true
PrivateDevices=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
SystemCallArchitectures=native

[Install]
WantedBy=multi-user.target

Let me know what you think.

Last edited by 7thCore (Today 10:38:05)


The Linux philosophy is 'Laugh in the face of danger'. Oops. Wrong One. 'Do it yourself'. Yes, that's it.

Offline

#4 Today 11:52:06

Lone_Wolf
Administrator
From: Netherlands, Europe
Registered: 2005-10-04
Posts: 14,837

Re: PKGBUILD Review: psono-ee-combo

things have improved.

prepare() - docker fetch
makepkg has a special feature DLAGENTS .
Using this you would add the docker image in source= array and use a custom DLAGENT to download the image.

No idea if that's possible for docker_fetch, but it's worth it to look into.

Note that you should choose which tools are needed to build (and add them to makedeps unless they're a member of base-devel)  .
Don't check for curl/wget , choose one and use that.

makedepends are only needed during build and can/should be removed after.

# Given a JSON input on stdin, extract the string value associated with the
    # specified key. This avoids an extra dependency on a tool like `jq`.
    extract() {
        local key="$1"
        # Extract "<key>":"<val>" (assumes key/val won't contain double quotes).
        # The colon may have whitespace on either side.
        grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]\+\"" |
        # Extract just <val> by deleting the last '"', and then greedily deleting
        # everything up to '"'.
        sed -e 's/"$//' -e 's/.*"//'
    }

jq is already a makedepend, why not use it ?


from install script

 printf "\e[32mUpdating Python packages from requirements.txt for Psono Fileserver\e[0m\n"
  runuser -l psono -s /bin/bash -c "/usr/bin/python3.14 -m venv /var/lib/psono/.python_env/psono-fileserver-3.14"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-fileserver-3.14/bin/python3.14 -m pip install --upgrade pip"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-fileserver-3.14/bin/pip3.14 install -r /var/lib/psono/psono-fileserver/requirements.txt"
  runuser -l psono -s /bin/bash -c "/var/lib/psono/.python_env/psono-fileserver-3.14/bin/pip3.14 install gunicorn"

commands like those are ok for a user virtual python environment but I've never seen them used for a systemwide python app install .

It's questionable if AUR moderators will accept this approach, but it will definitely block adoption of this package into official repos .
Package those applications following https://wiki.archlinux.org/title/Python … guidelines is much better (though likely harder)


Disliking systemd intensely, but not satisfied with alternatives so focusing on taming systemd.

clean chroot building not flexible enough ?
Try clean chroot manager by graysky

Offline

#5 Today 14:33:53

twelveeighty
Member
Registered: 2011-09-04
Posts: 1,432

Re: PKGBUILD Review: psono-ee-combo

I think a much better approach would be to move the most egregious steps, especially the 'pip' installs inside the venv, from the install script to a build() function in the PKGBUILD. Since it's running in a venv, they can (should) be performed by an unprivileged user at build time. And then use the install() function to move the resultant files into /var/lib/psono, owed by the 'psono' user.

However, if this package already uses both venv and docker images, it does raise the question: why isn't this running entirely in a container? Or vice-versa: entirely without needing the Docker overhead and networking/file management pains?

Offline

Board footer

Powered by FluxBB