diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce4ce72..ace20db 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,7 +69,7 @@ jobs: pip3 install pyinstaller sysv-ipc geoip2 flask ./build.sh && mkdir ./build-debian && mv -f *.tar.gz *.sha512sum ./build-debian if [ ${{ matrix.debian-release }} = 'bookworm' ]; then - WEBSPY=1 ./build.sh && mkdir ./build-ubuntu && mv -f *.tar.gz *.sha512sum ./build-debian + ./build.sh _WITH_BUNDLE && mkdir ./build-ubuntu && mv -f *.tar.gz *.sha512sum ./build-debian fi - uses: actions/upload-artifact@v4 with: @@ -151,7 +151,7 @@ jobs: pip3 install -r requirements.txt pip3 install pyinstaller sysv-ipc geoip2 flask ./build.sh && mkdir ./build-alpine && mv -f *.tar.gz *.sha512sum ./build-alpine - WEBSPY=1 ./build.sh && mkdir ./build-alpine && mv -f *.tar.gz *.sha512sum ./build-alpine + ./build.sh _WITH_BUNDLE && mkdir ./build-alpine && mv -f *.tar.gz *.sha512sum ./build-alpine - uses: actions/upload-artifact@v4 with: name: build-alpine-artifacts diff --git a/README.md b/README.md index e55c15f..8e7c6b2 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,12 @@ The build script will check and warn about wrong python version and missing modu ## Build in docker -Same as github actions (CI), but locally: +Same as github actions (CI), but build in local container. -`./docker-build-distro.sh ubuntu debian centos-stream alma rocky alpine` +``` +./docker-build-distro.sh -h +USAGE: ./docker-build-distro.sh +``` ## Issues diff --git a/build.sh b/build.sh index 0ade939..ead6c6c 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,7 @@ #!/bin/sh +# Build pyspy binaries with pyinstaller + # Usage: # ./build.sh _WITH_GEOIP _WITH_HTTPD _WITH_FLASK _WITH_BUNDLE @@ -16,17 +18,6 @@ ARGS="--hidden-import sysv_ipc" OPTS="_WITH_GEOIP _WITH_HTTPD _WITH_FLASK _WITH_BUNDLE" PACKFILES="../spy.conf spy ../webspy" -if [ "${WEBSPY:-0}" -eq 1 ]; then - ARGS=" $ARGS --add-data webspy:./webspy " - PACKFILES="../spy.conf spy" - PACKSUFFIX="-web" - # shellcheck disable=SC2086 - if echo $OPTS | grep -q "_WITH_BUNDLE"; then - sed -i "s/^\(_WITH_BUNDLE\) *= *.*$/\1 = True/" "$PYSRC" - fi - echo "build: including webspy dir in pyinstaller bundle..." -fi - if [ ! -s requirements.txt ] || [ -z "$REQS" ]; then echo "build: WARNING missing requirements" fi @@ -44,21 +35,24 @@ for a in "$@"; do ARGS="--hidden-import flask" REQS="$REQS flask" fi + if echo "$a" | grep -q "_WITH_BUNDLE"; then + echo "build: including webspy dir in pyinstaller bundle..." + ARGS=" $ARGS --add-data webspy:./webspy " + PACKFILES="../spy.conf spy" + PACKSUFFIX="-web" + fi for o in $OPTS; do if echo "$a" | grep -q "$o"; then if grep -Eiq "^$a *= *false$" "$PYSRC"; then sed -i 's/^\('"$a"'\) *= *.*$/\1 = True/' "$PYSRC" && - echo "Set $a to: True" + echo "build: set $a to True" fi fi done done # disable flask dev -# shellcheck disable=SC2086 -if echo $OPTS | grep -q "_WITH_FLASK"; then - sed -i "s/^\(FLASK_OPTIONS\['debug']\) *= *.*$/\1 = False/" "$PYSRC" -fi +sed -i "s/^\(FLASK_OPTIONS\['debug']\) *= *.*$/\1 = False/" "$PYSRC" echo "build: creating one single executable file..." @@ -78,25 +72,18 @@ command -V python3 || { echo "build: ERROR python3 not found" exit 1 } -#command -V bc || { echo "ERROR: bc not found"; exit 1; } PYVER="$(python3 --version | sed 's/.* \([0-9]\.[0-9]\{1,2\}\).*/\1/' | grep -E '^[0-9.]+$' || echo 0)" PYVER_OK=0 -#if command -V bc >/dev/null 2>&1; then -# if [ "$(echo "$PYVER >= $PYREQVER" | bc)" -eq 1 ]; then -# PYVER_OK=1 -# fi -#else - PYVER_MAY="$(echo "$PYVER" | sed 's/\([0-9]\)\.[0-9]\+/\1/')" - PYVER_MIN="$(echo "$PYVER" | sed 's/[0-9]\.\([0-9]\+\)/\1/')" - PYREQVER_MAY="$(echo $PYREQVER | sed 's/\([0-9]\)\.[0-9]/\1/')" - PYREQVER_MIN="$(echo $PYREQVER | sed 's/[0-9]\.\([0-9]\+\)/\1/')" - if [ "$PYVER_MAY" -gt "$PYREQVER_MAY" ]; then - PYVER_OK=1 - elif [ "$PYVER_MAY" -eq "$PYREQVER_MAY" ] && [ "$PYVER_MIN" -ge "$PYREQVER_MIN" ]; then - PYVER_OK=1 - fi -#fi +PYVER_MAY="$(echo "$PYVER" | sed 's/\([0-9]\)\.[0-9]\+/\1/')" +PYVER_MIN="$(echo "$PYVER" | sed 's/[0-9]\.\([0-9]\+\)/\1/')" +PYREQVER_MAY="$(echo $PYREQVER | sed 's/\([0-9]\)\.[0-9]/\1/')" +PYREQVER_MIN="$(echo $PYREQVER | sed 's/[0-9]\.\([0-9]\+\)/\1/')" +if [ "$PYVER_MAY" -gt "$PYREQVER_MAY" ]; then + PYVER_OK=1 +elif [ "$PYVER_MAY" -eq "$PYREQVER_MAY" ] && [ "$PYVER_MIN" -ge "$PYREQVER_MIN" ]; then + PYVER_OK=1 +fi if [ "$PYVER_OK" -eq 1 ]; then echo "build: python version is OK (need Python ${PYREQVER}+ got v${PYVER})" else @@ -141,3 +128,9 @@ if [ "$PYINSTALLER" -eq 1 ]; then exit 1 fi fi + +# reset to defaults +sed -i "s/^\(_WITH_GEOIP\) *= *.*$/\1 = False/" "$PYSRC" +sed -i "s/^\(_WITH_HTTPD\) *= *.*$/\1 = True/" "$PYSRC" +sed -i "s/^\(_WITH_FLASK\) *= *.*$/\1 = True/" "$PYSRC" +sed -i "s/^\(_WITH_BUNDLE\) *= *.*$/\1 = False/" "$PYSRC" diff --git a/docker-build-distro.sh b/docker-run-build.sh similarity index 80% rename from docker-build-distro.sh rename to docker-run-build.sh index c1285de..b4f046e 100755 --- a/docker-build-distro.sh +++ b/docker-run-build.sh @@ -1,7 +1,15 @@ #!/bin/sh -# from github workflow -# runs latest docker image by default +# build in local docker container, uses same cmds as github workblow + +# run latest images by default +TAG="${2:-latest}" + +# add post build cmds +CLEANUP=" + test -e venv/pyvenv.cfg && rm -rf venv + test -e build/spy && rm -rf build +" UBUNTU=" python3 -m venv venv && . venv/bin/activate @@ -19,7 +27,7 @@ DEBIAN=" pip3 install --force -r requirements.txt pip3 install --force pyinstaller sysv-ipc geoip2 flask ./build.sh && mkdir -p ./build-debian && mv -f *.tar.gz *.sha512sum ./build-debian - WEBSPY=1 ./build.sh && mkdir -p ./build-debian && mv -f *.tar.gz *.sha512sum ./build-debian + ./build.sh _WITH_BUNDLE && mkdir -p ./build-debian && mv -f *.tar.gz *.sha512sum ./build-debian " _CENTOS_EOL=" yum install -y gcc python3-devel python3-pip python3-virtualenv @@ -36,7 +44,7 @@ ALPINE=" pip3 install -r requirements.txt pip3 install pyinstaller sysv-ipc geoip2 flask ./build.sh && mkdir -p ./build-alpine && mv -f *.tar.gz *.sha512sum ./build-alpine - WEBSPY=1 ./build.sh && mkdir -p ./build-alpine && mv -f *.tar.gz *.sha512sum ./build-alpine + ./build.sh _WITH_BUNDLE && mkdir -p ./build-alpine && mv -f *.tar.gz *.sha512sum ./build-alpine " RHEL=" dnf install -y gcc python3-devel python3-pip @@ -54,12 +62,13 @@ func_docker_run() { if [ -n "$DEBUG" ] && [ "$DEBUG" -eq 1 ]; then docker run -it --workdir /build -v "$PWD:/build" "$image" sh else - docker run --rm --workdir /build -v "$PWD:/build" "$image" sh -c "$*" + docker run --rm --workdir /build -v "$PWD:/build" "$image" sh -c " + $* + $CLEANUP + " fi } -TAG="${2:-latest}" - # shellcheck disable=SC2046 case $1 in ubuntu) func_docker_run "ubuntu:$TAG" "$UBUNTU" ;; @@ -69,5 +78,5 @@ case $1 in alma) func_docker_run "almalinux:$TAG" "$RHEL" ;; rocky) func_docker_run "rockylinux:$TAG" "$RHEL" ;; alpine) func_docker_run "alpine:$TAG" "$ALPINE" ;; - *) echo "USAGE: $0" $(grep -Pow ' \K[a-z-]+\)' docker-build-distro.sh | cut -d')' -f1) ;; + *) printf "USAGE: %s <%s>\n" "$0" "$(grep -Pow ' \K[a-z-]+\)' "$0" | sed 's/)//g' | sed ':a;N;$!ba;s/\n/|/g')" ;; esac diff --git a/spy.py b/spy.py index 726f388..a269606 100755 --- a/spy.py +++ b/spy.py @@ -1332,11 +1332,9 @@ def create_app() -> object: static_path = os.path.join(SCRIPT_DIR, 'webspy/static/') # if webspy dir does not exist, use incl dir from bundle if PYINSTALLER and _WITH_BUNDLE: - try: - os.path.isdir(tmpl_path) - os.path.isdir(static_path) - except OSError: + if not os.path.isdir(tmpl_path): tmpl_path = os.path.join(BUNDLE_DIR, 'webspy/templates/') + if not os.path.isdir(static_path): static_path = os.path.join(BUNDLE_DIR, 'webspy/static/') app = flask.Flask(__name__, template_folder=tmpl_path, static_folder=static_path, static_url_path='/static') app.secret_key = 'SECRET_KEY' diff --git a/webspy/templates/js/spy.js b/webspy/templates/js/spy.js index 0e71741..7408e6e 100644 --- a/webspy/templates/js/spy.js +++ b/webspy/templates/js/spy.js @@ -1,3 +1,7 @@ +// spy.js: used by webspy + +// this is a flask template so url_users and url_totals get set + var timeout_id const debug = false; const div_users = document.getElementById("include_spy_users"); @@ -6,15 +10,22 @@ const div_response = document.getElementById("spy_api_result"); const url_users = {{ url_for('webspy', route='users') | tojson }}; const url_totals = {{ url_for('webspy', route='totals') | tojson }}; -function set_norefresh() { - setTimeout(() => { - clearTimeout(timeout_id) - }, 12000); +// stop refreshing + +function set_norefresh(delay_ms = 6000) { if (document.getElementById('show_info')) { - document.getElementById('show_info').innerText = ('autorefresh: off (reload page to re-enable)'); + document.getElementById('show_info').innerText = ('autorefresh: stopping...'); } + setTimeout(() => { + clearTimeout(timeout_id) + if (document.getElementById('show_info')) { + document.getElementById('show_info').innerText = ('autorefresh: off (reload page to re-enable)'); + } + }, delay_ms); } +// user: show, kick, ... + function api_call(endpoint, username) { fetch(encodeURI(`${endpoint}/${username}`), { method: "GET", @@ -34,6 +45,9 @@ function api_call(endpoint, username) { div_response.style = "display:none;" }, 8000); } + +// reverse sort checkbox + let spy_params = new URLSearchParams(window.location.search); let el = document.getElementById('sort_rev') ? document.getElementById('sort_rev') : null let p = spy_params.get('sort_rev') @@ -43,6 +57,8 @@ if (el && p == "") { el.checked = true; } +// main refresh div loop + (function loop() { timeout_id = window.setTimeout(() => { let spy_params = new URLSearchParams(window.location.search); @@ -63,9 +79,13 @@ if (el && p == "") { loop() }, 1000); })() + if (debug) { console.log(`post loop timeout_id=${timeout_id}`) } + +// theme switcher + const toggleSwitch = document.querySelector('.theme-switch input[type="checkbox"]'); toggleSwitch.addEventListener('change', switchTheme, false); @@ -79,10 +99,11 @@ function switchTheme(e) { localStorage.setItem('theme', 'light'); } } + const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null; + if (currentTheme) { document.documentElement.setAttribute('data-theme', currentTheme); - if (currentTheme === 'dark') { toggleSwitch.checked = true; }