diff --git a/.dockerignore b/.dockerignore index 55fe62a70..a587de477 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,7 +7,6 @@ # Application docker docs -installation shared # webapp diff --git a/.flake8 b/.flake8 index d11edbe8a..0c2cd276e 100644 --- a/.flake8 +++ b/.flake8 @@ -15,7 +15,7 @@ per-file-ignores = count = True max-complexity = 12 statistics = True -filename = *.py,*.py.* +filename = *.py extend-exclude = # Ignore all scratch development directories scratch*, diff --git a/.github/ISSUE_TEMPLATE/bug_template.md b/.github/ISSUE_TEMPLATE/bug_template.md index 1be942778..508cf50b9 100644 --- a/.github/ISSUE_TEMPLATE/bug_template.md +++ b/.github/ISSUE_TEMPLATE/bug_template.md @@ -50,7 +50,7 @@ Otherwise the output of `cat /etc/os-release` i.e. `master` the following command will help with that -`cd /home/pi/RPi-Jukebox-RFID/ && git status | head -2` +`cd ~/RPi-Jukebox-RFID/ && git status | head -2` --> ### Installscript diff --git a/.github/workflows/pythonpackage_future3.yml b/.github/workflows/pythonpackage_future3.yml index 48e27b15d..3834d83db 100644 --- a/.github/workflows/pythonpackage_future3.yml +++ b/.github/workflows/pythonpackage_future3.yml @@ -6,13 +6,11 @@ on: - 'future3/**' paths: - '**.py' - - '**.py.*' pull_request: branches: - 'future3/**' paths: - '**.py' - - '**.py.*' jobs: build: @@ -21,7 +19,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 @@ -41,8 +39,27 @@ jobs: pip3 install -r src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt pip3 install -r src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt pip3 install -r src/jukebox/components/rfid/hardware/rc522_spi/requirements.txt + - name: Run pytest + run: | + ./run_pytest.sh --cov --cov-report xml + - name: Report to Coveralls (parallel) + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: coverage.xml + format: cobertura + parallel: true - name: Lint with flake8 run: | pip3 install flake8 # Stop the build if linting fails ./run_flake8.sh + + finish: + needs: build + runs-on: ubuntu-latest + steps: + - name: Close parallel build + uses: coverallsapp/github-action@v2 + with: + parallel-finished: true diff --git a/.github/workflows/test_docker_debian_codename_sub_v3.yml b/.github/workflows/test_docker_debian_codename_sub_v3.yml new file mode 100644 index 000000000..dde60cb2a --- /dev/null +++ b/.github/workflows/test_docker_debian_codename_sub_v3.yml @@ -0,0 +1,186 @@ +name: Subworkflow Test Install Scripts Debian V3 + +on: + workflow_call: + inputs: + debian_codename: + required: true + type: string + platform: + required: true + type: string + docker_image_name: + required: false + type: string + default: rpi-jukebox-rfid-v3 + cache_scope: + required: false + type: string + default: ${{ github.ref }}-test-debian-v3 + local_registry_port: + required: false + type: number + default: 5000 + runs_on: + required: false + type: string + default: ubuntu-latest + +env: + TEST_USER_NAME: testuser + TEST_USER_GROUP: testusergroup + +# let only one instance run the test so cache is not corrupted. +# cancel already running instances as only the last run will be relevant +concurrency: + group: ${{ inputs.cache_scope }}-${{ inputs.debian_codename }}-${{ inputs.platform }} + cancel-in-progress: true + +jobs: + + # Build container for test execution + build: + runs-on: ${{ inputs.runs_on }} + + outputs: + cache_key: ${{ steps.vars.outputs.cache_key }} + image_file_name: ${{ steps.vars.outputs.image_file_name }} + image_tag_name: ${{ steps.vars.outputs.image_tag_name }} + + # create local docker registry to use locally build images + services: + registry: + image: registry:2 + ports: + - ${{ inputs.local_registry_port }}:5000 + + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + with: + # network=host driver-opt needed to push to local registry + driver-opts: network=host + + - name: Set Output pre-vars + id: pre-vars + env: + DEBIAN_CODENAME: ${{ inputs.debian_codename }} + DOCKER_IMAGE_NAME: ${{ inputs.docker_image_name }} + CACHE_SCOPE: ${{ inputs.cache_scope }} + PLATFORM: ${{ inputs.platform }} + run: | + PLATFORM=${PLATFORM////_} + echo "image_tag_name=${{ env.DOCKER_IMAGE_NAME }}:${{ env.DEBIAN_CODENAME }}-${PLATFORM}-test" >> $GITHUB_OUTPUT + echo "image_file_name=${{ env.DOCKER_IMAGE_NAME }}-${{ env.DEBIAN_CODENAME }}-${PLATFORM}.tar" >> $GITHUB_OUTPUT + echo "cache_scope=${{ env.CACHE_SCOPE }}-${{ env.DEBIAN_CODENAME }}-${PLATFORM}" >> $GITHUB_OUTPUT + + - name: Set Output vars + id: vars + env: + LOCAL_REGISTRY_PORT: ${{ inputs.local_registry_port }} + run: | + echo "image_tag_name=${{ steps.pre-vars.outputs.image_tag_name }}" >> $GITHUB_OUTPUT + echo "image_tag_name_local_base=localhost:${{ env.LOCAL_REGISTRY_PORT }}/${{ steps.pre-vars.outputs.image_tag_name }}-base" >> $GITHUB_OUTPUT + echo "image_file_name=${{ steps.pre-vars.outputs.image_file_name }}" >> $GITHUB_OUTPUT + echo "image_file_path=./${{ steps.pre-vars.outputs.image_file_name }}" >> $GITHUB_OUTPUT + echo "cache_scope=${{ steps.pre-vars.outputs.cache_scope }}" >> $GITHUB_OUTPUT + echo "cache_key=${{ steps.pre-vars.outputs.cache_scope }}-${{ github.sha }}#${{ github.run_attempt }}" >> $GITHUB_OUTPUT + + # Build base image for debian version name. Layers will be cached and image pushes to local registry + - name: Build Image - Base + uses: docker/build-push-action@v5 + with: + context: . + load: false + push: true + file: ./ci/ci-debian.Dockerfile + target: test-code + platforms: ${{ inputs.platform }} + tags: ${{ steps.vars.outputs.image_tag_name_local_base }} + cache-from: type=gha,scope=${{ steps.vars.outputs.cache_scope }} + cache-to: type=gha,mode=max,scope=${{ steps.vars.outputs.cache_scope }} + build-args: | + DEBIAN_CODENAME=${{ inputs.debian_codename }} + USER_NAME=${{ env.TEST_USER_NAME }} + USER_GROUP=${{ env.TEST_USER_GROUP }} + GIT_BRANCH=${{ github.head_ref || github.ref_name }} + GIT_USER=${{ github.event.pull_request.head.user.login || github.repository_owner }} + + # Build new image with updates packages based on base image. Layers will NOT be chached. Result is written to file. + - name: Build Image - Update + uses: docker/build-push-action@v5 + with: + context: . + load: false + push: false + file: ./ci/ci-debian.Dockerfile + target: test-update + platforms: ${{ inputs.platform }} + tags: ${{ steps.vars.outputs.image_tag_name }} + cache-from: type=gha,scope=${{ steps.vars.outputs.cache_scope }} + # DON'T use 'cache-to' here as the layer is then cached and this build would be useless + outputs: type=docker,dest=${{ steps.vars.outputs.image_file_path }} + build-args: | + BASE_TEST_IMAGE=${{ steps.vars.outputs.image_tag_name_local_base }} + + - name: Artifact Upload Docker Image + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.vars.outputs.image_file_name }} + path: ${{ steps.vars.outputs.image_file_path }} + retention-days: 1 + + + # Run tests with build image + test: + needs: [build] + runs-on: ${{ inputs.runs_on }} + + strategy: + fail-fast: false + matrix: + test_script: ['run_install_common.sh', 'run_install_faststartup.sh', 'run_install_webapp_local.sh', 'run_install_webapp_download.sh', 'run_install_libzmq_local.sh'] + + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + + - name: Artifact Download Docker Image + uses: actions/download-artifact@v3 + with: + name: ${{ needs.build.outputs.image_file_name }} + + - name: Load Docker Image + run: | + docker load --input ${{ needs.build.outputs.image_file_name }} + + # Run test + - name: Run Test ${{ inputs.debian_codename }}-${{ env.TEST_USER_NAME }}-${{ matrix.test_script }} + uses: tj-actions/docker-run@v2 + with: + image: ${{ needs.build.outputs.image_tag_name }} + options: --platform ${{ inputs.platform }} --user ${{ env.TEST_USER_NAME }} --init + name: ${{ matrix.test_script }} + args: | + ./${{ matrix.test_script }} + + # cleanup after test execution + cleanup: + # run only if tests didn't fail: keep the artifact to make job reruns possible + if: ${{ !failure() }} + needs: [build, test] + runs-on: ${{ inputs.runs_on }} + + steps: + - name: Artifact Delete Docker Image + uses: geekyeggo/delete-artifact@v2 + with: + name: ${{ needs.build.outputs.image_file_name }} diff --git a/.github/workflows/test_docker_debian_v3.yml b/.github/workflows/test_docker_debian_v3.yml new file mode 100644 index 000000000..6f90048ec --- /dev/null +++ b/.github/workflows/test_docker_debian_v3.yml @@ -0,0 +1,33 @@ +name: Test Install Scripts Debian v3 + +on: + schedule: + # run at 17:00 every sunday + - cron: '0 17 * * 0' + push: + pull_request: + # The branches below must be a subset of the branches above + branches: [ future3/develop ] + +# let only one instance run the test so cache is not corrupted. +# cancel already running instances as only the last run will be relevant +concurrency: + group: ${{ github.ref }}-test-debian-v3 + cancel-in-progress: true + +jobs: + + # Build container and run tests. Duplication of job intended for better visualization. + run_bookworm_armv7: + name: 'bookworm armv7' + uses: ./.github/workflows/test_docker_debian_codename_sub_v3.yml + with: + debian_codename: 'bookworm' + platform: linux/arm/v7 + + run_bullseye_armv7: + name: 'bullseye armv7' + uses: ./.github/workflows/test_docker_debian_codename_sub_v3.yml + with: + debian_codename: 'bullseye' + platform: linux/arm/v7 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3bc03705..dbf12f84d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ For bug fixes and improvements just open an issue or PR as described below. If y * By default this will get you to the `future3/main` branch. You will move to the `future3/develop` branch, do this: ~~~bash -cd /home/pi/RPi-Jukebox-RFID +cd ~/RPi-Jukebox-RFID git checkout future3/develop git fetch origin git reset --hard origin/future3/develop @@ -122,7 +122,7 @@ If you touched *any* Python file (even if only for fixing spelling errors), run It contains out setup file. ~~~bash -cd /home/pi/RPi-Jukebox-RFID +cd ~/RPi-Jukebox-RFID ./run_flake8.sh ~~~ @@ -135,7 +135,7 @@ Tests are very few at the moment, but it cannot hurt to run them. If you have te them. ~~~bash -cd /home/pi/RPi-Jukebox-RFID/ +cd ~/RPi-Jukebox-RFID/ ./run_pytest.sh ~~~ diff --git a/README.md b/README.md index 8b494698d..5824bebbb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # RFID Jukebox Version 3 (aka future3) +![GitHub last commit (branch)](https://img.shields.io/github/last-commit/MiczFlor/RPi-Jukebox-RFID/future3/develop) + +[![Test Install Scripts Debian v3](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/test_docker_debian_v3.yml/badge.svg?branch=future3%2Fdevelop)](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/test_docker_debian_v3.yml) [![Python + Docs Checks and Tests](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/pythonpackage_future3.yml/badge.svg?branch=future3%2Fdevelop)](https://github.com/MiczFlor/RPi-Jukebox-RFID/actions/workflows/pythonpackage_future3.yml) [![Coverage Status](https://coveralls.io/repos/github/MiczFlor/RPi-Jukebox-RFID/badge.svg?branch=future3/develop)](https://coveralls.io/github/MiczFlor/RPi-Jukebox-RFID?branch=future3/develop) + +[![Matrix chat](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#phoniebox_community:gitter.im) + ## What is this? The exiting, new **Version 3** of the RPi Jukebox RFID. A complete re-write of the Jukebox. @@ -13,8 +19,4 @@ The documentation can be found [here](./documentation/README.md) ## Installation? -Run the following one-liner in a shell and follow the instructions - -~~~bash -cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/develop/installation/install-jukebox.sh) -~~~ +[Install Phoniebox software](documentation/builders/installation.md#install-phoniebox-software) diff --git a/ci/ci-debian.Dockerfile b/ci/ci-debian.Dockerfile new file mode 100644 index 000000000..5228d83d5 --- /dev/null +++ b/ci/ci-debian.Dockerfile @@ -0,0 +1,88 @@ +# Base Target to build and install all needed base configuration and packages. Specifie the needed platform with the docker '--platform XXX' option +ARG DEBIAN_CODENAME=bookworm +ARG BASE_TEST_IMAGE=test-code +FROM debian:${DEBIAN_CODENAME}-slim as base +ARG DEBIAN_CODENAME + +ENV TERM=xterm DEBIAN_FRONTEND=noninteractive +ENV CI_RUNNING=true + +# create RPi configs to test installation +RUN touch /boot/config.txt +RUN echo "logo.nologo" > /boot/cmdline.txt + +RUN echo "--- install packages (1) ---" \ + && apt-get update \ + && apt-get -y install \ + apt-utils \ + curl \ + gnupg \ + && echo "--- add sources ---" \ + && curl -fsSL http://raspbian.raspberrypi.org/raspbian.public.key | gpg --dearmor > /usr/share/keyrings/raspberrypi-raspbian-keyring.gpg \ + && curl -fsSL http://archive.raspberrypi.org/debian/raspberrypi.gpg.key | gpg --dearmor > /usr/share/keyrings/raspberrypi-archive-debian-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/raspberrypi-raspbian-keyring.gpg] http://raspbian.raspberrypi.org/raspbian/ ${DEBIAN_CODENAME} main contrib non-free rpi" > /etc/apt/sources.list.d/raspi.list \ + && echo "deb [signed-by=/usr/share/keyrings/raspberrypi-archive-debian-keyring.gpg] http://archive.raspberrypi.org/debian/ ${DEBIAN_CODENAME} main" >> /etc/apt/sources.list.d/raspi.list \ + && echo "--- install packages (2) ---" \ + && apt-get update \ + && apt-get -y upgrade \ + && apt-get -y install \ + build-essential \ + iproute2 \ + openssh-client \ + sudo \ + systemd \ + wireless-tools \ + wget \ + wpasupplicant \ + && rm -rf /var/lib/apt/lists/* + +# Set NonInteractive for sudo usage in container. 'sudo' package needed +RUN echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections +# ------ + +# Base Target for setting up a test user. user can be selected with the docker '--user YYY' option +FROM base as test-user +ARG USER_NAME=pi +ARG USER_GROUP=$USER_NAME +ARG USER_ID=1000 + +ENV TEST_USER_GROUP=test +RUN groupadd --gid 1002 $TEST_USER_GROUP + +RUN groupadd --gid 1000 $USER_GROUP \ + && useradd -u $USER_ID -g $USER_GROUP -G sudo,$TEST_USER_GROUP -d /home/$USER_NAME -m -s /bin/bash -p '$1$iV7TOwOe$6ojkJQXyEA9bHd/SqNLNj0' $USER_NAME \ + && echo "$USER_NAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USER_NAME + +ENV XDG_RUNTIME_DIR=/run/user/$USER_ID DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$USER_ID/bus +# ------ + +# Target for adding envs and scripts from the repo to test installation +FROM test-user as test-code +ARG GIT_BRANCH +ARG GIT_USER + +ENV GIT_BRANCH=$GIT_BRANCH GIT_USER=$GIT_USER + +COPY --chown=root:$TEST_USER_GROUP --chmod=770 packages-core.txt ./ + +RUN echo "--- install internal packages ---" \ + && apt-get update \ + && sed 's/#.*//g' packages-core.txt | xargs apt-get -y install \ + && rm -rf /var/lib/apt/lists/* + +ENV INSTALL_SCRIPT_PATH=/code + +WORKDIR ${INSTALL_SCRIPT_PATH} +COPY --chown=root:$TEST_USER_GROUP --chmod=770 installation/install-jukebox.sh ./ + +WORKDIR ${INSTALL_SCRIPT_PATH}/tests +COPY --chown=root:$TEST_USER_GROUP --chmod=770 ci/installation/*.sh ./ +# ------ + + +# Target for applying latest updates (should not be cached!) +FROM $BASE_TEST_IMAGE as test-update +RUN apt-get update \ + && apt-get -y upgrade \ + && rm -rf /var/lib/apt/lists/* +# ------ diff --git a/ci/installation/run_install_common.sh b/ci/installation/run_install_common.sh new file mode 100644 index 000000000..102c71aa4 --- /dev/null +++ b/ci/installation/run_install_common.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for a common installation path. Including autohotspot + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +export ENABLE_WEBAPP_PROD_DOWNLOAD=true +# Run installation (in interactive mode) +# y - start setup +# n - use static ip +# n - deactivate ipv6 +# y - setup autohotspot +# n - use custom password +# n - deactivate bluetooth +# n - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# y - setup samba +# y - setup webapp +# n - setup kiosk mode +# - - install node (forced WebApp Download) +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +n +n +y +n +n +n +n +y +y +n +n +' diff --git a/ci/installation/run_install_faststartup.sh b/ci/installation/run_install_faststartup.sh new file mode 100644 index 000000000..46cda25ec --- /dev/null +++ b/ci/installation/run_install_faststartup.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for disabling features (suggestions for faststartup). Skips installing all additionals. + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +# Run installation (in interactive mode) +# y - start setup +# y - use static ip +# y - deactivate ipv6 +# n - setup autohotspot +# y - deactivate bluetooth +# y - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# n - setup samba +# n - setup webapp +# - - setup kiosk mode (only with webapp = y) +# - - install node (only with webapp = y) +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +y +y +n +y +y +n +n +n +n +' diff --git a/ci/installation/run_install_libzmq_local.sh b/ci/installation/run_install_libzmq_local.sh new file mode 100644 index 000000000..20f246ff8 --- /dev/null +++ b/ci/installation/run_install_libzmq_local.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for the libzmq local build (no precompiled download) + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +export BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=true +# Run installation (in interactive mode) +# y - start setup +# n - use static ip +# n - deactivate ipv6 +# n - setup autohotspot +# n - deactivate bluetooth +# n - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# n - setup samba +# n - setup webapp +# - - setup kiosk mode (only with webapp = y) +# - - install node (only with webapp = y) +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +n +n +n +n +n +n +n +n +n +' diff --git a/ci/installation/run_install_webapp_download.sh b/ci/installation/run_install_webapp_download.sh new file mode 100644 index 000000000..69496e8e4 --- /dev/null +++ b/ci/installation/run_install_webapp_download.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for the WebApp (download) and dependent features path. + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +export ENABLE_WEBAPP_PROD_DOWNLOAD=true +# Run installation (in interactive mode) +# y - start setup +# n - use static ip +# n - deactivate ipv6 +# n - setup autohotspot +# n - deactivate bluetooth +# n - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# n - setup samba +# y - setup webapp +# y - setup kiosk mode +# - - install node (forced webapp download) +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +n +n +n +n +n +n +n +y +y +n +' diff --git a/ci/installation/run_install_webapp_local.sh b/ci/installation/run_install_webapp_local.sh new file mode 100644 index 000000000..d4f122fd5 --- /dev/null +++ b/ci/installation/run_install_webapp_local.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# Install Phoniebox and test it +# Used e.g. for tests on Docker + +# Objective: +# Test for the WebApp (build locally) and dependent features path. + +SOURCE="${BASH_SOURCE[0]}" +SCRIPT_DIR="$(dirname "$SOURCE")" +LOCAL_INSTALL_SCRIPT_PATH="${INSTALL_SCRIPT_PATH:-${SCRIPT_DIR}/../../installation}" +LOCAL_INSTALL_SCRIPT_PATH="${LOCAL_INSTALL_SCRIPT_PATH%/}" + +export ENABLE_WEBAPP_PROD_DOWNLOAD=false +# Run installation (in interactive mode) +# y - start setup +# n - use static ip +# n - deactivate ipv6 +# n - setup autohotspot +# n - deactivate bluetooth +# n - disable on-chip audio +# - - mpd overwrite config (only with existing installation) +# n - setup rfid reader +# n - setup samba +# y - setup webapp +# y - setup kiosk mode +# y - install node +# n - reboot + +"${LOCAL_INSTALL_SCRIPT_PATH}/install-jukebox.sh" <<< 'y +n +n +n +n +n +n +n +y +y +y +n +' diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b2216894e..1679bbeb5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -40,6 +40,7 @@ services: restart: unless-stopped tty: true volumes: + - ../src/jukebox:/root/RPi-Jukebox-RFID/src/jukebox - ../shared:/root/RPi-Jukebox-RFID/shared - ./config/docker.pulse.mpd.conf:/root/.config/mpd/mpd.conf command: python run_jukebox.py diff --git a/docker/jukebox.Dockerfile b/docker/jukebox.Dockerfile index 5e7a08915..f842a7394 100644 --- a/docker/jukebox.Dockerfile +++ b/docker/jukebox.Dockerfile @@ -1,7 +1,5 @@ FROM debian:bullseye-slim -# Prepare Raspberry Pi like environment - # These are only dependencies that are required to get as close to the # Raspberry Pi environment as possible. RUN apt-get update && apt-get install -y \ @@ -36,7 +34,18 @@ WORKDIR ${HOME} COPY --chown=${USER}:${USER} . ${INSTALLATION_PATH}/ RUN pip install --no-cache-dir -r ${INSTALLATION_PATH}/requirements.txt -RUN pip install pyzmq + +ENV ZMQ_TMP_DIR libzmq +ENV ZMQ_VERSION 4.3.5 +ENV ZMQ_PREFIX /usr/local + +RUN [ "$(uname -m)" = "aarch64" ] && ARCH="arm64" || ARCH="$(uname -m)"; \ + wget https://github.com/pabera/libzmq/releases/download/v${ZMQ_VERSION}/libzmq5-${ARCH}-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz; \ + tar -xzf libzmq.tar.gz -C ${ZMQ_PREFIX}; \ + rm -f libzmq.tar.gz; + +RUN export ZMQ_PREFIX=${PREFIX} && export ZMQ_DRAFT_API=1 +RUN pip install -v --no-binary pyzmq --pre pyzmq EXPOSE 5555 5556 diff --git a/documentation/builders/audio.md b/documentation/builders/audio.md index 07d24e40f..4fd82e457 100644 --- a/documentation/builders/audio.md +++ b/documentation/builders/audio.md @@ -10,17 +10,17 @@ Stream transfer happens on user input or automatically on the connection of an a This is mainly targeted at Bluetooth Headsets/Speakers. Audio outputs run via PulseAudio and the basic configuration should be easy. -There is a [configuration tool](../developers/coreapps.md#run_configure_audio.py), +There is a [configuration tool](../developers/coreapps.md#Audio), to setup the configuration for the Jukebox Core App. -To set up the audio +### To set up the audio 1. Follow the setup steps according to your sound card 2. Check that the sound output works [as described below](audio.md#checking-system-sound-output) -3. Run the the tool [run_configure_audio](../developers/coreapps.md#run_configure_audio.py) +3. Run the [audio configuration tool](../developers/coreapps.md#Audio) 4. [Fine-tune audio parameters](audio.md#additional-options) -## Checking system sound output +#### Checking system sound output Run the following steps in a console: @@ -31,7 +31,7 @@ $ pactl list sinks short 1 bluez_sink.C4_FB_20_63_CO_FE.a2dp_sink module-bluez5-device.c s16le 2ch 44100Hz # Set the default sink (this will be reset at reboot) -$ pactl set-default-sink sink_name +$ pactl set-default-sink # Check default sink is correctly set $ pactl info @@ -50,10 +50,10 @@ You can also try different PulseAudio sinks without setting the default sink. In volume level for this sink: ```bash -$ paplay -d sink_name /usr/share/sounds/alsa/Front_Center.wav +$ paplay -d /usr/share/sounds/alsa/Front_Center.wav ``` -# Bluetooth +## Bluetooth Bluetooth setup consists of three steps @@ -134,8 +134,4 @@ You are, of course, free to modify the PulseAudio configuration to your needs. R 1. [PulseAudio Documentation](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User) 2. [PulseAudio Examples](https://wiki.archlinux.org/title/PulseAudio/Examples) -In this case, run the configuration tool with below parameter to avoid touching the PulseAudio configuration file. - -```bash -$ ./run_configure_audio.py --ro_pulse -``` +In this case, run the [audio configuration tool](../developers/coreapps.md#Audio) with the parameter `--ro_pulse` to avoid touching the PulseAudio configuration file. diff --git a/documentation/builders/autohotspot.md b/documentation/builders/autohotspot.md index 7f064cb53..69e6f4d6a 100644 --- a/documentation/builders/autohotspot.md +++ b/documentation/builders/autohotspot.md @@ -28,7 +28,7 @@ After connecting to the `Phoniebox_Hotspot` you are able to connect via ssh to your Jukebox ``` bash -ssh pi@10.0.0.5 +ssh @10.0.0.5 ``` ## Changing basic configuration of the hotspot diff --git a/documentation/builders/concepts.md b/documentation/builders/concepts.md index c08a33caa..37c13db89 100644 --- a/documentation/builders/concepts.md +++ b/documentation/builders/concepts.md @@ -16,10 +16,10 @@ The Remote Procedure Call (RPC) server allows remotely triggering actions (e.g., Why should you care? Because we use the same protocol when triggering actions from other inputs like a card swipe, a GPIO button press, etc. How that works is described in [RPC Commands](rpc-commands.md). -We also have a tool to send RPC commands to the running Jukebox application: [run_rpc_tool.py](../developers/coreapps.md#run_rpc_toolpy) +We also have a [tool to send RPC commands](../developers/coreapps.md#RPC) to the running Jukebox application. ## Publishing Message Queue The Publishing Message Queue is the complementary part to the RPC where the core application publishes its status and status updates. As a user, you need not worry about it. -If you want to interact with the Jukebox from your own application, this is where you get the current state from. Details about the protocol can be found here (TBD). A sniffer tool exists which listens and prints the incoming status messages: [run_publicity_sniffer.py](../developers/coreapps.md#run_publicity_snifferpy). +If you want to interact with the Jukebox from your own application, this is where you get the current state from. Details about the protocol can be found here (TBD). A [sniffer tool](../developers/coreapps.md#Publicity-Sniffer) exists which listens and prints the incoming status messages. diff --git a/documentation/builders/configuration.md b/documentation/builders/configuration.md index e0240ddbe..f09e8098d 100644 --- a/documentation/builders/configuration.md +++ b/documentation/builders/configuration.md @@ -1,13 +1,13 @@ # Jukebox Configuration -The Jukebox configuration is managed by set of files located in `../shared/settings`. +The Jukebox configuration is managed by a set of files located in `shared/settings`. Some configuration changes can be made through the WebUI and take immediate effect. The majority of configuration options is only available by editing the config files - *when the service is not running!* Don't fear (overly), they contain commentaries. -For several aspects we have :ref:`developer/coreapps:Configuration Tools` and detailed guides: +For several aspects we have [configuration tools](../developers/coreapps.md#configuration-tools) and detailed guides: * [Audio Configuration](./audio.md#audio-configuration) * [RFID Reader Configuration](../developers/rfid/basics.md#reader-configuration) @@ -24,7 +24,8 @@ $ systemctl --user stop jukebox-daemon $ nano ./shared/settings/jukebox.yaml # Start Jukebox in console and check the log output (optional) -$ ./src/jukebox/run_jukebox.py +$ cd src/jukebox +$ ./run_jukebox.py # and if OK, press Ctrl-C and restart the service # Restart the service @@ -36,5 +37,6 @@ This could be useful if you want your Jukebox to only allow a lower volume when at night time when there is time to go to bed :-) ```bash -$./run_jukebox.py --conf ../path/to/custom/config.yaml +$ cd src/jukebox +$ ./run_jukebox.py --conf path/to/custom/config.yaml ``` diff --git a/documentation/builders/installation.md b/documentation/builders/installation.md index d0befca29..756e2a03c 100644 --- a/documentation/builders/installation.md +++ b/documentation/builders/installation.md @@ -16,7 +16,7 @@ Before you can install the Phoniebox software, you need to prepare your Raspberr * Click `Edit Settings` * Switch to the `General` tab * Provide a hostname. (When on Mac, you will be able to use it to connect via SSH) - * Username currently MUST be `pi`. Other usernames are currently not supported. + * Username * Password * Wifi * Set locale settings @@ -72,7 +72,7 @@ You will need a terminal, like PuTTY for Windows or the Terminal app for Mac to 7. Eject your SD card and insert it into your Raspberry Pi. 8. Start your Raspberry Pi by attaching a power supply. -9. Login into your Raspberry Pi, username is `pi` and password is `raspberry`. +9. Login into your Raspberry Pi If `raspberrypi.local` does not work, find out your Raspberry Pi's IP address from your router. @@ -85,22 +85,24 @@ Run the following command in your SSH terminal and follow the instructions cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/main/installation/install-jukebox.sh) ``` -This will get the latest stable release from the branch future3/main. +This will get the latest **stable release** from the branch *future3/main*. + To install directly from a specific branch and/or a different repository specify the variables like this: ```bash - cd; GIT_USER='MiczFlor' GIT_BRANCH='future3/develop' bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/develop/installation/install-jukebox.sh) ``` This will switch directly to the specified feature branch during installation. > [!NOTE] -> For all branches *except* the current Release, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See [Steps to install](../developers/development-environment.md#steps-to-install) +> For all branches *except* the current Release future3/main, you will need to build the Web App locally on the Pi. This is not part of the installation process due to memory limitation issues. See [Developer steps to install](../developers/development-environment.md#steps-to-install) If you suspect an error you can monitor the installation-process with ```bash cd; tail -f INSTALL-.log ``` + +After successful installation, continue with [configuring your Phoniebox](configuration.md). diff --git a/documentation/builders/rpc-commands.md b/documentation/builders/rpc-commands.md index f578ccd99..f98da8cb4 100644 --- a/documentation/builders/rpc-commands.md +++ b/documentation/builders/rpc-commands.md @@ -107,6 +107,6 @@ You will find some more examples the configuration of the [Card Database](card-d ## For developers -To send RPC commands for testing and debugging purpose you can use the CLI Tool [`run_rpc_tool.py`](../developers/coreapps.md#run_rpc_toolpy) +To send RPC commands for testing and debugging purpose you can use the [CLI Tool](../developers/coreapps.md#RPC). Also here is a ready-to-use decoding functions which decodes an RPC command (with or without alias) from a YAML entry:func:`jukebox.utils.decode_rpc_command`. diff --git a/documentation/builders/system.md b/documentation/builders/system.md index 5b1ce5c0c..0e2c19485 100644 --- a/documentation/builders/system.md +++ b/documentation/builders/system.md @@ -10,9 +10,8 @@ The system consists of 4. [Web UI](system.md#web-ui) which is served through an Nginx web server 5. A set of [Configuration Tools](../developers/coreapps.md#configuration-tools) and a set of [Developer Tools](../developers/coreapps.md#developer-tools) -.. note:: The default install puts everything into the folder `/home/pi/RPi-Jukebox-RFID`. - Another folder might work, but is certainly not tested. Things are installed for the default user `pi`. Again, - another user might work, but is not tested. +.. note:: The default install puts everything into the users home folder `~/RPi-Jukebox-RFID`. + Another folder might work, but is certainly not tested. ## Music Player Daemon (MPD) @@ -71,7 +70,7 @@ Service control and service configuration file location is identical to MPD. ## Jukebox Core Service -The :ref:`developer/coreapps:Jukebox Core` runs as a *user-local* service with the name `jukebox-daemon`. +The [Jukebox Core Service](../developers/coreapps.md#Jukebox-Core) runs as a *user-local* service with the name `jukebox-daemon`. Similar to MPD, it's important that it does run as system-wide service to be able to interact with PulseAudio. The service can be controlled with the `systemctl`-command by adding the parameter `--user` @@ -102,7 +101,7 @@ Starting and stopping the service can be useful for debugging or configuration c The Web UI is served using nginx. Nginx runs as a system service. The home directory is localed at ``` -/home/pi/RPi-Jukebox-RFID/src/webapp/build +./src/webapp/build ``` The Nginx configuration is located at diff --git a/documentation/builders/troubleshooting.md b/documentation/builders/troubleshooting.md index a0618ab5d..a83384ec5 100644 --- a/documentation/builders/troubleshooting.md +++ b/documentation/builders/troubleshooting.md @@ -15,8 +15,8 @@ Debugging your setup runs in several steps ## The short answer ```bash -../shared/logs/app.log : Complete Debug Messages -../shared/logs/errors.log: Only Errors and Warnings +shared/logs/app.log : Complete Debug Messages +shared/logs/errors.log: Only Errors and Warnings ``` These files always contain the messages of the current run only. @@ -33,9 +33,9 @@ http://ip.of.your.box/logs ## The long answer: A few more details -If started without parameters, the Jukebox checks for the existence of `../shared/settings/logger.yaml` +If started without parameters, the Jukebox checks for the existence of `shared/settings/logger.yaml` and if present, uses that configuration for logging. This file is created by the installation process. -The default configuration file is also provided in `../resources/default-settings/logger.default.yaml`. +The default configuration file is also provided in `resources/default-settings/logger.default.yaml`. We use Python's logging module to provide the debug messages which is configured through this file. **We are still in the Pre-Release phase which means full debug logging is enabled by default.** @@ -47,8 +47,8 @@ The default logging config does 2 things: 1. It writes 2 log files: ```bash -../shared/logs/app.log : Complete Debug Messages -../shared/logs/errors.log : Only Errors and Warnings +shared/logs/app.log : Complete Debug Messages +shared/logs/errors.log : Only Errors and Warnings ``` 2. Prints logging messages to the console. If run as a service, only error messages are emitted to console to avoid spamming the system log files. @@ -63,11 +63,12 @@ on the console log. $ systemctl --user stop jukebox-daemon # Start the Jukebox in debug mode: +$ cd src/jukebox + # with default logger: $ ./run_jukebox.py - # or with custom logger configuration: -$ ./run_jukebox.py --logger ../path/to/logger.yaml +$ ./run_jukebox.py --logger path/to/custom/logger.yaml ``` ### Fallback configuration @@ -77,6 +78,7 @@ Attention: This only emits messages to the console and does not write to the log This is more a fallback features: ``` bash +$ cd src/jukebox $ ./run_jukebox.py -vv ``` diff --git a/documentation/builders/update.md b/documentation/builders/update.md index e09137ca3..f6dab2c91 100644 --- a/documentation/builders/update.md +++ b/documentation/builders/update.md @@ -2,6 +2,12 @@ ## Updating your Jukebox Version 3 +### Update from v3.2.1 and prior + +As there are some significant changes in the installation, a new setup on a fresh image is required. + +### General + Things on Version 3 are moving fast and you may want to keep up with recent changes. Since we are in Alpha Release stage, a fair number of fixes are expected to be committed in the near future. diff --git a/documentation/developers/README.md b/documentation/developers/README.md index 9136c5f31..6c973d6ba 100644 --- a/documentation/developers/README.md +++ b/documentation/developers/README.md @@ -3,7 +3,7 @@ ## Getting started * [Development Environment](./development-environment.md) -* [Setting up Docker](./docker.md) +* [Python Development Notes](pyhton.md) ## Reference diff --git a/documentation/developers/coreapps.md b/documentation/developers/coreapps.md index 2fa8b84e9..3e4fc7b1f 100644 --- a/documentation/developers/coreapps.md +++ b/documentation/developers/coreapps.md @@ -4,14 +4,14 @@ The Jukebox\'s core apps are located in `src/jukebox`. Run the following command to learn more about each app and its parameters: ``` bash -$ ./run_app_name.py -h +$ cd src/jukebox +$ ./ -h ``` -## Jukebox Core -### `run_jukebox.py` +## Jukebox Core -[run_jukebox.py](../../src/jukebox/run_jukebox.py) +**Scriptname:** [run_jukebox.py](../../src/jukebox/run_jukebox.py) This is the main app and starts the Jukebox Core. @@ -19,49 +19,44 @@ Usually this runs as a service, which is started automatically after boot-up. At For debugging, it is usually desirable to run the Jukebox directly from the console rather than as service. This gives direct logging info in the console and allows changing command line parameters. See [Troubleshooting](../builders/troubleshooting.md). + ## Configuration Tools Before running the configuration tools, stop the Jukebox Core service. See [Best practice procedure](../builders/configuration.md#best-practice-procedure). -### `run_configure_audio.py` - -[run_configure_audio.py](../../src/jukebox/run_configure_audio.py) +### Audio -Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. +**Scriptname:** [run_configure_audio.py](../../src/jukebox/run_configure_audio.py) -Will also setup equalizer and mono down mixer in the pulseaudio config file. +Setup tool to register the PulseAudio sinks as primary and secondary audio outputs. -Run this once after installation. Can be re-run at any time to change the settings. For more information see [Audio Configuration](../builders/audio.md). +Will also setup equalizer and mono down mixer in the pulseaudio config file. Run this once after installation. Can be re-run at any time to change the settings. For more information see [Audio Configuration](../builders/audio.md). -### `run_register_rfid_reader.py` +### RFID Reader -[run_register_rfid_reader.py](../../src/jukebox/run_register_rfid_reader.py) +**Scriptname:** [run_register_rfid_reader.py](../../src/jukebox/run_register_rfid_reader.py) -Setup tool to configure the RFID Readers. +Setup tool to configure the RFID Readers. Run this once to register and configure the RFID readers with the Jukebox. Can be re-run at any time to change the settings. For more information see [RFID Readers](./rfid/README.md). > [!NOTE] > This tool will always write a new configurations file. Thus, overwrite the old one (after checking with the user). Any manual modifications to the settings will have to be re-applied -## Developer Tools - -### `run_rpc_tool.py` -[run_rpc_tool.py](../../src/jukebox/run_rpc_tool.py) - -Command Line Interface to the Jukebox RPC Server. +## Developer Tools -A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging. +### RPC -The tool features auto-completion and command history. +**Scriptname:** [run_rpc_tool.py](../../src/jukebox/run_rpc_tool.py) -The list of available commands is fetched from the running Jukebox service. +Command Line Interface to the Jukebox RPC Server. +A command line tool for sending RPC commands to the running jukebox app. This uses the same interface as the WebUI. Can be used for additional control or for debugging.The tool features auto-completion and command history. The list of available commands is fetched from the running Jukebox service. -### `run_publicity_sniffer.py` +### Publicity Sniffer - [run_publicity_sniffer.py](../../src/jukebox/run_publicity_sniffer.py) +**Scriptname:** [run_publicity_sniffer.py](../../src/jukebox/run_publicity_sniffer.py) A command line tool that monitors all messages being sent out from the Jukebox via the publishing interface. Received messages are printed in the console. Mainly used for debugging. diff --git a/documentation/developers/developer-issues.md b/documentation/developers/developer-issues.md index 1ed531738..c8dd3b6c3 100644 --- a/documentation/developers/developer-issues.md +++ b/documentation/developers/developer-issues.md @@ -46,6 +46,7 @@ Alternatively, use the provided script, which sets the variable for you (provided your swap size is large enough): ``` bash +$ cd src/webapp $ ./run_rebuild.sh ``` diff --git a/documentation/developers/development-environment.md b/documentation/developers/development-environment.md index d79a6101e..9abefbee0 100644 --- a/documentation/developers/development-environment.md +++ b/documentation/developers/development-environment.md @@ -1,30 +1,30 @@ # Development Environment -You have 3 development options: -## Directly on Raspberry Pi -The full setup is running on the RPi and you access files via SSH. -Pretty easy to set up as you simply do a normal install and switch to -the `future3/develop` branch. +You have 3 development options. Each option has its pros and cons. To interact with GPIO or other hardware, it's required to develop directly on a Raspberry Pi. For general development of Python code (Jukebox) or JavaScript (Webapp), we recommend Docker. Developing on your local machine (Linux, Mac, Windows) works as well and requires all dependencies to be installed locally. + +* [Develop in Docker](#develop-in-docker) +* [Develop on Raspberry Pi](#develop-on-raspberry-pi) +* [Develop on local machine](#develop-on-local-machine) + +## Develop in Docker + +There is a complete [Docker setup](./docker.md). + +## Develop on Raspberry Pi + +The full setup is running on the RPi and you access files via SSH. Pretty easy to set up as you simply do a normal install and switch to the `future3/develop` branch. ### Steps to install -We recommend to use at least a Pi 3 or Pi Zero 2 for development. This -hardware won\'t be needed in production, but it can be slow while -developing. +We recommend to use at least a Pi 3 or Pi Zero 2 for development. This hardware won\'t be needed in production, but it can be slow while developing. 1. Install the latest Pi OS on a SD card. 2. Boot up your Raspberry Pi. -3. [Install](../builders/installation.md) the Jukebox software as if you were building a - Phoniebox. You can install from your own fork and feature branch if - you wish which can be changed later as well. The original repository - will be set as `upstream`. +3. [Install](../builders/installation.md) the Jukebox software as if you were building a Phoniebox. You can install from your own fork and feature branch you wish which can be changed later as well. The original repository will be set as `upstream`. 4. Once the installation has successfully ran, reboot your Pi. -5. Due to some resource constraints, the Webapp does not build the - latest changes and instead consumes the latest official release. To - change that, you need to install NodeJS and build the Webapp - locally. +5. Due to some resource constraints, the Webapp does not build the latest changes and instead consumes the latest official release. To change that, you need to install NodeJS and build the Webapp locally. 6. Install NodeJS using the existing installer ``` bash @@ -34,8 +34,7 @@ developing. ``` 7. To free up RAM, reboot your Pi. -8. Build the Webapp using the existing build command. If the build - fails, you might have forgotten to reboot. +8. Build the Webapp using the existing build command. If the build fails, you might have forgotten to reboot. ``` bash cd ~/RPi-Jukebox-RFID/src/webapp; \ @@ -43,30 +42,16 @@ developing. ``` 9. The Webapp should now be updated. -10. To continuously update Webapp, pull the latest changes from your - repository and rerun the command above. +10. To continuously update Webapp, pull the latest changes from your repository and rerun the command above. -## Locally on any Linux machine +## Develop on local machine -The jukebox also runs on any Linux machine. The Raspberry Pi specific -stuff will not work of course. That is no issue depending our your -development area. USB RFID Readers, however, will work. You will have -to install and configure [MPD (Music Player -Daemon)](https://www.musicpd.org/). +The jukebox also runs on any Linux machine. The Raspberry Pi specific stuff will not work of course. That is no issue depending our your development area. USB RFID Readers, however, will work. You will have to install and configure [MPD (Music Player Daemon)](https://www.musicpd.org/). -In addition to the `requirements.txt`, you will this -dependency. On the Raspberry PI, the latest stable release of ZMQ does -not support WebSockets. We need to compile the latest version from -Github, which is taken care of by the installation script. For regular -machines, the normal package can be installed: +In addition to the `requirements.txt`, you will this dependency. On the Raspberry PI, the latest stable release of ZMQ does not support WebSockets. We need to compile the latest version from Github, which is taken care of by the installation script. For regular machines, the normal package can be installed: ``` bash pip install pyzmq ``` -You will have to start Jukebox core application and the WebUI -separately. The MPD usually runs as a service. - -## Using Docker container - -There is a complete [Docker setup](./docker.md). +You will have to start Jukebox core application and the WebUI separately. The MPD usually runs as a service. diff --git a/documentation/developers/docker.md b/documentation/developers/docker.md index 96b5a64b6..39518e248 100644 --- a/documentation/developers/docker.md +++ b/documentation/developers/docker.md @@ -33,7 +33,7 @@ need to adapt some of those commands to your needs. * Override/Merge the values from the following [Override file](https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3/develop/docker/config/jukebox.overrides.yaml) in your `jukebox.yaml`. * **\[Currently required\]** Update all relative paths (`../..`) in to `/home/pi/RPi-Jukebox-RFID`. -4. Change directory into the `./RPi-Jukebox-RFID/shared/audiofolders` +4. Change directory into the `./shared/audiofolders` and copy a set of MP3 files into this folder (for more fun when testing). diff --git a/documentation/developers/known-issues.md b/documentation/developers/known-issues.md index 598ecd791..1460c1cb4 100644 --- a/documentation/developers/known-issues.md +++ b/documentation/developers/known-issues.md @@ -1,13 +1,24 @@ # Known Issues -## Browsers +## Installing `libzmq` in Docker fails -The Web UI will **not** work with Firefox, due to an issue with websockets and pyzmq. Please use a different -browser for now. +To speed up the Docker build process, we are distributing pre-build versions of libzmq with drafts flag at the latest version. In case the download fails because the respective architecture build does not exist, you can build the version yourself. + +Add `build-essential` to be installed additionally with `apt-get`. Additionally, replace the command to download the pre-built library with the following command. + +``` +# Compile ZMQ +RUN cd ${HOME} && mkdir ${ZMQ_TMP_DIR} && cd ${ZMQ_TMP_DIR}; \ + wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz; \ + tar -xzf libzmq.tar.gz; \ + rm -f libzmq.tar.gz; \ + zeromq-${ZMQ_VERSION}/configure --prefix=${ZMQ_PREFIX} --enable-drafts; \ + make && make install +``` ## Configuration -In `jukebox.yaml` (and all other config files): do not use relative paths with `~/some/dir`. -Always use entire explicit path, e.g. `/home/pi/some/dir`. +In `jukebox.yaml` (and all other config files): +Always use relative path from settingsfile `../../`, but do not use relative paths with `~/`. **Sole** exception is in `playermpd.mpd_conf`. diff --git a/documentation/developers/libzmq.md b/documentation/developers/libzmq.md new file mode 100644 index 000000000..3407d971e --- /dev/null +++ b/documentation/developers/libzmq.md @@ -0,0 +1,68 @@ +# `libzmq` for Raspberry Pi + +## `libzmp` Releases + +The Jukebox requires `libzmq` to work properly. We provide downloadable builds to speed up the installation process of the Phoniebox. + +* https://github.com/pabera/libzmq/releases + +> [!NOTE] +> We can't use stable builds that are distributed by [zeromq](https://github.com/zeromq/libzmq/releases) directly because the Jukebox requires draft builds to support WebSockets. These [draft builds](https://pyzmq.readthedocs.io/en/latest/howto/draft.html) are not officially provided through zeromq for Raspberry Pi architecture (e.g. `armv6` or `armv7`). + +## Building `libzmp` + +If you need to update the `libzmq` version in the future, follow these steps. + +### Install Cross-Compilation Environment + +First, you need to install Dockcross. Dockcross provides Docker images for cross-compilation. + +1. **Pull the Dockcross Image**: For Raspberry Pi B, 4 or Zero 2 we need `linux-armv7`, for older models `linux-armv6`. The following example shows how to compile for `armv7` (32 bit, `arm32v7`). If you want to compile for another target, change the commands accordingly. For Docker Development environments, other targets like `arm64` or `x86_64` become relevant. + +```bash +docker pull dockcross/linux-armv7 +``` + +3. **Create a Dockcross Script**: After pulling the image, you create a Dockcross script which will be used to run the cross-compilation tools in the Docker container. + +```bash +docker run --rm dockcross/linux-armv7 > ./dockcross-linux-armv7 +chmod +x ./dockcross-linux-armv7 +``` + +This command creates a script named `dockcross-linux-armv7` in your current directory. + +### Cross-Compiling libzmq + +With Dockcross installed, you can now modify your `libzmq` compilation process to use the Dockcross environment. + +1. **Download `libzmq` Source**: Similar to your original process, download the `libzmq` source code: + +```bash +ZMQ_VERSION=4.3.5 +wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz +tar -xzf libzmq.tar.gz +``` + +2. **Cross-Compilation using Dockcross**: + +Modify your build process to run inside the Dockcross environment: + +```bash +./dockcross-linux-armv7 bash -c '\ +cd zeromq-${ZMQ_VERSION} && \ +./configure --prefix=/usr/local --enable-drafts && \ +make -j$(nproc) && \ +make install DESTDIR=$(pwd)/../installed' +``` + +> [!NOTE] +> In the script above, you need to update ${ZMQ_VERSION} to the actual value as the script does not jave access to your host machine ENV variables. + +This command configures and builds `libzmq` inside the Docker container. The `DESTDIR` variable is used to specify where to install the files inside the container. + +3. **Compress the Compiled Binaries**: After compilation, the binaries are located in the `installed` directory inside your `zeromq-${ZMQ_VERSION}` directory. + +``` +tar -czvf libzmq5-armv7-${ZMQ_VERSION}.tar.gz -C installed/usr/local --exclude='.' include lib +``` diff --git a/documentation/developers/pyhton.md b/documentation/developers/pyhton.md new file mode 100644 index 000000000..31659ef3c --- /dev/null +++ b/documentation/developers/pyhton.md @@ -0,0 +1,18 @@ +# Python Development Notes + +## Prerequisites + +> [!NOTE] +> All Python scripts must be run within a [virtual environment](https://docs.python.org/3/library/venv.html) (`.venv`). All Python plugins are installed encapsulated within this environment. + +Before you can run Python code, you need to enable the virtual environment. On the Raspberry Pi, it's located in the project root `~/RPi-Jukebox-RFID/.venv`. Depending on your setup, the absolute path can vary. + +``` +$ source ~/RPi-Jukebox-RFID/.venv/bin/activate +``` + +If the virtual environment has been activated correctly, your terminal will now show a prefix (`.venv`). If you want to leave the venv again execute deactivate. + +``` +$ deactivate +``` diff --git a/documentation/developers/rfid/basics.md b/documentation/developers/rfid/basics.md index 7a5aa37d9..7d557ed06 100644 --- a/documentation/developers/rfid/basics.md +++ b/documentation/developers/rfid/basics.md @@ -35,8 +35,7 @@ Readers operate on one of two different frequencies: 125kHz or 13.56 MHz. Make s ## Reader Configuration During the installation process, you can already configure a RFID -reader. To manually configure RFID reader(s), -[please run the tool](../coreapps.md#run_register_rfid_reader.py), (`src/jukebox/run_register_rfid_reader.py`). +reader. To manually configure RFID reader(s) run the [RFID reader configuration tool](../coreapps.md#RFID-Reader). It will generate a reader configuration file at `shared/settings/rfid.yaml`. You can re-run the tool to change the @@ -67,7 +66,7 @@ Indicates the Python package used for this reader. Filled by the RFID configurat #### config: -Filled by the [RFID configuration tool](../coreapps.md#run_register_rfid_reader.py) (`src/jukebox/run_register_rfid_reader.py`) based on default values and user input. After running the tool, you may manually change some settings here, as not everything can be configured through the tool. Note that re-running the tool will completely rewrite the configuration file. +Filled by the [RFID reader configuration tool](../coreapps.md#RFID-Reader) based on default values and user input. After running the tool, you may manually change some settings here, as not everything can be configured through the tool. Note that re-running the tool will completely rewrite the configuration file. #### same_id_delay: float \| integer diff --git a/documentation/developers/rfid/genericusb.md b/documentation/developers/rfid/genericusb.md index 23975cf14..544f0245c 100644 --- a/documentation/developers/rfid/genericusb.md +++ b/documentation/developers/rfid/genericusb.md @@ -4,7 +4,7 @@ This module covers all types of USB-based RFID input readers. If you plan to connect multiple USB-based RFID readers to the Jukebox, make -sure to connect all of them before running the registration tool [run_register_rfid_reader.py](../coreapps.md#run_register_rfid_readerpy). +sure to connect all of them before running the [RFID reader configuration tool](../coreapps.md#RFID-Reader). > [!NOTE] > The user needs to be part of the group \'input\' for evdev to work. This should usually be the case. However, a user can be added with: diff --git a/documentation/developers/rfid/mfrc522_spi.md b/documentation/developers/rfid/mfrc522_spi.md index f7710b2b1..5dc46aab6 100644 --- a/documentation/developers/rfid/mfrc522_spi.md +++ b/documentation/developers/rfid/mfrc522_spi.md @@ -6,7 +6,7 @@ RC522 RFID reader via SPI connection. ## Installation -Run the [run_register_rfid_reader.py](../coreapps.md#run_register_rfid_reader.py) tool for guided +Run the [RFID reader configuration tool](../coreapps.md#RFID-Reader) for guided installation. ## Options diff --git a/documentation/developers/rfid/template_reader.md b/documentation/developers/rfid/template_reader.md index 0f4c3e939..6a7a8b0c3 100644 --- a/documentation/developers/rfid/template_reader.md +++ b/documentation/developers/rfid/template_reader.md @@ -9,9 +9,9 @@ This template provides the skeleton API for a new Reader. If you follow the conventions outlined below, your new reader will be picked up automatically There is no extra need to register the reader module with -the Phoniebox. Just re-run `the reader config tool `. +the Phoniebox. Just re-run [RFID reader configuration tool](../coreapps.md#RFID-Reader). -Follow the instructions in [template_new_reader.py] +Follow the instructions in [template_new_reader.py](../../../src/jukebox/components/rfid/hardware/template_new_reader/template_new_reader.py) Also have a look at the other reader subpackages to see how stuff works with an example diff --git a/documentation/developers/status.md b/documentation/developers/status.md index 50968293c..eac874608 100644 --- a/documentation/developers/status.md +++ b/documentation/developers/status.md @@ -227,6 +227,7 @@ Topics marked _in progress_ are already in the process of implementation by comm - [x] Enable/Disable Auto-Hotspot - [x] `run_npm_build` script - [x] Must consider `export NODE_OPTIONS=--max-old-space-size=512` +- [ ] Upload audio files via WebUI https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2138 ## Installation Procedure diff --git a/installation/README.md b/installation/README.md index aff262bd8..178f1b7be 100644 --- a/installation/README.md +++ b/installation/README.md @@ -3,18 +3,15 @@ ## Logging - Bash Script output rules ```bash -Output to both console and logfile: "$ command | tee /dev/fd/3" -Output to console only "$ command 1>&3" -Output to logfile only: "$ command" -No output to both console and logfile: "$ command > /dev/null" +run_and_print_lc "Run a command and log its output to both console and logfile" +print_lc "This message will be logged to both console and logfile" +print_c "This message will only be logged to the console" +log "This message will only be logged to the logfile" +clear_c "Clears the console screen" ``` [Learn more about bash script outputs](https://stackoverflow.com/questions/18460186/writing-outputs-to-log-file-and-console) -## Quick Installation +## Installation -Note: Replace the branch in this command to be the one you like to install depending on your needs. Release branch is preset. - -```bash -cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/main/installation/install-jukebox.sh) -``` +[Install Phoniebox software](../documentation/builders/installation.md#install-phoniebox-software) diff --git a/installation/includes/00_constants.sh b/installation/includes/00_constants.sh index e19dd1327..380e1de2e 100644 --- a/installation/includes/00_constants.sh +++ b/installation/includes/00_constants.sh @@ -1,7 +1,11 @@ RPI_BOOT_CONFIG_FILE="/boot/config.txt" +RPI_BOOT_CMDLINE_FILE="/boot/cmdline.txt" SHARED_PATH="${INSTALLATION_PATH}/shared" SETTINGS_PATH="${SHARED_PATH}/settings" SYSTEMD_USR_PATH="/usr/lib/systemd/user" +VIRTUAL_ENV="${INSTALLATION_PATH}/.venv" +# Do not change this directory! It must match MPDs expectation where to find the user configuration +MPD_CONF_PATH="${HOME}/.config/mpd/mpd.conf" # The default upstream user, release branch, and develop branch # These are used to prepare the repo for developers diff --git a/installation/includes/01_default_config.sh b/installation/includes/01_default_config.sh index eb6e45a91..fa1bafb61 100644 --- a/installation/includes/01_default_config.sh +++ b/installation/includes/01_default_config.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=false +BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE=${BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE:-"false"} ENABLE_STATIC_IP=true DISABLE_IPv6=true ENABLE_AUTOHOTSPOT=false @@ -11,7 +11,9 @@ DISABLE_SSH_QOS=true DISABLE_BOOT_SCREEN=true DISABLE_BOOT_LOGS_PRINT=true SETUP_MPD=true +ENABLE_MPD_OVERWRITE_INSTALL=true UPDATE_RASPI_OS=${UPDATE_RASPI_OS:-"true"} +ENABLE_RFID_READER=true ENABLE_SAMBA=true ENABLE_WEBAPP=true ENABLE_KIOSK_MODE=false diff --git a/installation/includes/02_helpers.sh b/installation/includes/02_helpers.sh index 9ad8e71b8..7520241c6 100644 --- a/installation/includes/02_helpers.sh +++ b/installation/includes/02_helpers.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Helpers +### Helpers # $1->start, $2->end calc_runtime_and_print() { @@ -9,7 +9,7 @@ calc_runtime_and_print() { ((m=(${runtime}%3600)/60)) ((s=${runtime}%60)) - echo "Done in ${h}h ${m}m ${s}s." + echo "Done in ${h}h ${m}m ${s}s" } run_with_timer() { @@ -17,42 +17,279 @@ run_with_timer() { $1; # Executes the function passed as an argument - calc_runtime_and_print time_start $(date +%s) | tee /dev/fd/3 - echo "--------------------------------------" + run_and_print_lc calc_runtime_and_print time_start $(date +%s) } -_download_file_from_google_drive() { - GD_SHARING_ID=${1} - TAR_FILENAME=${2} - wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=${GD_SHARING_ID}' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=${GD_SHARING_ID}" -O ${TAR_FILENAME} && rm -rf /tmp/cookies.txt - echo "Downloaded from Google Drive ID ${GD_SHARING_ID} into ${TAR_FILENAME}" +run_with_log_frame() { + local time_start=$(date +%s); + local description="$2" + log "\n\n" + log "#########################################################" + print_lc "${description}" + + $1; # Executes the function passed as an argument + + local done_in=$(calc_runtime_and_print time_start $(date +%s)) + log "\n${done_in} - ${description}" + log "#########################################################" } -get_onboard_audio() { - if grep -q -E "^dtparam=([^,]*,)*audio=(on|true|yes|1).*" ${RPI_BOOT_CONFIG_FILE} - then - echo 1 +get_architecture() { + local arch="" + if [ "$(uname -m)" = "armv7l" ]; then + arch="armv7" + elif [ "$(uname -m)" = "armv6l" ]; then + arch="armv6" + elif [ "$(uname -m)" = "aarch64" ]; then + arch="arm64" else - echo 0 + arch="$(uname -m)" fi -} -check_os_type() { - # Check if current distro is a 32 bit version - # Support for 64 bit Distros has not been checked (or precisely: is known not to work) - # All RaspianOS versions report as machine "armv6l" or "armv7l", if 32 bit (even the ARMv8 cores!) + echo $arch +} - local os_type - os_type=$(uname -m) +get_version_string() { + local python_file="$1" + local version_major + local version_minor + local version_patch - echo -e "\nChecking OS type '$os_type'" | tee /dev/fd/3 + version_major=$(grep 'VERSION_MAJOR\s*=\s*[0-9]*' "${python_file}" | awk -F= '{print $2}' | tr -d ' ') + version_minor=$(grep 'VERSION_MINOR\s*=\s*[0-9]*' "${python_file}" | awk -F= '{print $2}' | tr -d ' ') + version_patch=$(grep 'VERSION_PATCH\s*=\s*[0-9]*' "${python_file}" | awk -F= '{print $2}' | tr -d ' ') - if [[ $os_type == "armv7l" || $os_type == "armv6l" ]]; then - echo -e " ... OK!\n" | tee /dev/fd/3 + if [ -n "$version_major" ] && [ -n "$version_minor" ] && [ -n "$version_patch" ]; then + local version_string="${version_major}.${version_minor}.${version_patch}" + echo ${version_string} else - echo "ERROR: Only 32 bit operating systems supported. Please use a 32bit version of RaspianOS!" | tee /dev/fd/3 - echo "You can fix this problem for 64bit kernels: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041" | tee /dev/fd/3 - exit 1 + exit_on_error "ERROR: Unable to extract version information from ${python_file}" fi +} + +### Verify helpers +print_verify_installation() { + log "\n + ------------------------------------------------------- + Check installation +" +} + +# Check if the file(s) exists +verify_files_exists() { + local files="$@" + log " Verify '${files}' exists" + + if [[ -z "${files}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + for file in $files + do + test ! -f ${file} && exit_on_error "ERROR: '${file}' does not exists or is not a file!" + done + log " CHECK" +} + +# Check if the dir(s) exists +verify_dirs_exists() { + local dirs="$@" + log " Verify '${dirs}' exists" + + if [[ -z "${dirs}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + for dir in $dirs + do + test ! -d ${dir} && exit_on_error "ERROR: '${dir}' does not exists or is not a dir!" + done + log " CHECK" +} + +# Check if the file(s) has/have the expected owner and modifications +verify_files_chmod_chown() { + local mod_expected=$1 + local user_expected=$2 + local group_expected=$3 + local files="${@:4}" + log " Verify '${mod_expected}' '${user_expected}:${group_expected}' is set for '${files}'" + + if [[ -z "${mod_expected}" || -z "${user_expected}" || -z "${group_expected}" || -z "${files}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + for file in $files + do + test ! -f ${file} && exit_on_error "ERROR: '${file}' does not exists or is not a file!" + + mod_actual=$(stat --format '%a' "${file}") + user_actual=$(stat -c '%U' "${file}") + group_actual=$(stat -c '%G' "${file}") + test ! "${mod_expected}" -eq "${mod_actual}" && exit_on_error "ERROR: '${file}' actual mod '${mod_actual}' differs from expected '${mod_expected}'!" + test ! "${user_expected}" == "${user_actual}" && exit_on_error "ERROR: '${file}' actual owner '${user_actual}' differs from expected '${user_expected}'!" + test ! "${group_expected}" == "${group_actual}" && exit_on_error "ERROR: '${file}' actual group '${group_actual}' differs from expected '${group_expected}'!" + done + log " CHECK" +} + +# Check if the dir(s) has/have the expected owner and modifications +verify_dirs_chmod_chown() { + local mod_expected=$1 + local user_expected=$2 + local group_expected=$3 + local dirs="${@:4}" + log " Verify '${mod_expected}' '${user_expected}:${group_expected}' is set for '${dirs}'" + + if [[ -z "${mod_expected}" || -z "${user_expected}" || -z "${group_expected}" || -z "${dirs}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + for dir in $dirs + do + test ! -d ${dir} && exit_on_error "ERROR: '${dir}' does not exists or is not a dir!" + + mod_actual=$(stat --format '%a' "${dir}") + user_actual=$(stat -c '%U' "${dir}") + group_actual=$(stat -c '%G' "${dir}") + test ! "${mod_expected}" -eq "${mod_actual}" && exit_on_error "ERROR: '${dir}' actual mod '${mod_actual}' differs from expected '${mod_expected}'!" + test ! "${user_expected}" == "${user_actual}" && exit_on_error "ERROR: '${dir}' actual owner '${user_actual}' differs from expected '${user_expected}'!" + test ! "${group_expected}" == "${group_actual}" && exit_on_error "ERROR: '${dir}' actual group '${group_actual}' differs from expected '${group_expected}'!" + done + log " CHECK" +} + +verify_file_contains_string() { + local string="$1" + local file="$2" + log " Verify '${string}' found in '${file}'" + + if [[ -z "${string}" || -z "${file}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + if [[ ! $(grep -iw "${string}" "${file}") ]]; then + exit_on_error "ERROR: '${string}' not found in '${file}'" + fi + log " CHECK" +} + +verify_file_contains_string_once() { + local string="$1" + local file="$2" + log " Verify '${string}' found in '${file}'" + + if [[ -z "${string}" || -z "${file}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local file_contains_string_count=$(grep -oiw "${string}" "${file}" | wc -l) + if [ "$file_contains_string_count" -lt 1 ]; then + exit_on_error "ERROR: '${string}' not found in '${file}'" + elif [ "$file_contains_string_count" -gt 1 ]; then + exit_on_error "ERROR: '${string}' found more than once in '${file}'" + fi + log " CHECK" +} + +verify_service_state() { + local service="$1" + local desired_state="$2" + local option="${3:+$3 }" # optional, dont't quote in next call! + log " Verify service '${option}${service}' is '${desired_state}'" + + if [[ -z "${service}" || -z "${desired_state}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local actual_state=$(systemctl is-active ${option}${service}) + if [[ ! "${actual_state}" == "${desired_state}" ]]; then + exit_on_error "ERROR: service '${option}${service}' is not '${desired_state}' (state: '${actual_state}')." + fi + log " CHECK" +} + +verify_service_enablement() { + local service="$1" + local desired_enablement="$2" + local option="${3:+$3 }" # optional, dont't quote in next call! + log " Verify service ${option}${service} is ${desired_enablement}" + + if [[ -z "${service}" || -z "${desired_enablement}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local actual_enablement=$(systemctl is-enabled ${option}${service}) + if [[ ! "${actual_enablement}" == "${desired_enablement}" ]]; then + exit_on_error "ERROR: service ${option}${service} is not ${desired_enablement} (state: ${actual_enablement})." + fi + log " CHECK" +} + +verify_optional_service_enablement() { + local service="$1" + local desired_enablement="$2" + local option="${3:+$3 }" # optional, dont't quote in next call! + log " Verify service ${option}${service} is ${desired_enablement}" + + if [[ -z "${service}" || -z "${desired_enablement}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local actual_enablement=$(systemctl is-enabled ${option}${service}) 2>/dev/null + if [[ -z "${actual_enablement}" ]]; then + log " INFO: optional service ${option}${service} is not installed." + elif [[ "${actual_enablement}" == "static" ]]; then + log " INFO: optional service ${option}${service} is set static." + elif [[ ! "${actual_enablement}" == "${desired_enablement}" ]]; then + exit_on_error "ERROR: service ${option}${service} is not ${desired_enablement} (state: ${actual_enablement})." + fi + log " CHECK" +} + +# Reads a textfile and returns all lines as args. +# Does filter out comments, egg-prefixes and version suffixes +# Arguments: +# 1 : textfile to read +get_args_from_file() { + local package_file="$1" + sed 's/.*#egg=//g' ${package_file} | sed -E 's/(#|=|>|<).*//g' | xargs echo +} + +# Check if all passed packages are installed. Fail on first missing. +verify_apt_packages() { + local packages="$@" + log " Verify packages are installed: '${packages}'" + + if [[ -z "${packages}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + + local apt_list_installed=$(apt -qq list --installed 2>/dev/null) + for package in ${packages} + do + if [[ ! $(echo "${apt_list_installed}" | grep -i "^${package}/.*installed") ]]; then + exit_on_error "ERROR: ${package} is not installed" + fi + done + log " CHECK" +} + +# Check if all passed modules are installed. Fail on first missing. +verify_pip_modules() { + local modules="$@" + log " Verify modules are installed: '${modules}'" + + if [[ -z "${modules}" ]]; then + exit_on_error "ERROR: at least one parameter value is missing!" + fi + local pip_list_installed=$(pip list 2>/dev/null) + for module in ${modules} + do + if [[ ! $(echo "${pip_list_installed}" | grep -i "^${module} ") ]]; then + exit_on_error "ERROR: ${module} is not installed" + fi + done + log " CHECK" } diff --git a/installation/includes/03_welcome.sh b/installation/includes/03_welcome.sh index 7aeaa56ab..5b3ee84be 100644 --- a/installation/includes/03_welcome.sh +++ b/installation/includes/03_welcome.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash welcome() { - clear 1>&3 - echo "######################################################### + clear_c + print_c "######################################################### # # # ___ __ ______ _ __________ ____ __ _ _ # # / _ \/ // / __ \/ |/ / _/ __/( _ \ / \( \/ ) # @@ -29,16 +29,16 @@ in a separate SSH session: cd; tail -f ${INSTALLATION_LOGFILE} Let's set up your Phoniebox. -Do you want to start the installation? [Y/n]" 1>&3 +Do you want to start the installation? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) exit ;; *) - echo "Starting installation + print_c "Starting installation --------------------- -" 1>&3 +" ;; esac } diff --git a/installation/includes/04_cleanup.sh b/installation/includes/04_cleanup.sh index fd714132f..d6e39266c 100644 --- a/installation/includes/04_cleanup.sh +++ b/installation/includes/04_cleanup.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash -cleanup() { - sudo rm -rf /var/lib/apt/lists/* +_run_cleanup() { + sudo rm -rf /var/lib/apt/lists/* +} - echo "DONE: cleanup" +cleanup() { + run_with_log_frame _run_cleanup "Cleanup" } diff --git a/installation/includes/05_finish.sh b/installation/includes/05_finish.sh index 50c2f6d9d..22ba6ae80 100644 --- a/installation/includes/05_finish.sh +++ b/installation/includes/05_finish.sh @@ -2,7 +2,7 @@ finish() { local local_hostname=$(hostname) - echo -e "####################### FINISHED ######################## + print_lc "####################### FINISHED ######################## Installation complete! @@ -14,41 +14,20 @@ Your SSH connection will disconnect. After the reboot, you can access the WebApp in your browser at http://${local_hostname}.local or http://${CURRENT_IP_ADDRESS} Don't forget to upload files. - -Do you want to reboot now? [Y/n]" 1>&3 +" +print_c "Do you want to reboot now? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) - echo "Reboot aborted" | tee /dev/fd/3 - echo "DONE: finish" + print_lc "Reboot aborted" + log "DONE: finish" exit ;; *) - echo "Rebooting ..." | tee /dev/fd/3 - echo "DONE: finish" + print_lc "Rebooting ..." + log "DONE: finish" sudo reboot ;; esac } - -# Generic emergency error handler that exits the script immediately -# Print additional custom message if passed as first argument -# Examples: -# cd some-dir || exit_on_error -# cd some-dir || exit_on_error "During installation of some" -exit_on_error () { - - echo -e "\n****************************************" | tee /dev/fd/3 - echo "ERROR OCCURRED! -A non-recoverable error occurred. -Check install log for details:" | tee /dev/fd/3 - echo "$INSTALLATION_LOGFILE" | tee /dev/fd/3 - echo "****************************************" | tee /dev/fd/3 - if [[ -n $1 ]]; then - echo "$1" | tee /dev/fd/3 - echo "****************************************" | tee /dev/fd/3 - fi - echo "Abort!" - exit 1 -} diff --git a/installation/install-jukebox.sh b/installation/install-jukebox.sh index 44b03fca0..84827f99d 100755 --- a/installation/install-jukebox.sh +++ b/installation/install-jukebox.sh @@ -20,78 +20,137 @@ GIT_URL="https://github.com/${GIT_USER}/${GIT_REPO_NAME}" echo GIT_BRANCH $GIT_BRANCH echo GIT_URL $GIT_URL -CURRENT_USER="${SUDO_USER:-$USER}" +CURRENT_USER="${SUDO_USER:-$(whoami)}" +CURRENT_USER_GROUP=$(id -gn "$CURRENT_USER") HOME_PATH=$(getent passwd "$CURRENT_USER" | cut -d: -f6) -echo "Current User: $CURRENT_USER" -echo "User home dir: $HOME_PATH" INSTALLATION_PATH="${HOME_PATH}/${GIT_REPO_NAME}" INSTALL_ID=$(date +%s) +INSTALLATION_LOGFILE="${HOME_PATH}/INSTALL-${INSTALL_ID}.log" + +# Manipulate file descriptor for logging +_setup_logging(){ + if [ "$CI_RUNNING" == "true" ]; then + exec 3>&1 2>&1 + else + exec 3>&1 1>>"${INSTALLATION_LOGFILE}" 2>&1 || { echo "ERROR: Cannot create log file."; exit 1; } + fi + echo "Log start: ${INSTALL_ID}" +} + +# Function to log to both console and logfile +print_lc() { + local message="$1" + echo -e "$message" | tee /dev/fd/3 +} + +# Function to log to logfile only +log() { + local message="$1" + echo -e "$message" +} -checkPrerequisite() { - #currently the user 'pi' is mandatory - #https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/1785 - if [ "${CURRENT_USER}" != "pi" ]; then - echo - echo "ERROR: User must be 'pi'!" - echo " Other usernames are currently not supported." - echo " Please check the wiki for further information" - exit 2 +# Function to run a command where the output will be logged to both console and logfile +run_and_print_lc() { + "$@" | tee /dev/fd/3 +} + +# Function to log to console only +print_c() { + local message="$1" + echo -e "$message" 1>&3 +} + +# Function to clear console screen +clear_c() { + clear 1>&3 +} + +# Generic emergency error handler that exits the script immediately +# Print additional custom message if passed as first argument +# Examples: +# a command || exit_on_error +# a command || exit_on_error "Execution of command failed" +exit_on_error () { + print_lc "\n****************************************" + print_lc "ERROR OCCURRED! +A non-recoverable error occurred. +Check install log for details:" + print_lc "$INSTALLATION_LOGFILE" + print_lc "****************************************" + if [[ -n $1 ]]; then + print_lc "$1" + print_lc "****************************************" fi + log "Abort!" + exit 1 +} - if [ "${HOME_PATH}" != "/home/pi" ]; then - echo - echo "ERROR: HomeDir must be '/home/pi'!" - echo " Other usernames are currently not supported." - echo " Please check the wiki for further information" - exit 2 +# Check if current distro is a 32 bit version +# Support for 64 bit Distros has not been checked (or precisely: is known not to work) +# All RaspianOS versions report as machine "armv6l" or "armv7l", if 32 bit (even the ARMv8 cores!) +_check_os_type() { + local os_type=$(uname -m) + + print_lc "\nChecking OS type '$os_type'" + + if [[ $os_type == "armv7l" || $os_type == "armv6l" ]]; then + print_lc " ... OK!\n" + else + print_lc "ERROR: Only 32 bit operating systems supported. Please use a 32bit version of RaspianOS!" + print_lc "You can fix this problem for 64bit kernels: https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/2041" + exit 1 fi } -download_jukebox_source() { +_download_jukebox_source() { + log "#########################################################" + print_c "Downloading Phoniebox software from Github ..." + print_lc "Download Source: ${GIT_URL}/${GIT_BRANCH}" + + cd "${HOME_PATH}" || exit_on_error "ERROR: Changing to home dir failed." wget -qO- "${GIT_URL}/tarball/${GIT_BRANCH}" | tar xz # Use case insensitive search/sed because user names in Git Hub are case insensitive - GIT_REPO_DOWNLOAD=$(find . -maxdepth 1 -type d -iname "${GIT_USER}-${GIT_REPO_NAME}-*") - echo "GIT REPO DOWNLOAD = $GIT_REPO_DOWNLOAD" - GIT_HASH=$(echo "$GIT_REPO_DOWNLOAD" | sed -rn "s/.*${GIT_USER}-${GIT_REPO_NAME}-([0-9a-fA-F]+)/\1/ip") + local git_repo_download=$(find . -maxdepth 1 -type d -iname "${GIT_USER}-${GIT_REPO_NAME}-*") + log "GIT REPO DOWNLOAD = $git_repo_download" + GIT_HASH=$(echo "$git_repo_download" | sed -rn "s/.*${GIT_USER}-${GIT_REPO_NAME}-([0-9a-fA-F]+)/\1/ip") # Save the git hash for this particular download for later git repo initialization - echo "GIT HASH = $GIT_HASH" - if [[ -z ${GIT_REPO_DOWNLOAD} ]]; then - echo "ERROR in finding git download. Panic." - exit 1 + log "GIT HASH = $GIT_HASH" + if [[ -z "${git_repo_download}" ]]; then + exit_on_error "ERROR: Couldn't find git download." fi - if [[ -z ${GIT_HASH} ]]; then - echo "ERROR in determining git hash from download. Panic." - exit 1 + if [[ -z "${GIT_HASH}" ]]; then + exit_on_error "ERROR: Couldn't determine git hash from download." fi - mv "$GIT_REPO_DOWNLOAD" "$GIT_REPO_NAME" - unset GIT_REPO_DOWNLOAD + mv "$git_repo_download" "$GIT_REPO_NAME" + log "\nDONE: Downloading Phoniebox software from Github" + log "#########################################################" } +_load_sources() { + # Load / Source dependencies + for i in "${INSTALLATION_PATH}"/installation/includes/*; do + source "$i" + done -### CHECK PREREQUISITE -checkPrerequisite - -### RUN INSTALLATION -INSTALLATION_LOGFILE="${HOME_PATH}/INSTALL-${INSTALL_ID}.log" -exec 3>&1 1>>"${INSTALLATION_LOGFILE}" 2>&1 || { echo "Cannot create log file. Panic."; exit 1; } -echo "Log start: ${INSTALL_ID}" + for j in "${INSTALLATION_PATH}"/installation/routines/*; do + source "$j" + done +} -clear 1>&3 -echo "Downloading Phoniebox software from Github ..." 1>&3 -echo "Download Source: ${GIT_URL}/${GIT_BRANCH}" | tee /dev/fd/3 +### SETUP LOGGING +_setup_logging -download_jukebox_source -cd "${INSTALLATION_PATH}" || { echo "ERROR in changing to install dir. Panic."; exit 1; } +### CHECK PREREQUISITE +_check_os_type -# Load / Source dependencies -for i in "${INSTALLATION_PATH}"/installation/includes/*; do - source "$i" -done +### RUN INSTALLATION +log "Current User: $CURRENT_USER" +log "User home dir: $HOME_PATH" -for j in "${INSTALLATION_PATH}"/installation/routines/*; do - source "$j" -done +_download_jukebox_source +cd "${INSTALLATION_PATH}" || exit_on_error "ERROR: Changing to install dir failed." +_load_sources welcome run_with_timer install diff --git a/installation/routines/customize_options.sh b/installation/routines/customize_options.sh index d5ac7c547..4007b88f5 100644 --- a/installation/routines/customize_options.sh +++ b/installation/routines/customize_options.sh @@ -8,15 +8,15 @@ _option_static_ip() { CURRENT_GATEWAY=$(echo "${CURRENT_ROUTE}" | awk '{ print $3; exit }') CURRENT_INTERFACE=$(echo "${CURRENT_ROUTE}" | awk '{ print $5; exit }') CURRENT_IP_ADDRESS=$(echo "${CURRENT_ROUTE}" | awk '{ print $7; exit }') - clear 1>&3 - echo "----------------------- STATIC IP ----------------------- + clear_c + print_c "----------------------- STATIC IP ----------------------- Setting a static IP will save a lot of start up time. The static adress will be '${CURRENT_IP_ADDRESS}' from interface '${CURRENT_INTERFACE}' with the gateway '${CURRENT_GATEWAY}'. -Set a static IP? [Y/n]" 1>&3 +Set a static IP? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -25,18 +25,18 @@ Set a static IP? [Y/n]" 1>&3 *) ;; esac - echo "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" + log "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" } _option_ipv6() { # DISABLE_IPv6 - clear 1>&3 - echo "------------------------- IP V6 ------------------------- + clear_c + print_c "------------------------- IP V6 ------------------------- IPv6 is only needed if you intend to use it. Otherwise it can be disabled. -Do you want to disable IPv6? [Y/n]" 1>&3 +Do you want to disable IPv6? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -45,19 +45,19 @@ Do you want to disable IPv6? [Y/n]" 1>&3 *) ;; esac - echo "DISABLE_IPv6=${DISABLE_IPv6}" + log "DISABLE_IPv6=${DISABLE_IPv6}" } _option_autohotspot() { # ENABLE_AUTOHOTSPOT - clear 1>&3 - echo "---------------------- AUTOHOTSPOT ---------------------- + clear_c + print_c "---------------------- AUTOHOTSPOT ---------------------- When enabled, this service spins up a WiFi hotspot when the Phoniebox is unable to connect to a known WiFi. This way you can still access it. -Do you want to enable an Autohotpot? [y/N]" 1>&3 +Do you want to enable an Autohotpot? [y/N]" read -r response case "$response" in [yY][eE][sS]|[yY]) @@ -68,13 +68,13 @@ Do you want to enable an Autohotpot? [y/N]" 1>&3 esac if [ "$ENABLE_AUTOHOTSPOT" = true ]; then - echo "Do you want to set a custom Password? (default: ${AUTOHOTSPOT_PASSWORD}) [y/N] " 1>&3 + print_c "Do you want to set a custom Password? (default: ${AUTOHOTSPOT_PASSWORD}) [y/N] " read -r response_pw_q case "$response_pw_q" in [yY][eE][sS]|[yY]) while [ $(echo ${response_pw}|wc -m) -lt 8 ] do - echo "Please type the new password (at least 8 character)." 1>&3 + print_c "Please type the new password (at least 8 character)." read -r response_pw done AUTOHOTSPOT_PASSWORD="${response_pw}" @@ -84,29 +84,27 @@ Do you want to enable an Autohotpot? [y/N]" 1>&3 esac if [ "$ENABLE_STATIC_IP" = true ]; then - echo "Wifi hotspot cannot be enabled with static IP. Disabling static IP configuration." 1>&3 - echo "--------------------- - " 1>&3 + print_c "Wifi hotspot cannot be enabled with static IP. Disabling static IP configuration." ENABLE_STATIC_IP=false - echo "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" + log "ENABLE_STATIC_IP=${ENABLE_STATIC_IP}" fi fi - echo "ENABLE_AUTOHOTSPOT=${ENABLE_AUTOHOTSPOT}" + log "ENABLE_AUTOHOTSPOT=${ENABLE_AUTOHOTSPOT}" if [ "$ENABLE_AUTOHOTSPOT" = true ]; then - echo "AUTOHOTSPOT_PASSWORD=${AUTOHOTSPOT_PASSWORD}" + log "AUTOHOTSPOT_PASSWORD=${AUTOHOTSPOT_PASSWORD}" fi } _option_bluetooth() { # DISABLE_BLUETOOTH - clear 1>&3 - echo "----------------------- BLUETOOTH ----------------------- + clear_c + print_c "----------------------- BLUETOOTH ----------------------- Turning off Bluetooth will save energy and start up time, if you do not plan to use it. -Do you want to disable Bluetooth? [Y/n]" 1>&3 +Do you want to disable Bluetooth? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -115,41 +113,88 @@ Do you want to disable Bluetooth? [Y/n]" 1>&3 *) ;; esac - echo "DISABLE_BLUETOOTH=${DISABLE_BLUETOOTH}" + log "DISABLE_BLUETOOTH=${DISABLE_BLUETOOTH}" +} + +_option_mpd() { + clear_c + if [[ "$SETUP_MPD" == true ]]; then + if [[ -f "${MPD_CONF_PATH}" || -f "${SYSTEMD_USR_PATH}/mpd.service" ]]; then + print_c "-------------------------- MPD -------------------------- + +It seems there is a MPD already installed. +Note: It is important that MPD runs as a user service! +Would you like to overwrite your configuration? [Y/n]" + read -r response + case "$response" in + [nN][oO]|[nN]) + ENABLE_MPD_OVERWRITE_INSTALL=false + ;; + *) + ;; + esac + fi + fi + + log "SETUP_MPD=${SETUP_MPD}" + if [ "$SETUP_MPD" == true ]; then + log "ENABLE_MPD_OVERWRITE_INSTALL=${ENABLE_MPD_OVERWRITE_INSTALL}" + fi +} + +_option_rfid_reader() { + # ENABLE_RFID_READER + clear_c + print_c "---------------------- RFID READER ---------------------- + +Phoniebox can be controlled with rfid cards/tags, if you +have a rfid reader connected. +Choose yes to setup a reader. You get prompted for +the type selection and configuration later on. + +Do you want to setup a rfid reader? [Y/n]" + read -r response + case "$response" in + [nN][oO]|[nN]) + ENABLE_RFID_READER=false + ;; + *) + ;; + esac + log "ENABLE_RFID_READER=${ENABLE_RFID_READER}" } _option_samba() { # ENABLE_SAMBA - clear 1>&3 - echo "------------------------- SAMBA ------------------------- + clear_c + print_c "------------------------- SAMBA ------------------------- Samba is required to conveniently copy files to your Phoniebox via a network share. If you don't need it, feel free to skip the installation. If you are unsure, stick to YES! -Do you want to install Samba? [Y/n]" 1>&3 +Do you want to install Samba? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) ENABLE_SAMBA=false - ENABLE_KIOSK_MODE=false ;; *) ;; esac - echo "ENABLE_SAMBA=${ENABLE_SAMBA}" + log "ENABLE_SAMBA=${ENABLE_SAMBA}" } _option_webapp() { # ENABLE_WEBAPP - clear 1>&3 - echo "------------------------ WEBAPP ------------------------- + clear_c + print_c "------------------------ WEBAPP ------------------------- This is only required if you want to use a graphical interface to manage your Phoniebox! -Would you like to install the web application? [Y/n]" 1>&3 +Would you like to install the web application? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -159,20 +204,20 @@ Would you like to install the web application? [Y/n]" 1>&3 *) ;; esac - echo "ENABLE_WEBAPP=${ENABLE_WEBAPP}" + log "ENABLE_WEBAPP=${ENABLE_WEBAPP}" } _option_kiosk_mode() { # ENABLE_KIOSK_MODE - clear 1>&3 - echo "----------------------- KIOSK MODE ---------------------- + clear_c + print_c "----------------------- KIOSK MODE ---------------------- If you have a screen attached to your RPi, this will launch the web application right after boot. It will only install the necessary xserver dependencies and not the entire RPi desktop environment. -Would you like to enable the Kiosk Mode? [y/N]" 1>&3 +Would you like to enable the Kiosk Mode? [y/N]" read -r response case "$response" in [yY][eE][sS]|[yY]) @@ -181,18 +226,18 @@ Would you like to enable the Kiosk Mode? [y/N]" 1>&3 *) ;; esac - echo "ENABLE_KIOSK_MODE=${ENABLE_KIOSK_MODE}" + log "ENABLE_KIOSK_MODE=${ENABLE_KIOSK_MODE}" } _options_update_raspi_os() { # UPDATE_RASPI_OS - clear 1>&3 - echo "----------------------- UPDATE OS ----------------------- + clear_c + print_c "----------------------- UPDATE OS ----------------------- This shall be done eventually, but increases the installation time a lot. -Would you like to update the operating system? [Y/n]" 1>&3 +Would you like to update the operating system? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) @@ -201,14 +246,14 @@ Would you like to update the operating system? [Y/n]" 1>&3 *) ;; esac - echo "UPDATE_RASPI_OS=${UPDATE_RASPI_OS}" + log "UPDATE_RASPI_OS=${UPDATE_RASPI_OS}" } _option_disable_onboard_audio() { # Disable BCM on-chip audio (typically Headphones) # not needed when external sound card is sued - clear 1>&3 - echo "--------------------- ON-CHIP AUDIO --------------------- + clear_c + print_c "--------------------- ON-CHIP AUDIO --------------------- If you are using an external sound card (e.g. USB, HifiBerry, PirateAudio, etc), we recommend to disable @@ -222,7 +267,7 @@ We will do our best not to mess anything up. However, a backup copy will be written to ${DISABLE_ONBOARD_AUDIO_BACKUP} ) -Disable Pi's on-chip audio (headphone / jack output)? [y/N]" 1>&3 +Disable Pi's on-chip audio (headphone / jack output)? [y/N]" read -r response case "$response" in [yY][eE][sS]|[yY]) @@ -231,52 +276,59 @@ Disable Pi's on-chip audio (headphone / jack output)? [y/N]" 1>&3 *) ;; esac - echo "DISABLE_ONBOARD_AUDIO=${DISABLE_ONBOARD_AUDIO}" + log "DISABLE_ONBOARD_AUDIO=${DISABLE_ONBOARD_AUDIO}" } _option_webapp_devel_build() { # Let's detect if we are on the official release branch - if [[ "$GIT_BRANCH" != "${GIT_BRANCH_RELEASE}" || "$GIT_USER" != "$GIT_UPSTREAM_USER" ]]; then + if [[ "$GIT_BRANCH" != "${GIT_BRANCH_RELEASE}" || "$GIT_USER" != "$GIT_UPSTREAM_USER" || "$CI_RUNNING" == "true" ]]; then ENABLE_INSTALL_NODE=true # Unless ENABLE_WEBAPP_PROD_DOWNLOAD is forced to true by user override, do not download a potentially stale build - if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" = "release-only" ]]; then + if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == "release-only" ]]; then ENABLE_WEBAPP_PROD_DOWNLOAD=false fi - if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" = false ]]; then - clear 1>&3 - echo "--------------------- WEBAPP NODE --------------------- + if [[ "$ENABLE_WEBAPP_PROD_DOWNLOAD" == false ]]; then + clear_c + print_c "--------------------- WEBAPP NODE --------------------- You are installing from a non-release branch. This means, you will need to build the web app locally. For that you'll need Node. -Do you want to install Node? [Y/n]" 1>&3 +Do you want to install Node? [Y/n]" read -r response case "$response" in [nN][oO]|[nN]) ENABLE_INSTALL_NODE=false + ENABLE_WEBAPP_PROD_DOWNLOAD=true ;; *) ;; esac # This message will be displayed at the end of the installation process - FIN_MESSAGE="$FIN_MESSAGE\nATTENTION: You need to build the web app locally with - $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u - This must be done after reboot, due to memory restrictions. - Read the documentation regarding local Web App builds!" + local tmp_fin_message="ATTENTION: You need to build the web app locally with + $ cd ~/RPi-Jukebox-RFID/src/webapp && ./run_rebuild.sh -u + This must be done after reboot, due to memory restrictions. + Read the documentation regarding local Web App builds!" + FIN_MESSAGE="${FIN_MESSAGE:+$FIN_MESSAGE\n}${tmp_fin_message}" fi fi -} -customize_options() { - echo "Customize Options starts" + log "ENABLE_INSTALL_NODE=${ENABLE_INSTALL_NODE}" + if [ "$ENABLE_INSTALL_NODE" != true ]; then + log "ENABLE_WEBAPP_PROD_DOWNLOAD=${ENABLE_WEBAPP_PROD_DOWNLOAD}" + fi +} +_run_customize_options() { _option_ipv6 _option_static_ip _option_autohotspot _option_bluetooth _option_disable_onboard_audio + _option_mpd + _option_rfid_reader _option_samba _option_webapp if [[ $ENABLE_WEBAPP == true ]] ; then @@ -286,6 +338,8 @@ customize_options() { # Bullseye is currently under active development and should be updated in any case. # Hence, removing the step below as it becomse mandatory # _options_update_raspi_os +} - echo "Customize Options ends" +customize_options() { + run_with_log_frame _run_customize_options "Customize Options" } diff --git a/installation/routines/install.sh b/installation/routines/install.sh index ca25a17a3..62d602f17 100644 --- a/installation/routines/install.sh +++ b/installation/routines/install.sh @@ -1,19 +1,18 @@ install() { - check_os_type - clear 1>&3 + clear_c customize_options - clear 1>&3 + clear_c set_raspi_config - if [ "$DISABLE_SSH_QOS" = true ] ; then set_ssh_qos; fi; - if [ "$UPDATE_RASPI_OS" = true ] ; then update_raspi_os; fi; + set_ssh_qos + update_raspi_os init_git_repo_from_tardir setup_jukebox_core - if [ "$SETUP_MPD" = true ] ; then setup_mpd; fi; - if [ "$ENABLE_SAMBA" = true ] ; then setup_samba; fi; - if [ "$ENABLE_WEBAPP" = true ] ; then setup_jukebox_webapp; fi; - if [ "$ENABLE_KIOSK_MODE" = true ] ; then setup_kiosk_mode; fi; + setup_mpd + setup_samba + setup_jukebox_webapp + setup_kiosk_mode setup_rfid_reader optimize_boot_time - if [ "$ENABLE_AUTOHOTSPOT" = true ] ; then setup_autohotspot; fi; + setup_autohotspot cleanup } diff --git a/installation/routines/optimize_boot_time.sh b/installation/routines/optimize_boot_time.sh index 2fea66a86..bb6f71902 100644 --- a/installation/routines/optimize_boot_time.sh +++ b/installation/routines/optimize_boot_time.sh @@ -2,18 +2,24 @@ # Reference: https://panther.software/configuration-code/raspberry-pi-3-4-faster-boot-time-in-few-easy-steps/ +OPTIMIZE_DHCP_CONF="/etc/dhcpcd.conf" +OPTIMIZE_BOOT_CMDLINE_OPTIONS="consoleblank=1 logo.nologo quiet loglevel=0 plymouth.enable=0 vt.global_cursor_default=0 plymouth.ignore-serial-consoles splash fastboot noatime nodiratime noram" +OPTIMIZE_DHCP_CONF_HEADER="## Jukebox DHCP Config" +OPTIMIZE_IPV6_CONF_HEADER="## Jukebox IPV6 Config" +OPTIMIZE_BOOT_CONF_HEADER="## Jukebox Boot Config" + _optimize_disable_irrelevant_services() { - echo " * Disable keyboard-setup.service" + log " Disable keyboard-setup.service" sudo systemctl disable keyboard-setup.service - echo " * Disable triggerhappy.service" + log " Disable triggerhappy.service" sudo systemctl disable triggerhappy.service sudo systemctl disable triggerhappy.socket - echo " * Disable raspi-config.service" + log " Disable raspi-config.service" sudo systemctl disable raspi-config.service - echo " * Disable apt-daily.service & apt-daily-upgrade.service" + log " Disable apt-daily.service & apt-daily-upgrade.service" sudo systemctl disable apt-daily.service sudo systemctl disable apt-daily-upgrade.service sudo systemctl disable apt-daily.timer @@ -23,30 +29,28 @@ _optimize_disable_irrelevant_services() { # TODO: If false, actually make sure bluetooth is enabled _optimize_handle_bluetooth() { if [ "$DISABLE_BLUETOOTH" = true ] ; then - echo " * Disable hciuart.service and bluetooth" + print_lc " Disable bluetooth" sudo systemctl disable hciuart.service sudo systemctl disable bluetooth.service fi } # TODO: Allow options to enable/disable wifi, Dynamic/Static IP etc. -_optimize_handle_network_connection() { +_optimize_static_ip() { # Static IP Address and DHCP optimizations - local DHCP_CONF="/etc/dhcpcd.conf" - if [ "$ENABLE_STATIC_IP" = true ] ; then - echo " * Set static IP address" | tee /dev/fd/3 - if grep -q "## Jukebox DHCP Config" "$DHCP_CONF"; then - echo " Skipping. Already set up!" | tee /dev/fd/3 + print_lc " Set static IP address" + if grep -q "${OPTIMIZE_DHCP_CONF_HEADER}" "$OPTIMIZE_DHCP_CONF"; then + log " Skipping. Already set up!" else # DHCP has not been configured - echo " * ${CURRENT_INTERFACE} is the default network interface" | tee /dev/fd/3 - echo " * ${CURRENT_GATEWAY} is the Router Gateway address" | tee /dev/fd/3 - echo " * Using ${CURRENT_IP_ADDRESS} as the static IP for now" | tee /dev/fd/3 + log " ${CURRENT_INTERFACE} is the default network interface" + log " ${CURRENT_GATEWAY} is the Router Gateway address" + log " Using ${CURRENT_IP_ADDRESS} as the static IP for now" - sudo tee -a $DHCP_CONF <<-EOF + sudo tee -a $OPTIMIZE_DHCP_CONF <<-EOF -## Jukebox DHCP Config +${OPTIMIZE_DHCP_CONF_HEADER} interface ${CURRENT_INTERFACE} static ip_address=${CURRENT_IP_ADDRESS}/24 static routers=${CURRENT_GATEWAY} @@ -55,59 +59,114 @@ static domain_name_servers=${CURRENT_GATEWAY} EOF fi - else - echo " * Skipped static IP address" fi } # TODO: Allow both Enable and Disable _optimize_ipv6_arp() { if [ "$DISABLE_IPv6" = true ] ; then - echo " * Disabling IPV6 and ARP" - sudo tee -a $DHCP_CONF <<-EOF + print_lc " Disabling IPV6" + if grep -q "${OPTIMIZE_IPV6_CONF_HEADER}" "$OPTIMIZE_DHCP_CONF"; then + log " Skipping. Already set up!" + else + sudo tee -a $OPTIMIZE_DHCP_CONF <<-EOF -## Jukebox boot speed-up settings +${OPTIMIZE_IPV6_CONF_HEADER} noarp ipv4only noipv6 EOF - + fi fi } # TODO: Allow both Enable and Disable _optimize_handle_boot_screen() { if [ "$DISABLE_BOOT_SCREEN" = true ] ; then - echo " * Disable RPi rainbow screen" - BOOT_CONFIG='/boot/config.txt' - sudo tee -a $BOOT_CONFIG <<-EOF + log " Disable RPi rainbow screen" + if grep -q "${OPTIMIZE_BOOT_CONF_HEADER}" "$RPI_BOOT_CONFIG_FILE"; then + log " Skipping. Already set up!" + else + sudo tee -a $RPI_BOOT_CONFIG_FILE <<-EOF -## Jukebox Settings +${OPTIMIZE_BOOT_CONF_HEADER} disable_splash=1 EOF + fi fi } # TODO: Allow both Enable and Disable _optimize_handle_boot_logs() { if [ "$DISABLE_BOOT_LOGS_PRINT" = true ] ; then - echo " * Disable boot logs" - BOOT_CMDLINE='/boot/cmdline.txt' - sudo sed -i "$ s/$/ consoleblank=1 logo.nologo quiet loglevel=0 plymouth.enable=0 vt.global_cursor_default=0 plymouth.ignore-serial-consoles splash fastboot noatime nodiratime noram/" $BOOT_CMDLINE + log " Disable boot logs" + + if [ ! -s "${RPI_BOOT_CMDLINE_FILE}" ];then + sudo tee "${RPI_BOOT_CMDLINE_FILE}" <<-EOF +${OPTIMIZE_BOOT_CMDLINE_OPTIONS} +EOF + else + for option in $OPTIMIZE_BOOT_CMDLINE_OPTIONS + do + if ! grep -qiw "$option" "${RPI_BOOT_CMDLINE_FILE}" ; then + sudo sed -i "s/$/ $option/" "${RPI_BOOT_CMDLINE_FILE}" + fi + done + fi fi } -optimize_boot_time() { - echo "Optimize boot time" | tee /dev/fd/3 - _optimize_disable_irrelevant_services - _optimize_handle_bluetooth - _optimize_handle_network_connection - _optimize_ipv6_arp - _optimize_handle_boot_screen - _optimize_handle_boot_logs +_optimize_check() { + print_verify_installation + + verify_optional_service_enablement keyboard-setup.service disabled + verify_optional_service_enablement triggerhappy.service disabled + verify_optional_service_enablement triggerhappy.socket disabled + verify_optional_service_enablement raspi-config.service disabled + verify_optional_service_enablement apt-daily.service disabled + verify_optional_service_enablement apt-daily-upgrade.service disabled + verify_optional_service_enablement apt-daily.timer disabled + verify_optional_service_enablement apt-daily-upgrade.timer disabled - echo "DONE: optimize_boot_time" + if [ "$DISABLE_BLUETOOTH" = true ] ; then + verify_optional_service_enablement hciuart.service disabled + verify_optional_service_enablement bluetooth.service disabled + fi + + if [ "$ENABLE_STATIC_IP" = true ] ; then + verify_file_contains_string_once "${OPTIMIZE_DHCP_CONF_HEADER}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string "${CURRENT_INTERFACE}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string "${CURRENT_IP_ADDRESS}" "${OPTIMIZE_DHCP_CONF}" + verify_file_contains_string "${CURRENT_GATEWAY}" "${OPTIMIZE_DHCP_CONF}" + fi + if [ "$DISABLE_IPv6" = true ] ; then + verify_file_contains_string_once "${OPTIMIZE_IPV6_CONF_HEADER}" "${OPTIMIZE_DHCP_CONF}" + fi + if [ "$DISABLE_BOOT_SCREEN" = true ] ; then + verify_file_contains_string_once "${OPTIMIZE_BOOT_CONF_HEADER}" "${RPI_BOOT_CONFIG_FILE}" + fi + + if [ "$DISABLE_BOOT_LOGS_PRINT" = true ] ; then + for option in $OPTIMIZE_BOOT_CMDLINE_OPTIONS + do + verify_file_contains_string_once $option "${RPI_BOOT_CMDLINE_FILE}" + done + fi +} + +_run_optimize_boot_time() { + _optimize_disable_irrelevant_services + _optimize_handle_bluetooth + _optimize_static_ip + _optimize_ipv6_arp + _optimize_handle_boot_screen + _optimize_handle_boot_logs + _optimize_check +} + +optimize_boot_time() { + run_with_log_frame _run_optimize_boot_time "Optimize boot time" } diff --git a/installation/routines/set_raspi_config.sh b/installation/routines/set_raspi_config.sh index 1b1c3ee55..7f39a0ba5 100644 --- a/installation/routines/set_raspi_config.sh +++ b/installation/routines/set_raspi_config.sh @@ -1,33 +1,33 @@ #!/usr/bin/env bash - -set_raspi_config() { - echo "Set default raspi-config" | tee /dev/fd/3 +_run_set_raspi_config() { # Source: https://raspberrypi.stackexchange.com/a/66939 # Autologin - echo " * Enable Autologin for user" + log " Enable Autologin for user" sudo raspi-config nonint do_boot_behaviour B2 # Wait for network at boot - # echo " * Enable 'Wait for network at boot'" + # log " Enable 'Wait for network at boot'" # sudo raspi-config nonint do_boot_wait 1 # power management of wifi: switch off to avoid disconnecting - echo " * Disable Wifi power management to avoid disconnecting" + log " Disable Wifi power management to avoid disconnecting" sudo iwconfig wlan0 power off # On-board audio - if [[ $(get_onboard_audio) -eq 1 ]]; then - DISABLE_ONBOARD_AUDIO=${DISABLE_ONBOARD_AUDIO:-false} - if [[ $DISABLE_ONBOARD_AUDIO = true ]]; then - echo " * Disable on-chip BCM audio" - echo "Backup ${RPI_BOOT_CONFIG_FILE} --> ${DISABLE_ONBOARD_AUDIO_BACKUP}" + if [ "$DISABLE_ONBOARD_AUDIO" == true ]; then + log " Disable on-chip BCM audio" + if grep -q -E "^dtparam=([^,]*,)*audio=(on|true|yes|1).*" "${RPI_BOOT_CONFIG_FILE}" ; then + log " Backup ${RPI_BOOT_CONFIG_FILE} --> ${DISABLE_ONBOARD_AUDIO_BACKUP}" sudo cp "${RPI_BOOT_CONFIG_FILE}" "${DISABLE_ONBOARD_AUDIO_BACKUP}" sudo sed -i "s/^\(dtparam=\([^,]*,\)*\)audio=\(on\|true\|yes\|1\)\(.*\)/\1audio=off\4/g" "${RPI_BOOT_CONFIG_FILE}" + else + log " On board audio seems to be off already. Not touching ${RPI_BOOT_CONFIG_FILE}" fi - else - echo "On board audio seems to be off already. Not touching ${RPI_BOOT_CONFIG_FILE}" fi +} +set_raspi_config() { + run_with_log_frame _run_set_raspi_config "Set default raspi-config" } diff --git a/installation/routines/set_ssh_qos.sh b/installation/routines/set_ssh_qos.sh index ad67ddc41..76242f712 100644 --- a/installation/routines/set_ssh_qos.sh +++ b/installation/routines/set_ssh_qos.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash set_ssh_qos() { - # The latest version of SSH installed on the Raspberry Pi 3 uses QoS headers, which disagrees with some - # routers and other hardware. This causes immense delays when remotely accessing the RPi over ssh. - echo " * Set SSH QoS to best effort" - echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/sshd_config - echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/ssh_config + if [ "$DISABLE_SSH_QOS" == true ] ; then + # The latest version of SSH installed on the Raspberry Pi 3 uses QoS headers, which disagrees with some + # routers and other hardware. This causes immense delays when remotely accessing the RPi over ssh. + log " Set SSH QoS to best effort" + echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/sshd_config + echo -e "IPQoS 0x00 0x00\n" | sudo tee -a /etc/ssh/ssh_config + fi } diff --git a/installation/routines/setup_autohotspot.sh b/installation/routines/setup_autohotspot.sh index 214a90e0f..a083b3fcf 100644 --- a/installation/routines/setup_autohotspot.sh +++ b/installation/routines/setup_autohotspot.sh @@ -1,14 +1,34 @@ #!/usr/bin/env bash +# inspired by +# https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection + + +AUTOHOTSPOT_HOSTAPD_CONF_FILE="/etc/hostapd/hostapd.conf" +AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE="/etc/default/hostapd" +AUTOHOTSPOT_DNSMASQ_CONF_FILE="/etc/dnsmasq.conf" +AUTOHOTSPOT_DHCPD_CONF_FILE="/etc/dhcpcd.conf" + +AUTOHOTSPOT_TARGET_PATH="/usr/bin/autohotspot" + _get_interface() { # interfaces may vary WIFI_INTERFACE=$(iw dev | grep "Interface"| awk '{ print $2 }') - WIFI_REGION=$(iw reg get | grep country | awk '{ print $2}' | cut -d: -f1) -} + WIFI_REGION=$(iw reg get | grep country | head -n 1 | awk '{ print $2}' | cut -d: -f1) + # fix for CI runs on docker + if [ "${CI_RUNNING}" == "true" ]; then + if [ -z "${WIFI_INTERFACE}" ]; then + WIFI_INTERFACE="CI TEST INTERFACE" + fi + if [ -z "${WIFI_REGION}" ]; then + WIFI_REGION="CI TEST REGION" + fi + fi +} _install_packages() { - sudo apt-get -y install hostapd dnsmasq + sudo apt-get -y install hostapd dnsmasq iw # disable services. We want to start them manually sudo systemctl unmask hostapd @@ -17,18 +37,18 @@ _install_packages() { } _configure_hostapd() { - HOSTAPD_CUSTOM_FILE="${INSTALLATION_PATH}"/resources/autohotspot/hostapd.conf - HOSTAPD_CONF_FILE="/etc/hostapd/hostapd.conf" + local HOSTAPD_CUSTOM_FILE="${INSTALLATION_PATH}"/resources/autohotspot/hostapd.conf + sed -i "s/WIFI_INTERFACE/${WIFI_INTERFACE}/g" "${HOSTAPD_CUSTOM_FILE}" sed -i "s/AUTOHOTSPOT_PASSWORD/${AUTOHOTSPOT_PASSWORD}/g" "${HOSTAPD_CUSTOM_FILE}" sed -i "s/WIFI_REGION/${WIFI_REGION}/g" "${HOSTAPD_CUSTOM_FILE}" - sudo cp "${HOSTAPD_CUSTOM_FILE}" "${HOSTAPD_CONF_FILE}" + sudo cp "${HOSTAPD_CUSTOM_FILE}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" - sudo sed -i "s@^#DAEMON_CONF=.*@DAEMON_CONF=\"${HOSTAPD_CONF_FILE}\"@g" /etc/default/hostapd + sudo sed -i "s@^#DAEMON_CONF=.*@DAEMON_CONF=\"${AUTOHOTSPOT_HOSTAPD_CONF_FILE}\"@g" "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" } _configure_dnsmasq() { - sudo tee -a /etc/dnsmasq.conf <<-EOF + sudo tee -a "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" <<-EOF #AutoHotspot Config #stop DNSmasq from using resolv.conf no-resolv @@ -42,33 +62,58 @@ EOF _other_configuration() { sudo mv /etc/network/interfaces /etc/network/interfaces.bak sudo touch /etc/network/interfaces - echo nohook wpa_supplicant | sudo tee -a /etc/dhcpcd.conf + echo nohook wpa_supplicant | sudo tee -a "${AUTOHOTSPOT_DHCPD_CONF_FILE}" } _install_service_and_timer() { sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot.service /etc/systemd/system/autohotspot.service sudo systemctl enable autohotspot.service - sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot.timer /etc/cron.d/autohotspot + + local cron_autohotspot_file="/etc/cron.d/autohotspot" + sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot.timer "${cron_autohotspot_file}" + sudo sed -i "s|%%USER%%|${CURRENT_USER}|g" "${cron_autohotspot_file}" } _install_autohotspot_script() { - TARGET_PATH="/usr/bin/autohotspot" - sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot "${TARGET_PATH}" - sudo chmod +x "${TARGET_PATH}" + sudo cp "${INSTALLATION_PATH}"/resources/autohotspot/autohotspot "${AUTOHOTSPOT_TARGET_PATH}" + sudo chmod +x "${AUTOHOTSPOT_TARGET_PATH}" } -setup_autohotspot() { - echo "Install AutoHotspot functionality" | tee /dev/fd/3 - # inspired by - # https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection - _get_interface +_autohotspot_check() { + print_verify_installation + + verify_apt_packages hostapd dnsmasq iw + + verify_service_enablement hostapd.service disabled + verify_service_enablement dnsmasq.service disabled + verify_service_enablement autohotspot.service enabled + + verify_files_exists "/etc/cron.d/autohotspot" + verify_files_exists "${AUTOHOTSPOT_TARGET_PATH}" + + verify_file_contains_string "${WIFI_INTERFACE}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "${AUTOHOTSPOT_PASSWORD}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "${WIFI_REGION}" "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" + verify_file_contains_string "${AUTOHOTSPOT_HOSTAPD_CONF_FILE}" "${AUTOHOTSPOT_HOSTAPD_DAEMON_CONF_FILE}" + + verify_file_contains_string "${WIFI_INTERFACE}" "${AUTOHOTSPOT_DNSMASQ_CONF_FILE}" + verify_file_contains_string "nohook wpa_supplicant" "${AUTOHOTSPOT_DHCPD_CONF_FILE}" +} + +_run_setup_autohotspot() { _install_packages + _get_interface _configure_hostapd _configure_dnsmasq _other_configuration _install_autohotspot_script _install_service_and_timer + _autohotspot_check +} - echo "DONE: setup_autohotspot" +setup_autohotspot() { + if [ "$ENABLE_AUTOHOTSPOT" == true ] ; then + run_with_log_frame _run_setup_autohotspot "Install AutoHotspot" + fi } diff --git a/installation/routines/setup_git.sh b/installation/routines/setup_git.sh index 740d43ae3..c04ff7acf 100644 --- a/installation/routines/setup_git.sh +++ b/installation/routines/setup_git.sh @@ -2,7 +2,7 @@ GIT_ABORT_MSG="Aborting dir to git repo conversion. Your directory content is untouched, you simply cannot use git for updating / developing" _git_install_os_dependencies() { - echo "Install Git dependencies" + log " Install Git dependencies" sudo apt-get -y update; sudo apt-get -y install \ git \ --no-install-recommends \ @@ -12,9 +12,9 @@ _git_install_os_dependencies() { } _git_convert_tardir_git_repo() { - echo "****************************************************" - echo "*** Converting tar-ball download into git repository" - echo "****************************************************" + log "**************************************************** +*** Converting tar-ball download into git repository +****************************************************" # Just in case, the git version is not new enough, we split up git init -b "${GIT_BRANCH}" into: git -c init.defaultBranch=main init @@ -30,19 +30,19 @@ _git_convert_tardir_git_repo() { # We simply get everything from the beginning of future 3 development but excluding Version 2.X if [[ $GIT_USE_SSH == true ]]; then git remote add origin "git@github.com:${GIT_USER}/${GIT_REPO_NAME}.git" - echo "*** Git fetch (SSH) *******************************" + log "\n*** Git fetch (SSH) *******************************" # Prevent: The authenticity of host 'github.com (140.82.121.4)' can't be established. # Do only for this one command, so we do not disable the checks forever if ! git -c core.sshCommand='ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' fetch origin "${GIT_BRANCH}" --set-upstream --shallow-since=2021-04-21 --tags; then - echo "" - echo "*** NOTICE *****************************************" - echo "* Error in getting Git Repository using SSH! USING FALLBACK HTTPS." - echo "* Note: This is only relevant for developers!" - echo "* Did you forget to upload the ssh key for this machine to GitHub?" - echo "* Defaulting to HTTPS protocol. You can change back to SSH later with" - echo "* git remote set-url origin git@github.com:${GIT_USER}/${GIT_REPO_NAME}.git" - echo "* git remote set-url upstream git@github.com:${GIT_UPSTREAM_USER}/${GIT_REPO_NAME}.git" + log "\n*** NOTICE ***************************************** +* Error in getting Git Repository using SSH! USING FALLBACK HTTPS. +* Note: This is only relevant for developers! +* Did you forget to upload the ssh key for this machine to GitHub? +* Defaulting to HTTPS protocol. You can change back to SSH later with +* git remote set-url origin git@github.com:${GIT_USER}/${GIT_REPO_NAME}.git +* git remote set-url upstream git@github.com:${GIT_UPSTREAM_USER}/${GIT_REPO_NAME}.git\n" + git remote remove origin GIT_USE_SSH=false else @@ -58,30 +58,31 @@ _git_convert_tardir_git_repo() { if [[ "$GIT_USER" != "$GIT_UPSTREAM_USER" ]]; then git remote add upstream "https://github.com/${GIT_UPSTREAM_USER}/${GIT_REPO_NAME}.git" fi - echo "*** Git fetch (HTTPS) *****************************" + log "\n*** Git fetch (HTTPS) *****************************" if ! git fetch origin --set-upstream --shallow-since=2021-04-21 --tags "${GIT_BRANCH}"; then - echo "Error: Could not fetch repository!" - echo -e "$GIT_ABORT_MSG" + log "Error: Could not fetch repository!" + log "$GIT_ABORT_MSG" return fi fi HASH_BRANCH=$(git rev-parse FETCH_HEAD) || { echo -e "$GIT_ABORT_MSG"; return; } - echo "*** FETCH_HEAD ($GIT_BRANCH) = $HASH_BRANCH" + + log "\n*** FETCH_HEAD ($GIT_BRANCH) = $HASH_BRANCH" git add . # Checkout the exact hash that we have downloaded as tarball - echo "*** Git checkout commit" + log "*** Git checkout commit" git -c advice.detachedHead=false checkout "$GIT_HASH" || { echo -e "$GIT_ABORT_MSG"; return; } HASH_HEAD=$(git rev-parse HEAD) || { echo -e "$GIT_ABORT_MSG"; return; } - echo "*** REQUESTED COMMIT = $HASH_HEAD" + log "*** REQUESTED COMMIT = $HASH_HEAD" # Let's move onto the relevant branch, WITHOUT touching the current checked-out commit # Since we have fetched with --set-upstream above this initializes the tracking branch - echo "*** Git initialize branch" + log "*** Git initialize branch" git checkout -b "$GIT_BRANCH" if [[ "$GIT_USER" != "$GIT_UPSTREAM_USER" ]]; then - echo "*** Get upstream release tags" + log "*** Get upstream release tags" # Always get the upstream release branch to get all release tags # in case they have not been copied to user repository git fetch upstream --shallow-since=2021-04-21 --tags "${GIT_BRANCH_RELEASE}" @@ -97,7 +98,7 @@ _git_convert_tardir_git_repo() { if [[ $GIT_BRANCH != "${GIT_BRANCH_RELEASE}" ]]; then OUTPUT=$(git fetch origin --shallow-since=2021-04-21 --tags "${GIT_BRANCH_RELEASE}" 2>&1) if [[ $? -ne 128 ]]; then - echo "*** Preparing ${GIT_BRANCH_RELEASE} in background" + log "*** Preparing ${GIT_BRANCH_RELEASE} in background" echo -e "$OUTPUT" fi unset OUTPUT @@ -105,7 +106,7 @@ _git_convert_tardir_git_repo() { if [[ $GIT_BRANCH != "${GIT_BRANCH_DEVELOP}" ]]; then OUTPUT=$(git fetch origin --shallow-since=2021-04-21 --tags "${GIT_BRANCH_DEVELOP}" 2>&1) if [[ $? -ne 128 ]]; then - echo "*** Preparing ${GIT_BRANCH_DEVELOP} in background" + log "*** Preparing ${GIT_BRANCH_DEVELOP} in background" echo -e "$OUTPUT" fi unset OUTPUT @@ -113,23 +114,24 @@ _git_convert_tardir_git_repo() { # Provide some status outputs to the user if [[ "${HASH_BRANCH}" != "${HASH_HEAD}" ]]; then - echo "*** IMPORTANT NOTICE *******************************" - echo "* Your requested branch has moved on while you were installing." - echo "* Don't worry! We will stay within the the exact download version!" - echo "* But we set up the git repo to be ready for updating." - echo "* To start updating (observe updating guidelines!), do:" - echo "* $ git pull origin $GIT_BRANCH" + log "\n*** IMPORTANT NOTICE ******************************* +* Your requested branch has moved on while you were installing. +* Don't worry! We will stay within the exact download version! +* But we set up the git repo to be ready for updating. +* To start updating (observe updating guidelines!), do: +* $ git pull origin $GIT_BRANCH\n" + fi - echo "*** Git remotes ************************************" + log "*** Git remotes ************************************" git remote -v - echo "*** Git status *************************************" + log "*** Git status *************************************" git status -sb - echo "*** Git log ****************************************" + log "*** Git log ****************************************" git log --oneline "HEAD^..origin/$GIT_BRANCH" - echo "*** Git describe ***********************************" + log "*** Git describe ***********************************" git describe --tag --dirty='-dirty' - echo "****************************************************" + log "****************************************************" cp -f .githooks/* .git/hooks @@ -137,12 +139,20 @@ _git_convert_tardir_git_repo() { unset HASH_BRANCH } -init_git_repo_from_tardir() { - echo "Install Git & init repository" | tee /dev/fd/3 +_git_repo_check() { + print_verify_installation - cd "${INSTALLATION_PATH}" || exit_on_error - _git_install_os_dependencies - _git_convert_tardir_git_repo + verify_apt_packages git + verify_dirs_chmod_chown 755 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${INSTALLATION_PATH}/.git" +} + +_run_init_git_repo_from_tardir() { + cd "${INSTALLATION_PATH}" || exit_on_error + _git_install_os_dependencies + _git_convert_tardir_git_repo + _git_repo_check +} - echo "DONE: init_git_repo_from_tardir" +init_git_repo_from_tardir() { + run_with_log_frame _run_init_git_repo_from_tardir "Install Git & init repository" } diff --git a/installation/routines/setup_jukebox_core.sh b/installation/routines/setup_jukebox_core.sh index e6dfa6d8f..a7d0f29b6 100644 --- a/installation/routines/setup_jukebox_core.sh +++ b/installation/routines/setup_jukebox_core.sh @@ -1,32 +1,28 @@ #!/usr/bin/env bash # Constants -GD_ID_COMPILED_LIBZMQ_ARMV7="1KP6BqLF-i2dCUsHhOUpOwwuOmKsB5GKY" # ARMv7: https://drive.google.com/file/d/1KP6BqLF-i2dCUsHhOUpOwwuOmKsB5GKY/view?usp=sharing -GD_ID_COMPILED_LIBZMQ_ARMV6="1iygOm-G1cg_3YERuVRT6FhGBE34ZkwgV" # ARMv6: https://drive.google.com/file/d/1iygOm-G1cg_3YERuVRT6FhGBE34ZkwgV/view?usp=sharing -GD_ID_COMPILED_PYZMQ_ARMV7="" -GD_ID_COMPILED_PYZMQ_ARMV6="1lDsV_pVcXbg6YReHb9AldMkyRZCpc6-n" # https://drive.google.com/file/d/1lDsV_pVcXbg6YReHb9AldMkyRZCpc6-n/view?usp=sharing +JUKEBOX_ZMQ_TMP_DIR="${HOME_PATH}/libzmq" +JUKEBOX_ZMQ_PREFIX="/usr/local" +JUKEBOX_ZMQ_VERSION="4.3.5" -ZMQ_TMP_DIR="libzmq" -ZMQ_PREFIX="/usr/local" +JUKEBOX_PULSE_CONFIG="${HOME_PATH}"/.config/pulse/default.pa +JUKEBOX_SERVICE_NAME="${SYSTEMD_USR_PATH}/jukebox-daemon.service" _show_slow_hardware_message() { -echo " -------------------------------------------------------------------- + print_c " -------------------------------------------------------------------- | Your hardware is a little slower so this step will take a while. | | Go watch a movie but don't let your computer go to sleep for the | | SSH connection to remain intact. | - --------------------------------------------------------------------" 1>&3 + --------------------------------------------------------------------" } # Functions _jukebox_core_install_os_dependencies() { - echo " Install Jukebox OS dependencies" + print_lc " Install Jukebox OS dependencies" + + local apt_packages=$(get_args_from_file "${INSTALLATION_PATH}/packages-core.txt") sudo apt-get -y update && sudo apt-get -y install \ - at \ - alsa-utils \ - python3 python3-venv python3-dev \ - espeak ffmpeg mpg123 \ - pulseaudio pulseaudio-module-bluetooth pulseaudio-utils caps \ - libasound2-dev \ + $apt_packages \ --no-install-recommends \ --allow-downgrades \ --allow-remove-essential \ @@ -34,11 +30,10 @@ _jukebox_core_install_os_dependencies() { } _jukebox_core_install_python_requirements() { - echo " Install Python requirements" + print_lc " Install Python requirements" cd "${INSTALLATION_PATH}" || exit_on_error - VIRTUAL_ENV="${INSTALLATION_PATH}/.venv" python3 -m venv $VIRTUAL_ENV source "$VIRTUAL_ENV/bin/activate" @@ -47,36 +42,36 @@ _jukebox_core_install_python_requirements() { } _jukebox_core_configure_pulseaudio() { - echo "Copy PulseAudio configuration" - mkdir -p ~/.config/pulse - cp -f "${INSTALLATION_PATH}/resources/default-settings/pulseaudio.default.pa" ~/.config/pulse/default.pa + print_lc " Copy PulseAudio configuration" + mkdir -p $(dirname "$JUKEBOX_PULSE_CONFIG") + cp -f "${INSTALLATION_PATH}/resources/default-settings/pulseaudio.default.pa" "${JUKEBOX_PULSE_CONFIG}" } _jukebox_core_build_libzmq_with_drafts() { - LIBSODIUM_VERSION="1.0.18" - ZMQ_VERSION="4.3.4" - - { cd "${HOME_PATH}" && mkdir "${ZMQ_TMP_DIR}" && cd "${ZMQ_TMP_DIR}"; } || exit_on_error - wget --quiet https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VERSION}-RELEASE/libsodium-${LIBSODIUM_VERSION}.tar.gz - tar -zxvf libsodium-${LIBSODIUM_VERSION}.tar.gz - cd libsodium-${LIBSODIUM_VERSION} || exit_on_error - ./configure - make && make install - - cd "${HOME}/${ZMQ_TMP_DIR}" || exit_on_error - wget https://github.com/zeromq/libzmq/releases/download/v${ZMQ_VERSION}/zeromq-${ZMQ_VERSION}.tar.gz -O libzmq.tar.gz - tar -xzf libzmq.tar.gz - zeromq-${ZMQ_VERSION}/configure --prefix=${ZMQ_PREFIX} --enable-drafts - make && make install + print_lc " Building libzmq v${JUKEBOX_ZMQ_VERSION} with drafts support" + local zmq_filename="zeromq-${JUKEBOX_ZMQ_VERSION}" + local zmq_tar_filename="${zmq_filename}.tar.gz" + local cpu_count=${CPU_COUNT:-$(python3 -c "import os; print(os.cpu_count())")} + + cd "${JUKEBOX_ZMQ_TMP_DIR}" || exit_on_error + wget --quiet https://github.com/zeromq/libzmq/releases/download/v${JUKEBOX_ZMQ_VERSION}/${zmq_tar_filename} + tar -xzf ${zmq_tar_filename} + rm -f ${zmq_tar_filename} + cd ${zmq_filename} || exit_on_error + ./configure --prefix=${JUKEBOX_ZMQ_PREFIX} --enable-drafts --disable-Werror + make -j${cpu_count} && sudo make install } _jukebox_core_download_prebuilt_libzmq_with_drafts() { - local ZMQ_TAR_FILENAME="libzmq.tar.gz" - - _download_file_from_google_drive "${LIBZMQ_GD_DOWNLOAD_ID}" "${ZMQ_TAR_FILENAME}" - tar -xzf ${ZMQ_TAR_FILENAME} - rm -f ${ZMQ_TAR_FILENAME} - sudo rsync -a ./* ${ZMQ_PREFIX}/ + log " Download pre-compiled libzmq with drafts support" + local zmq_tar_filename="libzmq.tar.gz" + ARCH=$(get_architecture) + + cd "${JUKEBOX_ZMQ_TMP_DIR}" || exit_on_error + wget --quiet https://github.com/pabera/libzmq/releases/download/v${JUKEBOX_ZMQ_VERSION}/libzmq5-${ARCH}-${JUKEBOX_ZMQ_VERSION}.tar.gz -O ${zmq_tar_filename} + tar -xzf ${zmq_tar_filename} + rm -f ${zmq_tar_filename} + sudo rsync -a ./* ${JUKEBOX_ZMQ_PREFIX}/ } _jukebox_core_build_and_install_pyzmq() { @@ -87,62 +82,79 @@ _jukebox_core_build_and_install_pyzmq() { # Sources: # https://pyzmq.readthedocs.io/en/latest/howto/draft.html # https://github.com/MonsieurV/ZeroMQ-RPi/blob/master/README.md - echo " Build and install pyzmq with WebSockets Support" + # https://github.com/zeromq/pyzmq/issues/1523#issuecomment-1593120264 + print_lc " Install pyzmq with libzmq-drafts to support WebSockets" if ! pip list | grep -F pyzmq >> /dev/null; then - # Download pre-compiled libzmq from Google Drive because RPi has trouble compiling it - echo " Download pre-compiled libzmq from Google Drive because RPi has trouble compiling it" - { cd "${HOME_PATH}" && mkdir "${ZMQ_TMP_DIR}" && cd "${ZMQ_TMP_DIR}"; } || exit_on_error - - # ARMv7 as default - LIBZMQ_GD_DOWNLOAD_ID=${GD_ID_COMPILED_LIBZMQ_ARMV7} if [[ $(uname -m) == "armv6l" ]]; then - # ARMv6 as fallback - LIBZMQ_GD_DOWNLOAD_ID=${GD_ID_COMPILED_LIBZMQ_ARMV6} _show_slow_hardware_message fi + mkdir -p "${JUKEBOX_ZMQ_TMP_DIR}" || exit_on_error if [ "$BUILD_LIBZMQ_WITH_DRAFTS_ON_DEVICE" = true ] ; then _jukebox_core_build_libzmq_with_drafts else _jukebox_core_download_prebuilt_libzmq_with_drafts fi - ZMQ_PREFIX="${ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ - pip install --no-cache-dir --no-binary "pyzmq" --pre pyzmq + ZMQ_PREFIX="${JUKEBOX_ZMQ_PREFIX}" ZMQ_DRAFT_API=1 \ + pip install -v --no-binary pyzmq --pre pyzmq else - echo " Skipping. pyzmq already installed" + print_lc " Skipping. pyzmq already installed" fi } _jukebox_core_install_settings() { - echo " Register Jukebox settings" + print_lc " Register Jukebox settings" cp -f "${INSTALLATION_PATH}/resources/default-settings/jukebox.default.yaml" "${SETTINGS_PATH}/jukebox.yaml" cp -f "${INSTALLATION_PATH}/resources/default-settings/logger.default.yaml" "${SETTINGS_PATH}/logger.yaml" } _jukebox_core_register_as_service() { - echo " Register Jukebox Core user service" + print_lc " Register Jukebox Core user service" - local jukebox_service="${SYSTEMD_USR_PATH}/jukebox-daemon.service" - sudo cp -f "${INSTALLATION_PATH}/resources/default-services/jukebox-daemon.service" "${jukebox_service}" - sudo sed -i "s|%%INSTALLATION_PATH%%|${INSTALLATION_PATH}|g" "${jukebox_service}" - sudo chmod 644 "${jukebox_service}" + sudo cp -f "${INSTALLATION_PATH}/resources/default-services/jukebox-daemon.service" "${JUKEBOX_SERVICE_NAME}" + sudo sed -i "s|%%INSTALLATION_PATH%%|${INSTALLATION_PATH}|g" "${JUKEBOX_SERVICE_NAME}" + sudo chmod 644 "${JUKEBOX_SERVICE_NAME}" systemctl --user daemon-reload systemctl --user enable jukebox-daemon.service } -setup_jukebox_core() { - echo "Install Jukebox Core" | tee /dev/fd/3 +_jukebox_core_check() { + print_verify_installation + + local apt_packages=$(get_args_from_file "${INSTALLATION_PATH}/packages-core.txt") + verify_apt_packages $apt_packages + + verify_dirs_exists "${VIRTUAL_ENV}" + + local pip_modules=$(get_args_from_file "${INSTALLATION_PATH}/requirements.txt") + verify_pip_modules pyzmq $pip_modules + + verify_files_chmod_chown 644 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${JUKEBOX_PULSE_CONFIG}" - _jukebox_core_install_os_dependencies - _jukebox_core_install_python_requirements - _jukebox_core_configure_pulseaudio - _jukebox_core_build_and_install_pyzmq - _jukebox_core_install_settings - _jukebox_core_register_as_service + verify_files_chmod_chown 644 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${SETTINGS_PATH}/jukebox.yaml" + verify_files_chmod_chown 644 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${SETTINGS_PATH}/logger.yaml" - echo "DONE: setup_jukebox_core" + verify_files_chmod_chown 644 root root "${SYSTEMD_USR_PATH}/jukebox-daemon.service" + + verify_file_contains_string "${INSTALLATION_PATH}" "${JUKEBOX_SERVICE_NAME}" + + verify_service_enablement jukebox-daemon.service enabled --user +} + +_run_setup_jukebox_core() { + _jukebox_core_install_os_dependencies + _jukebox_core_install_python_requirements + _jukebox_core_build_and_install_pyzmq + _jukebox_core_configure_pulseaudio + _jukebox_core_install_settings + _jukebox_core_register_as_service + _jukebox_core_check +} + +setup_jukebox_core() { + run_with_log_frame _run_setup_jukebox_core "Install Jukebox Core" } diff --git a/installation/routines/setup_jukebox_webapp.sh b/installation/routines/setup_jukebox_webapp.sh index 54be3119c..f7407f96c 100644 --- a/installation/routines/setup_jukebox_webapp.sh +++ b/installation/routines/setup_jukebox_webapp.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Constants -GD_ID_COMPILED_WEBAPP="1um-smyfsVPzVZn18hhwuFt97XR3PjAbB" # https://drive.google.com/file/d/1um-smyfsVPzVZn18hhwuFt97XR3PjAbB/view?usp=sharing +WEBAPP_NGINX_SITE_DEFAULT_CONF="/etc/nginx/sites-available/default" # For ARMv7+ NODE_MAJOR=20 @@ -15,13 +15,13 @@ _jukebox_webapp_install_node() { sudo apt-get -y update if which node > /dev/null; then - echo " Found existing NodeJS. Hence, updating NodeJS" | tee /dev/fd/3 + print_lc " Found existing NodeJS. Hence, updating NodeJS" sudo npm cache clean -f sudo npm install --silent -g n sudo n --quiet latest sudo npm update --silent -g else - echo " Install NodeJS" | tee /dev/fd/3 + print_lc " Install NodeJS" # Zero and older versions of Pi with ARMv6 only # support experimental NodeJS @@ -39,14 +39,13 @@ _jukebox_webapp_install_node() { sudo apt-get update sudo apt-get install -y nodejs fi - fi } # TODO: Avoid building the app locally # Instead implement a Github Action that prebuilds on commititung a git tag _jukebox_webapp_build() { - echo " Building web application" + print_lc " Building web application" cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error npm ci --prefer-offline --no-audit --production rm -rf build @@ -55,45 +54,68 @@ _jukebox_webapp_build() { } _jukebox_webapp_download() { - echo " Downloading web application" | tee /dev/fd/3 + print_lc " Downloading web application" + local JUKEBOX_VERSION=$(get_version_string "${INSTALLATION_PATH}/src/jukebox/jukebox/version.py") local TAR_FILENAME="webapp-build.tar.gz" + local DOWNLOAD_URL="https://github.com/MiczFlor/RPi-Jukebox-RFID/releases/download/v${JUKEBOX_VERSION}/webapp-v${JUKEBOX_VERSION}.tar.gz" + log " DOWNLOAD_URL: ${DOWNLOAD_URL}" + cd "${INSTALLATION_PATH}/src/webapp" || exit_on_error - _download_file_from_google_drive ${GD_ID_COMPILED_WEBAPP} ${TAR_FILENAME} + # URL must be set to default repo as installation can be run from different repos as well where releases may not exist + wget --quiet ${DOWNLOAD_URL} -O ${TAR_FILENAME} tar -xzf ${TAR_FILENAME} rm -f ${TAR_FILENAME} cd "${INSTALLATION_PATH}" || exit_on_error } _jukebox_webapp_register_as_system_service_with_nginx() { - echo " Install and configure nginx" | tee /dev/fd/3 + print_lc " Install and configure nginx" sudo apt-get -qq -y update sudo apt-get -y purge apache2 sudo apt-get -y install nginx - sudo service nginx start - - sudo mv -f /etc/nginx/sites-available/default /etc/nginx/sites-available/default.orig - sudo cp -f "${INSTALLATION_PATH}/resources/default-settings/nginx.default" /etc/nginx/sites-available/default + sudo mv -f "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}.orig" + sudo cp -f "${INSTALLATION_PATH}/resources/default-settings/nginx.default" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" + sudo sed -i "s|%%INSTALLATION_PATH%%|${INSTALLATION_PATH}|g" "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" # make sure nginx can access the home directory of the user - sudo chmod o+x /home/pi + sudo chmod o+x "${HOME_PATH}" - sudo service nginx restart + sudo systemctl restart nginx.service } -setup_jukebox_webapp() { - echo "Install web application" | tee /dev/fd/3 +_jukebox_webapp_check() { + print_verify_installation - if [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true || $ENABLE_WEBAPP_PROD_DOWNLOAD == release-only ]] ; then - _jukebox_webapp_download - fi - if [[ $ENABLE_INSTALL_NODE == true ]] ; then - _jukebox_webapp_install_node - # Local Web App build during installation does not work at the moment - # Needs to be done after reboot! There will be a message at the end of the installation process - # _jukebox_webapp_build - fi - _jukebox_webapp_register_as_system_service_with_nginx + if [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true || $ENABLE_WEBAPP_PROD_DOWNLOAD == release-only ]] ; then + verify_dirs_exists "${INSTALLATION_PATH}/src/webapp/build" + fi + if [[ $ENABLE_INSTALL_NODE == true ]] ; then + verify_apt_packages nodejs + fi + + verify_apt_packages nginx + verify_files_exists "${WEBAPP_NGINX_SITE_DEFAULT_CONF}" - echo "DONE: setup_jukebox_webapp" + verify_service_enablement nginx.service enabled +} + +_run_setup_jukebox_webapp() { + if [[ $ENABLE_WEBAPP_PROD_DOWNLOAD == true || $ENABLE_WEBAPP_PROD_DOWNLOAD == release-only ]] ; then + _jukebox_webapp_download + fi + if [[ $ENABLE_INSTALL_NODE == true ]] ; then + _jukebox_webapp_install_node + # Local Web App build during installation does not work at the moment + # Needs to be done after reboot! There will be a message at the end of the installation process + # _jukebox_webapp_build + fi + _jukebox_webapp_register_as_system_service_with_nginx + _jukebox_webapp_check +} + +setup_jukebox_webapp() { + if [ "$ENABLE_WEBAPP" == true ] ; then + run_with_log_frame _run_setup_jukebox_webapp "Install web application" + fi } diff --git a/installation/routines/setup_kiosk_mode.sh b/installation/routines/setup_kiosk_mode.sh index b2857b624..b6e543768 100644 --- a/installation/routines/setup_kiosk_mode.sh +++ b/installation/routines/setup_kiosk_mode.sh @@ -1,6 +1,13 @@ #!/usr/bin/env bash +KIOSK_MODE_CONF_HEADER="## Jukebox Kiosk Mode" +KIOSK_MODE_XINITRC='/etc/xdg/openbox/autostart' +KIOSK_MODE_BASHRC="${HOME_PATH}/.bashrc" +KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK='/etc/chromium-browser/customizations/01-disable-update-check' +KIOSK_MODE_CHROMIUM_FLAG_UPDATE_INTERVAL='--check-for-update-interval=31536000' + _kiosk_mode_install_os_dependencies() { + print_lc " Install Kiosk Mode dependencies" # Resource: # https://blog.r0b.io/post/minimal-rpi-kiosk/ sudo apt-get -qq -y install --no-install-recommends \ @@ -12,19 +19,20 @@ _kiosk_mode_install_os_dependencies() { } _kiosk_mode_set_autostart() { + print_lc " Configure Kiosk Mode" local _DISPLAY='$DISPLAY' local _XDG_VTNR='$XDG_VTNR' - cat << EOF >> /home/pi/.bashrc -## Jukebox kiosk autostart + tee -a "${KIOSK_MODE_BASHRC}" <<-EOF + +${KIOSK_MODE_CONF_HEADER} [[ -z $_DISPLAY && $_XDG_VTNR -eq 1 ]] && startx -- -nocursor EOF - local XINITRC='/etc/xdg/openbox/autostart' - cat << EOF | sudo tee -a $XINITRC + sudo tee -a "${KIOSK_MODE_XINITRC}" <<-EOF -## Jukebox Kiosk Mode +${KIOSK_MODE_CONF_HEADER} # Disable any form of screen saver / screen blanking / power management xset s off xset s noblank @@ -46,16 +54,43 @@ EOF _kiosk_mode_update_settings() { # Resource: https://github.com/Thyraz/Sonos-Kids-Controller/blob/d1f061f4662c54ae9b8dc8b545f9c3ba39f670eb/README.md#kiosk-mode-installation - sudo touch /etc/chromium-browser/customizations/01-disable-update-check;echo CHROMIUM_FLAGS=\"\$\{CHROMIUM_FLAGS\} --check-for-update-interval=31536000\" | sudo tee /etc/chromium-browser/customizations/01-disable-update-check + sudo mkdir -p $(dirname "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}") + sudo rm -f "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}" + sudo tee -a "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}" <<-EOF +${KIOSK_MODE_CONF_HEADER} +CHROMIUM_FLAGS=\"\$\{CHROMIUM_FLAGS\} --check-for-update-interval=31536000\" +EOF +} + +_kiosk_mode_check() { + print_verify_installation + + verify_apt_packages xserver-xorg \ + x11-xserver-utils \ + xinit \ + openbox \ + chromium-browser + verify_files_exists "${KIOSK_MODE_BASHRC}" + verify_file_contains_string "${KIOSK_MODE_CONF_HEADER}" "${KIOSK_MODE_BASHRC}" + + verify_files_exists "${KIOSK_MODE_XINITRC}" + verify_file_contains_string "${KIOSK_MODE_CONF_HEADER}" "${KIOSK_MODE_XINITRC}" + + verify_files_exists "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}" + verify_file_contains_string "${KIOSK_MODE_CONF_HEADER}" "${KIOSK_MODE_CHROMIUM_CUSTOM_DISABLE_UPDATE_CHECK}" } -setup_kiosk_mode() { - echo "Setup Kiosk Mode" | tee /dev/fd/3 +_run_setup_kiosk_mode() { + _kiosk_mode_install_os_dependencies + _kiosk_mode_set_autostart + _kiosk_mode_update_settings + _kiosk_mode_check +} - _kiosk_mode_install_os_dependencies - _kiosk_mode_set_autostart - _kiosk_mode_update_settings - echo "DONE: setup_kiosk_mode" +setup_kiosk_mode() { + if [ "$ENABLE_KIOSK_MODE" == true ] ; then + run_with_log_frame _run_setup_kiosk_mode "Setup Kiosk Mode" + fi } diff --git a/installation/routines/setup_mpd.sh b/installation/routines/setup_mpd.sh index 53b9ac01b..6a95a95e5 100644 --- a/installation/routines/setup_mpd.sh +++ b/installation/routines/setup_mpd.sh @@ -3,14 +3,12 @@ AUDIOFOLDERS_PATH="${SHARED_PATH}/audiofolders" PLAYLISTS_PATH="${SHARED_PATH}/playlists" -# Do not change this directory! It must match MPDs expectation where to find the user configuration -MPD_CONF_PATH="$HOME/.config/mpd/mpd.conf" - _mpd_install_os_dependencies() { + log " Install MPD OS dependencies" sudo apt-get -y update - echo "Install MPD OS dependencies" - echo "Note: Installing MPD will cause a message: 'Job failed. See journalctl -xe for details'" - echo "It can be ignored! It's an artefact of the MPD installation - nothing we can do about it." + + log "Note: Installing MPD might cause a message: 'Job failed. See journalctl -xe for details' +It can be ignored! It's an artefact of the MPD installation - nothing we can do about it." sudo apt-get -y install \ mpd mpc \ --no-install-recommends \ @@ -20,8 +18,15 @@ _mpd_install_os_dependencies() { } _mpd_configure() { + print_lc " Configure MPD as user local service" + + # Make sure system-wide mpd is disabled + sudo systemctl stop mpd.socket + sudo systemctl stop mpd.service + sudo systemctl disable mpd.socket + sudo systemctl disable mpd.service # MPD will be setup as user process (rather than a system-wide process) - mkdir -p ~/.config/mpd + mkdir -p $(dirname "$MPD_CONF_PATH") cp -f "${INSTALLATION_PATH}/resources/default-settings/mpd.default.conf" "${MPD_CONF_PATH}" @@ -29,48 +34,38 @@ _mpd_configure() { sed -i 's|%%JUKEBOX_AUDIOFOLDERS_PATH%%|'"$AUDIOFOLDERS_PATH"'|' "${MPD_CONF_PATH}" sed -i 's|%%JUKEBOX_PLAYLISTS_PATH%%|'"$PLAYLISTS_PATH"'|' "${MPD_CONF_PATH}" + # Prepare user-service MPD to be started at next boot + systemctl --user daemon-reload + systemctl --user enable mpd.socket + systemctl --user enable mpd.service } -setup_mpd() { - echo "Install MPD" | tee /dev/fd/3 +_mpd_check() { + print_verify_installation - local MPD_EXECUTE_INSTALL=true + verify_apt_packages mpd mpc - if [[ -f ${MPD_CONF_PATH} || -f ${SYSTEMD_USR_PATH}/mpd.service ]]; then - echo "It seems there is a MPD already installed. -Note: It is important that MPD runs as a user service! -Would you like to overwrite your configuration? [Y/n]" 1>&3 - read -r response - case "$response" in - [nN][oO]|[nN]) - MPD_EXECUTE_INSTALL=false - ;; - *) - ;; - esac - fi + verify_files_chmod_chown 755 "${CURRENT_USER}" "${CURRENT_USER_GROUP}" "${MPD_CONF_PATH}" - echo "MPD_EXECUTE_INSTALL=${MPD_EXECUTE_INSTALL}" + verify_file_contains_string "${AUDIOFOLDERS_PATH}" "${MPD_CONF_PATH}" + verify_file_contains_string "${PLAYLISTS_PATH}" "${MPD_CONF_PATH}" - if [[ $MPD_EXECUTE_INSTALL == true ]] ; then + verify_service_enablement mpd.socket disabled + verify_service_enablement mpd.service disabled - # Install/update only if enabled: do not stuff up any existing configuration - _mpd_install_os_dependencies + verify_service_enablement mpd.socket enabled --user + verify_service_enablement mpd.service enabled --user +} - # Make sure system-wide mpd is disabled - echo "Configure MPD as user local service" | tee /dev/fd/3 - sudo systemctl stop mpd.socket - sudo systemctl stop mpd - sudo systemctl disable mpd.socket - sudo systemctl disable mpd +_run_setup_mpd() { + _mpd_install_os_dependencies _mpd_configure - # Prepare user-service MPD to be started at next boot - systemctl --user daemon-reload - systemctl --user enable mpd.socket - systemctl --user enable mpd - # Start MPD now, but not the socket: MPD is already started and we expect a reboot anyway - systemctl --user start mpd - fi - - echo "DONE: setup_mpd" + _mpd_check +} + +setup_mpd() { + # Install/update only if enabled: do not stuff up any existing configuration + if [[ "$SETUP_MPD" == true && $ENABLE_MPD_OVERWRITE_INSTALL == true ]] ; then + run_with_log_frame _run_setup_mpd "Install MPD" + fi } diff --git a/installation/routines/setup_rfid_reader.sh b/installation/routines/setup_rfid_reader.sh index 4ae693076..3003d79a4 100644 --- a/installation/routines/setup_rfid_reader.sh +++ b/installation/routines/setup_rfid_reader.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash -setup_rfid_reader() { - echo "Install RFID Reader" | tee /dev/fd/3 - - python "${INSTALLATION_PATH}/src/jukebox/run_register_rfid_reader.py" | tee /dev/fd/3 +_run_setup_rfid_reader() { + run_and_print_lc python "${INSTALLATION_PATH}/src/jukebox/run_register_rfid_reader.py" +} - echo "DONE: setup_rfid_reader" +setup_rfid_reader() { + if [ "$ENABLE_RFID_READER" == true ] ; then + run_with_log_frame _run_setup_rfid_reader "Install RFID Reader" + fi } diff --git a/installation/routines/setup_samba.sh b/installation/routines/setup_samba.sh index 0914439b7..c1875113e 100644 --- a/installation/routines/setup_samba.sh +++ b/installation/routines/setup_samba.sh @@ -1,7 +1,10 @@ #!/usr/bin/env bash +SMB_CONF="/etc/samba/smb.conf" +SMB_CONF_HEADER="## Jukebox Samba Config" + _samba_install_os_dependencies() { - echo "Install Samba Core dependencies" + log " Install Samba Core dependencies" sudo apt-get -qq -y update; sudo apt-get -qq -y install \ samba samba-common-bin \ --no-install-recommends \ @@ -11,25 +14,24 @@ _samba_install_os_dependencies() { } _samba_set_user() { - local SMB_CONF="/etc/samba/smb.conf" - local SMB_USER="pi" + print_lc " Configure Samba" local SMB_PASSWD="raspberry" # Samba has not been configured - if grep -q "## Jukebox Samba Config" "$SMB_CONF"; then - echo " Skipping. Already set up!" | tee /dev/fd/3 + if grep -q "$SMB_CONF_HEADER" "$SMB_CONF"; then + print_lc " Skipping. Already set up!" else # Create Samba user - (echo "${SMB_PASSWD}"; echo "${SMB_PASSWD}") | sudo smbpasswd -s -a $SMB_USER + (echo "${SMB_PASSWD}"; echo "${SMB_PASSWD}") | sudo smbpasswd -s -a "${CURRENT_USER}" sudo chown root:root $SMB_CONF sudo chmod 777 $SMB_CONF # Create Samba Mount Points sudo cat << EOF >> $SMB_CONF -## Jukebox Samba Config +${SMB_CONF_HEADER} [phoniebox] - comment= Pi Jukebox + comment=Pi Jukebox path=${SHARED_PATH} browseable=Yes writeable=Yes @@ -43,13 +45,31 @@ EOF fi } -setup_samba() { - echo "Install Samba and configure user" | tee /dev/fd/3 +_samba_check() { + print_verify_installation + + verify_apt_packages samba samba-common-bin - # Skip interactive Samba WINS config dialog - echo "samba-common samba-common/dhcp boolean false" | sudo debconf-set-selections - _samba_install_os_dependencies - _samba_set_user + verify_files_chmod_chown 644 root root "${SMB_CONF}" - echo "DONE: setup_samba" + verify_file_contains_string "${SMB_CONF_HEADER}" "${SMB_CONF}" + verify_file_contains_string "${SHARED_PATH}" "${SMB_CONF}" + + if ! (sudo pdbedit -L | grep -qw "^${CURRENT_USER}") ; then + exit_on_error "ERROR: samba user not found" + fi +} + +_run_setup_samba() { + # Skip interactive Samba WINS config dialog + echo "samba-common samba-common/dhcp boolean false" | sudo debconf-set-selections + _samba_install_os_dependencies + _samba_set_user + _samba_check +} + +setup_samba() { + if [ "$ENABLE_SAMBA" == true ] ; then + run_with_log_frame _run_setup_samba "Install Samba" + fi } diff --git a/installation/routines/update_raspi_os.sh b/installation/routines/update_raspi_os.sh index b7c356454..f38e975ed 100644 --- a/installation/routines/update_raspi_os.sh +++ b/installation/routines/update_raspi_os.sh @@ -1,9 +1,14 @@ #!/usr/bin/env bash -update_raspi_os() { - echo "Updating Raspberry Pi OS" | tee /dev/fd/3 - - sudo apt-get -qq -y update; sudo apt-get -qq -y full-upgrade; sudo apt-get -qq -y autoremove +_run_update_raspi_os() { + sudo apt-get -qq -y update && sudo apt-get -qq -y full-upgrade || exit_on_error "Failed to Update Raspberry Pi OS" + if [ "$CI_RUNNING" != "true" ]; then + sudo apt-get -qq -y autoremove + fi +} - echo "DONE: update_raspi_os" +update_raspi_os() { + if [ "$UPDATE_RASPI_OS" == true ] ; then + run_with_log_frame _run_update_raspi_os "Updating Raspberry Pi OS" + fi } diff --git a/packages-core.txt b/packages-core.txt new file mode 100644 index 000000000..b2f6779a2 --- /dev/null +++ b/packages-core.txt @@ -0,0 +1,17 @@ +# Define packages for apt-get. These can be installed with +# 'sed 's/#.*//g' packages.txt | xargs sudo apt-get install' + +at +alsa-utils +caps +espeak +ffmpeg +libasound2-dev +mpg123 +pulseaudio +pulseaudio-module-bluetooth +pulseaudio-utils +python3 +python3-venv +python3-dev +rsync diff --git a/requirements.txt b/requirements.txt index f9f452799..bd2ea6651 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ wheel evdev pyalsaaudio pulsectl -python_mpd2 +python-mpd2 ruamel.yaml # For playlistgenerator requests @@ -28,8 +28,8 @@ gpiozero # On regular Linux PCs, Websocket is enabled in the Python package # pyzmq - # Code quality flake8>=4.0.0 pytest +pytest-cov mock diff --git a/resources/autohotspot/autohotspot.timer b/resources/autohotspot/autohotspot.timer index a35f15757..eb2acaebe 100644 --- a/resources/autohotspot/autohotspot.timer +++ b/resources/autohotspot/autohotspot.timer @@ -1,3 +1,3 @@ # cron timer for autohotspot -*/5 * * * * pi sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot -@reboot pi sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot +*/5 * * * * %%USER%% sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot +@reboot %%USER%% sudo /usr/bin/autohotspot 2>&1 | logger -t autohotspot diff --git a/resources/default-settings/jukebox.default.yaml b/resources/default-settings/jukebox.default.yaml index 6e565c684..36661c992 100644 --- a/resources/default-settings/jukebox.default.yaml +++ b/resources/default-settings/jukebox.default.yaml @@ -1,5 +1,5 @@ # IMPORTANT: -# Do not use paths with '~/some/dir' - always use '/home/pi/some/dir' +# Always use relative path from settingsfile `../../`, but do not use relative paths with `~/`. # Sole (!) exception is in playermpd.mpd_conf system: box_name: Jukebox @@ -30,7 +30,7 @@ pulse: toggle_on_connect: true # Limit maximum volume range to XX % - can be changed through the UI (for temporary use) soft_max_volume: 70 - # Run the tool run_configure_audio.py to configure the pulseaudio sinks + # Run the audio configuration tool to configure the pulseaudio sinks # # After startup, the audio output defaults to primary # Any Bluetooth device should be the secondary (as it may not always be available directly after boot) diff --git a/resources/default-settings/nginx.default b/resources/default-settings/nginx.default index b949beb26..d664f4cdd 100644 --- a/resources/default-settings/nginx.default +++ b/resources/default-settings/nginx.default @@ -2,7 +2,7 @@ server { listen 80 default_server; listen [::]:80 default_server; - root /home/pi/RPi-Jukebox-RFID/src/webapp/build; + root %%INSTALLATION_PATH%%/src/webapp/build; index index.html index.htm; @@ -21,7 +21,7 @@ server { } location /logs { - root /home/pi/RPi-Jukebox-RFID/shared; + root %%INSTALLATION_PATH%%/shared; autoindex on; autoindex_exact_size off; @@ -31,14 +31,14 @@ server { } location @buildwebui { - root /home/pi/RPi-Jukebox-RFID/resources/html; + root %%INSTALLATION_PATH%%/resources/html; try_files /runbuildui.html =404; internal; } error_page 404 = /404.html; location /404.html { - root /home/pi/RPi-Jukebox-RFID/resources/html; + root %%INSTALLATION_PATH%%/resources/html; internal; } } diff --git a/run_pytest.sh b/run_pytest.sh index a3cbd8df6..766f05182 100755 --- a/run_pytest.sh +++ b/run_pytest.sh @@ -10,4 +10,4 @@ SCRIPT_DIR="$(dirname "$SOURCE")" cd "$SCRIPT_DIR" || (echo "Could not change to top-level project directory" && exit 1) # Run pytest -pytest -c pytest.ini +pytest -c pytest.ini $@ diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py index ecac65ab8..3975fdb67 100644 --- a/src/jukebox/components/playermpd/__init__.py +++ b/src/jukebox/components/playermpd/__init__.py @@ -80,6 +80,7 @@ # Toggle (und 2nd Swipe generell) ist immer vom Status des Zielsystems abhängig und kann damit nur vom Zielsystem geändert # werden. Bei Wifi also braucht man 3 Funktionen: on / off / toggle. Toggle ist dann first swipe / second swipe +import os import mpd import threading import logging @@ -574,6 +575,11 @@ def list_song_by_artist_and_album(self, albumartist, album): @plugs.tag def get_song_by_url(self, song_url): + # MPD can play absolute paths but can find songs in its database only by relative path + # In certain situations, `song_url` can be an absolute path. Then, it will be trimed to be relative + _music_library_path_absolute = os.path.expanduser(components.player.get_music_library_path()) + song_url = song_url.replace(f'{_music_library_path_absolute}/', '') + with self.mpd_lock: song = self.mpd_retry_with_mutex(self.mpd_client.find, 'file', song_url) diff --git a/src/jukebox/components/rfid/configure/__init__.py b/src/jukebox/components/rfid/configure/__init__.py index 7f9e232f2..0a8ff7aca 100755 --- a/src/jukebox/components/rfid/configure/__init__.py +++ b/src/jukebox/components/rfid/configure/__init__.py @@ -10,6 +10,8 @@ logger = logging.getLogger() +NO_RFID_READER = 'No RFID Reader' + def reader_install_dependencies(reader_path: str, dependency_install: str) -> None: """ @@ -80,6 +82,40 @@ def reader_load_module(reader_name): return reader_module +def _get_reader_descriptions(reader_dirs: list[str]) -> dict[str, tuple[str, str]]: + # Try to load the description modules from all valid directories (as this has no dependencies) + # If unavailable, use placeholder description + reader_descriptions = {} + for reader_type in reader_dirs: + reader_description_module_name = '' + reader_description = '' + if reader_type == NO_RFID_READER: + # Add Option to not add a RFid Reader + reader_description_module_name = reader_type + reader_description = reader_type + else: + reader_description_module_name = f"{reader_type + '/' + reader_type + '.py'}" + try: + reader_description_module = (importlib.import_module('components.rfid.hardware.' + reader_type + + '.description', 'pkg.subpkg')) + reader_description = reader_description_module.DESCRIPTION + except ModuleNotFoundError: + # The developer for this reader simply omitted to provide a description module + # Or there is no valid module in this directory, despite correct naming scheme. + # But this we will only find out later, because we want to be as lenient as possible + # and don't already load and check reader modules the user is + # not selecting (and thus no interested in) + logger.warning(f"No module 'description.py' available for reader subpackage '{reader_type}'") + reader_description = '(No description provided!)' + except AttributeError: + # The module loaded ok, but has no identifier 'DESCRIPTION' + logger.warning(f"Module 'description.py' of reader subpackage '{reader_type}' is missing 'DESCRIPTION'. " + f"Spelling error?") + reader_description = '(No description provided!)' + reader_descriptions[reader_type] = (reader_description, reader_description_module_name) + return reader_descriptions + + def query_user_for_reader(dependency_install='query') -> dict: """ Ask the user to select a RFID reader and prompt for the reader's configuration @@ -115,39 +151,20 @@ def query_user_for_reader(dependency_install='query') -> dict: package_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/../hardware') logger.debug(f"Package location: {package_dir}") # For known included readers, specify manual order - included_readers = ['generic_usb', 'rdm6300_serial', 'rc522_spi', 'pn532_i2c_py532', 'fake_reader_gui'] + included_readers = [NO_RFID_READER, 'generic_usb', 'rdm6300_serial', 'rc522_spi', 'pn532_i2c_py532', 'fake_reader_gui'] # Get all local directories (i.e subpackages) that conform to naming/structuring convention (except known readers) # Naming convention: modname/modname.py - reader_dirs = [x for x in os.listdir(package_dir) + additional_readers = [x for x in os.listdir(package_dir) if (os.path.isdir(package_dir + '/' + x) and os.path.exists(package_dir + '/' + x + '/' + x + '.py') and os.path.isfile(package_dir + '/' + x + '/' + x + '.py') and not x.endswith('template_new_reader') and x not in included_readers)] - reader_dirs = [*included_readers, *sorted(reader_dirs, key=lambda x: x.casefold())] + reader_dirs = [*included_readers, *sorted(additional_readers, key=lambda x: x.casefold())] + logger.debug(f"reader_dirs = {reader_dirs}") - # Try to load the description modules from all valid directories (as this has no dependencies) - # If unavailable, use placeholder description - reader_description_modules = [] - reader_descriptions = [] - for reader_type in reader_dirs: - try: - reader_description_modules.append(importlib.import_module('components.rfid.hardware.' + reader_type - + '.description', 'pkg.subpkg')) - reader_descriptions.append(reader_description_modules[-1].DESCRIPTION) - except ModuleNotFoundError: - # The developer for this reader simply omitted to provide a description module - # Or there is no valid module in this directory, despite correct naming scheme. But this we will only find out - # later, because we want to be as lenient as possible and don't already load and check reader modules the user is - # not selecting (and thus no interested in) - logger.warning(f"No module 'description.py' available for reader subpackage '{reader_type}'") - reader_descriptions.append('(No description provided!)') - except AttributeError: - # The module loaded ok, but has no identifier 'DESCRIPTION' - logger.warning(f"Module 'description.py' of reader subpackage '{reader_type}' is missing 'DESCRIPTION'. " - f"Spelling error?") - reader_descriptions.append('(No description provided!)') + reader_descriptions = _get_reader_descriptions(reader_dirs) # Prepare the configuration collector with the base values config_dict = {'rfid': {'readers': {}}} @@ -157,14 +174,21 @@ def query_user_for_reader(dependency_install='query') -> dict: while True: # List all modules and query user print("Choose Reader Module from list:\n") - for idx, (des, mod) in enumerate(zip(reader_descriptions, reader_dirs)): + for idx, (des, mod) in enumerate(reader_descriptions.values()): print(f" {Colors.lightgreen}{idx:2d}{Colors.reset}: {Colors.lightcyan}{Colors.bold}{des:40s}{Colors.reset} " - f"(Module: {mod + '/' + mod + '.py'})") + f"(Module: {mod})") print("") reader_id = pyil.input_int("Reader module number?", min=0, max=len(reader_descriptions) - 1, prompt_color=Colors.lightgreen, prompt_hint=True) + # The (short) name of the selected reader module, which is identical to the directory name - reader_select_name.append(reader_dirs[reader_id]) + reader_selected = list(reader_descriptions.keys())[reader_id] + print(f"Reader selected: '{reader_selected}'") + if reader_selected == NO_RFID_READER: + logger.debug(f"Entry '{NO_RFID_READER}' selected. skip") + break + + reader_select_name.append(reader_selected) # If this reader has not been selected before, auto install dependencies if reader_select_name[-1] not in reader_select_name[:-1]: diff --git a/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt b/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt index 931c9db0f..937256e86 100644 --- a/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt +++ b/src/jukebox/components/rfid/hardware/fake_reader_gui/requirements.txt @@ -1,5 +1,6 @@ # This GUI-based mock reader also requires: tkinter # tkinter is a standard Python package and needs not be installed separately # It is available on most Unix systems (but not on headless Raspbian RPi where running a GUI is difficult anyway) +# You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` ttkthemes diff --git a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt index 9b156854b..f7fe9563f 100644 --- a/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt +++ b/src/jukebox/components/rfid/hardware/pn532_i2c_py532/requirements.txt @@ -1,3 +1,4 @@ # PN532 related requirements # You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` + py532lib diff --git a/src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt b/src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt index f6c1a1f57..df92852d9 100644 --- a/src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt +++ b/src/jukebox/components/rfid/hardware/rdm6300_serial/requirements.txt @@ -1 +1,4 @@ +# RDM6300 related requirements +# You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt` + pyserial diff --git a/src/jukebox/components/rfid/reader/__init__.py b/src/jukebox/components/rfid/reader/__init__.py index 9245a127a..db0ccb1da 100644 --- a/src/jukebox/components/rfid/reader/__init__.py +++ b/src/jukebox/components/rfid/reader/__init__.py @@ -239,14 +239,22 @@ def run(self): # noqa: C901 @plugs.finalize def finalize(): - jukebox.cfghandler.load_yaml(cfg_rfid, cfg_main.getn('rfid', 'reader_config')) - - # Load all the required modules - # Start a ReaderRunner-Thread for each Reader - for reader_cfg_key in cfg_rfid['rfid']['readers'].keys(): - _READERS[reader_cfg_key] = ReaderRunner(reader_cfg_key) - for reader_cfg_key in cfg_rfid['rfid']['readers'].keys(): - _READERS[reader_cfg_key].start() + try: + reader_config_file = cfg_main.getn('rfid', 'reader_config') + jukebox.cfghandler.load_yaml(cfg_rfid, reader_config_file) + except FileNotFoundError: + cfg_rfid.config_dict({'rfid': {'readers': {}}}) + log.warning(f"rfid reader database file not found. Creating empty database: '{reader_config_file}'") + # Save the empty rfid reader database, to make sure we can create the file and have access to it + cfg_rfid.save(only_if_changed=False) + + if 'rfid' in cfg_rfid and 'readers' in cfg_rfid['rfid']: + # Load all the required modules + # Start a ReaderRunner-Thread for each Reader + for reader_cfg_key in cfg_rfid['rfid']['readers'].keys(): + _READERS[reader_cfg_key] = ReaderRunner(reader_cfg_key) + for reader_cfg_key in cfg_rfid['rfid']['readers'].keys(): + _READERS[reader_cfg_key].start() @plugs.atexit diff --git a/src/jukebox/misc/loggingext.py b/src/jukebox/misc/loggingext.py index 9328cfea8..771248d8f 100644 --- a/src/jukebox/misc/loggingext.py +++ b/src/jukebox/misc/loggingext.py @@ -1,6 +1,7 @@ """ ############## Logger + ############## We use a hierarchical Logger structure based on pythons logging module. It can be finely configured with a yaml file. diff --git a/src/webapp/src/components/Library/lists/index.js b/src/webapp/src/components/Library/lists/index.js index a153619e6..22970d9a9 100644 --- a/src/webapp/src/components/Library/lists/index.js +++ b/src/webapp/src/components/Library/lists/index.js @@ -28,7 +28,7 @@ const LibraryLists = () => { const [cardId] = useState(searchParams.get('cardId')); const [musicFilter, setMusicFilter] = useState(''); - const handleMusicFilder = (event) => { + const handleMusicFolder = (event) => { setMusicFilter(event.target.value); }; @@ -49,7 +49,7 @@ const LibraryLists = () => { {isSelecting && }