netx/packaging/linux/build_in_container.sh
oliver 846aa3e680 Linux x86_64 offline release; configurable API and bundled PostgreSQL ports.
- packaging/linux: netx-<ver>-linux-x86_64.tar.gz with bundled Python 3.12,
  PostgreSQL 16 and web/dist (glibc >= 2.17). install.sh (install/update/
  reinstall), netxctl (start/stop/status/logs, set-port, set-bind,
  set-pg-port, reconfigure-db, check-update/update/auto-update, backup,
  uninstall), systemd units, nohup fallback without systemd. Privilege
  drop, detaching and tar go through the bundled Python, so bare images
  without su/runuser/setsid/tar work.
- Updates: same GitHub/Forgejo/manifest sources as Windows, sha256
  verified, pre-update pg_dump, automatic rollback when the new version
  does not come up.
- Non-loopback bind: generated bootstrap admin password and
  NETX_UME_VERIFY_TLS=true, as security_bootstrap requires.
- Build in manylinux2014 + check_bundle.sh (GLIBC <= 2.17, no missing
  libs); release-linux.yml builds on v* tags and smoke-tests offline on
  CentOS 7, Rocky 9, Ubuntu 18.04/24.04, Debian 12, openSUSE Leap 15 and
  openEuler 22.03 before attaching to the release.
- Windows: Setup DB page gets a bundled PG port field and
  /BundledPgPort=; set_pg_port.ps1 + tray "Built-in database port";
  reconfigure keeps the configured port; postgresql.conf is written
  without a BOM (PS 5.1 Set-Content -Encoding utf8 broke postgres).
- Forgejo/GitHub publish scripts include the Linux package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:09:07 +08:00

109 lines
5.2 KiB
Bash
Executable file

