From 282b9bd3ce00cb543e175ca46c2debe0143a5638 Mon Sep 17 00:00:00 2001 From: Thomas Spalinger Date: Tue, 8 Sep 2020 11:54:51 +0200 Subject: [PATCH] Some build and package improvements (#45) * add missing package version to change log * fix some warnings of lintian during deb build * simplification of the debian package * introduce build script with docker/podman support * use already existing ssh proxy, has the nice effect of not displaying the welcome message * update readme for 1.0.2 * fix missing argument in apt-get autoremove --- .gitignore | 4 + on-boot-script/Dockerfile | 18 ++++ on-boot-script/README.md | 22 ++-- on-boot-script/build_deb.sh | 101 ++++++++++++++++++ .../dpkg-build-files/debian/changelog | 12 +++ .../dpkg-build-files/debian/control | 3 +- .../dpkg-build-files/debian/copyright | 2 +- on-boot-script/dpkg-build-files/debian/files | 2 - .../dpkg-build-files/debian/install | 2 + .../dpkg-build-files/debian/postinst | 55 +++++----- on-boot-script/dpkg-build-files/debian/postrm | 39 +++++++ .../dpkg-build-files/debian/preinst | 37 +++++++ on-boot-script/dpkg-build-files/debian/prerm | 40 +++++++ on-boot-script/dpkg-build-files/debian/rules | 19 ---- .../dpkg-build-files/debian/source/format | 2 +- .../debian/source/lintian-overrides | 3 + on-boot-script/dpkg-build-files/on_boot.sh | 9 ++ .../dpkg-build-files/udm-boot.service | 13 +++ .../packages/udm-boot_1.0.2_all.deb | Bin 0 -> 3184 bytes 19 files changed, 323 insertions(+), 60 deletions(-) create mode 100644 on-boot-script/Dockerfile create mode 100755 on-boot-script/build_deb.sh delete mode 100644 on-boot-script/dpkg-build-files/debian/files create mode 100755 on-boot-script/dpkg-build-files/debian/postrm create mode 100755 on-boot-script/dpkg-build-files/debian/preinst create mode 100755 on-boot-script/dpkg-build-files/debian/prerm mode change 100644 => 100755 on-boot-script/dpkg-build-files/debian/rules create mode 100644 on-boot-script/dpkg-build-files/debian/source/lintian-overrides create mode 100755 on-boot-script/dpkg-build-files/on_boot.sh create mode 100644 on-boot-script/dpkg-build-files/udm-boot.service create mode 100644 on-boot-script/packages/udm-boot_1.0.2_all.deb diff --git a/.gitignore b/.gitignore index 85e7c1d..781eebf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /.idea/ +/**/debian/files +/**/debian/*.substvars +debhelper-build-stamp +.debhelper diff --git a/on-boot-script/Dockerfile b/on-boot-script/Dockerfile new file mode 100644 index 0000000..1089acd --- /dev/null +++ b/on-boot-script/Dockerfile @@ -0,0 +1,18 @@ +FROM debian:stretch-slim + +RUN set -ex \ + && echo 'deb http://deb.debian.org/debian stretch-backports main' > /etc/apt/sources.list.d/backports.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + devscripts \ + fakeroot \ + debhelper=12.\* dh-autoreconf=17\* \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /tmp/* /var/tmp/* /var/log/* /var/lib/apt/lists/* /var/log/alternatives.log + +RUN chmod a+rwx,u+t /tmp + +ENTRYPOINT [] +CMD ["/bin/sh"] diff --git a/on-boot-script/README.md b/on-boot-script/README.md index d0e9f89..88001b5 100644 --- a/on-boot-script/README.md +++ b/on-boot-script/README.md @@ -8,7 +8,7 @@ ## Compatibility 1. Should work on any UDM/UDMPro after 1.6.3 -2. Tested and confirmed on 1.6.6, 1.7.0, 1.7.2rc4, 1.7.3rc1, 1.8.0rc7 +2. Tested and confirmed on 1.6.6, 1.7.0, 1.7.2rc4, 1.7.3rc1, 1.8.0rc7, 1.8.0 ### Upgrade from earlier way @@ -20,8 +20,12 @@ rm /etc/systemd/system/udmboot.service ``` -* The new package is exactly the old steps packaged in a debian package -* [dpkg-build-files](dpkg-build-files) contains the scripts that build the package (using dh_make and debuild) if you want to build it yourself / change it +* [build_deb.sh](build_deb.sh) can be used to build the package by yourself. + * [dpkg-build-files](dpkg-build-files) contains the sources that debuild uses to build the package if you want to build it yourself / change it + * by default it uses docker or podman to build the debian package + * use ```./build_deb.sh build``` to not using a container + * the resulting package will be in [packages/](packages/) + * Built on Ubuntu-20.04 on Windows 10/WSL2 ## Steps @@ -32,15 +36,15 @@ unifi-os shell ``` -2. Download [udm-boot_1.0.1-1_all.deb](packages/udm-boot_1.0.1-1_all.deb) and install it and go back to the UDM +2. Download [udm-boot_1.0.2_all.deb](packages/udm-boot_1.0.2_all.deb) and install it and go back to the UDM ```bash - curl -L https://raw.githubusercontent.com/boostchicken/udm-utilities/master/on-boot-script/packages/udm-boot_1.0.1-1_all.deb -o udm-boot_1.0.1-1_all.deb - dpkg -i udm-boot_1.0.1-1_all.deb + curl -L https://raw.githubusercontent.com/boostchicken/udm-utilities/master/on-boot-script/packages/udm-boot_1.0.2_all.deb -o udm-boot_1.0.2_all.deb + dpkg -i udm-boot_1.0.2_all.deb exit ``` -3. Copy any shell scripts you want to run to /mnt/data/on_boot.d on your UDM (not the unifi-os shell)and make sure they are executable and have the correct shebang (#!/bin/sh) +3. Copy any shell scripts you want to run to /mnt/data/on_boot.d on your UDM (not the unifi-os shell) and make sure they are executable and have the correct shebang (#!/bin/sh) Examples: * Start a DNS Container [10-dns.sh](../dns-common/on_boot.d/10-dns.sh) @@ -48,6 +52,10 @@ ## Version History +### 1.0.2 + +* Some build improvements and more clean installation + ### 1.0.1 * Fully automated install, all that is left is populating /mnt/data/on_boot.d diff --git a/on-boot-script/build_deb.sh b/on-boot-script/build_deb.sh new file mode 100755 index 0000000..00ea955 --- /dev/null +++ b/on-boot-script/build_deb.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +set -e + +WORK_DIR="$(cd "$(dirname "$0")" && echo "${PWD}")" +TARGET_DIR="${WORK_DIR}/packages" +SOURCE_DIR="${WORK_DIR}/dpkg-build-files" +CONTAINER_FILE="${WORK_DIR}/Dockerfile" +CONTAINER_CONTEXT="${WORK_DIR}" + +fatal() { + echo "ERROR: ${1}" 1>&2 + exit ${2-1} +} + +build_in_container=false +build=false +build_container=false +if [ $# -eq 0 ]; then + build_in_container=true +fi +if [ "${1}" = "build" ]; then + build=true +fi +if [ "${1}" = "build_container" ]; then + build_container=true +fi + + +build_in_container() { + docker_exec="$(command -v docker || true)" + podman_exec="$(command -v podman || true)" + container_exec="${docker_exec:-"${podman_exec}"}" + container_args="" + + if [ ! -f "${container_exec}" ]; then + fatal "docker or podman not found" + fi + if [ ! -f "${CONTAINER_FILE}" ]; then + fatal "container file ${CONTAINER_FILE} not found" + fi + + if [ "${container_exec}" = "${docker_exec}" ]; then + # docker does not map user, so we run it as user + container_args="--user "$(id -u):$(id -g)"" + fi + "${container_exec}" build --file "${CONTAINER_FILE}" --tag udm-boot-deb-builder "${CONTAINER_CONTEXT}" + "${container_exec}" run -it \ + ${container_args} \ + -v "${SOURCE_DIR}:/source:ro" \ + -v "${TARGET_DIR}:/target:rw" \ + -v "${WORK_DIR}/build_deb.sh:/build_deb.sh:ro" \ + --rm \ + udm-boot-deb-builder \ + /build_deb.sh build_container +} + + +build() { + source_dir=$1 + target_dir=$2 + version="$(dpkg-parsechangelog --show-field version -l "${source_dir}/debian/changelog")" + name="$(dpkg-parsechangelog --show-field source -l "${source_dir}/debian/changelog")" + package_name="${name}-${version}" + build_dir="$(mktemp --tmpdir="/tmp" --directory "${name}.XXXXXXXXXX")" + build_package_dir="${build_dir}/${package_name}" + + if [ ! -d "${source_dir}" ]; then + fatal "source dir ${source_dir} not found" + fi + if [ ! -d "${target_dir}" ]; then + fatal "target dir ${target_dir} not found" + fi + + mkdir -p "${build_package_dir}" + cp -r "${source_dir}"/* "${build_package_dir}" + ( + cd "${build_package_dir}" + # we could exclude "source" here to skip building the source, + # but lintian warns only in the source build about some stuff + debuild -us -uc --build=source,all --lintian-opts --profile debian + ) + + find "${build_dir}" -maxdepth 1 -type f -exec mv {} "${target_dir}" \; + rm -rf "${build_dir}" +} + +build_container() { + build "/source" "/target" +} + +if [ $build_in_container = true ]; then + build_in_container +fi +if [ $build = true ]; then + build "${SOURCE_DIR}" "${TARGET_DIR}" +fi +if [ $build_container = true ]; then + build_container +fi + diff --git a/on-boot-script/dpkg-build-files/debian/changelog b/on-boot-script/dpkg-build-files/debian/changelog index 00e6193..37483b5 100644 --- a/on-boot-script/dpkg-build-files/debian/changelog +++ b/on-boot-script/dpkg-build-files/debian/changelog @@ -1,3 +1,15 @@ +udm-boot (1.0.2) unstable; urgency=medium + + * optimized package structure + + -- spali Thu, 03 Sep 2020 16:28:40 +0200 + +udm-boot (1.0.1-1) unstable; urgency=medium + + * Full automation + + -- Boostchicken Sun, 05 Jul 2020 18:46:14 -0700 + udm-boot (1.0.0-1) unstable; urgency=medium * Initial release, happy firmware persisting! diff --git a/on-boot-script/dpkg-build-files/debian/control b/on-boot-script/dpkg-build-files/debian/control index cbf5539..dc37a35 100644 --- a/on-boot-script/dpkg-build-files/debian/control +++ b/on-boot-script/dpkg-build-files/debian/control @@ -1,5 +1,5 @@ Source: udm-boot -Section: unknown +Section: contrib/utils Priority: optional Maintainer: Boostchicken Build-Depends: debhelper-compat (= 12) @@ -10,5 +10,6 @@ Homepage: https://github.com/boostchicken/udm-utilities Package: udm-boot Architecture: all +Depends: ${misc:Depends} Description: Run things on boot on UDM Run things on boot! diff --git a/on-boot-script/dpkg-build-files/debian/copyright b/on-boot-script/dpkg-build-files/debian/copyright index 2d8063a..510b569 100644 --- a/on-boot-script/dpkg-build-files/debian/copyright +++ b/on-boot-script/dpkg-build-files/debian/copyright @@ -5,5 +5,5 @@ Source: https://github.com/boostchicken/udm-utilities Files: * Copyright: 2020 dorman@ataxia.cloud -License: GPLv3 +License: GPL-3 https://github.com/boostchicken/udm-utilities/blob/master/LICENSE diff --git a/on-boot-script/dpkg-build-files/debian/files b/on-boot-script/dpkg-build-files/debian/files deleted file mode 100644 index 4208e4f..0000000 --- a/on-boot-script/dpkg-build-files/debian/files +++ /dev/null @@ -1,2 +0,0 @@ -udm-boot_1.0.0-1_all.deb unknown optional -udm-boot_1.0.0-1_amd64.buildinfo unknown optional diff --git a/on-boot-script/dpkg-build-files/debian/install b/on-boot-script/dpkg-build-files/debian/install index e69de29..2b46370 100644 --- a/on-boot-script/dpkg-build-files/debian/install +++ b/on-boot-script/dpkg-build-files/debian/install @@ -0,0 +1,2 @@ +on_boot.sh usr/share/udm-boot/ +udm-boot.service lib/systemd/system/ diff --git a/on-boot-script/dpkg-build-files/debian/postinst b/on-boot-script/dpkg-build-files/debian/postinst index 3941a14..1844b73 100644 --- a/on-boot-script/dpkg-build-files/debian/postinst +++ b/on-boot-script/dpkg-build-files/debian/postinst @@ -1,38 +1,30 @@ #!/bin/sh +# postinst script for udm-boot +# +# see: dh_installdeb(1) + set -e +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + case "$1" in configure) - echo '#!/bin/sh + scp -P "$(cat /etc/unifi-os/ssh_proxy_port)" -o StrictHostKeyChecking=no -q /usr/share/udm-boot/on_boot.sh root@localhost:/mnt/data/on_boot.sh + /sbin/ssh-proxy 'chmod +x /mnt/data/on_boot.sh && mkdir -p /mnt/data/on_boot.d' - if [ -d /mnt/data/on_boot.d ]; then - for i in /mnt/data/on_boot.d/*.sh; do - if [ -r $i ]; then - . $i - fi - done - fi - ' > /tmp/on_boot.sh - scp -o StrictHostKeyChecking=no /tmp/on_boot.sh root@127.0.1.1:/mnt/data/on_boot.sh - ssh -o StrictHostKeyChecking=no root@127.0.1.1 'chmod +x /mnt/data/on_boot.sh' - ssh -o StrictHostKeyChecking=no root@127.0.1.1 'mkdir -p /mnt/data/on_boot.d' - - rm /tmp/on_boot.sh - - echo "#!/bin/sh - ssh -o StrictHostKeyChecking=no root@127.0.1.1 '/mnt/data/on_boot.sh'" > /etc/init.d/udm.sh - chmod +x /etc/init.d/udm.sh - echo "[Unit] - Description=Run On Startup UDM - After=network.target - - [Service] - ExecStart=/etc/init.d/udm.sh - - [Install] - WantedBy=multi-user.target" > /etc/systemd/system/udmboot.service - systemctl enable udmboot - systemctl start udmboot + deb-systemd-invoke enable udm-boot + deb-systemd-invoke start udm-boot ;; abort-upgrade|abort-remove|abort-deconfigure) @@ -44,4 +36,9 @@ case "$1" in ;; esac +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + exit 0 diff --git a/on-boot-script/dpkg-build-files/debian/postrm b/on-boot-script/dpkg-build-files/debian/postrm new file mode 100755 index 0000000..65f68ec --- /dev/null +++ b/on-boot-script/dpkg-build-files/debian/postrm @@ -0,0 +1,39 @@ +#!/bin/sh +# postrm script for udm-boot +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + # reserved for future use + true + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/on-boot-script/dpkg-build-files/debian/preinst b/on-boot-script/dpkg-build-files/debian/preinst new file mode 100755 index 0000000..bf46754 --- /dev/null +++ b/on-boot-script/dpkg-build-files/debian/preinst @@ -0,0 +1,37 @@ +#!/bin/sh +# preinst script for udm-boot +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + install|upgrade) + # reserved for future use + true + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/on-boot-script/dpkg-build-files/debian/prerm b/on-boot-script/dpkg-build-files/debian/prerm new file mode 100755 index 0000000..5b52e6d --- /dev/null +++ b/on-boot-script/dpkg-build-files/debian/prerm @@ -0,0 +1,40 @@ +#!/bin/sh +# prerm script for udm-boot +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + remove|upgrade|deconfigure) + # reserved for future use + true + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/on-boot-script/dpkg-build-files/debian/rules b/on-boot-script/dpkg-build-files/debian/rules old mode 100644 new mode 100755 index 5aafcca..abde6ef --- a/on-boot-script/dpkg-build-files/debian/rules +++ b/on-boot-script/dpkg-build-files/debian/rules @@ -1,24 +1,5 @@ #!/usr/bin/make -f -# See debhelper(7) (uncomment to enable) -# output every command that modifies files on the build system. -#export DH_VERBOSE = 1 - - -# see FEATURE AREAS in dpkg-buildflags(1) -#export DEB_BUILD_MAINT_OPTIONS = hardening=+all - -# see ENVIRONMENT in dpkg-buildflags(1) -# package maintainers to append CFLAGS -#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic -# package maintainers to append LDFLAGS -#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed - %: dh $@ - -# dh_make generated override targets -# This is example for Cmake (See https://bugs.debian.org/641051 ) -#override_dh_auto_configure: -# dh_auto_configure -- # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) diff --git a/on-boot-script/dpkg-build-files/debian/source/format b/on-boot-script/dpkg-build-files/debian/source/format index 163aaf8..89ae9db 100644 --- a/on-boot-script/dpkg-build-files/debian/source/format +++ b/on-boot-script/dpkg-build-files/debian/source/format @@ -1 +1 @@ -3.0 (quilt) +3.0 (native) diff --git a/on-boot-script/dpkg-build-files/debian/source/lintian-overrides b/on-boot-script/dpkg-build-files/debian/source/lintian-overrides new file mode 100644 index 0000000..17a81f9 --- /dev/null +++ b/on-boot-script/dpkg-build-files/debian/source/lintian-overrides @@ -0,0 +1,3 @@ +udm-boot source: changelog-should-mention-nmu +udm-boot source: source-nmu-has-incorrect-version-number +udm-boot source: odd-historical-debian-changelog-version diff --git a/on-boot-script/dpkg-build-files/on_boot.sh b/on-boot-script/dpkg-build-files/on_boot.sh new file mode 100755 index 0000000..7058b2e --- /dev/null +++ b/on-boot-script/dpkg-build-files/on_boot.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ -d /mnt/data/on_boot.d ]; then + for i in /mnt/data/on_boot.d/*.sh; do + if [ -r $i ]; then + . $i + fi + done +fi diff --git a/on-boot-script/dpkg-build-files/udm-boot.service b/on-boot-script/dpkg-build-files/udm-boot.service new file mode 100644 index 0000000..136229e --- /dev/null +++ b/on-boot-script/dpkg-build-files/udm-boot.service @@ -0,0 +1,13 @@ +[Unit] +Description=Run On Startup UDM +After=network.target + +[Service] +Type=oneshot +ExecStart=/sbin/ssh-proxy '/mnt/data/on_boot.sh' +RemainAfterExit=true +StandardOutput=journal + +[Install] +WantedBy=multi-user.target + diff --git a/on-boot-script/packages/udm-boot_1.0.2_all.deb b/on-boot-script/packages/udm-boot_1.0.2_all.deb new file mode 100644 index 0000000000000000000000000000000000000000..468d6bc2458c403545759cdba8a79b7f7d36fa9b GIT binary patch literal 3184 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