#!/usr/bin/env bash
# Runs inside manylinux2014 (CentOS 7, glibc 2.17). Called by build_linux_release.sh.
# Mounts: /src (repo, ro) /webdist (ro) /cache (downloads + built PG) /out
set -euo pipefail
. /src/packaging/linux/versions.env
: "${VERSION:?}"
ARCH=x86_64
NAME="netx-${VERSION}-linux-${ARCH}"
BUILD=/tmp/build
STAGE="${BUILD}/${NAME}"
JOBS="$(nproc)"
rm -rf /tmp/build
mkdir -p "${STAGE}"
# ------------------------------------------------------------ PostgreSQL (cached)
PG_TGZ="/cache/pgsql-${PG_VERSION}-linux-${ARCH}.tar.gz"
if [[ ! -f "${PG_TGZ}" ]]; then
echo "==> building PostgreSQL ${PG_VERSION}"
rm -rf /tmp/pgsrc /opt/pgsql
mkdir -p /tmp/pgsrc
tar xjf "/cache/$(basename "${PG_URL}")" -C /tmp/pgsrc
cd "/tmp/pgsrc/postgresql-${PG_VERSION}"
# No ICU/readline/OpenSSL: nothing for the target box to provide. SCRAM works
# without OpenSSL; the server only listens on 127.0.0.1.
./configure --prefix=/opt/pgsql --without-icu --without-readline --without-openssl \
--with-zlib --disable-rpath CFLAGS="-O2" >/tmp/pg-configure.log
make -j"${JOBS}" >/tmp/pg-make.log
make install >/tmp/pg-install.log
cd /opt/pgsql
keep=" postgres pg_ctl initdb psql pg_dump pg_dumpall pg_restore pg_isready pg_controldata pg_upgrade "
for f in bin/*; do
[[ "${keep}" == *" $(basename "${f}") "* ]] || rm -f "${f}"
done
rm -rf /opt/pgsql/include /opt/pgsql/lib/pgxs /opt/pgsql/lib/pkgconfig
rm -f /opt/pgsql/lib/*.a
cp -L "$(ldconfig -p | awk '/libz\.so\.1 .*x86-64/ {print $NF; exit}')" lib/libz.so.1
for f in bin/*; do patchelf --set-rpath '$ORIGIN/../lib' "${f}"; done
for f in lib/*.so*; do
[[ -L "${f}" ]] || patchelf --set-rpath '$ORIGIN' "${f}"
done
strip --strip-unneeded bin/* lib/*.so* 2>/dev/null || true
tar czf "${PG_TGZ}" -C /opt pgsql
fi
tar xzf "${PG_TGZ}" -C "${STAGE}"
# ------------------------------------------------------------ Python + deps
echo "==> Python ${PY_VERSION} + requirements"
tar xzf "/cache/$(basename "${PY_URL}")" -C "${STAGE}" # -> python/
PY="${STAGE}/python/bin/python3"
export PIP_DISABLE_PIP_VERSION_CHECK=1
[[ -n "${PIP_INDEX_URL:-}" ]] || unset PIP_INDEX_URL
mkdir -p /cache/pip
# Running on glibc 2.17 makes pip pick manylinux2014-compatible wheels only.
"${PY}" -m pip install --cache-dir /cache/pip --no-warn-script-location --only-binary=:all: \
-r /src/requirements.txt
echo "==> slimming runtime"
PYLIB="${STAGE:?}/python/lib/python3.12"
for d in test idlelib tkinter turtledemo ensurepip turtle.py; do
rm -rf "${PYLIB:?}/${d}"
done
rm -f "${PYLIB:?}"/lib-dynload/_tkinter* "${PYLIB:?}"/lib-dynload/_test* "${PYLIB:?}"/lib-dynload/xxlimited*
for d in "${STAGE:?}"/python/lib/tcl* "${STAGE:?}"/python/lib/tk* "${STAGE:?}"/python/lib/itcl* \
"${STAGE:?}"/python/lib/thread* "${STAGE:?}"/python/lib/libtcl* "${STAGE:?}"/python/lib/libtk* \
"${STAGE:?}/python/share"; do
[[ -e "${d}" ]] && rm -rf "${d}"
done
find "${PYLIB}/site-packages" -depth -type d \( -name tests -o -name test -o -name testing \) \
-exec rm -rf {} + 2>/dev/null || true
find "${STAGE}/python" -name __pycache__ -type d -prune -exec rm -rf {} +
find "${STAGE}/python" -name '*.pyc' -delete
# ------------------------------------------------------------ app files
echo "==> app files"
cp -r /src/netx_api /src/alembic "${STAGE}/"
for f in pyproject.toml requirements.txt alembic.ini README.md LICENSE SECURITY.md; do
if [[ -f "/src/${f}" ]]; then cp "/src/${f}" "${STAGE}/"; fi
done
find "${STAGE}/netx_api" "${STAGE}/alembic" -name __pycache__ -type d -prune -exec rm -rf {} +
rm -rf "${STAGE:?}/netx_api/tests"
mkdir -p "${STAGE}/web"
cp -r /webdist "${STAGE}/web/dist"
mkdir -p "${STAGE}/packaging/linux"
for f in lib.sh netxctl netx_admin.py systemd logrotate.netx README.md; do
cp -r "/src/packaging/linux/${f}" "${STAGE}/packaging/linux/"
done
cp /src/packaging/linux/install.sh "${STAGE}/install.sh"
# Sources may come from a Windows checkout: force LF + exec bits.
sed -i 's/\r$//' "${STAGE}/install.sh" "${STAGE}"/packaging/linux/{lib.sh,netxctl,netx_admin.py,logrotate.netx} \
"${STAGE}"/packaging/linux/systemd/*
chmod 0755 "${STAGE}/install.sh" "${STAGE}/packaging/linux/netxctl"
cat >"${STAGE}/version.json" <<JSON
{"version": "${VERSION}", "arch": "${ARCH}", "pg_major": ${PG_MAJOR}, "pg_version": "${PG_VERSION}", "python": "${PY_VERSION}", "built_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
JSON
# ------------------------------------------------------------ checks
(cd "${STAGE}" && PYTHONPATH="${STAGE}" "${PY}" -c "import netx_api.main, psycopg, cryptography, netmiko, bcrypt, psutil; print('import smoke OK')")
find "${STAGE}" -name __pycache__ -type d -prune -exec rm -rf {} +
"${STAGE}/pgsql/bin/postgres" --version
bash /src/packaging/linux/check_bundle.sh "${STAGE}" "${MAX_GLIBC}"
# ------------------------------------------------------------ archive
echo "==> archive"
tar --owner=0 --group=0 --numeric-owner -czf "/out/${NAME}.tar.gz" -C "${BUILD}" "${NAME}"
(cd /out && sha256sum "${NAME}.tar.gz" >"${NAME}.tar.gz.sha256")
chown "${HOST_UID:-0}:${HOST_GID:-0}" "/out/${NAME}.tar.gz" "/out/${NAME}.tar.gz.sha256" "${PG_TGZ}" 2>/dev/null || true
chown -R "${HOST_UID:-0}:${HOST_GID:-0}" /cache/pip 2>/dev/null || true
du -sh "${STAGE}" "/out/${NAME}.tar.gz"