├── stage3_homebridge ├── 03-nginx │ ├── 00-packages │ ├── files │ │ ├── status.json │ │ ├── nginx-homebridge-self-signed-cert.service │ │ ├── nginx-homebridge-self-signed-cert │ │ ├── homebridge.local │ │ └── custom_502.html │ └── 01-run.sh ├── EXPORT_IMAGE ├── 07-other-packages │ └── 00-packages ├── 02-wifi-connect │ ├── 00-packages │ ├── files │ │ ├── wifi-powersave-off.conf │ │ ├── log-iface-events.sh │ │ └── wifi-connect.service │ └── 01-run.sh ├── 09-userconf-patches │ ├── 00-patches │ │ ├── series │ │ ├── 02-userconf.diff │ │ └── 01-userconf-service.diff │ └── README.md ├── prerun.sh ├── 01-homebridge │ ├── 00-packages │ ├── files │ │ ├── bashrc.partial │ │ ├── first-boot-homebridge.service │ │ ├── 010_homebridge-nopasswd │ │ ├── motd-linux │ │ ├── issue │ │ ├── first-boot-homebridge │ │ ├── 20-hb-nginx-check │ │ ├── motd-homebridge │ │ ├── hb-config-new │ │ └── hb-config │ └── 00-run.sh ├── 04-tzupdate │ ├── files │ │ └── tzupdate.service │ └── 00-run.sh ├── 05-ffmpeg │ └── 01-run.sh └── 08-create-manifest │ └── 00-run.sh ├── .dockerignore ├── media └── Raspbian Image.gif ├── stage0_prep ├── files │ └── raspberrypi.gpg └── prerun.sh ├── config ├── .github ├── workflows │ ├── stale.yml │ ├── labeler.yml │ ├── pr-labeler.yml │ ├── wiki-change-notification.yml │ ├── release-stage-1_update_dependencies.yml │ ├── release-stage-3_package_release.yml │ └── create_raspbian_pi-gen.yml ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ ├── wiki-change-request.yml │ ├── bug-report.yml │ └── support-request.yml ├── homebridge-dependency-bot-stable.json ├── labeler.yml ├── dependabot.yml ├── SECURITY.md └── actions │ └── trigger-and-wait-workflow │ ├── README.md │ └── action.yml ├── stable └── package.json ├── .gitignore ├── combine-rpi-imager-snipplet.py ├── setup_local_test_environment.sh ├── LICENSE ├── TRIXIE_SUDO_PATCH.md ├── README.md ├── scripts ├── hb-config-vnc-uninstall └── create-release-body.sh └── make_rpi-imager-snipplet.py /stage3_homebridge/03-nginx/00-packages: -------------------------------------------------------------------------------- 1 | nginx 2 | -------------------------------------------------------------------------------- /stage3_homebridge/EXPORT_IMAGE: -------------------------------------------------------------------------------- 1 | IMG_SUFFIX="" 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | output/ 2 | work/ 3 | deploy/ 4 | apt-cacher-ng/ 5 | .git/objects/* 6 | -------------------------------------------------------------------------------- /stage3_homebridge/07-other-packages/00-packages: -------------------------------------------------------------------------------- 1 | libusb-1.0-0-dev 2 | libudev-dev 3 | -------------------------------------------------------------------------------- /stage3_homebridge/03-nginx/files/status.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Starting now..." 3 | } 4 | -------------------------------------------------------------------------------- /stage3_homebridge/02-wifi-connect/00-packages: -------------------------------------------------------------------------------- 1 | wpasupplicant 2 | network-manager 3 | dnsmasq 4 | hostapd -------------------------------------------------------------------------------- /stage3_homebridge/02-wifi-connect/files/wifi-powersave-off.conf: -------------------------------------------------------------------------------- 1 | [connection] 2 | wifi.powersave=2 3 | -------------------------------------------------------------------------------- /stage3_homebridge/09-userconf-patches/00-patches/series: -------------------------------------------------------------------------------- 1 | 01-userconf-service.diff 2 | 02-userconf.diff -------------------------------------------------------------------------------- /stage3_homebridge/prerun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [ ! -d "${ROOTFS_DIR}" ]; then 4 | copy_previous 5 | fi 6 | -------------------------------------------------------------------------------- /media/Raspbian Image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homebridge/homebridge-raspbian-image/HEAD/media/Raspbian Image.gif -------------------------------------------------------------------------------- /stage3_homebridge/09-userconf-patches/README.md: -------------------------------------------------------------------------------- 1 | We added this due to this issue - https://github.com/RPi-Distro/userconf-pi/pull/11 -------------------------------------------------------------------------------- /stage0_prep/files/raspberrypi.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homebridge/homebridge-raspbian-image/HEAD/stage0_prep/files/raspberrypi.gpg -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/00-packages: -------------------------------------------------------------------------------- 1 | apt-transport-https 2 | ca-certificates 3 | curl 4 | gnupg-agent 5 | python3-setuptools 6 | python3-pip 7 | gcc 8 | g++ 9 | make 10 | ntpsec 11 | jq 12 | libavahi-compat-libdnssd-dev 13 | git 14 | -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/bashrc.partial: -------------------------------------------------------------------------------- 1 | 2 | # include homebridge bashrc if loading from Homebridge UI Terminal 3 | pstree -s $$ | grep "hb-service" > /dev/null 4 | [ "$?" -eq 0 ] && [ -f /opt/homebridge/bashrc ] && . /opt/homebridge/bashrc 5 | -------------------------------------------------------------------------------- /stage3_homebridge/04-tzupdate/files/tzupdate.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Timezone Update Service 3 | After=nginx.service 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=/usr/local/bin/tzupdate 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | IMG_NAME="Homebridge" 4 | TARGET_HOSTNAME="homebridge" 5 | ENABLE_SSH=1 6 | STAGE_LIST="stage0_prep stage0 stage1 stage2 stage3_homebridge" 7 | #FIRST_USER_NAME="pi" 8 | #FIRST_USER_PASS="raspberry" 9 | RELEASE="trixie" 10 | #CONTINUE=1 -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale workflow 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '30 1 * * *' 7 | 8 | jobs: 9 | stale: 10 | uses: homebridge/.github/.github/workflows/stale.yml@latest 11 | secrets: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /stage3_homebridge/03-nginx/files/nginx-homebridge-self-signed-cert.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=nginx-homebridge-self-signed-cert service 3 | Before=nginx.service 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=/usr/local/sbin/nginx-homebridge-self-signed-cert 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/first-boot-homebridge.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Homebridge image first boot startup script 3 | Before=NetworkManager.service wifi-connect.service 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=/usr/local/sbin/first-boot-homebridge 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /stage3_homebridge/09-userconf-patches/00-patches/02-userconf.diff: -------------------------------------------------------------------------------- 1 | --- a/rootfs/usr/lib/userconf-pi/userconf 2 | +++ b/rootfs/usr/lib/userconf-pi/userconf 3 | @@ -1,5 +1,7 @@ 4 | #!/bin/sh 5 | 6 | +set -e 7 | + 8 | rename_user () { 9 | usermod -l "$NEWNAME" "$FIRSTUSER" 10 | usermod -m -d "/home/$NEWNAME" "$NEWNAME" 11 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#example-of-a-codeowners-file 2 | 3 | * @NorthernMan54 @bwp91 @donavanbecker 4 | 5 | # Only a collaborator can review / approve changes to this list 6 | 7 | /.github/CODEOWNERS @homebridge/collaborators -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Labeler 2 | 3 | on: 4 | pull_request_target: # required for auto labeler 5 | types: [opened, reopened, synchronize] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | stale: 10 | uses: homebridge/.github/.github/workflows/labeler.yml@latest 11 | secrets: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | name: PR Labeler 2 | 3 | on: 4 | pull_request: # required for auto labeler 5 | types: [opened, reopened, synchronize] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | stale: 10 | uses: homebridge/.github/.github/workflows/pr-labeler.yml@latest 11 | secrets: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/010_homebridge-nopasswd: -------------------------------------------------------------------------------- 1 | # Allow homebridge user to run shutdown/reboot commands without password 2 | # Both /sbin and /usr/sbin paths are included for compatibility across Debian versions 3 | homebridge ALL=(root) NOPASSWD: /sbin/shutdown, /sbin/reboot, /sbin/poweroff, /usr/sbin/shutdown, /usr/sbin/reboot, /usr/sbin/poweroff -------------------------------------------------------------------------------- /stage3_homebridge/04-tzupdate/00-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # 4 | # Install the tzupdate service to update the timezone on boot 5 | # 6 | 7 | install -m 644 files/tzupdate.service "${ROOTFS_DIR}/etc/systemd/system/" 8 | 9 | on_chroot << EOF 10 | sudo pip install --break-system-packages tzupdate 11 | 12 | systemctl daemon-reload 13 | systemctl enable tzupdate 14 | EOF 15 | -------------------------------------------------------------------------------- /.github/workflows/wiki-change-notification.yml: -------------------------------------------------------------------------------- 1 | name: Wiki Changed Discord Notifications 2 | 3 | on: 4 | gollum 5 | 6 | jobs: 7 | notify: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: 'oznu/gh-wiki-edit-discord-notification@main' 11 | with: 12 | discord-webhook-url: ${{ secrets.DISCORD_WEBHOOK_WIKI_EDIT }} 13 | ignore-collaborators: true 14 | -------------------------------------------------------------------------------- /stage3_homebridge/02-wifi-connect/files/log-iface-events.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Homebridge Raspbian Image - WiFi Connect 5 | # This script stops the WiFi Connect Hotspot service when eth0 comes up 6 | # 7 | 8 | interface=$1 9 | event=$2 10 | 11 | if [[ $interface = "eth0" ]] && [[ $event = "up" ]]; then 12 | systemctl stop wifi-connect.service 13 | exit 0 14 | fi 15 | -------------------------------------------------------------------------------- /stage0_prep/prerun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Homebridge Image does not need Stage 2 export files 4 | rm -f ../stage[2]/EXPORT* 5 | 6 | ls -lR ../.. 7 | 8 | echo "Copying raspberrypi.gpg to pi-gen stage0/files" 9 | echo "Workaround for https://github.com/RPi-Distro/pi-gen/issues/862" 10 | # Copy raspberrypi.gpg to pi-gen stage0/files 11 | cp files/raspberrypi.gpg ../pi-gen/stage0/files/raspberrypi.gpg 12 | -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/motd-linux: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | printf "\nThe programs included with the Debian GNU/Linux system are free software;\n" 4 | printf "the exact distribution terms for each program are described in the\n" 5 | printf "individual files in /usr/share/doc/*/copyright.\n" 6 | 7 | printf "Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent\n" 8 | printf "permitted by applicable law.\n" 9 | -------------------------------------------------------------------------------- /stable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@homebridge/homebridge-raspbian-image", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "Stub file to track versions of homebridge-apt-pkg and ffmpeg-for-homebridge for the 'stable' release stream of the homebridge-raspbian-image package", 6 | "dependencies": { 7 | "@homebridge/homebridge-apt-pkg": "1.8.5", 8 | "ffmpeg-for-homebridge": "2.2.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Homebridge Discord 4 | url: https://discord.gg/kqNCe2D 5 | about: A place to discuss and collaborate with other Homebridge users and developers. 6 | - name: Homebridge Reddit 7 | url: https://www.reddit.com/r/homebridge/ 8 | about: A place to discuss Homebridge, get help with it, ask questions about it, post plugins, and more. 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | deploy/* 2 | work/* 3 | postrun.sh 4 | .pc 5 | *-pc 6 | apt-cacher-ng/ 7 | *zip 8 | 9 | # These are all for a local pi-gen test instance 10 | 11 | pi-gen 12 | Dockerfile 13 | build.sh 14 | build-docker.sh 15 | docker-compose.yml 16 | export-image 17 | export-noobs 18 | imagetool.sh 19 | make_rpi-imager-snipplet.py 20 | stage0 21 | stage1 22 | stage2 23 | stage3 24 | stage4 25 | stage5 26 | .DS_Store 27 | /output 28 | /.previous 29 | -------------------------------------------------------------------------------- /stage3_homebridge/09-userconf-patches/00-patches/01-userconf-service.diff: -------------------------------------------------------------------------------- 1 | --- a/rootfs/usr/lib/userconf-pi/userconf-service 2 | +++ b/rootfs/usr/lib/userconf-pi/userconf-service 3 | @@ -19,6 +19,10 @@ 4 | MSG="$MSG\nCannot be root." 5 | RET=1 6 | fi 7 | + if getent passwd "$NEW_USER" >/dev/null 2>&1; then 8 | + MSG="$MSG\nCannot use existing username '$NEW_USER'." 9 | + RET=1 10 | + fi 11 | if [ "$RET" -ne 0 ]; then 12 | echo "$MSG" 13 | fi 14 | -------------------------------------------------------------------------------- /stage3_homebridge/02-wifi-connect/files/wifi-connect.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=WiFi Captive Portal 3 | After=NetworkManager.service 4 | Before=nginx.service homebridge.service 5 | 6 | [Service] 7 | Type=oneshot 8 | Environment="PATH=/opt/homebridge/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 9 | Environment="npm_config_prefix=/opt/wifi-connect" 10 | ExecStart=/opt/wifi-connect/bin/wifi-connect 11 | StandardOutput=journal+console 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /.github/homebridge-dependency-bot-stable.json: -------------------------------------------------------------------------------- 1 | { 2 | "auto_merge": true, 3 | "git_user": { 4 | "name": "Homebridge Dependency Bot", 5 | "email": "actions@github.com" 6 | }, 7 | "directories": [ 8 | { 9 | "directory": "stable", 10 | "packages": [ 11 | { 12 | "name": "@homebridge/homebridge-apt-pkg", 13 | "tag": "latest" 14 | }, 15 | { 16 | "name": "ffmpeg-for-homebridge", 17 | "tag": "latest" 18 | } 19 | ] 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Add 'beta' label to any PR where the base branch name starts with `beta` or has a `beta` section in the name 2 | beta: 3 | - base-branch: ['^beta', 'beta', 'beta*'] 4 | 5 | # Add 'beta' label to any PR where the base branch name starts with `beta` or has a `beta` section in the name 6 | alpha: 7 | - base-branch: ['^alpha', 'alpha', 'alpha*'] 8 | 9 | # Add 'latest' label to any PR where the base branch name starts with `latest` or has a `latest` section in the name 10 | latest: 11 | - base-branch: ['^latest', 'latest', 'latest*'] -------------------------------------------------------------------------------- /stage3_homebridge/03-nginx/files/nginx-homebridge-self-signed-cert: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -e /etc/nginx/sites-available/homebridge.local ]; then 4 | mkdir -p /etc/nginx/ssl 5 | if [ ! -f /etc/nginx/ssl/homebridge.local.crt ] || [ ! -f /etc/nginx/ssl/homebridge.local.key ]; then 6 | openssl req -x509 -out /etc/nginx/ssl/homebridge.local.crt -keyout /etc/nginx/ssl/homebridge.local.key \ 7 | -newkey rsa:2048 -nodes -sha256 -days 1000 \ 8 | -subj '/CN=localhost' -extensions EXT -config <( \ 9 | printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") 10 | fi 11 | fi 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | updates: 4 | # Check GitHub Actions for updates 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | time: "09:00" 10 | # Create PRs for out-of-date actions 11 | open-pull-requests-limit: 10 12 | # Add labels to PRs 13 | labels: 14 | - "dependencies" 15 | # Group related updates together (optional) 16 | groups: 17 | actions: 18 | patterns: 19 | - "*" 20 | # Automatically rebase PRs when conflicts occur 21 | rebase-strategy: "auto" 22 | # Commit message prefix 23 | commit-message: 24 | prefix: "chore" 25 | prefix-development: "chore" 26 | include: "scope" -------------------------------------------------------------------------------- /combine-rpi-imager-snipplet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | 4 | def read_json(file_path): 5 | with open(file_path, 'r') as file: 6 | return json.load(file) 7 | 8 | def write_json(file_path, data): 9 | with open(file_path, 'w') as file: 10 | json.dump(data, file, indent=2) 11 | 12 | # Read JSON data from the files 13 | data1 = read_json('rpi-image-repo-32bit.json') 14 | data2 = read_json('rpi-image-repo-64bit.json') 15 | 16 | # Combine the lists in the "os_list" key 17 | combined_list = data1['os_list'] + data2['os_list'] 18 | 19 | # Create a new dictionary with the combined list 20 | combined_data = {"os_list": combined_list} 21 | 22 | # Save the combined JSON data to a file 23 | write_json('rpi-image-repo.json', combined_data) 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | labels: ["enhancement"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to contribute to this project! 9 | - type: textarea 10 | id: feature-description 11 | attributes: 12 | label: Feature Description 13 | description: | 14 | Please provide an overview of the what feature you'd like to see. 15 | 16 | * What is happening? 17 | * What you expect to happen? 18 | * What problems would this new feature solve? 19 | placeholder: | 20 | Tip: You can attach images or files by clicking this area to highlight it and then dragging files in. 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/issue: -------------------------------------------------------------------------------- 1 | Homebridge Raspbian - \n (\r) 2 | 3 | \e{lightmagenta}\e{bold} _ _ _ _ _ 4 | | | | | | | (_) | | 5 | | |__| | ___ _ __ ___ ___| |__ _ __ _ __| | __ _ ___ 6 | | __ |/ _ \\| '_ ` _ \\ / _ \\ '_ \\| '__| |/ _` |/ _` |/ _ \\ 7 | | | | | (_) | | | | | | __/ |_) | | | | (_| | (_| | __/ 8 | |_| |_|\\___/|_| |_| |_|\\___|_.__/|_| |_|\\__,_|\\__, |\\___| 9 | __/ | 10 | |___/ \e{reset} 11 | 12 | Browse to the one of the following addresses from another device on your network: 13 | 14 | * \e{green}http://\n.local\e{reset} 15 | * \e{green}http://\4\e{reset} 16 | 17 | All Homebridge configuration can be completed via the web interface. 18 | -------------------------------------------------------------------------------- /stage3_homebridge/03-nginx/01-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # 4 | # Setup nginx 5 | # 6 | 7 | install -m 644 files/homebridge.local "${ROOTFS_DIR}/etc/nginx/sites-available/" 8 | install -m 644 files/custom_502.html "${ROOTFS_DIR}/usr/share/nginx/html/" 9 | install -m 644 files/status.json "${ROOTFS_DIR}/usr/share/nginx/html/" 10 | install -m 644 files/nginx-homebridge-self-signed-cert.service "${ROOTFS_DIR}/etc/systemd/system/" 11 | install -m 644 files/nginx-homebridge-self-signed-cert "${ROOTFS_DIR}/usr/local/sbin/" 12 | 13 | on_chroot << EOF 14 | rm -rf /etc/nginx/sites-enabled/default 15 | rm -rf /etc/nginx/sites-available/default 16 | ln -sf /etc/nginx/sites-available/homebridge.local /etc/nginx/sites-enabled/homebridge.local 17 | 18 | chmod +x /usr/local/sbin/nginx-homebridge-self-signed-cert 19 | 20 | systemctl enable nginx 21 | systemctl enable nginx-homebridge-self-signed-cert 22 | EOF 23 | -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/first-boot-homebridge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Support configuring WiFi via Raspberry Pi Imager Tool 4 | # if ssid is set in wpa_supplicant.conf, don't start wifi-connect, disable network manager, and enable dhcpcd 5 | if grep -q "ssid" "/etc/wpa_supplicant/wpa_supplicant.conf"; then 6 | systemctl disable wifi-connect 7 | systemctl stop wifi-connect 8 | systemctl disable NetworkManager 9 | systemctl stop NetworkManager 10 | systemctl enable dhcpcd 11 | systemctl start dhcpcd 12 | fi 13 | 14 | 15 | # Update /etc/issue to show RPI Model 16 | if [ -f /proc/device-tree/model ]; then 17 | RPI_MODEL=$(cat /proc/device-tree/model) 18 | if [ -n "$RPI_MODEL" ]; then 19 | sed -i "2iRPI Model: \\\e{yellow}${RPI_MODEL}\\\e{reset}" /etc/issue 20 | fi 21 | fi 22 | # disable this service so it does not run again 23 | systemctl disable first-boot-homebridge 24 | systemctl daemon-reload 25 | -------------------------------------------------------------------------------- /stage3_homebridge/05-ffmpeg/01-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | 4 | # 5 | # Install ffmpeg 6 | # 7 | 8 | case "$ARCH" in 9 | arm64) 10 | FFMPEG_ARCH="aarch64" 11 | ;; 12 | armhf) 13 | FFMPEG_ARCH="arm32v7" 14 | ;; 15 | *) 16 | echo "Unsupported ARCH: $ARCH" 17 | exit 1 18 | ;; 19 | esac 20 | 21 | on_chroot << EOF 22 | uname -a 23 | 24 | set -e 25 | set -x 26 | 27 | wget -q "https://github.com/homebridge/ffmpeg-for-homebridge/releases/download/${FFMPEG_FOR_HOMEBRIDGE_VERSION}/ffmpeg-alpine-${FFMPEG_ARCH}.tar.gz" 28 | tar xzf "ffmpeg-alpine-${FFMPEG_ARCH}.tar.gz" -C / --no-same-owner 29 | rm -rf ffmpeg-alpine-${FFMPEG_ARCH}.tar.gzz 30 | 31 | ffmpeg || exit 0 32 | EOF 33 | 34 | # https://github.com/homebridge/ffmpeg-for-homebridge/releases/download/v2.2.0/ffmpeg-alpine-aarch64.tar.gz 35 | 36 | # https://github.com/homebridge/ffmpeg-for-homebridge/releases/v2.2.0/download/ffmpeg-alpine-aarch64.tar.gz -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/20-hb-nginx-check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # default port 4 | UI_PORT=8581 5 | 6 | if [ -f /var/lib/homebridge/config.json ]; then 7 | PORT_FROM_CONFIG=$(cat /var/lib/homebridge/config.json | jq '.platforms[] | select(.platform == "config") | .port' 2> /dev/null) 8 | if [ ${#PORT_FROM_CONFIG} -gt 0 ]; then 9 | UI_PORT=$PORT_FROM_CONFIG 10 | fi 11 | fi 12 | 13 | # save the result in /etc/hb-ui-port 14 | echo "$UI_PORT" > /etc/hb-ui-port 15 | 16 | # update the nginx config 17 | if [ -f /etc/nginx/sites-available/homebridge.local ]; then 18 | printf "Setting port $UI_PORT in /etc/nginx/sites-available/homebridge.local\n" 19 | sed -i "/proxy_pass/c\ proxy_pass http://127.0.0.1:${UI_PORT};" /etc/nginx/sites-available/homebridge.local 20 | if systemctl is-active --quiet nginx.service; then 21 | printf "Reloading nginx...\n" 22 | nginx -s reload 23 | fi 24 | fi 25 | 26 | exit 0 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/wiki-change-request.yml: -------------------------------------------------------------------------------- 1 | name: Wiki Change Request 2 | description: want change? 3 | labels: ["wiki change request"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Please read the following before you start filling out this form: 9 | 10 | * This form is for requesting changes to the Homebridge Organization wiki pages only. 11 | - type: textarea 12 | id: proposed-change 13 | attributes: 14 | label: Proposed Change 15 | description: | 16 | Please describe the change you would like to see made to the wiki page. 17 | 18 | If you are requesting a new page, please describe the page you would like to see created. 19 | 20 | placeholder: | 21 | Tip: You can attach images or files by clicking this area to highlight it and then dragging files in. 22 | validations: 23 | required: true 24 | - type: input 25 | id: wiki-page 26 | attributes: 27 | label: Wiki Page Link 28 | description: | 29 | Please provide a link to the wiki page you would like to see changed. 30 | 31 | If you are requesting a new page, please provide details of where you would like to see this page linked from. 32 | validations: 33 | required: true 34 | -------------------------------------------------------------------------------- /setup_local_test_environment.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | 6 | docker system prune -a 7 | docker volume prune 8 | 9 | rm -rf pi-gen 10 | 11 | 12 | # export ARCH=aarch64 # For arm64 (64-bit) 13 | # export ARCH=arm32v7 # For armhf (32-bit)# 14 | 15 | git clone https://github.com/RPi-Distro/pi-gen 16 | cd pi-gen 17 | git switch arm64 18 | cd .. 19 | 20 | echo 21 | echo "Copying setup for use" 22 | echo 23 | 24 | cp -r config stable ./pi-gen 25 | cp -r stage* ./pi-gen 26 | 27 | export BUILD_VERSION="$(date +%Y%m%d)-HB Test" 28 | export HOMEBRIDGE_APT_PKG_VERSION=$(jq -r '.dependencies["@homebridge/homebridge-apt-pkg"]' ./stable/package.json | sed 's/\^//') 29 | export FFMPEG_FOR_HOMEBRIDGE_VERSION=v$(jq -r '.dependencies["ffmpeg-for-homebridge"]' ./stable/package.json | sed 's/\^//') 30 | export RELEASE_STREAM="stable" 31 | 32 | echo -e "\nexport BUILD_VERSION=\"$BUILD_VERSION\"" | tee -a ./pi-gen/config 33 | echo "export HOMEBRIDGE_APT_PKG_VERSION=\"$HOMEBRIDGE_APT_PKG_VERSION\"" | tee -a ./pi-gen/config 34 | echo "export FFMPEG_FOR_HOMEBRIDGE_VERSION=\"$FFMPEG_FOR_HOMEBRIDGE_VERSION\"" | tee -a ./pi-gen/config 35 | echo "export RELEASE_STREAM=\"$RELEASE_STREAM\"" | tee -a ./pi-gen/config 36 | 37 | echo 38 | echo 'To build the image, run:' 39 | echo 40 | echo ' docker rm -v pigen_work ; ./pi-gen/build-docker.sh' 41 | echo 42 | -------------------------------------------------------------------------------- /stage3_homebridge/02-wifi-connect/01-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # 4 | # Install @homebridge/wifi-connect 5 | # 6 | 7 | apt-get update 8 | apt-get install -y jq 9 | 10 | export LTS="$(curl -s https://nodejs.org/dist/index.json | jq -r 'map(select(.lts))[0].version')" 11 | 12 | install -m 644 files/wifi-connect.service "${ROOTFS_DIR}/etc/systemd/system/" 13 | install -m 755 files/log-iface-events.sh "${ROOTFS_DIR}/etc/NetworkManager/dispatcher.d/" 14 | install -m 644 files/wifi-powersave-off.conf "${ROOTFS_DIR}/etc/NetworkManager/conf.d/" 15 | 16 | on_chroot << EOF 17 | echo "Installing Node.js for WiFi Connect $LTS..." 18 | 19 | set -e 20 | set -x 21 | 22 | mkdir -p /opt/wifi-connect 23 | 24 | #wget -q "https://unofficial-builds.nodejs.org/download/release/$LTS/node-$LTS-linux-armv6l.tar.gz" 25 | #tar xzf "node-$LTS-linux-armv6l.tar.gz" -C /opt/wifi-connect --strip-components=1 --no-same-owner 26 | #rm -rf node-$LTS-linux-armv6l.tar.gz 27 | 28 | #exit 1 29 | 30 | export PATH="/opt/homebridge/bin:$PATH" 31 | export npm_config_prefix=/opt/wifi-connect 32 | 33 | node -v 34 | npm -v 35 | 36 | npm install -g @homebridge/wifi-connect 37 | 38 | systemctl daemon-reload 39 | systemctl enable wifi-connect 40 | systemctl enable NetworkManager 41 | #systemctl disable dhcpcd 42 | systemctl disable dnsmasq 43 | systemctl disable hostapd 44 | EOF 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 oznu 2 | Copyright (c) 2015 Raspberry Pi (Trading) Ltd. 3 | 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /stage3_homebridge/08-create-manifest/00-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Create the manifest.json file for Homebridge Raspbian Image 4 | 5 | 6 | APT_MANIFEST_FILE=$(ls "${ROOTFS_DIR}/opt/homebridge/homebridge_apt_pkg"*.manifest 2>/dev/null | head -n 1) 7 | if [[ -f "$APT_MANIFEST_FILE" ]]; then 8 | # Preserve all lines from the manifest file except header lines, keeping original line returns 9 | # Keep only lines starting and ending with |, excluding those containing Package or ------ 10 | APT_MANIFEST=$(awk '/^\|.*\|$/ && !/Package/ && !/------/' "$APT_MANIFEST_FILE" | sed 's/\r$//') 11 | else 12 | echo "Manifest file not found: ${ROOTFS_DIR}/opt/homebridge/homebridge_apt_pkg*.manifest" 13 | ls -l ${ROOTFS_DIR}/opt/homebridge/ 14 | fi 15 | 16 | # WiFi connect is installed in /opt/wifi-connect/lib/node_modules/@homebridge/wifi-connect/package.json 17 | WIFI_CONNECT_VERSION=$(jq -r .version ${ROOTFS_DIR}/opt/wifi-connect/lib/node_modules/@homebridge/wifi-connect/package.json) 18 | on_chroot << EOF 19 | 20 | MANIFEST_FILE="/opt/homebridge/homebridge_raspbian_image_${ARCH}.manifest" 21 | 22 | cat < "\${MANIFEST_FILE}" 23 | Homebridge Raspbian ${ARCH} Image Package Manifest 24 | 25 | Release Version: \${BUILD_VERSION//\"/} 26 | 27 | | Package | Version | 28 | |:-------:|:-------:| 29 | | Debian | \${RELEASE} | 30 | $( [[ -n "$APT_MANIFEST" ]] && echo "$APT_MANIFEST" ) 31 | | ffmpeg for homebridge | \${FFMPEG_FOR_HOMEBRIDGE_VERSION//\"/} | 32 | | Homebridge APT Package | \${HOMEBRIDGE_APT_PKG_VERSION//\"/} | 33 | | WiFi Connect | ${WIFI_CONNECT_VERSION} | 34 | EOM 35 | EOF -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/motd-homebridge: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RED='\033[0;31m' 4 | GREEN='\033[0;32m' 5 | BOLD='\e[1m' 6 | NC='\033[0m' 7 | 8 | MODEL=$(tr -d '\0' 9 | Run the following command from ssh to patch your existing installation: 10 | 11 | ```bash 12 | curl -fsSL https://raw.githubusercontent.com/homebridge/homebridge-raspbian-image/latest/stage3_homebridge/01-homebridge/files/010_homebridge-nopasswd | sudo tee /etc/sudoers.d/010_homebridge-nopasswd > /dev/null && sudo chmod 0440 /etc/sudoers.d/010_homebridge-nopasswd && sudo visudo -c 13 | ``` 14 | 15 | ### Alternative (if you prefer to see the content first): 16 | 17 | ```bash 18 | sudo bash -c 'cat > /etc/sudoers.d/010_homebridge-nopasswd << "EOF" 19 | # Allow homebridge user to run shutdown/reboot commands without password 20 | # Both /sbin and /usr/sbin paths are included for compatibility across Debian versions 21 | homebridge ALL=(root) NOPASSWD: /sbin/shutdown, /sbin/reboot, /sbin/poweroff, /usr/sbin/shutdown, /usr/sbin/reboot, /usr/sbin/poweroff 22 | EOF 23 | chmod 0440 /etc/sudoers.d/010_homebridge-nopasswd && visudo -c' 24 | ``` 25 | 26 | This command will: 27 | 1. Create the sudoers configuration file for the homebridge user 28 | 2. Set the correct permissions (0440) 29 | 3. Validate the sudoers syntax 30 | 31 | ## What This Fixes 32 | 33 | This patch allows the homebridge user to run shutdown, reboot, and poweroff commands without requiring a password, which is necessary for the Homebridge UI's restart and shutdown features to work properly in Debian Trixie. 34 | 35 | ## Verification 36 | 37 | After applying the patch, you can verify it worked by checking: 38 | 39 | ```bash 40 | sudo -l -U homebridge 41 | ``` 42 | 43 | You should see output showing that the homebridge user can run shutdown, reboot, and poweroff commands without a password. 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | 10 | Before we start, be sure you are aware of the following points: 11 | 12 | * If your issue is specific to a certain plugin, create the issue on that plugin's GitHub project page instead. 13 | * Avoid duplicating any existing issues which already track or resolve your problem, search the existing issues first. 14 | * Aim to find a descriptive and precise title for your bug report. 15 | - type: dropdown 16 | id: raspberry-pi-model 17 | attributes: 18 | label: Raspberry Pi Model 19 | options: 20 | - Raspberry Pi 1 B 21 | - Raspberry Pi 2 22 | - Raspberry Pi 3 23 | - Raspberry Pi Zero W 24 | - Raspberry Pi Zero WH 25 | - Raspberry Pi Zero 2 W 26 | - Raspberry Pi 3 A+ 27 | - Raspberry Pi 3 B+ 28 | - Raspberry Pi 4 B 29 | - Other (specify in description) 30 | description: | 31 | Please select which Raspberry Pi model you are using. 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: describe-the-bug 36 | attributes: 37 | label: Describe The Bug 38 | description: | 39 | Pleased provide a clear and concise description of what the bug is. Be sure to include: 40 | 41 | * What is happening? 42 | * What you expect to happen? 43 | * Clear steps explaining how to reproduce the problem. 44 | placeholder: | 45 | Tip: You can attach images or files by clicking this area to highlight it and then dragging files in. 46 | validations: 47 | required: true 48 | - type: textarea 49 | id: logs 50 | attributes: 51 | label: Logs 52 | render: text 53 | description: | 54 | Please provide the relevant logs showing the error that occurred. 55 | 56 | * Keep this short. Do not paste in hundereds of lines repeating the same error. 57 | * Show the logs from the service being restarted until the error occurs. 58 | validations: 59 | required: false -------------------------------------------------------------------------------- /stage3_homebridge/03-nginx/files/homebridge.local: -------------------------------------------------------------------------------- 1 | server { 2 | # http:// 3 | listen 80; # http IPv4 4 | listen [::]:80; # http IPv6 5 | 6 | # https:// 7 | listen 443 ssl http2; # https IPv4 8 | listen [::]:443 ssl http2; # https IPv6 9 | 10 | # replace the _ with your domain name if required 11 | # eg. 12 | # server_name example.com; 13 | server_name _; # replace with your domain 14 | 15 | # path to ssl certificate file 16 | # If using Let's Encrypt replace this path with the location of the "fullchain.pem" file for your domain 17 | # eg. 18 | # ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; 19 | ssl_certificate /etc/nginx/ssl/homebridge.local.crt; 20 | 21 | # path to ssl private key file 22 | # If using Let's Encrypt replace this path with the location of the "privkey.pem" file for your domain 23 | # eg. 24 | # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; 25 | ssl_certificate_key /etc/nginx/ssl/homebridge.local.key; # replace with path to 26 | 27 | ssl_session_cache builtin:1000 shared:SSL:10m; 28 | ssl_protocols TLSv1.2 TLSv1.3; 29 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 30 | ssl_prefer_server_ciphers off; 31 | # ssl_stapling on; 32 | # ssl_stapling_verify on; 33 | 34 | # large upload size to allow for uploading 3rd party backup files 35 | client_max_body_size 2G; 36 | 37 | error_page 502 /custom_502.html; 38 | 39 | location /startup-status.json { 40 | alias /usr/share/nginx/html/status.json; 41 | } 42 | 43 | location / { 44 | proxy_pass http://127.0.0.1:8581; 45 | proxy_http_version 1.1; 46 | proxy_buffering off; 47 | proxy_set_header Host $host; 48 | proxy_set_header Upgrade $http_upgrade; 49 | proxy_set_header Connection "Upgrade"; 50 | proxy_set_header X-Real-IP $remote_addr; 51 | proxy_set_header X-Forward-For $proxy_add_x_forwarded_for; 52 | } 53 | 54 | location = /custom_502.html { 55 | root /usr/share/nginx/html; 56 | internal; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-request.yml: -------------------------------------------------------------------------------- 1 | name: Support Request 2 | description: Need help? 3 | labels: ["question"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Please read the following before you start filling out this form: 9 | 10 | * If your issue is specific to a certain plugin, create the issue on that plugin's GitHub project page instead. 11 | * If your issue is for Homebridge, create the issue on the Homebridge GitHub project page instead. 12 | * Support via GitHub issues is limited. You may find it more benefitial ask questions on the [Homebridge Discord](https://discord.gg/kqNCe2D) or [Reddit](https://www.reddit.com/r/homebridge/) communities instead. 13 | * We do not provide support for extra packages, such as PiHole or Deconz, beyond the basic installation scripts. 14 | * Search through existing issues (resolved or open) which might provide a solution to your problem already. 15 | * Aim to find a descriptive and precise title for your support request. 16 | - type: dropdown 17 | id: raspberry-pi-model 18 | attributes: 19 | label: Raspberry Pi Model 20 | options: 21 | - Raspberry Pi 1 B 22 | - Raspberry Pi 2 23 | - Raspberry Pi 3 24 | - Raspberry Pi Zero W 25 | - Raspberry Pi Zero WH 26 | - Raspberry Pi Zero 2 W 27 | - Raspberry Pi 3 A+ 28 | - Raspberry Pi 3 B+ 29 | - Raspberry Pi 4 B 30 | - Other (specify in description) 31 | description: | 32 | Please select which Raspberry Pi model you are using. 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: current-situation 37 | attributes: 38 | label: Current Situation 39 | description: | 40 | Please provide an overview of the current situation and illustrate potential problems or shortcomings. 41 | 42 | * What is happening? 43 | * What you expect to happen? 44 | * Clear steps explaining how to reproduce the problem. 45 | placeholder: | 46 | Tip: You can attach images or files by clicking this area to highlight it and then dragging files in. 47 | validations: 48 | required: true 49 | - type: textarea 50 | id: logs 51 | attributes: 52 | label: Logs 53 | render: text 54 | description: | 55 | Please provide the relevant logs showing the error that occurred. 56 | 57 | * Keep this short. Do not paste in hundereds of lines repeating the same error. 58 | * Show the logs from the service being restarted until the error occurs. 59 | validations: 60 | required: false -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/00-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # 4 | # Install Homebridge and Homebridge Config UI X 5 | # 6 | 7 | # 8 | # Executables Files 9 | # 10 | install -m 755 files/hb-config-new "${ROOTFS_DIR}/usr/local/sbin/hb-config" 11 | 12 | # Pre-start files 13 | install -v -d "${ROOTFS_DIR}/etc/hb-service/homebridge/prestart.d" 14 | install -m 755 files/20-hb-nginx-check "${ROOTFS_DIR}/etc/hb-service/homebridge/prestart.d/" 15 | 16 | # First boot service 17 | install -m 644 files/first-boot-homebridge.service "${ROOTFS_DIR}/etc/systemd/system/" 18 | install -m 755 files/first-boot-homebridge "${ROOTFS_DIR}/usr/local/sbin/" 19 | 20 | # Sudoers configuration for homebridge user 21 | install -v -d "${ROOTFS_DIR}/etc/sudoers.d" 22 | install -m 0440 files/010_homebridge-nopasswd "${ROOTFS_DIR}/etc/sudoers.d/" 23 | 24 | # 25 | # MOTD 26 | # 27 | install -m 755 files/issue "${ROOTFS_DIR}/etc/issue" 28 | install -m 755 files/motd-linux "${ROOTFS_DIR}/etc/update-motd.d/15-linux" 29 | install -m 755 files/motd-homebridge "${ROOTFS_DIR}/etc/update-motd.d/20-homebridge" 30 | install -m 633 files/bashrc.partial "${ROOTFS_DIR}/tmp/bashrc.partial" 31 | 32 | export 33 | 34 | # 35 | # Set Version 36 | # 37 | echo "$IMG_NAME" > "${ROOTFS_DIR}/etc/hb-release" 38 | 39 | on_chroot << EOF 40 | 41 | curl -sSfL https://repo.homebridge.io/KEY.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/homebridge.gpg > /dev/null 42 | echo "deb [signed-by=/usr/share/keyrings/homebridge.gpg] https://repo.homebridge.io ${RELEASE_STREAM} main" | tee /etc/apt/sources.list.d/homebridge.list > /dev/null 43 | 44 | apt-get update 45 | echo apt-get install homebridge=${HOMEBRIDGE_APT_PKG_VERSION} 46 | apt-get install homebridge=${HOMEBRIDGE_APT_PKG_VERSION} 47 | 48 | # Add the homebridge user to the sudo group 49 | usermod -aG sudo homebridge 50 | 51 | mkdir -p /etc/systemd/system/homebridge.service.d 52 | echo "[Service]" > /etc/systemd/system/homebridge.service.d/override.conf 53 | echo "Environment=\"UIX_CAN_SHUTDOWN_RESTART_HOST=1\"" >> /etc/systemd/system/homebridge.service.d/override.conf 54 | echo "Environment=\"UIX_STORAGE_PATH=/var/lib/homebridge\"" >> /etc/systemd/system/homebridge.service.d/override.conf 55 | 56 | # correct ownership 57 | # chown -R ${FIRST_USER_NAME}:${FIRST_USER_NAME} /var/lib/homebridge 58 | 59 | # empty motd 60 | > /etc/motd 61 | 62 | # make a symlink to the main config directory 63 | #[ -e /home/${FIRST_USER_NAME}/.homebridge ] || ln -fs /var/lib/homebridge /home/${FIRST_USER_NAME}/.homebridge 64 | [ -e /root/.homebridge ] || ln -fs /var/lib/homebridge /root/.homebridge 65 | 66 | # include homebridge bashrc in first user's bashrc 67 | cat /tmp/bashrc.partial >> /home/${FIRST_USER_NAME}/.bashrc 68 | rm -rf /tmp/bashrc.partial 69 | 70 | # set ui port for use in motd message 71 | echo "8581" > /etc/hb-ui-port 72 | 73 | # prioritise dns over mdns 74 | sed -i 's/files mdns4_minimal \[NOTFOUND=return\] dns/files dns mdns4_minimal \[NOTFOUND=return\]/' /etc/nsswitch.conf 75 | 76 | systemctl daemon-reload 77 | systemctl enable homebridge 78 | systemctl enable first-boot-homebridge 79 | EOF 80 | 81 | -------------------------------------------------------------------------------- /.github/actions/trigger-and-wait-workflow/README.md: -------------------------------------------------------------------------------- 1 | # Trigger and Wait for Workflow Action 2 | 3 | A reusable GitHub Action that triggers a workflow and waits for its completion with timeout protection and proper error handling. 4 | 5 | ## Features 6 | 7 | - **Sequential Execution**: Prevents workflow collision by waiting for completion before proceeding 8 | - **Timeout Protection**: Configurable timeout (default 30 minutes) to prevent infinite waiting 9 | - **Error Handling**: Handles all workflow states including failure and cancellation 10 | - **Clear Logging**: Provides detailed progress information with status updates and elapsed time tracking 11 | - **Modular Design**: Single responsibility with clean input/output interface 12 | - **Conditional Execution**: Only runs when changes are detected and auto-merge is enabled 13 | 14 | ## Usage 15 | 16 | ```yaml 17 | - name: Trigger and Wait for Stage 2 Workflow 18 | uses: ./.github/actions/trigger-and-wait-workflow 19 | with: 20 | workflow-file: 'release-stage-2_build_and_release.yml' 21 | ref: 'latest' 22 | release-type: ${{ matrix.release_type }} 23 | scheduled: ${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'Scheduled' }} 24 | github-token: ${{ secrets.GH_TOKEN }} 25 | changes-detected: ${{ steps.homebridge-bot.outputs.changes_detected }} 26 | auto-merge: ${{ steps.homebridge-bot.outputs.auto_merge }} 27 | timeout-minutes: '30' 28 | ``` 29 | 30 | ## Inputs 31 | 32 | | Input | Required | Default | Description | 33 | |-------|----------|---------|-------------| 34 | | `workflow-file` | Yes | - | The workflow file to trigger (e.g., `release-stage-2_build_and_release.yml`) | 35 | | `ref` | Yes | `latest` | The git ref to run the workflow on | 36 | | `release-type` | Yes | - | The release type parameter to pass to the workflow | 37 | | `scheduled` | No | `Manual` | The scheduled parameter to pass to the workflow | 38 | | `github-token` | Yes | - | GitHub token for authentication | 39 | | `timeout-minutes` | No | `30` | Maximum wait time in minutes | 40 | 41 | ## Outputs 42 | 43 | | Output | Description | 44 | |--------|-------------| 45 | | `workflow-conclusion` | The conclusion of the triggered workflow (`success`, `failure`, `cancelled`, etc.) | 46 | | `run-id` | The run ID of the triggered workflow | 47 | 48 | ## How It Works 49 | 50 | The action performs these steps sequentially: 51 | 52 | 1. **Trigger Workflow**: Uses `gh workflow run` to trigger the specified workflow 53 | 2. **Find Workflow Run**: Uses `gh run list` to locate the most recently triggered workflow 54 | 3. **Extract Run ID**: Extracts the run ID from the workflow URL using regex pattern matching 55 | 4. **Wait for Completion**: Polls workflow status using `gh run view` until completion with timeout protection 56 | 57 | ## Error Handling 58 | 59 | - **Timeout**: If the workflow doesn't complete within the specified timeout, the action will exit with code 0 (non-failing timeout) 60 | - **Workflow Failure**: If the triggered workflow fails, the action will exit with code 1 61 | - **API Errors**: If GitHub CLI commands fail, the action will exit with code 1 62 | - **Invalid URLs**: If the workflow URL cannot be parsed, the action will exit with code 1 63 | 64 | ## Implementation Details 65 | 66 | Based on the GitHub CLI polling pattern recommended in [cli/cli#4001](https://github.com/cli/cli/issues/4001#issuecomment-2742170405), this action ensures sequential workflow execution to prevent collision during the publishing phase. 67 | 68 | The action only executes when both `changes-detected` and `auto-merge` are `true`, making it safe to use in conditional scenarios where no action should be taken. -------------------------------------------------------------------------------- /.github/workflows/release-stage-1_update_dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Release Stage 1 - Dependency Management - Update and Build 2 | run-name: Release Stage 1 - Dependency Management - ${{ github.event_name == 'workflow_dispatch' && format('Manual - {0}', github.event.inputs.release-stream) || 'Scheduled' }} Run 3 | 4 | on: 5 | schedule: 6 | # Daily updates - 6 AM Eastern (10 AM UTC), processes stable, beta and alpha streams 7 | - cron: '0 10 * * *' 8 | workflow_dispatch: 9 | inputs: 10 | release-stream: 11 | description: 'Choose release stream to update and build' 12 | required: true 13 | type: choice 14 | options: 15 | - stable 16 | default: stable 17 | # skip-build: 18 | # description: 'Skip build and only update dependencies' 19 | # required: false 20 | # type: boolean 21 | # default: false 22 | publish: 23 | description: 'Publish Release to RPI Imager Registry' 24 | type: boolean 25 | default: true 26 | 27 | concurrency: 28 | group: release-stage-1-update-dependencies 29 | cancel-in-progress: false 30 | 31 | permissions: 32 | contents: write 33 | pull-requests: write 34 | checks: read 35 | actions: write 36 | 37 | jobs: 38 | define-matrix: 39 | name: Define Jobs Matrix 40 | runs-on: ubuntu-latest 41 | outputs: 42 | matrix: ${{ steps.set-matrix.outputs.matrix }} 43 | steps: 44 | - name: Set matrix based on trigger 45 | id: set-matrix 46 | run: | 47 | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then 48 | if [[ "${{ github.event.inputs.release-stream }}" == "all" ]]; then 49 | echo 'matrix={"stream":["stable"]}' >> $GITHUB_OUTPUT 50 | else 51 | echo "matrix={\"stream\":[\"${{ github.event.inputs.release-stream }}\"]}" >> $GITHUB_OUTPUT 52 | fi 53 | else 54 | # Scheduled run - process stable, beta, and alpha streams 55 | echo 'matrix={"stream":["stable"]}' >> $GITHUB_OUTPUT 56 | fi 57 | 58 | - name: Output matrix for debugging 59 | run: | 60 | echo "::notice::Dependencies Matrix: ${{ steps.set-matrix.outputs.matrix }}" 61 | 62 | update-dependencies: 63 | name: Update ${{ matrix.stream }} Dependencies 64 | needs: define-matrix 65 | runs-on: ubuntu-latest 66 | strategy: 67 | max-parallel: 1 68 | fail-fast: false 69 | matrix: 70 | stream: ${{ fromJson(needs.define-matrix.outputs.matrix).stream }} 71 | steps: 72 | - name: Update Dependencies ${{ matrix.stream }} 73 | id: homebridge-bot 74 | uses: homebridge/dependency-bot@latest 75 | with: 76 | config_file: '.github/homebridge-dependency-bot-${{ matrix.stream }}.json' 77 | release_stream: ${{ matrix.stream }} 78 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 79 | 80 | - name: Log Skipped ${{ matrix.stream }} Stage 2 Trigger 81 | if: steps.homebridge-bot.outputs.changes_detected != 'true' || steps.homebridge-bot.outputs.auto_merge != 'true' 82 | run: | 83 | echo "::warning::${{ matrix.stream }} Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" 84 | 85 | - name: Checkout repository (for trigger step) 86 | if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' 87 | uses: actions/checkout@v5 88 | 89 | - name: Trigger and Wait for ${{ matrix.stream }} Stage 2 Workflow 90 | if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' 91 | uses: ./.github/actions/trigger-and-wait-workflow 92 | with: 93 | workflow-file: 'create_raspbian_pi-gen.yml' 94 | ref: 'latest' 95 | github-token: ${{ secrets.GH_TOKEN }} 96 | timeout-minutes: '120' 97 | workflow-fields: | 98 | release-stream=${{ matrix.stream }} 99 | Scheduled=${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'Scheduled' }} 100 | publish=${{ github.event.inputs.publish }} 101 | run-name: "${{ matrix.stream }}:${{ github.run_id }}" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | 8 | 9 | # Homebridge Raspberry Pi Image 10 | 11 | [![Build](https://github.com/homebridge/homebridge-raspbian-image/actions/workflows/create_raspbian_pi-gen.yml/badge.svg)](https://github.com/homebridge/homebridge-raspbian-image/actions/workflows/create_raspbian_pi-gen.yml) 12 | [![GitHub release (latest by date)](https://badgen.net/github/release/homebridge/homebridge-raspbian-image?label=Version)](https://github.com/homebridge/homebridge-raspbian-image/releases/latest) 13 | [![GitHub All Releases](https://img.shields.io/github/downloads/homebridge/homebridge-raspbian-image/total)](https://somsubhra.github.io/github-release-stats/?username=homebridge&repository=homebridge-raspbian-image&page=1&per_page=30) 14 | 15 | 16 | 17 | This project provides a free [Raspbian](https://www.raspberrypi.org/downloads/raspbian/) based Raspberry Pi image with [Homebridge](https://github.com/homebridge/homebridge) and [Homebridge Config UI X](https://github.com/homebridge/homebridge-config-ui-x) pre-installed. 18 | 19 | * Runs on RPI 2 or higher models supporting ARMv7 cpu's or greater ( Last version supporting RPI 1 and RPi Zero W was [v1.2.4](https://github.com/homebridge/homebridge-raspbian-image/releases/tag/v1.2.4)) 20 | * Built on Raspbian Lite (no desktop) 21 | * Simple WiFi Setup 22 | * Includes [ffmpeg](https://github.com/homebridge/ffmpeg-for-homebridge) pre-compiled with audio support (libfdk-aac) 23 | * Includes a user friendly, easy to use web based GUI to configure Homebridge and monitor your Raspberry Pi 24 | * Visual configuration for over 400 plugins (no manual config.json editing required) 25 | 26 | This image also provides a command called `hb-config` which helps you keep Node.js up-to-date, perform maintenance on your Homebridge server, and install additional optional software such as *[Pi Hole](https://github.com/homebridge/homebridge-raspbian-image/wiki/How-To-Install-Pi-Hole)* and *[deCONZ](https://github.com/homebridge/homebridge-raspbian-image/wiki/How-To-Install-deCONZ-for-ConBee-or-RaspBee)*. 27 | 28 | The Homebridge service is installed using the method described in the official [Raspberry Pi Installation Guide](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Raspbian) on the [Homebridge](https://github.com/homebridge/homebridge) project wiki. 29 | 30 | ## Installation Instructions 31 | 32 |

33 | 34 |

35 | 36 | For full installation instructions, please refer to the [Homebridge Raspbian Image Wiki](https://github.com/homebridge/homebridge-raspbian-image/wiki/Getting-Started) 37 | 38 | ## Security and Privacy 39 | 40 | * **Privacy:** The *Homebridge Raspbian Image*, as well as the [Homebridge](https://github.com/homebridge/homebridge) and [Homebridge Config UI X](https://github.com/homebridge/homebridge-config-ui-x) software components, do not contain any *analytics*, *call home*, or similar features that would allow the project maintainers to track you or the usage of this image. 41 | * **Security:** The *Homebridge Raspbian Image* is kept up-to-date with the latest [official Raspbian builds](https://github.com/RPi-Distro/pi-gen). To find out more, or to report a security issue or vulnerability, please see the project's [SECURITY](.github/SECURITY.md) policy. 42 | * **Transparency:** The *Homebridge Raspbian Image* project is open source and each image is built using the public GitHub Action runners. The build logs for each release are publicly available on the project's [GitHub Actions](https://github.com/homebridge/homebridge-raspbian-image/actions/workflows/main.yml) page and every release contains a SHA-256 checksum of the image you can use to verify the integrity of your download. 43 | 44 | ## Community 45 | 46 | The official Homebridge Discord server and Reddit community are where users can discuss Homebridge and ask for help. 47 | 48 | 49 | 50 | [![Homebridge Discord](https://discordapp.com/api/guilds/432663330281226270/widget.png?style=banner2)](https://discord.gg/kqNCe2D) [![Homebridge Reddit](.github/homebridge-reddit.svg?sanitize=true)](https://www.reddit.com/r/homebridge/) 51 | 52 | 53 | 54 | ## Help 55 | 56 | The *Homebridge Raspberry Pi Image* wiki contains more information and instructions on how to further customise your install: 57 | 58 | https://github.com/homebridge/homebridge-raspbian-image/wiki 59 | -------------------------------------------------------------------------------- /scripts/hb-config-vnc-uninstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # filepath: /Users/sgracey/Code/homebridge-raspbian-image/stage3_homebridge/01-homebridge/files/hb-config-vnc-uninstall 3 | 4 | # VNC and Desktop Uninstall Script for Homebridge Raspberry Pi Image 5 | # This script removes the VNC server and optional desktop components 6 | 7 | set -e 8 | 9 | RED='\033[0;31m' 10 | GREEN='\033[0;32m' 11 | YELLOW='\033[1;33m' 12 | BOLD='\e[1m' 13 | NC='\033[0m' 14 | 15 | # Check if running as root 16 | if [ $(id -u) -ne 0 ]; then 17 | printf "${RED}Script must be run as root. Try 'sudo $0'${NC}\n" 18 | exit 1 19 | fi 20 | 21 | echo -e "${BOLD}VNC and Desktop Uninstall Script${NC}" 22 | echo "This script will remove VNC server and optionally the desktop environment" 23 | echo "" 24 | 25 | # Check what's currently installed 26 | VNC_INSTALLED=false 27 | DESKTOP_INSTALLED=false 28 | 29 | if systemctl list-unit-files | grep -q "vncserver-x11-serviced.service"; then 30 | VNC_INSTALLED=true 31 | fi 32 | 33 | if dpkg -l | grep -q "^ii.*raspberrypi-ui-mods"; then 34 | DESKTOP_INSTALLED=true 35 | fi 36 | 37 | if [ "$VNC_INSTALLED" = false ] && [ "$DESKTOP_INSTALLED" = false ]; then 38 | echo -e "${YELLOW}Neither VNC nor desktop environment appears to be installed.${NC}" 39 | exit 0 40 | fi 41 | 42 | # Show what will be removed 43 | echo "Current installation status:" 44 | [ "$VNC_INSTALLED" = true ] && echo " - VNC Server: INSTALLED" 45 | [ "$VNC_INSTALLED" = false ] && echo " - VNC Server: not installed" 46 | [ "$DESKTOP_INSTALLED" = true ] && echo " - Desktop Environment: INSTALLED" 47 | [ "$DESKTOP_INSTALLED" = false ] && echo " - Desktop Environment: not installed" 48 | echo "" 49 | 50 | # Confirm before proceeding 51 | read -p "Do you want to continue with uninstallation? (y/N): " -n 1 -r 52 | echo 53 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then 54 | echo "Uninstallation cancelled." 55 | exit 0 56 | fi 57 | 58 | # Stop and disable VNC services 59 | if [ "$VNC_INSTALLED" = true ]; then 60 | echo -e "${BOLD}Stopping VNC services...${NC}" 61 | 62 | systemctl stop vncserver-x11-serviced.service 2>/dev/null || true 63 | systemctl disable vncserver-x11-serviced.service 2>/dev/null || true 64 | 65 | # Remove any custom VNC service files 66 | if [ -f /etc/systemd/system/vncserver-pi.service ]; then 67 | systemctl stop vncserver-pi.service 2>/dev/null || true 68 | systemctl disable vncserver-pi.service 2>/dev/null || true 69 | rm -f /etc/systemd/system/vncserver-pi.service 70 | fi 71 | 72 | systemctl daemon-reload 73 | 74 | echo -e "${GREEN}VNC services stopped and disabled${NC}" 75 | fi 76 | 77 | # Ask about removing VNC package 78 | if [ "$VNC_INSTALLED" = true ]; then 79 | echo "" 80 | read -p "Remove RealVNC Server package? (y/N): " -n 1 -r 81 | echo 82 | if [[ $REPLY =~ ^[Yy]$ ]]; then 83 | echo -e "${BOLD}Removing RealVNC Server...${NC}" 84 | apt-get purge -y realvnc-vnc-server 2>/dev/null || true 85 | apt-get autoremove -y 86 | echo -e "${GREEN}RealVNC Server removed${NC}" 87 | fi 88 | fi 89 | 90 | # Ask about removing desktop environment 91 | if [ "$DESKTOP_INSTALLED" = true ]; then 92 | echo "" 93 | echo -e "${YELLOW}WARNING: Removing the desktop environment will free up ~2GB of disk space${NC}" 94 | echo -e "${YELLOW}but may take 10-15 minutes to complete.${NC}" 95 | echo "" 96 | read -p "Remove desktop environment (raspberrypi-ui-mods)? (y/N): " -n 1 -r 97 | echo 98 | if [[ $REPLY =~ ^[Yy]$ ]]; then 99 | echo -e "${BOLD}Removing desktop environment...${NC}" 100 | echo "This may take several minutes..." 101 | 102 | apt-get purge -y raspberrypi-ui-mods 2>/dev/null || true 103 | apt-get autoremove -y 104 | apt-get clean 105 | 106 | echo -e "${GREEN}Desktop environment removed${NC}" 107 | fi 108 | fi 109 | 110 | echo "" 111 | echo -e "${BOLD}${GREEN}Uninstallation complete!${NC}" 112 | echo "" 113 | 114 | # Show final status 115 | echo "Final status:" 116 | systemctl list-unit-files | grep -q "vncserver-x11-serviced.service" && echo " - VNC Server: still installed" || echo " - VNC Server: removed" 117 | dpkg -l | grep -q "^ii.*raspberrypi-ui-mods" && echo " - Desktop Environment: still installed" || echo " - Desktop Environment: removed" 118 | echo "" 119 | 120 | # Suggest reboot if significant changes were made 121 | if [[ $REPLY =~ ^[Yy]$ ]]; then 122 | echo -e "${YELLOW}A reboot is recommended to complete the uninstallation.${NC}" 123 | read -p "Reboot now? (y/N): " -n 1 -r 124 | echo 125 | if [[ $REPLY =~ ^[Yy]$ ]]; then 126 | sync 127 | reboot 128 | fi 129 | fi 130 | 131 | exit 0 -------------------------------------------------------------------------------- /make_rpi-imager-snipplet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | import zipfile 4 | import hashlib 5 | import os 6 | import argparse 7 | from datetime import date 8 | import glob 9 | 10 | # Credits for this go to https://github.com/guysoft/CustomPiOS/blob/devel/src/make_rpi-imager-snipplet.py 11 | 12 | 13 | def calculate_sha256(data): 14 | sha256_hash = hashlib.sha256() 15 | sha256_hash.update(data) 16 | return sha256_hash.hexdigest() 17 | 18 | 19 | def calculate_sha256_zip(zip_file_path): 20 | # Calculate the SHA256 hash of the zip file 21 | with open(zip_file_path, 'rb') as file: 22 | sha256_hash = hashlib.sha256() 23 | while True: 24 | # Read the file in chunks 25 | chunk = file.read(4096) 26 | if not chunk: 27 | break 28 | sha256_hash.update(chunk) 29 | 30 | zip_hash = sha256_hash.hexdigest() 31 | 32 | # Open the zip file in binary mode 33 | with zipfile.ZipFile(zip_file_path, 'r') as zip_obj: 34 | # Get the list of file names in the zip 35 | file_names = zip_obj.namelist() 36 | 37 | if file_names: 38 | # Get the first file name in the zip 39 | first_file_name = file_names[0] 40 | 41 | # Calculate the SHA256 hash of the first file's data within the zip 42 | sha256_hash = hashlib.sha256() 43 | with zip_obj.open(first_file_name) as first_file: 44 | while True: 45 | # Read the first file's data in chunks 46 | chunk = first_file.read(4096) 47 | if not chunk: 48 | break 49 | sha256_hash.update(chunk) 50 | 51 | first_file_hash = sha256_hash.hexdigest() 52 | 53 | return zip_hash, first_file_hash 54 | 55 | # Return None if the zip file is empty 56 | return zip_hash, None 57 | 58 | 59 | def handle_arg(key, optional=False): 60 | if optional and key not in os.environ.keys(): 61 | return 62 | if key in os.environ.keys(): 63 | return os.environ[key] 64 | else: 65 | print("Error: Missing value in your distro config file for rpi-imager json generator: " + str(key)) 66 | exit(1) 67 | 68 | 69 | if __name__ == "__main__": 70 | parser = argparse.ArgumentParser( 71 | add_help=True, description='Create a json snipplet from an image to be used with the make_rpi-imager_list.py and eventually published in a repo') 72 | parser.add_argument('workspace_suffix', nargs='?', default="default", 73 | type=str, help='Suffix of workspace folder') 74 | parser.add_argument('-u', '--rpi_imager_url', type=str, 75 | default="MISSING_URL", help='url to the uploaded image url') 76 | 77 | args = parser.parse_args() 78 | 79 | workspace_path = os.path.join(os.getcwd(), "pi-gen", "deploy") 80 | if args.workspace_suffix != "" and args.workspace_suffix != "default": 81 | workspace_path += "-" + args.workspace_suffix 82 | 83 | name = handle_arg("RPI_IMAGER_NAME") 84 | description = handle_arg("RPI_IMAGER_DESCRIPTION") 85 | url = args.rpi_imager_url 86 | icon = handle_arg("RPI_IMAGER_ICON") 87 | website = handle_arg("RPI_IMAGER_WEBSITE", True) 88 | release_date = date.today().strftime("%Y-%m-%d") 89 | 90 | devices=[] 91 | devices = json.loads(handle_arg("RPI_IMAGER_DEVICES")) 92 | 93 | # print("devices: ", devices) 94 | zip_local = glob.glob(os.path.join(workspace_path, "*.zip"))[0] 95 | 96 | if url == "MISSING_URL": 97 | url = os.path.basename(zip_local) 98 | 99 | output_path = os.path.join(workspace_path, "rpi-image-repo.json") 100 | 101 | json_out = {"name": name, 102 | "description": description, 103 | "init_format": "systemd", 104 | "url": url, 105 | "icon": icon, 106 | "release_date": release_date 107 | } 108 | 109 | json_out["devices"] = [json.loads(json.dumps(i)) for i in devices] 110 | if website is not None: 111 | json_out["website"] = website 112 | 113 | json_out["extract_size"] = None 114 | with zipfile.ZipFile(zip_local) as zipSize: 115 | json_out["extract_size"] = zipSize.filelist[0].file_size 116 | 117 | json_out["image_download_size"] = os.stat(zip_local).st_size 118 | 119 | json_out["extract_sha256"] = None 120 | json_out["image_download_sha256"] = None 121 | json_out["image_download_sha256"], json_out["extract_sha256"] = calculate_sha256_zip( 122 | zip_local) 123 | 124 | output_json = {} 125 | output_json['os_list'] = [] 126 | output_json['os_list'].append(json_out) 127 | 128 | with open(output_path, "w") as w: 129 | json.dump(output_json, w, indent=2) 130 | 131 | print("Done generating rpi-imager json snipplet to " + output_path) 132 | -------------------------------------------------------------------------------- /.github/actions/trigger-and-wait-workflow/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Trigger and Wait for Workflow' 2 | description: 'Triggers a workflow and waits for its completion with timeout protection' 3 | inputs: 4 | workflow-file: 5 | description: 'The workflow file to trigger (e.g., release-stage-2_build_and_release.yml)' 6 | required: true 7 | ref: 8 | description: 'The git ref to run the workflow on' 9 | required: true 10 | default: 'latest' 11 | github-token: 12 | description: 'GitHub token for authentication' 13 | required: true 14 | timeout-minutes: 15 | description: 'Maximum wait time in minutes' 16 | required: false 17 | default: '30' 18 | run-name: 19 | description: 'Unique name of the triggered workflow run' 20 | required: false 21 | default: '' 22 | workflow-fields: 23 | description: 'Fields to pass to the workflow as key=value pairs (JSON or newline separated)' 24 | required: false 25 | default: '' 26 | 27 | outputs: 28 | workflow-conclusion: 29 | description: 'The conclusion of the triggered workflow' 30 | value: ${{ steps.wait-completion.outputs.conclusion }} 31 | run-id: 32 | description: 'The run ID of the triggered workflow' 33 | value: ${{ steps.extract-run-id.outputs.run_id }} 34 | 35 | runs: 36 | using: 'composite' 37 | steps: 38 | - name: Trigger ${{ inputs.run-name }} Workflow 39 | shell: bash 40 | env: 41 | GH_TOKEN: ${{ inputs.github-token }} 42 | run: | 43 | echo "::notice::Triggering ${{ inputs.run-name }} workflow - ${{ inputs.workflow-file }}" 44 | 45 | # Parse workflow-fields input and build --field arguments 46 | FIELD_ARGS="" 47 | if [[ -n "${{ inputs.workflow-fields }}" ]]; then 48 | while IFS='=' read -r key value; do 49 | # Only add if both key and value are non-empty 50 | if [[ -n "$key" && -n "$value" ]]; then 51 | FIELD_ARGS+=" --field $key=$value" 52 | fi 53 | done <<< "${{ inputs.workflow-fields }}" 54 | fi 55 | 56 | echo "::notice::Using fields: $FIELD_ARGS" 57 | 58 | gh workflow run "${{ inputs.workflow-file }}" \ 59 | --ref "${{ inputs.ref }}" \ 60 | --field run_name="${{ inputs.run-name }}" \ 61 | $FIELD_ARGS || { 62 | echo "::error::Failed to trigger ${{ inputs.workflow-file }} workflow"; 63 | exit 1; 64 | } 65 | 66 | echo "::notice::${{ inputs.run-name }} workflow triggered successfully" 67 | 68 | - name: Find ${{ inputs.run-name }} Workflow Run 69 | id: find-workflow 70 | shell: bash 71 | env: 72 | GH_TOKEN: ${{ inputs.github-token }} 73 | run: | 74 | echo "::group::Waiting for ${{ inputs.run-name }} workflow to appear in run list..." 75 | 76 | # Initialize variables 77 | WORKFLOW_URL="" 78 | MAX_RETRIES=12 # Retry for up to 1 minute (12 retries * 5 seconds) 79 | RETRY_COUNT=0 80 | 81 | # Retry loop 82 | while [[ -z "$WORKFLOW_URL" || "$WORKFLOW_URL" == "null" ]]; do 83 | if [[ $RETRY_COUNT -ge $MAX_RETRIES ]]; then 84 | echo "::error::Could not find triggered ${{ inputs.run-name }} workflow run after 1 minute" 85 | gh run list --workflow "${{ inputs.workflow-file }}" --event workflow_dispatch --branch "${{ inputs.ref }}" --limit 5 --json url,name 86 | exit 1 87 | fi 88 | 89 | # Increment retry count 90 | RETRY_COUNT=$((RETRY_COUNT + 1)) 91 | 92 | # Check for the workflow run 93 | WORKFLOW_URL=$(gh run list --workflow "${{ inputs.workflow-file }}" \ 94 | --event workflow_dispatch \ 95 | --branch "${{ inputs.ref }}" \ 96 | --limit 10 \ 97 | --json url,name \ 98 | --jq '[.[] | select(.name | contains("${{ inputs.run-name }}"))] | .[].url' | \ 99 | head -n 1) 100 | 101 | if [[ -z "$WORKFLOW_URL" || "$WORKFLOW_URL" == "null" ]]; then 102 | echo "::notice::Retry $RETRY_COUNT/$MAX_RETRIES: ${{ inputs.run-name }} workflow not found. Retrying in 5 seconds..." 103 | sleep 5 104 | fi 105 | done 106 | 107 | echo "::endgroup::" 108 | echo "::notice::Found ${{ inputs.run-name }} workflow run: $WORKFLOW_URL" 109 | echo "workflow_url=$WORKFLOW_URL" >> $GITHUB_OUTPUT 110 | 111 | - name: Extract ${{ inputs.run-name }} Workflow Run ID 112 | id: extract-run-id 113 | shell: bash 114 | run: | 115 | WORKFLOW_URL="${{ steps.find-workflow.outputs.workflow_url }}" 116 | 117 | # Extract run ID from the URL 118 | if [[ "$WORKFLOW_URL" =~ actions/runs/([0-9]+) ]]; then 119 | RUN_ID="${BASH_REMATCH[1]}" 120 | echo "::notice::Extracted run ID: $RUN_ID" 121 | echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT 122 | else 123 | echo "::error::Could not extract run ID from workflow URL: $WORKFLOW_URL" 124 | exit 1 125 | fi 126 | 127 | - name: Wait for ${{ inputs.run-name }} Workflow Completion 128 | id: wait-completion 129 | shell: bash 130 | env: 131 | GH_TOKEN: ${{ inputs.github-token }} 132 | run: | 133 | RUN_ID="${{ steps.extract-run-id.outputs.run_id }}" 134 | echo "::group::Waiting for ${{ inputs.run-name }} workflow (run $RUN_ID) to complete..." 135 | 136 | # Set maximum wait time (convert minutes to seconds) 137 | MAX_WAIT_SECONDS=$(( ${{ inputs.timeout-minutes }} * 60 )) 138 | START_TIME=$(date +%s) 139 | 140 | # Poll the workflow status until it completes 141 | while true; do 142 | # Check if we've exceeded the maximum wait time 143 | CURRENT_TIME=$(date +%s) 144 | ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) 145 | 146 | if [[ $ELAPSED_TIME -gt $MAX_WAIT_SECONDS ]]; then 147 | echo "::error::${{ inputs.run-name }} workflow timed out after ${{ inputs.timeout-minutes }} minutes" 148 | exit 0 149 | fi 150 | 151 | STATUS=$(gh run view "$RUN_ID" --json status -q '.status') 152 | 153 | case "$STATUS" in 154 | "completed") 155 | CONCLUSION=$(gh run view "$RUN_ID" --json conclusion -q '.conclusion') 156 | echo "conclusion=$CONCLUSION" >> $GITHUB_OUTPUT 157 | if [[ "$CONCLUSION" == "success" ]]; then 158 | echo "::notice::${{ inputs.run-name }} workflow completed successfully" 159 | break 160 | else 161 | echo "::error::${{ inputs.run-name }} workflow failed with conclusion: $CONCLUSION" 162 | break 163 | fi 164 | ;; 165 | "in_progress"|"queued"|"requested"|"waiting") 166 | echo "::notice::${{ inputs.run-name }} workflow status: $STATUS - continuing to wait... (elapsed: ${ELAPSED_TIME}s)" 167 | sleep 30 168 | ;; 169 | *) 170 | echo "::error::${{ inputs.run-name }} workflow has unexpected status: $STATUS" 171 | break 172 | ;; 173 | esac 174 | done 175 | echo "::endgroup::" -------------------------------------------------------------------------------- /.github/workflows/release-stage-3_package_release.yml: -------------------------------------------------------------------------------- 1 | name: Release Stage 3 - Package and Publish Raspbian Homebridge Image 2 | run-name: Release Stage 3 - Package and Publish Raspbian Homebridge Image - ${{ inputs.release_tag }} - Run ${{ inputs.run_id }} 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | run_id: 8 | description: 'GitHub Run ID of the stage 2 build workflow that produced the artifacts' 9 | required: true 10 | type: string 11 | release_tag: 12 | description: 'Release TAG to use for this release (e.g. v1.0.1)' 13 | required: true 14 | type: string 15 | publish: 16 | description: 'Publish Release to RPI Imager Registry' 17 | type: boolean 18 | default: false 19 | 20 | concurrency: 21 | group: release-stage-3-package-raspbian-image 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | download-and-prepare: 26 | name: Download Artifacts and Prepare Release 27 | runs-on: ubuntu-latest 28 | outputs: 29 | has-32bit: ${{ steps.check-artifacts.outputs.has-32bit }} 30 | has-64bit: ${{ steps.check-artifacts.outputs.has-64bit }} 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v5 34 | 35 | - name: Download Build Artifacts (32-bit) 36 | id: download-32bit 37 | continue-on-error: true 38 | uses: actions/download-artifact@v4 39 | with: 40 | name: build-32bit 41 | path: ./artifacts/32bit 42 | run-id: ${{ inputs.run_id }} 43 | github-token: ${{ secrets.GITHUB_TOKEN }} 44 | 45 | - name: Download Build Artifacts (64-bit) 46 | id: download-64bit 47 | continue-on-error: true 48 | uses: actions/download-artifact@v4 49 | with: 50 | name: build-64bit 51 | path: ./artifacts/64bit 52 | run-id: ${{ inputs.run_id }} 53 | github-token: ${{ secrets.GITHUB_TOKEN }} 54 | 55 | - name: Check Downloaded Artifacts 56 | id: check-artifacts 57 | run: | 58 | echo "Checking downloaded artifacts..." 59 | ls -lR ./artifacts/ 60 | 61 | if [ -d "./artifacts/32bit" ] && [ "$(ls -A ./artifacts/32bit)" ]; then 62 | echo "has-32bit=true" >> $GITHUB_OUTPUT 63 | echo "✅ 32-bit artifacts found" 64 | else 65 | echo "has-32bit=false" >> $GITHUB_OUTPUT 66 | echo "⚠️ 32-bit artifacts not found" 67 | fi 68 | 69 | if [ -d "./artifacts/64bit" ] && [ "$(ls -A ./artifacts/64bit)" ]; then 70 | echo "has-64bit=true" >> $GITHUB_OUTPUT 71 | echo "✅ 64-bit artifacts found" 72 | else 73 | echo "has-64bit=false" >> $GITHUB_OUTPUT 74 | echo "⚠️ 64-bit artifacts not found" 75 | fi 76 | 77 | - name: Prepare Release Assets 78 | run: | 79 | mkdir -p ./release-assets 80 | 81 | # Copy all artifacts to release assets directory 82 | if [ -d "./artifacts/32bit" ]; then 83 | cp ./artifacts/32bit/*.zip ./release-assets/ 2>/dev/null || true 84 | cp ./artifacts/32bit/*manifest ./release-assets/ 2>/dev/null || true 85 | cp ./artifacts/32bit/rpi-image-repo-32bit.json ./release-assets/ 2>/dev/null || true 86 | fi 87 | 88 | if [ -d "./artifacts/64bit" ]; then 89 | cp ./artifacts/64bit/*.zip ./release-assets/ 2>/dev/null || true 90 | cp ./artifacts/64bit/*manifest ./release-assets/ 2>/dev/null || true 91 | cp ./artifacts/64bit/rpi-image-repo-64bit.json ./release-assets/ 2>/dev/null || true 92 | fi 93 | 94 | echo "Release assets prepared:" 95 | ls -lh ./release-assets/ 96 | 97 | - name: Combine RPI Imager JSON files 98 | if: steps.check-artifacts.outputs.has-32bit == 'true' || steps.check-artifacts.outputs.has-64bit == 'true' 99 | run: | 100 | # Copy individual JSON files for combination 101 | [ -f "./release-assets/rpi-image-repo-32bit.json" ] && cp "./release-assets/rpi-image-repo-32bit.json" ./ || true 102 | [ -f "./release-assets/rpi-image-repo-64bit.json" ] && cp "./release-assets/rpi-image-repo-64bit.json" ./ || true 103 | 104 | # Combine JSON files 105 | ./combine-rpi-imager-snipplet.py 106 | 107 | # Move combined file to release assets 108 | mv rpi-image-repo.json ./release-assets/ 109 | 110 | - name: Create Combined Release Body 111 | run: | 112 | # Combine all manifest files and release body 113 | cat ./artifacts/*/final-release-body.md > ./release-assets/combined-release-body.md 2>/dev/null || \ 114 | echo "# Homebridge Raspbian Image ${{ inputs.release_tag }}" > ./release-assets/combined-release-body.md 115 | 116 | echo "Release body created" 117 | 118 | - name: Upload Prepared Assets 119 | uses: actions/upload-artifact@v5 120 | with: 121 | name: release-assets 122 | path: ./release-assets/ 123 | retention-days: 7 124 | 125 | create-release: 126 | name: Create GitHub Release 127 | needs: download-and-prepare 128 | runs-on: ubuntu-latest 129 | steps: 130 | - name: Checkout repository 131 | uses: actions/checkout@v5 132 | 133 | - name: Download Prepared Assets 134 | uses: actions/download-artifact@v4 135 | with: 136 | name: release-assets 137 | path: ./release-assets 138 | 139 | - name: Create GitHub Release 140 | run: | 141 | gh release create "${{ inputs.release_tag }}" \ 142 | --title "${{ inputs.release_tag }}" \ 143 | --notes-file ./release-assets/combined-release-body.md \ 144 | --draft=false \ 145 | --prerelease=${{ inputs.publish == false }} \ 146 | ./release-assets/*.zip \ 147 | ./release-assets/*.json \ 148 | ./release-assets/*manifest 149 | env: 150 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 151 | 152 | - name: Release Created 153 | run: | 154 | echo "::notice::Release ${{ inputs.release_tag }} created successfully" 155 | RELEASE_URL=$(gh release view "${{ inputs.release_tag }}" --json url --jq '.url') 156 | echo "::notice::Release URL: $RELEASE_URL" 157 | env: 158 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 159 | 160 | publish-to-registry: 161 | name: Publish to Homebridge Registry 162 | needs: [create-release] 163 | if: ${{ inputs.publish }} 164 | runs-on: ubuntu-latest 165 | steps: 166 | - name: Generate GitHub App Token 167 | id: app-token 168 | uses: actions/create-github-app-token@v2 169 | with: 170 | app-id: ${{ vars.APP_ID }} 171 | private-key: ${{ secrets.PRIVATE_KEY }} 172 | repositories: 'homebridge.io' 173 | owner: 'homebridge' 174 | 175 | - name: Checkout repository 176 | uses: actions/checkout@v5 177 | 178 | - name: Download Release Assets 179 | uses: actions/download-artifact@v4 180 | with: 181 | name: release-assets 182 | path: ./release-assets 183 | 184 | - name: Push Image Info to Homebridge Registry 185 | uses: dmnemec/copy_file_to_another_repo_action@main 186 | env: 187 | API_TOKEN_GITHUB: ${{ steps.app-token.outputs.token }} 188 | with: 189 | source_file: './release-assets/rpi-image-repo.json' 190 | destination_repo: 'homebridge/homebridge.io' 191 | destination_branch: 'source' 192 | destination_folder: 'src/public/' 193 | user_email: 'github-actions[bot]@users.noreply.github.com' 194 | user_name: 'github-actions[bot]' 195 | commit_message: 'New Homebridge Raspbian Image Release ${{ inputs.release_tag }}' 196 | 197 | - name: Registry Updated 198 | run: | 199 | echo "::notice::Homebridge registry updated with ${{ inputs.release_tag }}" 200 | 201 | notify-discord: 202 | name: Send Discord Notification 203 | needs: [create-release, publish-to-registry] 204 | if: ${{ always() && inputs.publish && needs.create-release.result == 'success' }} 205 | uses: homebridge/.github/.github/workflows/discord-webhooks.yml@latest 206 | with: 207 | title: "Homebridge Raspbian Image Release" 208 | description: | 209 | Version `${{ inputs.release_tag }}` 210 | url: "https://github.com/homebridge/homebridge-raspbian-image/releases/tag/${{ inputs.release_tag }}" 211 | secrets: 212 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }} -------------------------------------------------------------------------------- /scripts/create-release-body.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # If used in GitHub Actions, ensure we have a full git history 4 | # - name: Checkout 5 | # uses: actions/checkout@v4 6 | # with: 7 | # fetch-depth: 0 8 | 9 | # Exit on error 10 | set -e 11 | 12 | # Logging functions 13 | GREEN='\033[0;32m' 14 | YELLOW='\033[1;33m' 15 | RED='\033[0;31m' 16 | BLUE='\033[0;34m' 17 | NC='\033[0m' # No Color 18 | 19 | log() { echo -e "${GREEN}[$(date +'%H:%M:%S')]${NC} $*" >&2; } 20 | warn() { echo -e "${YELLOW}[$(date +'%H:%M:%S')] WARN:${NC} $*" >&2; } 21 | error() { echo -e "${RED}[$(date +'%H:%M:%S')] ERROR:${NC} $*" >&2; } 22 | info() { echo -e "${BLUE}[$(date +'%H:%M:%S')] INFO:${NC} $*" >&2; } 23 | group_log() { 24 | if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then 25 | echo -e "::group::$*" 26 | else 27 | log "$*" 28 | fi 29 | } 30 | group_end() { 31 | if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then 32 | echo -e "::endgroup::" 33 | fi 34 | } 35 | 36 | # Determine repository root 37 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 38 | 39 | PREVIOUS_DIR="${REPO_ROOT}/.previous" 40 | rm -rf "$PREVIOUS_DIR" 41 | mkdir -p "$PREVIOUS_DIR" 42 | 43 | OUTPUT_DIR="${REPO_ROOT}/output" 44 | 45 | # Create output directory if it doesn't exist 46 | mkdir -p "$OUTPUT_DIR" 47 | 48 | MANIFEST="${OUTPUT_DIR}/release-body.md" 49 | rm -f "$MANIFEST" 50 | if [ -f "${REPO_ROOT}/assets/release-body-header.md" ]; then 51 | cp "${REPO_ROOT}/assets/release-body-header.md" "$MANIFEST" 52 | else 53 | touch $MANIFEST 54 | fi 55 | 56 | PKG_RELEASE_STREAM="${1:-stable}" 57 | 58 | info "Creating release body at ${MANIFEST}" 59 | 60 | # Get the latest tag to compare against, filtered by release type 61 | if [[ "${PKG_RELEASE_STREAM:-stable}" == "beta" ]]; then 62 | # For beta releases, only look at beta tags 63 | LATEST_TAG=$(git tag -l | grep -E "beta" | sort -V | tail -2 | head -1 2>/dev/null || echo "") 64 | elif [[ "${PKG_RELEASE_STREAM:-stable}" == "alpha" ]]; then 65 | # For alpha releases, only look at alpha tags 66 | LATEST_TAG=$(git tag -l | grep -E "alpha" | sort -V | tail -2 | head -1 2>/dev/null || echo "") 67 | else 68 | # For stable releases, only look at stable tags (no beta or alpha in name) 69 | LATEST_TAG=$(git tag -l | grep -v -E "(beta|alpha)" | sort -V | tail -2 | head -1 2>/dev/null || echo "") 70 | fi 71 | 72 | group_log "Latest Tag Lookup" 73 | git tag -l 74 | group_end 75 | 76 | log "Latest tag for stream '${PKG_RELEASE_STREAM:-stable}': ${LATEST_TAG:-none}" 77 | 78 | # Check for package manifest changes if we have a previous tag 79 | HAS_PACKAGE_CHANGES=false 80 | if [ -n "$LATEST_TAG" ]; then 81 | # Get the previous package.json for comparison 82 | PACKAGE_JSON_PATH="" 83 | case "${PKG_RELEASE_STREAM:-stable}" in 84 | beta) 85 | if [[ "$BUILD_ARCH" == "aarch64" || "$BUILD_ARCH" == "x86_64" ]]; then 86 | PACKAGE_JSON_PATH="beta/package.json" 87 | else 88 | PACKAGE_JSON_PATH="beta/package.json" 89 | fi 90 | ;; 91 | alpha) 92 | if [[ "$BUILD_ARCH" == "aarch64" || "$BUILD_ARCH" == "x86_64" ]]; then 93 | PACKAGE_JSON_PATH="alpha/package.json" 94 | else 95 | PACKAGE_JSON_PATH="alpha/package.json" 96 | fi 97 | ;; 98 | *) 99 | if [[ "$BUILD_ARCH" == "aarch64" || "$BUILD_ARCH" == "x86_64" ]]; then 100 | PACKAGE_JSON_PATH="stable/package.json" 101 | else 102 | PACKAGE_JSON_PATH="stable/package.json" 103 | fi 104 | ;; 105 | esac 106 | 107 | log "Previous package.json path for comparison: ${PACKAGE_JSON_PATH:-none}" 108 | 109 | # Compare package versions with previous tag 110 | if [ -n "$PACKAGE_JSON_PATH" ] && git show "$LATEST_TAG:$PACKAGE_JSON_PATH" >/dev/null 2>&1; then 111 | # Define the list of dependencies to check 112 | DEPENDENCIES=( 113 | "@homebridge/homebridge-apt-pkg" 114 | "ffmpeg-for-homebridge" 115 | ) 116 | 117 | # Iterate through the dependencies 118 | for DEP in "${DEPENDENCIES[@]}"; do 119 | # Get the previous version of the dependency from the latest tag 120 | PREV_VERSION=$(git show "$LATEST_TAG:$PACKAGE_JSON_PATH" 2>/dev/null | jq -r ".dependencies[\"$DEP\"] // \"unknown\"") 121 | 122 | # Get the current version of the dependency from the current package.json 123 | CURR_VERSION=$(jq -r ".dependencies[\"$DEP\"] // \"unknown\"" "${REPO_ROOT}/${PACKAGE_JSON_PATH}") 124 | 125 | # Check if the version has changed and add it to the changelog 126 | if [[ "$PREV_VERSION" != "$CURR_VERSION" && "$CURR_VERSION" != "unknown" ]]; then 127 | if [ "$HAS_PACKAGE_CHANGES" = false ]; then 128 | echo "### Package Manifest Changes" >> "$MANIFEST" 129 | echo >> "$MANIFEST" 130 | HAS_PACKAGE_CHANGES=true 131 | fi 132 | echo "* **${DEP}**: Updated from $PREV_VERSION to $CURR_VERSION" >> "$MANIFEST" 133 | fi 134 | done 135 | 136 | # Add a blank line to the manifest if there were package changes 137 | if [ "$HAS_PACKAGE_CHANGES" = true ]; then 138 | echo >> "$MANIFEST" 139 | fi 140 | else 141 | warn "Could not find previous package.json at tag ${LATEST_TAG} for comparison." 142 | fi 143 | fi 144 | 145 | if gh release download "$LATEST_TAG" --pattern "*.manifest" --clobber --dir "${PREVIOUS_DIR}"; then 146 | echo -e "\n## Changes Since Previous Release ($LATEST_TAG)\n" >> "$MANIFEST" 147 | group_log "Available previous manifests in ${PREVIOUS_DIR} and current manifests in ${OUTPUT_DIR}" 148 | ls -l "${PREVIOUS_DIR}" 149 | ls -l "${OUTPUT_DIR}" 150 | group_end 151 | # Iterate through all manifest files in ${OUTPUT_DIR} 152 | for OUTPUT_MANIFEST in "${OUTPUT_DIR}"/*manifest; do 153 | # Extract the base name of the manifest file 154 | MANIFEST_NAME=$(basename "$OUTPUT_MANIFEST") 155 | log "Processing manifest: $MANIFEST_NAME" 156 | # Check if a corresponding file exists in ${PREVIOUS_DIR} 157 | PREVIOUS_MANIFEST="${PREVIOUS_DIR}/${MANIFEST_NAME}" 158 | if [[ -f "$PREVIOUS_MANIFEST" ]]; then 159 | TMP_DIFF="/tmp/manifest.diff.$$" 160 | # Compare the manifests and capture differences 161 | echo diff -u "$PREVIOUS_MANIFEST" "$OUTPUT_MANIFEST" 162 | if ! diff -u "$PREVIOUS_MANIFEST" "$OUTPUT_MANIFEST" > "${TMP_DIFF}"; then 163 | # Check if there are any meaningful changes in the diff 164 | if grep -qE "^[+-]\|" "${TMP_DIFF}"; then 165 | echo "### Changes in ${MANIFEST_NAME}" >> "$MANIFEST" 166 | echo "\`\`\`diff" >> "$MANIFEST" 167 | # Include the diff output, sorted for readability 168 | grep -E "^[+-]\|" "${TMP_DIFF}" | sort -k2 | head -20 >> "$MANIFEST" 169 | echo "\`\`\`" >> "$MANIFEST" 170 | else 171 | warn "No meaningful changes found in differences for ${MANIFEST_NAME}." 172 | echo "No meaningful changes detected in ${MANIFEST_NAME}." >> "$MANIFEST" 173 | fi 174 | rm -f "${TMP_DIFF}" || true 175 | else 176 | warn "No differences found between current ${MANIFEST_NAME} and previous ${PREVIOUS_MANIFEST}." 177 | echo "No changes detected in ${MANIFEST_NAME}." >> "$MANIFEST" 178 | fi 179 | else 180 | warn "Previous manifest ${PREVIOUS_MANIFEST} not found for ${MANIFEST_NAME}." 181 | # If no corresponding file exists in ${PREVIOUS_DIR}, note it in the manifest 182 | echo "No previous manifest found for ${MANIFEST_NAME}." >> "$MANIFEST" 183 | fi 184 | done 185 | 186 | # Show Docker image specific changes 187 | 188 | # echo "See [commit history](https://github.com/homebridge/homebridge-vm-image/compare/$LATEST_TAG...${{ needs.set-versions.outputs.DOCKER_HOMEBRIDGE_VERSION }}) for Docker-specific changes." >> "$MANIFEST" 189 | else 190 | warn "Previous release manifest not found for tag ${LATEST_TAG}. Skipping manifest comparison." 191 | echo -e "\n## Changes Since Previous Release\n" >> "$MANIFEST" 192 | echo "Previous release manifest not available for comparison." >> "$MANIFEST" 193 | fi 194 | 195 | echo -e "\n### Homebridge Rasbian Image Changes" >> "$MANIFEST" 196 | if [ -n "$LATEST_TAG" ]; then 197 | # Get commits since the latest tag of the same type 198 | CHANGELOG_COMMITS=$(git log --oneline --no-merges "$LATEST_TAG"..HEAD 2>/dev/null) 199 | 200 | if [ -n "$CHANGELOG_COMMITS" ]; then 201 | # Add code changes section header 202 | if [ "$HAS_PACKAGE_CHANGES" = true ]; then 203 | echo "### Code Changes" >> "$MANIFEST" 204 | echo >> "$MANIFEST" 205 | fi 206 | # Format commits as changelog entries 207 | while IFS= read -r commit; do 208 | if [ -n "$commit" ]; then 209 | # Extract commit hash and message 210 | COMMIT_HASH=$(echo "$commit" | cut -d' ' -f1) 211 | COMMIT_MSG=$(echo "$commit" | cut -d' ' -f2-) 212 | echo "* $COMMIT_MSG (\`$COMMIT_HASH\`)" >> "$MANIFEST" 213 | fi 214 | done <<< "$CHANGELOG_COMMITS" 215 | else 216 | if [ "$HAS_PACKAGE_CHANGES" = false ]; then 217 | echo "* No new commits since last ${PKG_RELEASE_STREAM:-stable} release" >> "$MANIFEST" 218 | fi 219 | fi 220 | else 221 | # If no tags of this type exist, show recent commits 222 | RECENT_COMMITS=$(git log --oneline --no-merges -5 2>/dev/null) 223 | if [ -n "$RECENT_COMMITS" ]; then 224 | echo "### Recent Changes" >> "$MANIFEST" 225 | echo >> "$MANIFEST" 226 | while IFS= read -r commit; do 227 | if [ -n "$commit" ]; then 228 | COMMIT_HASH=$(echo "$commit" | cut -d' ' -f1) 229 | COMMIT_MSG=$(echo "$commit" | cut -d' ' -f2-) 230 | echo "* $COMMIT_MSG (\`$COMMIT_HASH\`)" >> "$MANIFEST" 231 | fi 232 | done <<< "$RECENT_COMMITS" 233 | else 234 | echo "* No commit history available" >> "$MANIFEST" 235 | fi 236 | fi 237 | 238 | echo >> "$MANIFEST" -------------------------------------------------------------------------------- /.github/workflows/create_raspbian_pi-gen.yml: -------------------------------------------------------------------------------- 1 | name: Release Stage 2 - Create Raspbian Homebridge Image using pi-gen 2 | run-name: Release Stage 2 - Create Raspbian Homebridge Image ${{ github.ref }} - ${{ inputs.run_name }} 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | # The github tag. e.g: v1.0.1 8 | # Download assets from a specific tag/version 9 | release-increment: 10 | description: 'Increment Release Version' 11 | type: choice 12 | options: 13 | - patch 14 | - minor 15 | - major 16 | default: 'patch' 17 | publish: 18 | description: 'Publish Release to RPI Imager Registry' 19 | type: boolean 20 | default: false 21 | release-stream: 22 | description: 'Release Stream to build against' 23 | type: choice 24 | options: 25 | - stable 26 | default: 'stable' 27 | required: false 28 | Scheduled: 29 | description: 'Whether this is a scheduled run' 30 | required: false 31 | type: choice 32 | options: 33 | - Scheduled 34 | - Manual 35 | default: Manual 36 | run_name: 37 | description: 'Unique name of the run' 38 | required: false 39 | type: string 40 | default: '' 41 | 42 | concurrency: 43 | group: release-stage-2-build-raspbian-image 44 | cancel-in-progress: false 45 | 46 | jobs: 47 | tag: 48 | name: Create Release Tag 49 | runs-on: ubuntu-latest 50 | outputs: 51 | version: ${{ steps.version.outputs.v-version }} 52 | steps: 53 | - name: Checkout code 54 | uses: actions/checkout@v5 55 | with: 56 | fetch-depth: 0 57 | 58 | - name: Get next version 59 | uses: reecetech/version-increment@2024.10.1 60 | id: version 61 | with: 62 | scheme: semver 63 | increment: ${{ inputs.release-increment }} 64 | 65 | - name: Display version information 66 | run: | 67 | echo "::notice::Next version will be: ${{ steps.version.outputs.v-version }}" 68 | echo "::notice::Current version: ${{ steps.version.outputs.current-v-version }}" 69 | echo "::notice::Release increment: ${{ inputs.release-increment }}" 70 | 71 | determine_package_versions: 72 | name: Determine package versions 73 | needs: tag 74 | runs-on: ubuntu-latest 75 | outputs: 76 | HOMEBRIDGE_APT_PKG_VERSION: ${{ steps.get_versions.outputs.HOMEBRIDGE_APT_PKG_VERSION }} 77 | FFMPEG_FOR_HOMEBRIDGE_VERSION: ${{ steps.get_versions.outputs.FFMPEG_FOR_HOMEBRIDGE_VERSION }} 78 | steps: 79 | - name: Checkout code 80 | uses: actions/checkout@v5 81 | 82 | - name: Get Homebridge version 83 | id: get_versions 84 | run: | 85 | HOMEBRIDGE_APT_PKG_VERSION=$(jq -r '.dependencies["@homebridge/homebridge-apt-pkg"]' ./stable/package.json | sed 's/\^//') 86 | FFMPEG_FOR_HOMEBRIDGE_VERSION=$(jq -r '.dependencies["ffmpeg-for-homebridge"]' ./stable/package.json | sed 's/\^//') 87 | 88 | echo "HOMEBRIDGE_APT_PKG_VERSION=${HOMEBRIDGE_APT_PKG_VERSION}" >> $GITHUB_OUTPUT 89 | echo "FFMPEG_FOR_HOMEBRIDGE_VERSION=v${FFMPEG_FOR_HOMEBRIDGE_VERSION}" >> $GITHUB_OUTPUT 90 | 91 | echo "::notice::Homebridge APT Package Version: ${HOMEBRIDGE_APT_PKG_VERSION}" 92 | echo "::notice::FFmpeg for Homebridge Version: v${FFMPEG_FOR_HOMEBRIDGE_VERSION}" 93 | 94 | - name: Show version summary 95 | run: | 96 | echo "## Version Information" >> $GITHUB_STEP_SUMMARY 97 | echo "" >> $GITHUB_STEP_SUMMARY 98 | echo "| Component | Version |" >> $GITHUB_STEP_SUMMARY 99 | echo "|-----------|---------|" >> $GITHUB_STEP_SUMMARY 100 | echo "| Release | ${{ needs.tag.outputs.version }} |" >> $GITHUB_STEP_SUMMARY 101 | echo "| Homebridge APT Package | ${{ steps.get_versions.outputs.HOMEBRIDGE_APT_PKG_VERSION }} |" >> $GITHUB_STEP_SUMMARY 102 | echo "| FFmpeg for Homebridge | ${{ steps.get_versions.outputs.FFMPEG_FOR_HOMEBRIDGE_VERSION }} |" >> $GITHUB_STEP_SUMMARY 103 | 104 | build_images: 105 | name: Build RPI Homebridge (${{ matrix.name }}) Image ${{ needs.tag.outputs.version }} 106 | needs: [tag, determine_package_versions] 107 | runs-on: [ubuntu-latest] 108 | strategy: 109 | fail-fast: false 110 | matrix: 111 | name: [ 112 | 32bit, 113 | 64bit 114 | ] 115 | include: 116 | - pi-gen-version: arm64 117 | release: trixie 118 | name: 64bit 119 | devices: '["pi5-64bit", "pi4-64bit", "pi3-64bit"]' 120 | recommended: "Recommended" 121 | 122 | - pi-gen-version: master 123 | release: trixie 124 | name: 32bit 125 | devices: '["pi5-32bit", "pi4-32bit", "pi3-32bit", "pi2-32bit", "pi1-32bit"]' 126 | recommended: "Not Recommended, support will be ending Spring 2027" 127 | 128 | # The build 129 | 130 | steps: 131 | 132 | - uses: actions/checkout@v5 133 | with: 134 | fetch-depth: 0 135 | 136 | # https://stackoverflow.com/questions/72444103/what-does-running-the-multiarch-qemu-user-static-does-before-building-a-containe 137 | 138 | # - name: Setup Dependencies 139 | # run: | 140 | # sudo apt-get update 141 | # sudo apt-get --yes --no-install-recommends install binfmt-support qemu-user-static 142 | # docker run --rm --privileged multiarch/qemu-user-static:register --reset 143 | 144 | # f022813 aka 1.7.0 145 | - uses: usimd/pi-gen-action@v1 146 | id: build 147 | with: 148 | # If you require the use of an apt proxy, set it here. This proxy setting will not 149 | # be included in the image, making it safe to use an apt-cacher or similar package 150 | # for development. 151 | apt-proxy: '' 152 | 153 | # Compression to apply on final image (either "none", "zip", "xz" or "gz"). 154 | compression: zip 155 | 156 | # Compression level to be used. From 0 to 9 (refer to the tool man page for more 157 | # information on this. Usually 0 is no compression but very fast, up to 9 with the 158 | # best compression but very slow). 159 | compression-level: 6 160 | 161 | # Disable the renaming of the first user during the first boot. This make it so 162 | # 'username' stays activated. 'username' must be set for this to work. Please be 163 | # aware of the implied security risk of defining a default username and password 164 | # for your devices. 165 | disable-first-boot-user-rename: 0 166 | 167 | # Additional options to include in PIGEN_DOCKER_OPTS 168 | # docker-opts: '' 169 | docker-opts: '--env BUILD_VERSION="${{ github.repository }}-${{ needs.tag.outputs.version }}-\(${{ matrix.name }}\)" --env HOMEBRIDGE_APT_PKG_VERSION="${{ needs.determine_package_versions.outputs.HOMEBRIDGE_APT_PKG_VERSION }}" --env FFMPEG_FOR_HOMEBRIDGE_VERSION="${{ needs.determine_package_versions.outputs.FFMPEG_FOR_HOMEBRIDGE_VERSION }}" --env RELEASE_STREAM=${{ inputs.release-stream }} --env IMG_SIZE=5G' 170 | 171 | # If set to `1`, cloud-init and netplan will be installed and configured. This 172 | # will allow you to configure your Raspberry Pi using cloud-init configuration 173 | # files. The cloud-init configuration files should be placed in the bootfs or by 174 | # editing the files in `stage2/04-cloud-init/files`. Cloud-init will be configured 175 | # to read them on first boot. 176 | enable-cloud-init: 0 177 | 178 | # Set whether a NOOBS image should be built as well. If enabled, the output 179 | # directory containing the NOOBS files will be saved as output variable 180 | # 'image-noobs-path'. 181 | enable-noobs: false 182 | 183 | # Enable SSH access to Pi. 184 | enable-ssh: 1 185 | 186 | # If this feature is enabled, the action will configure pi-gen to not export any 187 | # stage as image but the last one defined in property 'stage-list'. This is 188 | # helpful when building a single image flavor (in contrast to building a 189 | # lite/server and full-blown desktop image), since it speeds up the build process 190 | # significantly. 191 | export-last-stage-only: true 192 | 193 | # Comma or whitespace separated list of additional packages to install on host 194 | # before running pi-gen. Use this list to add any packages your custom stages may 195 | # require. Note that this is not affecting the final image. In order to add 196 | # additional packages, you need to add a respective 'XX-packages' file in your 197 | # custom stage. 198 | extra-host-dependencies: '' 199 | 200 | # Comma or whitespace separated list of additional modules to load on host before 201 | # running pi-gen. If your custom stage requires additional software or kernel 202 | # modules to be loaded, add them here. Note that this is not meant to configure 203 | # modules to be loaded in the target image. 204 | extra-host-modules: '' 205 | 206 | # Token to use for checking out pi-gen repo. 207 | github-token: ${{ github.token }} 208 | 209 | # Host name of the image. 210 | hostname: homebridge 211 | 212 | # Final image name. 213 | image-name: 'Homebridge-${{ matrix.release }}-${{ matrix.name }}' 214 | 215 | # Enabling this option will remove plenty of components from the GitHub Actions 216 | # runner that are not mandatory pre-requisites for a (vanilla) pi-gen build. This 217 | # shall increase the available disk space so that also large images can be 218 | # compiled on a free GHA runner (benchmark is the full image including a desktop 219 | # environment). If any packages are missing during the build consider adding them 220 | # to the `extra-host-dependencies` list. 221 | increase-runner-disk-size: true 222 | 223 | # Default keyboard keymap. 224 | keyboard-keymap: gb 225 | 226 | # Default keyboard layout. 227 | keyboard-layout: English (UK) 228 | 229 | # Default locale of the system image. 230 | locale: en_GB.UTF-8 231 | 232 | # Password of the intial user account, locked if empty. 233 | password: '' 234 | 235 | # Path where selected pi-gen ref will be checked out to. If the path does not yet 236 | # exist, it will be created (including its parents). 237 | pi-gen-dir: pi-gen 238 | 239 | # The release name to use in `/etc/issue.txt`. The default should only be used for 240 | # official Raspberry Pi builds. 241 | pi-gen-release: Raspberry Pi reference 242 | 243 | # GitHub repository to fetch pi-gen from, must be a fork from RPi-Distro/pi-gen. 244 | pi-gen-repository: RPi-Distro/pi-gen 245 | 246 | # Release version of pi-gen to use. This can both be a branch or tag name known in 247 | # the pi-gen repository. 248 | pi-gen-version: ${{ matrix.pi-gen-version }} 249 | 250 | # Setting to `1` will disable password authentication for SSH and enable public 251 | # key authentication. Note that if SSH is not enabled this will take effect when 252 | # SSH becomes enabled. 253 | pubkey-only-ssh: 0 254 | 255 | # Setting this to a value will make that value the contents of the 256 | # FIRST_USER_NAME's ~/.ssh/authorized_keys. Obviously the value should therefore 257 | # be a valid authorized_keys file. Note that this does not automatically enable 258 | # SSH. 259 | pubkey-ssh-first-user: '' 260 | 261 | # The release version to build images against. Valid values are jessie, stretch, 262 | # buster, bullseye, bookworm, trixie and testing. 263 | release: ${{ matrix.release }} 264 | 265 | # Setting to `1` will prevent pi-gen from dropping the "capabilities" feature. 266 | # Generating the root filesystem with capabilities enabled and running it from a 267 | # filesystem that does not support capabilities (like NFS) can cause issues. Only 268 | # enable this if you understand what it is. 269 | setfcap: '' 270 | 271 | # List of stage name to execute in given order. Relative and absolute paths to 272 | # custom stage directories are allowed here. Note that by default pi-gen exports 273 | # images in stage2 (lite), stage4 and stage5. You probably want to hook in custom 274 | # stages before one of the exported stages. Otherwise, the action will make sure 275 | # any custom stage will include an image export directive. 276 | stage-list: stage0_prep stage0 stage1 stage2 stage3_homebridge 277 | 278 | # An additional temporary apt repo to be used during the build process. This could 279 | # be useful if you require pre-release software to be included in the image. The 280 | # variable should contain sources in one-line-style format. "RELEASE" will be 281 | # replaced with the RELEASE variable. (see 282 | # https://manpages.debian.org/stable/apt/sources.list.5.en.html#ONE-LINE-STYLE_FORMAT) 283 | temp-repo: '' 284 | 285 | # System timezone. 286 | timezone: Europe/London 287 | 288 | # Name of the initial user account. 289 | username: pi 290 | 291 | # Print all output from pi-gen. 292 | verbose-output: true 293 | 294 | # Wifi country code of default network to connect to. 295 | wpa-country: '' 296 | 297 | # SSID of a default wifi network to connect to. 298 | wpa-essid: '' 299 | 300 | # Password of default wifi network to connect to. 301 | wpa-password: '' 302 | 303 | - name: Build complete 304 | run: | 305 | echo "::notice::Homebridge VM image version: Homebridge-${{ matrix.release }}-${{ matrix.name }}" 306 | echo "::notice::Run ID: ${{ github.run_id }}" 307 | echo "::notice::Architecture: ${{ matrix.name }}" 308 | echo "::notice::Release: ${{ needs.tag.outputs.version }}" 309 | echo "::notice::image-path: ${{ steps.build.outputs.image-path }}" 310 | IMGFILENAME=`basename ${{ steps.build.outputs.image-path }}` 311 | echo "IMGFILENAME=${IMGFILENAME}" >> $GITHUB_ENV 312 | echo "RPI_IMAGER_NAME: Homebridge ${{ matrix.release }} (${{ matrix.name }})" 313 | echo "RPI_IMAGER_DESCRIPTION: Official Homebridge Raspberry Pi Image ${{ matrix.release }} (${{ matrix.name }}) - ${{ matrix.recommended }}" 314 | echo "RPI_IMAGER_ICON: https://user-images.githubusercontent.com/3979615/116509191-3c35f880-a906-11eb-9a7f-7cad7c2aa641.png" 315 | echo "RPI_IMAGER_WEBSITE: https://github.com/homebridge/homebridge-raspbian-image/wiki/Getting-Started" 316 | echo "RPI_IMAGER_IMAGE_URL: https://github.com/${{ github.repository }}/releases/download/${{ needs.tag.outputs.version }}/${IMGFILENAME}" 317 | echo "RPI_IMAGER_DEVICES: '${{ matrix.devices }}'" 318 | 319 | # This is not currently used 320 | 321 | - name: Calculate Image Checksum 322 | id: get_sha256_checksum 323 | run: | 324 | export IMAGE_SHA256_CHECKSUM=$(shasum -a 256 ${{ steps.build.outputs.image-path }} | awk '{print $1}') 325 | echo "$IMAGE_SHA256_CHECKSUM ${{ steps.build.outputs.image-path }}" 326 | echo "::notice::IMAGE_SHA256_CHECKSUM ${IMGFILENAME} ==> ${IMAGE_SHA256_CHECKSUM}" 327 | 328 | - name: Prepare output directory 329 | run: | 330 | mkdir -p output 331 | cp ${{ steps.build.outputs.image-path }} output/ 332 | 333 | - name: Generate rpi-image-repo.json 334 | id: generate_rpi-image-repo 335 | run: | 336 | export RPI_IMAGER_NAME="Homebridge ${{ matrix.release }} (${{ matrix.name }})" 337 | export RPI_IMAGER_DESCRIPTION="Official Homebridge Raspberry Pi Image ${{ matrix.release }} (${{ matrix.name }}) - ${{ matrix.recommended }}" 338 | export RPI_IMAGER_ICON="https://user-images.githubusercontent.com/3979615/116509191-3c35f880-a906-11eb-9a7f-7cad7c2aa641.png" 339 | export RPI_IMAGER_WEBSITE="https://github.com/homebridge/homebridge-raspbian-image/wiki/Getting-Started" 340 | export RPI_IMAGER_IMAGE_URL="https://github.com/${{ github.repository }}/releases/download/${{ needs.tag.outputs.version }}/${{ env.IMGFILENAME }}" 341 | export RPI_IMAGER_DEVICES='${{ matrix.devices }}' 342 | ./make_rpi-imager-snipplet.py --rpi_imager_url ${RPI_IMAGER_IMAGE_URL} 343 | 344 | - name: Copy rpi-image-repo to output directory 345 | run: | 346 | cp pi-gen/deploy/rpi-image-repo.json output/rpi-image-repo-${{ matrix.name }}.json 347 | 348 | # Extract /opt/homebridge/*manifest from the image and upload to the release 349 | - name: Extract /opt/homebridge/*manifest from the image 350 | run: | 351 | # Unzip the image first 352 | unzip ${{ steps.build.outputs.image-path }} 353 | IMAGE_FILE=$(ls *.img | head -n1) 354 | echo "Found image: $IMAGE_FILE" 355 | 356 | mkdir mnt 357 | 358 | # Use losetup to automatically handle partition detection 359 | LOOP_DEVICE=$(sudo losetup -fP --show "$IMAGE_FILE") 360 | sudo mount ${LOOP_DEVICE}p2 mnt # p2 is usually the root partition 361 | 362 | ls -l mnt/opt/homebridge/ 363 | cp mnt/opt/homebridge/homebridge_raspbian_image*manifest output/ 364 | 365 | # Add extracted manifest(s) to the GitHub step summary (Markdown) 366 | for f in output/homebridge_raspbian_image*manifest; do 367 | if [ -f "$f" ]; then 368 | echo "## Extracted manifest: $(basename "$f")" >> $GITHUB_STEP_SUMMARY 369 | echo '' >> $GITHUB_STEP_SUMMARY 370 | echo '```' >> $GITHUB_STEP_SUMMARY 371 | sed -n '1,10000p' "$f" >> $GITHUB_STEP_SUMMARY 372 | echo '```' >> $GITHUB_STEP_SUMMARY 373 | fi 374 | done 375 | 376 | sudo umount mnt 377 | sudo losetup -d $LOOP_DEVICE 378 | rm -rf mnt 379 | 380 | - name: Build Release Body 381 | id: release_body 382 | run: | 383 | ./scripts/create-release-body.sh ${{ inputs.release-stream }} 384 | cat output/*manifest output/release-body.md > output/final-release-body.md 385 | env: 386 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 387 | 388 | - name: Upload Build Artifacts 389 | uses: actions/upload-artifact@v5 390 | with: 391 | retention-days: 7 392 | name: build-${{ matrix.name }} 393 | path: output/ 394 | 395 | trigger-stage-3: 396 | name: Trigger Release Stage 3 397 | needs: [tag, build_images] 398 | runs-on: ubuntu-latest 399 | steps: 400 | - name: Checkout repository 401 | uses: actions/checkout@v5 402 | 403 | - name: Trigger Release Stage 3 Workflow 404 | run: | 405 | gh workflow run release-stage-3_package_release.yml \ 406 | --ref latest \ 407 | --field run_id="${{ github.run_id }}" \ 408 | --field release_tag="${{ needs.tag.outputs.version }}" \ 409 | --field publish="${{ inputs.publish && 'true' || 'false' }}" 410 | env: 411 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 412 | 413 | - name: Workflow triggered 414 | run: | 415 | echo "::notice::Release Stage 3 workflow triggered for version ${{ needs.tag.outputs.version }}" 416 | echo "::notice::Run ID: ${{ github.run_id }}" 417 | echo "::notice::Publish to Registry: ${{ inputs.publish }}" 418 | -------------------------------------------------------------------------------- /stage3_homebridge/03-nginx/files/custom_502.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 502 - Homebridge Starting... 8 | 308 | 309 | 345 | 346 | 347 | 348 | 349 |
350 |
351 |
352 | 371 |
372 |

Homebridge

373 |

Starting now...

374 |
375 |
376 |
377 |
378 | 379 |
380 |
381 |
382 | 383 | 501 | 502 | 503 | 504 | -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/hb-config-new: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | INTERACTIVE=True 4 | ASK_TO_REBOOT=0 5 | CONFIG=/boot/config.txt 6 | 7 | RED='\033[0;31m' 8 | GREEN='\033[0;32m' 9 | BOLD='\e[1m' 10 | NC='\033[0m' 11 | 12 | is_pi () { 13 | ARCH=$(dpkg --print-architecture) 14 | if [ "$ARCH" = "armhf" ] || [ "$ARCH" = "arm64" ] ; then 15 | return 0 16 | else 17 | return 1 18 | fi 19 | } 20 | 21 | is_pione() { 22 | if grep -q "^Revision\s*:\s*00[0-9a-fA-F][0-9a-fA-F]$" /proc/cpuinfo; then 23 | return 0 24 | elif grep -q "^Revision\s*:\s*[ 123][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]0[0-36][0-9a-fA-F]$" /proc/cpuinfo ; then 25 | return 0 26 | else 27 | return 1 28 | fi 29 | } 30 | 31 | is_pitwo() { 32 | grep -q "^Revision\s*:\s*[ 123][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]04[0-9a-fA-F]$" /proc/cpuinfo 33 | return $? 34 | } 35 | 36 | is_pizero() { 37 | grep -q "^Revision\s*:\s*[ 123][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]0[9cC][0-9a-fA-F]$" /proc/cpuinfo 38 | return $? 39 | } 40 | 41 | is_pifour() { 42 | grep -q "^Revision\s*:\s*[ 123][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]11[0-9a-fA-F]$" /proc/cpuinfo 43 | return $? 44 | } 45 | 46 | get_pi_type() { 47 | if is_pione; then 48 | echo 1 49 | elif is_pitwo; then 50 | echo 2 51 | else 52 | echo 0 53 | fi 54 | } 55 | 56 | is_live() { 57 | grep -q "boot=live" $CMDLINE 58 | return $? 59 | } 60 | 61 | is_ssh() { 62 | if pstree -p | egrep --quiet --extended-regexp ".*sshd.*\($$\)"; then 63 | return 0 64 | else 65 | return 1 66 | fi 67 | } 68 | 69 | is_fkms() { 70 | if grep -q okay /proc/device-tree/soc/v3d@7ec00000/status 2> /dev/null || grep -q okay /proc/device-tree/soc/firmwarekms@7e600000/status 2> /dev/null ; then 71 | return 0 72 | else 73 | return 1 74 | fi 75 | } 76 | 77 | is_installed() { 78 | if [ "$(dpkg -l "$1" 2> /dev/null | tail -n 1 | cut -d ' ' -f 1)" != "ii" ]; then 79 | return 1 80 | else 81 | return 0 82 | fi 83 | } 84 | 85 | deb_ver () { 86 | ver=`cat /etc/debian_version | cut -d . -f 1` 87 | echo $ver 88 | } 89 | 90 | calc_wt_size() { 91 | # NOTE: it's tempting to redirect stderr to /dev/null, so supress error 92 | # output from tput. However in this case, tput detects neither stdout or 93 | # stderr is a tty and so only gives default 80, 24 values 94 | WT_HEIGHT=17 95 | WT_WIDTH=$(tput cols) 96 | 97 | if [ -z "$WT_WIDTH" ] || [ "$WT_WIDTH" -lt 60 ]; then 98 | WT_WIDTH=80 99 | fi 100 | if [ "$WT_WIDTH" -gt 178 ]; then 101 | WT_WIDTH=120 102 | fi 103 | WT_MENU_HEIGHT=$(($WT_HEIGHT-7)) 104 | } 105 | 106 | set_config_var() { 107 | lua - "$1" "$2" "$3" < "$3.bak" 108 | local key=assert(arg[1]) 109 | local value=assert(arg[2]) 110 | local fn=assert(arg[3]) 111 | local file=assert(io.open(fn)) 112 | local made_change=false 113 | for line in file:lines() do 114 | if line:match("^#?%s*"..key.."=.*$") then 115 | line=key.."="..value 116 | made_change=true 117 | end 118 | print(line) 119 | end 120 | if not made_change then 121 | print(key.."="..value) 122 | end 123 | EOF 124 | mv "$3.bak" "$3" 125 | } 126 | 127 | get_web_addresses() { 128 | IP=$(hostname -I) 129 | 130 | printf " ${BOLD}*${NC} ${GREEN}$1://$(hostname).local:$2$3${NC}\n" 131 | 132 | for ip in $IP; do 133 | if expr "$ip" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; then 134 | printf " ${BOLD}*${NC} ${GREEN}$1://$ip:$2$3${NC}\n" 135 | else 136 | printf " ${BOLD}*${NC} ${GREEN}$1://[$ip]:$2$3${NC}\n" 137 | fi 138 | done 139 | } 140 | 141 | get_ip_and_port() { 142 | IP=$(hostname -I) 143 | 144 | printf " ${BOLD}*${NC} ${GREEN}$(hostname).local:$1$2${NC}\n" 145 | 146 | for ip in $IP; do 147 | if expr "$ip" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; then 148 | printf " ${BOLD}*${NC} ${GREEN}$ip:$1$2${NC}\n" 149 | else 150 | printf " ${BOLD}*${NC} ${GREEN}[$ip]:$1$2${NC}\n" 151 | fi 152 | done 153 | } 154 | 155 | do_finish() { 156 | if [ $ASK_TO_REBOOT -eq 1 ]; then 157 | whiptail --yesno "Would you like to reboot now?" 20 60 2 158 | if [ $? -eq 0 ]; then # yes 159 | sync 160 | reboot 161 | fi 162 | fi 163 | exit 0 164 | } 165 | 166 | get_first_user() { 167 | printf "$(getent passwd 1000 | cut -d: -f1)" 168 | } 169 | 170 | do_homebridge_update() { 171 | pstree -s $$ | grep "hb-service" > /dev/null 172 | if [ "$?" -eq 0 ]; then 173 | whiptail --title "Error" --msgbox "You cannot run this command from the Homebridge UI Terminal.\n\nPlease connect using SSH (see wiki for instructions) and try again." 20 60 1 174 | printf "\n" 175 | return 176 | fi 177 | 178 | curl -sfL https://repo.homebridge.io/KEY.gpg | sudo gpg --dearmor | sudo tee /usr/share/keyrings/homebridge.gpg > /dev/null 179 | echo "deb [signed-by=/usr/share/keyrings/homebridge.gpg] https://repo.homebridge.io stable main" | sudo tee /etc/apt/sources.list.d/homebridge.list > /dev/null 180 | apt-get update --allow-releaseinfo-change 181 | apt-get install -y homebridge 182 | } 183 | 184 | do_restore_config() { 185 | pstree -s $$ | grep "hb-service" > /dev/null 186 | if [ "$?" -eq 0 ]; then 187 | whiptail --title "Error" --msgbox "You cannot run this command from the Homebridge UI Terminal.\n\nPlease connect using SSH (see wiki for instructions) and try again." 20 60 1 188 | printf "\n" 189 | return 190 | fi 191 | 192 | whiptail --yesno "This will remove all custom Homebridge config, plugins and restore to the factory default. You will need to manually remove any existing connections to this instance in the iOS Home app.\n\nAre you sure you want to proceed?" 20 60 2 193 | RET=$? 194 | if [ $RET -eq 0 ]; then 195 | apt-get purge -y homebridge 196 | do_homebridge_update 197 | fi 198 | } 199 | 200 | do_nodejs_update() { 201 | hb-service update-node 202 | exit 0 203 | } 204 | 205 | do_install_pihole() { 206 | pstree -s $$ | grep "hb-service" > /dev/null 207 | if [ "$?" -eq 0 ]; then 208 | whiptail --title "Error" --msgbox "You must not install Pi Hole via the Homebridge UI Terminal.\n\nFor a successful installation of Pi-Hole you must be logged in using SSH (see wiki for instructions)." 20 60 1 209 | printf "\n" 210 | return 211 | fi 212 | 213 | whiptail --title "Warning" --yesno "Installing Pi-Hole will prevent you from accessing the Homebridge UI on port 80.\n\nYou will still be able to access the Homebridge UI on port $(cat /etc/hb-ui-port) and via:\n\n* http://homebridge.local:$(cat /etc/hb-ui-port)\n* https://homebridge.local\n\nWARNING: Failing to configure Pi-Hole correctly or attempting to uninstall Pi-Hole may result in Homebridge not working correctly. You should take a backup of your Homebridge instance via the UI before proceeding.\n\nAre you sure you want to continue?" 20 60 1 214 | RET=$? 215 | if [ $RET -eq 0 ]; then 216 | if [ -f /etc/nginx/sites-available/homebridge.local ]; then 217 | sed -i "/listen 80;/c\ # listen 80; # http IPv4 - disabled by Pi-Hole installer" /etc/nginx/sites-available/homebridge.local 218 | sed -i "/listen \[::\]:80;/c\ # listen \[::\]:80; # http IPv6 - disabled by Pi-Hole installer" /etc/nginx/sites-available/homebridge.local 219 | if systemctl is-active --quiet nginx.service; then 220 | echo "Restarting Nginx..." 221 | systemctl restart nginx 222 | fi 223 | fi 224 | printf "Starting Pi-hole installer...\n" 225 | curl -sSL https://install.pi-hole.net | bash 226 | 227 | printf "\n${BOLD}Pi-hole has been installed${NC}\n" 228 | printf "Access Pi-hole in your browser by going to:\n" 229 | get_web_addresses http 80 /admin 230 | 231 | printf "\nYou can still access the Homebridge UI by going to:\n" 232 | get_web_addresses http $(cat /etc/hb-ui-port) 233 | get_web_addresses https 443 234 | printf "\n" 235 | exit 0 236 | fi 237 | } 238 | 239 | do_install_adguard_home() { 240 | if [ -e /etc/systemd/system/AdGuardHome.service ]; then 241 | whiptail --title "Remove AdGuard Home" --yesno "AdGuard Home is already installed.\n\nWould you like to UNINSTALL AdGuard Home?" 20 60 1 242 | RET=$? 243 | if [ $RET -eq 0 ]; then 244 | systemctl stop AdGuardHome 245 | systemctl disable AdGuardHome 246 | rm -rf /etc/systemd/system/AdGuardHome.service 247 | rm -rf /opt/AdGuardHome 248 | systemctl daemon-reload 249 | whiptail --title "Success" --msgbox "AdGuard Home has been uninstalled." 20 60 1 250 | printf "\n" 251 | fi 252 | else 253 | whiptail --title "Warning" --yesno "This will install the AdGuard Home software which uses the following ports:\n\n* 3000\n\nAre you sure you want to continue?" 20 60 1 254 | RET=$? 255 | if [ $RET -eq 0 ]; then 256 | rm -rf /tmp/adguardhome 257 | mkdir -p /tmp/adguardhome 258 | cd /tmp/adguardhome 259 | wget https://static.adguard.com/adguardhome/release/AdGuardHome_linux_armv6.tar.gz 260 | tar xvf AdGuardHome_linux_armv6.tar.gz -C /opt 261 | rm -rf /tmp/adguardhome 262 | cd /opt/AdGuardHome 263 | ./AdGuardHome -s install 264 | if [ $? -eq 0 ]; then 265 | # Complete 266 | ADGUARD_WEB=$(get_web_addresses http 3000 | sed 's/\x1b\[[0-9;]*m//g') 267 | whiptail --title "Success" --msgbox "AdGuard Home has been installed.\n\nFinish setting up AdGuard Home via:\n\n$ADGUARD_WEB" 20 60 1 268 | printf "\n" 269 | exit 0 270 | else 271 | whiptail --title "Error" --msgbox "AdGuard Home failed to install correctly. Review log above." 20 60 1 272 | printf "\n" 273 | exit 0 274 | fi 275 | fi 276 | fi 277 | } 278 | 279 | do_install_deconz() { 280 | whiptail --title "Warning" --yesno "This will install the deCONZ and Phoscon software which will use the following ports:\n\n* 4530 - Phoscon Web App\n* 4531 - WebSockets\n\nAre you sure you want to continue?" 20 60 1 281 | RET=$? 282 | if [ $RET -eq 0 ]; then 283 | isRaspbee="0" 284 | isRaspbee2="0" 285 | 286 | do_raspbee_setup() { 287 | echo "Setting up for RaspBee..." 288 | isRaspbee="1" 289 | 290 | # Install deps 291 | apt update --allow-releaseinfo-change 292 | sudo apt install -y i2c-tools build-essential raspberrypi-kernel-headers 293 | 294 | # WiringPi (Pi4 Only) 295 | if [ is_pifour ]; then 296 | curl -o /tmp/wiringpi-latest.deb https://project-downloads.drogon.net/wiringpi-latest.deb 297 | dpkg -i /tmp/wiringpi-latest.deb 298 | rm -rf /tmp/wiringpi-latest.deb 299 | else 300 | apt install wiringpi 301 | fi 302 | 303 | # Enable UART 304 | set_config_var enable_uart 1 $CONFIG 305 | } 306 | 307 | do_raspbee2_setup() { 308 | isRaspbee2="1" 309 | do_raspbee_setup 310 | 311 | # RTC-Installation 312 | [ -d /tmp/raspbee2-rtc-master ] && rm -rf /tmp/raspbee2-rtc-master 313 | curl -o /tmp/raspbee2-rtc.zip -L# https://github.com/dresden-elektronik/raspbee2-rtc/archive/master.zip 314 | unzip /tmp/raspbee2-rtc.zip -d /tmp 315 | cd /tmp/raspbee2-rtc-master 316 | make 317 | make install 318 | cd ~ 319 | rm -rf /tmp/raspbee2-rtc-master 320 | } 321 | 322 | do_conbee_setup() { 323 | echo "Setting up for ConBee / ConBee II..." 324 | isRaspbee="0" 325 | } 326 | 327 | FUN=$(whiptail --title "Choose Device" --menu "\nPlease select the type of device you have:" 20 60 3 --cancel-button Cancel --ok-button Select \ 328 | "C1 ConBee or ConBee II" "" \ 329 | "C2 RaspBee I" "" \ 330 | "C3 RaspBee II" "" \ 331 | 3>&1 1>&2 2>&3) 332 | RET=$? 333 | if [ $RET -eq 1 ]; then 334 | return 0 335 | elif [ $RET -eq 0 ]; then 336 | case "$FUN" in 337 | C1\ *) do_conbee_setup ;; 338 | C2\ *) do_raspbee_setup ;; 339 | C3\ *) do_raspbee2_setup ;; 340 | *) whiptail --msgbox "Programmer error: unrecognized option" 20 60 1 ;; 341 | esac || whiptail --msgbox "There was an error running option $FUN" 20 60 1 342 | fi 343 | 344 | # Detect Debian version 345 | if [ -f /etc/os-release ]; then 346 | . /etc/os-release 347 | DEBIAN_VERSION=$(echo $VERSION_ID | cut -d. -f1) 348 | DEBIAN_CODENAME=$(lsb_release -cs) 349 | else 350 | whiptail --title "Error" --msgbox "Cannot detect OS version" 20 60 1 351 | return 1 352 | fi 353 | 354 | echo "Detected Debian version: $DEBIAN_VERSION ($DEBIAN_CODENAME)" 355 | 356 | # Determine which repository to use 357 | if [ "$DEBIAN_VERSION" -ge 13 ]; then 358 | echo "Debian 13+ detected - using bookworm repository as fallback" 359 | REPO_CODENAME="bookworm" 360 | USE_MODERN_METHOD=true 361 | elif [ "$DEBIAN_VERSION" -ge 12 ]; then 362 | echo "Debian 12 detected" 363 | REPO_CODENAME="bookworm" 364 | USE_MODERN_METHOD=true 365 | else 366 | echo "Debian < 12 detected" 367 | REPO_CODENAME="${DEBIAN_CODENAME}" 368 | USE_MODERN_METHOD=false 369 | fi 370 | 371 | if [ "$USE_MODERN_METHOD" = true ]; then 372 | echo "Using modern keyring method" 373 | 374 | # Create keyrings directory if it doesn't exist 375 | mkdir -p /etc/apt/keyrings 376 | 377 | # Download and install the deCONZ GPG key 378 | wget -O - http://phoscon.de/apt/deconz.pub.key | gpg --dearmor -o /etc/apt/keyrings/deconz.gpg 379 | 380 | # Configure the APT repository with signed-by 381 | sh -c "echo 'deb [signed-by=/etc/apt/keyrings/deconz.gpg] http://phoscon.de/apt/deconz ${REPO_CODENAME} main' > /etc/apt/sources.list.d/deconz.list" 382 | else 383 | echo "Using legacy apt-key method" 384 | 385 | # Legacy method with apt-key 386 | wget -O - http://phoscon.de/apt/deconz.pub.key | apt-key add - 387 | 388 | # Configure the APT repository 389 | sh -c "echo 'deb http://phoscon.de/apt/deconz ${REPO_CODENAME} main' > /etc/apt/sources.list.d/deconz.list" 390 | fi 391 | 392 | # Update APT package list 393 | echo "Updating package lists..." 394 | apt update --allow-releaseinfo-change 395 | 396 | # Install deCONZ 397 | echo "Installing deCONZ..." 398 | apt install -y deconz 399 | 400 | # Add override service 401 | mkdir -p /etc/systemd/system/deconz.service.d 402 | cat > /etc/systemd/system/deconz.service.d/override.conf < /etc/systemd/system/vncserver-pi.service < /dev/null 2>&1 484 | #ExecStart=/usr/bin/vncserver -depth 24 -geometry 1280x800 :12 485 | #ExecStop=/usr/bin/vncserver -kill :12 486 | 487 | #[Install] 488 | #WantedBy=multi-user.target 489 | #EOL 490 | systemctl enable vncserver-x11-serviced.service && 491 | systemctl start vncserver-x11-serviced.service && 492 | # systemctl enable vncserver-pi.service && 493 | # systemctl start vncserver-pi.service && 494 | STATUS=enabled 495 | else 496 | return 1 497 | fi 498 | elif [ $RET -eq 1 ]; then 499 | if is_installed realvnc-vnc-server; then 500 | systemctl disable vncserver-x11-serviced.service 501 | systemctl stop vncserver-x11-serviced.service 502 | # systemctl disable vncserver-pi.service 503 | # systemctl stop vncserver-pi.service 504 | fi 505 | STATUS=disabled 506 | else 507 | return $RET 508 | fi 509 | 510 | if [ "$STATUS" = "disabled" ]; then 511 | whiptail --title "Success" --msgbox "The VNC Server and virtual desktop have been disabled." 20 60 1 512 | else 513 | # Complete 514 | VNC_ACCESS_URI=$(get_ip_and_port 5912 | sed 's/\x1b\[[0-9;]*m//g') 515 | whiptail --title "Success" --msgbox "The VNC Server and virtual desktop have been configured.\n\nYou can connect to the virtual desktop using the RealVNC Viewer on port 5912:\n\n$VNC_ACCESS_URI\n\nDownload RealVNC Viewer:\n\n * https://www.realvnc.com/download/viewer/" 20 60 1 516 | fi 517 | } 518 | 519 | do_vnc 520 | } 521 | 522 | do_extra_packages() { 523 | FUN=$(whiptail --title "Homebridge Raspberry Pi Configuration Tool (hb-config)" --menu "Advanced Options" $WT_HEIGHT $WT_WIDTH $WT_MENU_HEIGHT --cancel-button Back --ok-button Select \ 524 | "A0 AdGuard Home" "Install / Uninstall AdGuard Home" \ 525 | "A1 Pi-hole" "Install / Reconfigure Pi-hole" \ 526 | "A2 deCONZ / Phoscon" "Install / Update to manage your ConBee or RaspBee device" \ 527 | 3>&1 1>&2 2>&3) 528 | # "A3 RealVNC Server" "Enable / Disable desktop access over VNC" \ 529 | RET=$? 530 | if [ $RET -eq 1 ]; then 531 | return 0 532 | elif [ $RET -eq 0 ]; then 533 | case "$FUN" in 534 | A0\ *) do_install_adguard_home ;; 535 | A1\ *) do_install_pihole ;; 536 | A2\ *) do_install_deconz ;; 537 | # A3\ *) do_install_desktop_and_vnc ;; 538 | *) whiptail --msgbox "Programmer error: unrecognized option" 20 60 1 ;; 539 | esac || whiptail --msgbox "There was an error running option $FUN" 20 60 1 540 | fi 541 | } 542 | 543 | do_configure_nginx() { 544 | whiptail --title "Warning" --yesno "Using this tool will overwrite any manual changes you may have made to the Homebridge Nginx config file.\n\nAre you sure you wish to continue?" 20 60 1 545 | RET=$? 546 | tempDir=$(mktemp -d) 547 | if [ $RET -eq 0 ]; then 548 | whiptail --title "Homebridge Raspberry Pi Configuration Tool (hb-config)" --separate-output --checklist "\nNGINX Options:" 20 80 5 --cancel-button Cancel --ok-button Save \ 549 | "1" "Listen on port 80 - http:// " 1 \ 550 | "2" "Listen on port 443 - https:// " 1 \ 551 | "3" "Redirect http:// to https:// " 0 2>$tempDir/results 552 | 553 | if [ $? -eq 0 ]; then 554 | LISTEN_ON_PORT_80=false 555 | LISTEN_ON_PORT_443=false 556 | REDIRECT_HTTP_TO_HTTPS=false 557 | 558 | while read choice 559 | do 560 | case "$choice" in 561 | 1) LISTEN_ON_PORT_80=true ;; 562 | 2) LISTEN_ON_PORT_443=true ;; 563 | 3) REDIRECT_HTTP_TO_HTTPS=true ;; 564 | *) ;; 565 | esac 566 | done < $tempDir/results 567 | 568 | NGINX_PORT_80_CONFIG="" 569 | if [ "$LISTEN_ON_PORT_80" = "true" ]; then 570 | NGINX_PORT_80_CONFIG="# http://\n listen 80; # http IPv4\n listen [::]:80; # http IPv6\n" 571 | fi 572 | 573 | NGINX_PORT_443_CONFIG="" 574 | if [ "$LISTEN_ON_PORT_443" = "true" ]; then 575 | NGINX_PORT_443_CONFIG="# https://\n listen 443 ssl http2; # https IPv4\n listen [::]:443 ssl http2; # https IPv6\n" 576 | fi 577 | 578 | NGINX_REDIRECT_HTTP_TO_HTTPS="" 579 | if [ "$REDIRECT_HTTP_TO_HTTPS" = "true" ] && [ "$LISTEN_ON_PORT_80" = "true" ]; then 580 | NGINX_PORT_80_CONFIG="" 581 | NGINX_REDIRECT_HTTP_TO_HTTPS=" 582 | server { 583 | listen 80; # http IPv4 584 | listen [::]:80; # http IPv6 585 | 586 | # replace the _ with your domain name if required 587 | # eg. 588 | # server_name example.com; 589 | server_name _; 590 | 591 | return 302 https://\$host\$request_uri; 592 | }" 593 | fi 594 | 595 | NGINX_TEMPLATE="# this file was generated using the hb-config tool 596 | 597 | server { 598 | $NGINX_PORT_80_CONFIG 599 | $NGINX_PORT_443_CONFIG 600 | # replace the _ with your domain name if required 601 | # eg. 602 | # server_name example.com; 603 | server_name _; # replace with your domain 604 | 605 | # path to ssl certificate file 606 | # If using Let's Encrypt replace this path with the location of the fullchain.pem file for your domain 607 | # eg. 608 | # ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; 609 | ssl_certificate /etc/nginx/ssl/homebridge.local.crt; 610 | 611 | # path to ssl private key file 612 | # If using Let's Encrypt replace this path with the location of the privkey.pem file for your domain 613 | # eg. 614 | # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; 615 | ssl_certificate_key /etc/nginx/ssl/homebridge.local.key; 616 | 617 | ssl_session_cache builtin:1000 shared:SSL:10m; 618 | ssl_protocols TLSv1.2 TLSv1.3; 619 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 620 | ssl_prefer_server_ciphers off; 621 | # ssl_stapling on; 622 | # ssl_stapling_verify on; 623 | 624 | # large upload size to allow for uploading 3rd party backup files 625 | client_max_body_size 2G; 626 | 627 | error_page 502 /custom_502.html; 628 | 629 | location /startup-status.json { 630 | alias /usr/share/nginx/html/status.json; 631 | } 632 | 633 | location / { 634 | proxy_pass http://127.0.0.1:$(cat /etc/hb-ui-port); 635 | proxy_http_version 1.1; 636 | proxy_buffering off; 637 | proxy_set_header Host \$host; 638 | proxy_set_header Upgrade \$http_upgrade; 639 | proxy_set_header Connection \"Upgrade\"; 640 | proxy_set_header X-Real-IP \$remote_addr; 641 | proxy_set_header X-Forward-For \$proxy_add_x_forwarded_for; 642 | } 643 | 644 | location = /custom_502.html { 645 | root /usr/share/nginx/html; 646 | internal; 647 | } 648 | } 649 | $NGINX_REDIRECT_HTTP_TO_HTTPS 650 | " 651 | 652 | echo "$NGINX_TEMPLATE" > /etc/nginx/sites-available/homebridge.local 653 | /usr/local/sbin/nginx-homebridge-self-signed-cert 654 | systemctl enable nginx 655 | 656 | if systemctl is-active --quiet nginx.service; then 657 | echo "Restarting Nginx..." 658 | systemctl restart nginx 659 | else 660 | echo "Starting Nginx..." 661 | systemctl start nginx 662 | fi 663 | 664 | if [ $? -eq 0 ]; then 665 | whiptail --title "Success" --msgbox "NGINX has been configured successfully." 20 60 1 666 | else 667 | whiptail --title "Error" --msgbox "An error occured while attempting to reload the NGINX config." 20 60 1 668 | fi 669 | 670 | fi 671 | fi 672 | } 673 | 674 | do_configure_raspbian() { 675 | raspi-config 676 | } 677 | 678 | do_update() { 679 | tempDir=$(mktemp -d) 680 | scriptUrl=https://raw.githubusercontent.com/homebridge/homebridge-raspbian-image/latest/stage3_homebridge/01-homebridge/files/hb-config-new 681 | 682 | # download the update to a temporary directory 683 | scriptUrl=$scriptUrl?$(date --iso-8601) 684 | printf "Downloading hb-config update...\n$scriptUrl\n" 685 | curl -fL# $scriptUrl > $tempDir/hb-config 686 | 687 | # check the download was a success 688 | RET=$? 689 | if [ $RET -ne 0 ]; then 690 | whiptail --title "Error" --msgbox "An error occured while attempting to update the hb-config tool." 20 60 1 691 | else 692 | # syntax check the script is a valid bash script 693 | if bash -n $tempDir/hb-config; then 694 | chmod +x $tempDir/hb-config 695 | mv $tempDir/hb-config /usr/local/sbin/hb-config 696 | whiptail --title "Success" --msgbox "The hb-config tool has been updated and will now exit.\n\nIf you like the Homebridge Raspberry Pi image please show your support by starring the project on GitHub:\n\nhttps://github.com/homebridge/homebridge-raspbian-image" 20 60 1 697 | exit 0 698 | else 699 | whiptail --title "Error" --msgbox "The downloaded script was corrupted and did not pass the syntax check." 20 60 1 700 | fi 701 | fi 702 | } 703 | 704 | do_nmtui() { 705 | if systemctl is-active --quiet NetworkManager; then 706 | nmtui 707 | else 708 | whiptail --title "Error" --msgbox "These network options not available when WiFi was configured via the Raspberry Pi Imager tool.\n\nSee https://www.raspberrypi.com/documentation/computers/configuration.html#wireless-networking-command-line" 20 60 1 709 | fi 710 | } 711 | 712 | do_about() { 713 | IMAGE_VERSION=$(cat /etc/hb-release) 714 | 715 | whiptail --title "About" --msgbox "\ 716 | This tool provides a straight-forward way of managing Homebridge on a Raspberry Pi. Although it can be run at any time, some of the options may have difficulties if you have heavily customised your installation. 717 | 718 | Image Version: $IMAGE_VERSION 719 | 720 | Homebridge - @homebridge 721 | https://github.com/homebridge/homebridge 722 | 723 | Homebridge Config UI X - @homebridge 724 | https://github.com/homebridge/homebridge-config-ui-x 725 | 726 | Homebridge Raspberry Pi Image - @homebridge 727 | https://github.com/homebridge/homebridge-raspbian-image 728 | 729 | 730 | \ 731 | " 20 80 1 732 | } 733 | 734 | # Everything else needs to be run as root 735 | if [ $(id -u) -ne 0 ]; then 736 | printf "Script must be run as root. Try 'sudo hb-config'\n" 737 | exit 1 738 | fi 739 | 740 | # 741 | # Interactive use loop 742 | # 743 | if [ "$INTERACTIVE" = True ]; then 744 | [ -e $CONFIG ] || touch $CONFIG 745 | calc_wt_size 746 | while true; do 747 | if is_pi ; then 748 | FUN=$(whiptail --title "Homebridge Raspberry Pi Image Configuration Tool (hb-config)" --backtitle "$(cat /proc/device-tree/model)" --menu "Setup Options" $WT_HEIGHT $WT_WIDTH $WT_MENU_HEIGHT --cancel-button Finish --ok-button Select \ 749 | "1 Update Node.js" "Update Node.js to the latest LTS version" \ 750 | "2 Update Homebridge" "Update Homebridge and Node.js to the latest version" \ 751 | "3 Restore Config" "Restores the Homebridge config to the factory default" \ 752 | "4 Extra Packages" "Optional packages, eg. Pi-Hole, RealVNC" \ 753 | "5 Nginx Options" "Configure Homebridge Nginx settings" \ 754 | "6 Configure OS" "Open the Raspberry Pi OS Configuration Tool" \ 755 | "7 Networking" "Open the NetworkManager UI" \ 756 | "8 Update" "Update this tool to the latest version" \ 757 | "9 About" "Information about this configuration tool" \ 758 | 3>&1 1>&2 2>&3) 759 | else 760 | FUN=$(whiptail --title "Homebridge Raspberry Pi Image Configuration Tool (hb-config)" --menu "Setup Options" $WT_HEIGHT $WT_WIDTH $WT_MENU_HEIGHT --cancel-button Finish --ok-button Select \ 761 | "1 Update Node.js" "Update Node.js to the latest LTS version" \ 762 | "2 Update Homebridge" "Update Homebridge and Node.js to the latest version" \ 763 | "3 Restore Config" "Restores the Homebridge config to the factory default" \ 764 | "4 Extra Packages" "Optional packages, eg. Pi-Hole, RealVNC" \ 765 | "5 Nginx Options" "Configure Homebridge Nginx settings" \ 766 | "6 Configure OS" "Open the Raspberry Pi OS Configuration Tool" \ 767 | "7 Networking" "Open the NetworkManager UI" \ 768 | "8 Update" "Update this tool to the latest version" \ 769 | "9 About" "Information about this configuration tool" \ 770 | 3>&1 1>&2 2>&3) 771 | fi 772 | RET=$? 773 | if [ $RET -eq 1 ]; then 774 | do_finish 775 | elif [ $RET -eq 0 ]; then 776 | if is_pi ; then 777 | case "$FUN" in 778 | 1\ *) do_nodejs_update ;; 779 | 2\ *) do_homebridge_update ;; 780 | 3\ *) do_restore_config ;; 781 | 4\ *) do_extra_packages ;; 782 | 5\ *) do_configure_nginx ;; 783 | 6\ *) do_configure_raspbian ;; 784 | 7\ *) do_nmtui ;; 785 | 8\ *) do_update ;; 786 | 9\ *) do_about ;; 787 | *) whiptail --msgbox "Programmer error: unrecognized option" 20 60 1 ;; 788 | esac || whiptail --msgbox "There was an error running option $FUN" 20 60 1 789 | else 790 | case "$FUN" in 791 | 1\ *) do_nodejs_update ;; 792 | 2\ *) do_homebridge_update ;; 793 | 3\ *) do_restore_config ;; 794 | 4\ *) do_extra_packages ;; 795 | 5\ *) do_configure_nginx ;; 796 | 6\ *) do_configure_raspbian ;; 797 | 7\ *) do_nmtui ;; 798 | 8\ *) do_update ;; 799 | 9\ *) do_about ;; 800 | *) whiptail --msgbox "Programmer error: unrecognized option" 20 60 1 ;; 801 | esac || whiptail --msgbox "There was an error running option $FUN" 20 60 1 802 | fi 803 | else 804 | exit 1 805 | fi 806 | done 807 | fi 808 | -------------------------------------------------------------------------------- /stage3_homebridge/01-homebridge/files/hb-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | INTERACTIVE=True 4 | ASK_TO_REBOOT=0 5 | CONFIG=/boot/config.txt 6 | 7 | RED='\033[0;31m' 8 | GREEN='\033[0;32m' 9 | BOLD='\e[1m' 10 | NC='\033[0m' 11 | 12 | is_pi () { 13 | ARCH=$(dpkg --print-architecture) 14 | if [ "$ARCH" = "armhf" ] || [ "$ARCH" = "arm64" ] ; then 15 | return 0 16 | else 17 | return 1 18 | fi 19 | } 20 | 21 | is_pione() { 22 | if grep -q "^Revision\s*:\s*00[0-9a-fA-F][0-9a-fA-F]$" /proc/cpuinfo; then 23 | return 0 24 | elif grep -q "^Revision\s*:\s*[ 123][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]0[0-36][0-9a-fA-F]$" /proc/cpuinfo ; then 25 | return 0 26 | else 27 | return 1 28 | fi 29 | } 30 | 31 | is_pitwo() { 32 | grep -q "^Revision\s*:\s*[ 123][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]04[0-9a-fA-F]$" /proc/cpuinfo 33 | return $? 34 | } 35 | 36 | is_pizero() { 37 | grep -q "^Revision\s*:\s*[ 123][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]0[9cC][0-9a-fA-F]$" /proc/cpuinfo 38 | return $? 39 | } 40 | 41 | is_pifour() { 42 | grep -q "^Revision\s*:\s*[ 123][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]11[0-9a-fA-F]$" /proc/cpuinfo 43 | return $? 44 | } 45 | 46 | get_pi_type() { 47 | if is_pione; then 48 | echo 1 49 | elif is_pitwo; then 50 | echo 2 51 | else 52 | echo 0 53 | fi 54 | } 55 | 56 | is_live() { 57 | grep -q "boot=live" $CMDLINE 58 | return $? 59 | } 60 | 61 | is_ssh() { 62 | if pstree -p | egrep --quiet --extended-regexp ".*sshd.*\($$\)"; then 63 | return 0 64 | else 65 | return 1 66 | fi 67 | } 68 | 69 | is_fkms() { 70 | if grep -q okay /proc/device-tree/soc/v3d@7ec00000/status 2> /dev/null || grep -q okay /proc/device-tree/soc/firmwarekms@7e600000/status 2> /dev/null ; then 71 | return 0 72 | else 73 | return 1 74 | fi 75 | } 76 | 77 | is_installed() { 78 | if [ "$(dpkg -l "$1" 2> /dev/null | tail -n 1 | cut -d ' ' -f 1)" != "ii" ]; then 79 | return 1 80 | else 81 | return 0 82 | fi 83 | } 84 | 85 | deb_ver () { 86 | ver=`cat /etc/debian_version | cut -d . -f 1` 87 | echo $ver 88 | } 89 | 90 | calc_wt_size() { 91 | # NOTE: it's tempting to redirect stderr to /dev/null, so supress error 92 | # output from tput. However in this case, tput detects neither stdout or 93 | # stderr is a tty and so only gives default 80, 24 values 94 | WT_HEIGHT=17 95 | WT_WIDTH=$(tput cols) 96 | 97 | if [ -z "$WT_WIDTH" ] || [ "$WT_WIDTH" -lt 60 ]; then 98 | WT_WIDTH=80 99 | fi 100 | if [ "$WT_WIDTH" -gt 178 ]; then 101 | WT_WIDTH=120 102 | fi 103 | WT_MENU_HEIGHT=$(($WT_HEIGHT-7)) 104 | } 105 | 106 | set_config_var() { 107 | lua - "$1" "$2" "$3" < "$3.bak" 108 | local key=assert(arg[1]) 109 | local value=assert(arg[2]) 110 | local fn=assert(arg[3]) 111 | local file=assert(io.open(fn)) 112 | local made_change=false 113 | for line in file:lines() do 114 | if line:match("^#?%s*"..key.."=.*$") then 115 | line=key.."="..value 116 | made_change=true 117 | end 118 | print(line) 119 | end 120 | if not made_change then 121 | print(key.."="..value) 122 | end 123 | EOF 124 | mv "$3.bak" "$3" 125 | } 126 | 127 | get_web_addresses() { 128 | IP=$(hostname -I) 129 | 130 | printf " ${BOLD}*${NC} ${GREEN}$1://$(hostname).local:$2$3${NC}\n" 131 | 132 | for ip in $IP; do 133 | if expr "$ip" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; then 134 | printf " ${BOLD}*${NC} ${GREEN}$1://$ip:$2$3${NC}\n" 135 | else 136 | printf " ${BOLD}*${NC} ${GREEN}$1://[$ip]:$2$3${NC}\n" 137 | fi 138 | done 139 | } 140 | 141 | get_ip_and_port() { 142 | IP=$(hostname -I) 143 | 144 | printf " ${BOLD}*${NC} ${GREEN}$(hostname).local:$1$2${NC}\n" 145 | 146 | for ip in $IP; do 147 | if expr "$ip" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; then 148 | printf " ${BOLD}*${NC} ${GREEN}$ip:$1$2${NC}\n" 149 | else 150 | printf " ${BOLD}*${NC} ${GREEN}[$ip]:$1$2${NC}\n" 151 | fi 152 | done 153 | } 154 | 155 | do_finish() { 156 | if [ $ASK_TO_REBOOT -eq 1 ]; then 157 | whiptail --yesno "Would you like to reboot now?" 20 60 2 158 | if [ $? -eq 0 ]; then # yes 159 | sync 160 | reboot 161 | fi 162 | fi 163 | exit 0 164 | } 165 | 166 | do_stop_services() { 167 | systemctl stop homebridge 168 | } 169 | 170 | do_start_services() { 171 | systemctl start homebridge 172 | } 173 | 174 | do_restart_services() { 175 | whiptail --yesno "Would you like to restart Homebridge now?" 10 60 2 176 | RET=$? 177 | if [ $RET -eq 0 ]; then 178 | printf "Restarting Homebridge...\n" 179 | systemctl restart homebridge 180 | fi 181 | } 182 | 183 | do_homebridge_update() { 184 | printf "Updating Homebridge, please wait...\n" 185 | npm install -g --unsafe-perm homebridge@latest homebridge-config-ui-x@latest 186 | whiptail --msgbox "Homebridge is up-to-date." 10 60 1 187 | do_restart_services 188 | } 189 | 190 | do_nodejs_update() { 191 | printf "Updating Node.js to LTS, please wait...\n" 192 | 193 | INDEX=$(curl -s https://nodejs.org/dist/index.json) 194 | LTS=$(echo $INDEX | jq -r 'map(select(.lts))[0].version') 195 | CURRENT=$(node -v || echo "0.0.0") 196 | 197 | echo "Installed: $CURRENT" 198 | echo "Current Node.js LTS: $LTS" 199 | 200 | if [ $LTS != $CURRENT ]; then 201 | echo "Updating Node.js to $LTS..." 202 | 203 | PREFIX=$(npm -g prefix || echo "/usr/local") 204 | 205 | tempDir=$(mktemp -d) 206 | echo "Downloading to $tempDir/node-$LTS-linux-armv6l.tar.gz ..." 207 | curl -Lf# -o "$tempDir/node-$LTS-linux-armv6l.tar.gz" "https://unofficial-builds.nodejs.org/download/release/$LTS/node-$LTS-linux-armv6l.tar.gz" 208 | 209 | if [ $? -ne 0 ]; then 210 | echo "Failed to download node-$LTS-linux-armv6l.tar.gz. See logs above." 211 | exit 1 212 | fi 213 | 214 | echo "Cleaning up old version of npm..." 215 | sudo rm -rf "$PREFIX/lib/node_modules/npm" 216 | 217 | echo "Extracting node-$LTS-linux-armv6l.tar.gz to $PREFIX ..." 218 | sudo tar xz -f "$tempDir/node-$LTS-linux-armv6l.tar.gz" -C /usr/local --strip-components=1 --no-same-owner 219 | 220 | echo "Removing $tempDir/node-$LTS-linux-armv6l.tar.gz ..." 221 | sudo rm -rf "$tempDir/node-$LTS-linux-armv6l.tar.gz" 222 | 223 | echo "Node.js $(node -v)" 224 | echo "npm $(npm -v)" 225 | 226 | echo "Node.js updated to $LTS" 227 | 228 | echo "Rebuilding modules..." 229 | cd "$PREFIX/lib/node_modules" 230 | sudo npm --unsafe-perm rebuild 231 | 232 | whiptail --msgbox "Node.js upgraded from $CURRENT to $LTS." 10 60 1 233 | do_restart_services 234 | else 235 | whiptail --msgbox "Node.js $CURRENT is already up-to-date" 10 60 1 236 | fi 237 | } 238 | 239 | do_restore_config() { 240 | whiptail --yesno "This will remove all custom Homebridge config and restore it to the factory default. You will need to manually remove any existing connections to this instance in the iOS Home app.\n\nAre you sure you want to proceed?" 20 60 2 241 | RET=$? 242 | if [ $RET -eq 0 ]; then 243 | do_stop_services 244 | rm -rf /var/lib/homebridge/persist 245 | rm -rf /var/lib/homebridge/accessories 246 | rm -rf /var/lib/homebridge/config.json 247 | do_start_services 248 | fi 249 | } 250 | 251 | do_install_pihole() { 252 | whiptail --title "Warning" --yesno "Please make sure you are NOT attempting to run this command from the Homebridge UI web terminal.\n\nFor a successful installation of Pi-Hole you must be logged in using SSH (see wiki for instructions).\n\nIf you are already connected via SSH, you can proceed with the installation." 20 60 1 --no-button "Cancel" --yes-button "Continue" 253 | if [ $? -ne 0 ]; then 254 | return; 255 | fi 256 | 257 | whiptail --title "Warning" --yesno "Installing Pi-Hole will prevent you from accessing the Homebridge UI on port 80.\n\nYou will still be able to access the Homebridge UI on port $(cat /etc/hb-ui-port) and via:\n\n* http://homebridge.local:$(cat /etc/hb-ui-port)\n* https://homebridge.local\n\nWARNING: Failing to configure Pi-Hole correctly or attempting to uninstall Pi-Hole may result in Homebridge not working correctly. You should take a backup of your Homebridge instance via the UI before proceeding.\n\nAre you sure you want to continue?" 20 60 1 258 | RET=$? 259 | if [ $RET -eq 0 ]; then 260 | if [ -f /etc/nginx/sites-available/homebridge.local ]; then 261 | sed -i "/listen 80;/c\ # listen 80; # http IPv4 - disabled by Pi-Hole installer" /etc/nginx/sites-available/homebridge.local 262 | sed -i "/listen \[::\]:80;/c\ # listen \[::\]:80; # http IPv6 - disabled by Pi-Hole installer" /etc/nginx/sites-available/homebridge.local 263 | if systemctl is-active --quiet nginx.service; then 264 | echo "Restarting Nginx..." 265 | systemctl restart nginx 266 | fi 267 | fi 268 | printf "Starting Pi-hole installer...\n" 269 | curl -sSL https://install.pi-hole.net | bash 270 | 271 | printf "\n${BOLD}Pi-hole has been installed${NC}\n" 272 | printf "Access Pi-hole in your browser by going to:\n" 273 | get_web_addresses http 80 /admin 274 | 275 | printf "\nYou can still access the Homebridge UI by going to:\n" 276 | get_web_addresses http $(cat /etc/hb-ui-port) 277 | get_web_addresses https 443 278 | printf "\n" 279 | exit 0 280 | fi 281 | } 282 | 283 | do_install_nodered() { 284 | whiptail --title "Warning" --yesno "This will install Node-RED on your system as a service running on port 1880.\n\nAre you sure you want to continue?" 20 60 1 285 | RET=$? 286 | if [ $RET -eq 0 ]; then 287 | npm install -g --unsafe-perm node-red node-red-node-pi-gpio node-red-node-random node-red-node-ping node-red-contrib-play-audio node-red-node-smooth node-red-node-serialport node-red-contrib-homebridge-automation 288 | 289 | # add helper shortcuts 290 | if curl -f https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-icon.svg >/dev/null 2>&1; then 291 | sudo curl -sL -o /usr/share/icons/hicolor/scalable/apps/node-red-icon.svg https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-icon.svg 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null 292 | sudo curl -sL -o /usr/share/applications/Node-RED.desktop https://raw.githubusercontent.com/node-red/linux-installers/master/resources/Node-RED.desktop 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null 293 | sudo curl -sL -o /usr/bin/node-red-start https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-start 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null 294 | sudo curl -sL -o /usr/bin/node-red-stop https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-stop 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null 295 | sudo curl -sL -o /usr/bin/node-red-restart https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-restart 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null 296 | sudo curl -sL -o /usr/bin/node-red-reload https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-reload 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null 297 | sudo curl -sL -o /usr/bin/node-red-log https://raw.githubusercontent.com/node-red/linux-installers/master/resources/node-red-log 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null 298 | sudo curl -sL -o /etc/logrotate.d/nodered https://raw.githubusercontent.com/node-red/linux-installers/master/resources/nodered.rotate 2>&1 | sudo tee -a /var/log/nodered-install.log >>/dev/null 299 | sudo chmod +x /usr/bin/node-red-start 300 | sudo chmod +x /usr/bin/node-red-stop 301 | sudo chmod +x /usr/bin/node-red-restart 302 | sudo chmod +x /usr/bin/node-red-reload 303 | sudo chmod +x /usr/bin/node-red-log 304 | printf "Added shortcut commands\r\n" 305 | else 306 | printf "Could not add shortcut commands\r\n" 307 | fi 308 | 309 | cat > /etc/systemd/system/nodered.service <&1 1>&2 2>&3) 449 | RET=$? 450 | if [ $RET -eq 1 ]; then 451 | return 0 452 | elif [ $RET -eq 0 ]; then 453 | case "$FUN" in 454 | C1\ *) do_conbee_setup ;; 455 | C2\ *) do_raspbee_setup ;; 456 | C3\ *) do_raspbee2_setup ;; 457 | *) whiptail --msgbox "Programmer error: unrecognized option" 20 60 1 ;; 458 | esac || whiptail --msgbox "There was an error running option $FUN" 20 60 1 459 | fi 460 | 461 | # Import Phoscon public key 462 | wget -O - http://phoscon.de/apt/deconz.pub.key | sudo apt-key add - 463 | 464 | # Configure the APT repository for deCONZ 465 | # remove -beta when https://forum.phoscon.de/t/please-create-a-repo-for-debian-11-bullseye/432 is resolved 466 | sudo sh -c "echo 'deb http://phoscon.de/apt/deconz \ 467 | $(lsb_release -cs)-beta main' > \ 468 | /etc/apt/sources.list.d/deconz.list" 469 | 470 | # Update APT package list 471 | apt update --allow-releaseinfo-change 472 | 473 | # Install deCONZ 474 | apt install -y deconz 475 | 476 | # Add override service 477 | mkdir -p /etc/systemd/system/deconz.service.d 478 | cat > /etc/systemd/system/deconz.service.d/override.conf < /etc/systemd/system/vncserver-pi.service < /dev/null 2>&1 560 | ExecStart=/usr/bin/vncserver -depth 24 -geometry 1280x800 :12 561 | ExecStop=/usr/bin/vncserver -kill :12 562 | 563 | [Install] 564 | WantedBy=multi-user.target 565 | EOL 566 | systemctl enable vncserver-x11-serviced.service && 567 | systemctl start vncserver-x11-serviced.service && 568 | systemctl enable vncserver-pi.service && 569 | systemctl start vncserver-pi.service && 570 | STATUS=enabled 571 | else 572 | return 1 573 | fi 574 | elif [ $RET -eq 1 ]; then 575 | if is_installed realvnc-vnc-server; then 576 | systemctl disable vncserver-x11-serviced.service 577 | systemctl stop vncserver-x11-serviced.service 578 | systemctl disable vncserver-pi.service 579 | systemctl stop vncserver-pi.service 580 | fi 581 | STATUS=disabled 582 | else 583 | return $RET 584 | fi 585 | 586 | if [ "$STATUS" = "disabled" ]; then 587 | whiptail --title "Success" --msgbox "The VNC Server and virtual desktop have been disabled." 20 60 1 588 | else 589 | # Complete 590 | VNC_ACCESS_URI=$(get_ip_and_port 5912 | sed 's/\x1b\[[0-9;]*m//g') 591 | whiptail --title "Success" --msgbox "The VNC Server and virtual desktop have been configured.\n\nYou can connect to the virtual desktop using the RealVNC Viewer on port 5912:\n\n$VNC_ACCESS_URI\n\nDownload RealVNC Viewer:\n\n * https://www.realvnc.com/download/viewer/" 20 60 1 592 | fi 593 | } 594 | 595 | do_vnc 596 | } 597 | 598 | do_extra_packages() { 599 | FUN=$(whiptail --title "Homebridge Raspbian Configuration Tool (hb-config)" --menu "Advanced Options" $WT_HEIGHT $WT_WIDTH $WT_MENU_HEIGHT --cancel-button Back --ok-button Select \ 600 | "A0 AdGuard Home" "Install / Uninstall AdGuard Home" \ 601 | "A1 Pi-hole" "Install / Reconfigure Pi-hole" \ 602 | "A2 Node-RED" "Install / Update Node-RED" \ 603 | "A3 deCONZ / Phoscon" "Install / Update to manage your ConBee or RaspBee device" \ 604 | "A4 RealVNC Server" "Enable / Disable desktop access over VNC" \ 605 | 3>&1 1>&2 2>&3) 606 | RET=$? 607 | if [ $RET -eq 1 ]; then 608 | return 0 609 | elif [ $RET -eq 0 ]; then 610 | case "$FUN" in 611 | A0\ *) do_install_adguard_home ;; 612 | A1\ *) do_install_pihole ;; 613 | A2\ *) do_install_nodered ;; 614 | A3\ *) do_install_deconz ;; 615 | A4\ *) do_install_desktop_and_vnc ;; 616 | *) whiptail --msgbox "Programmer error: unrecognized option" 20 60 1 ;; 617 | esac || whiptail --msgbox "There was an error running option $FUN" 20 60 1 618 | fi 619 | } 620 | 621 | do_configure_nginx() { 622 | whiptail --title "Warning" --yesno "Using this tool will overwrite any manual changes you may have made to the Homebridge Nginx config file.\n\nAre you sure you wish to continue?" 20 60 1 623 | RET=$? 624 | tempDir=$(mktemp -d) 625 | if [ $RET -eq 0 ]; then 626 | whiptail --title "Homebridge Raspbian Configuration Tool (hb-config)" --separate-output --checklist "\nNGINX Options:" 20 80 5 --cancel-button Cancel --ok-button Save \ 627 | "1" "Listen on port 80 - http:// " 1 \ 628 | "2" "Listen on port 443 - https:// " 1 \ 629 | "3" "Redirect http:// to https:// " 0 2>$tempDir/results 630 | 631 | if [ $? -eq 0 ]; then 632 | LISTEN_ON_PORT_80=false 633 | LISTEN_ON_PORT_443=false 634 | REDIRECT_HTTP_TO_HTTPS=false 635 | 636 | while read choice 637 | do 638 | case "$choice" in 639 | 1) LISTEN_ON_PORT_80=true ;; 640 | 2) LISTEN_ON_PORT_443=true ;; 641 | 3) REDIRECT_HTTP_TO_HTTPS=true ;; 642 | *) ;; 643 | esac 644 | done < $tempDir/results 645 | 646 | NGINX_PORT_80_CONFIG="" 647 | if [ "$LISTEN_ON_PORT_80" = "true" ]; then 648 | NGINX_PORT_80_CONFIG="# http://\n listen 80; # http IPv4\n listen [::]:80; # http IPv6\n" 649 | fi 650 | 651 | NGINX_PORT_443_CONFIG="" 652 | if [ "$LISTEN_ON_PORT_443" = "true" ]; then 653 | NGINX_PORT_443_CONFIG="# https://\n listen 443 ssl http2; # https IPv4\n listen [::]:443 ssl http2; # https IPv6\n" 654 | fi 655 | 656 | NGINX_REDIRECT_HTTP_TO_HTTPS="" 657 | if [ "$REDIRECT_HTTP_TO_HTTPS" = "true" ] && [ "$LISTEN_ON_PORT_80" = "true" ]; then 658 | NGINX_PORT_80_CONFIG="" 659 | NGINX_REDIRECT_HTTP_TO_HTTPS=" 660 | server { 661 | listen 80; # http IPv4 662 | listen [::]:80; # http IPv6 663 | 664 | # replace the _ with your domain name if required 665 | # eg. 666 | # server_name example.com; 667 | server_name _; 668 | 669 | return 302 https://\$host\$request_uri; 670 | }" 671 | fi 672 | 673 | NGINX_TEMPLATE="# this file was generated using the hb-config tool 674 | 675 | server { 676 | $NGINX_PORT_80_CONFIG 677 | $NGINX_PORT_443_CONFIG 678 | # replace the _ with your domain name if required 679 | # eg. 680 | # server_name example.com; 681 | server_name _; # replace with your domain 682 | 683 | # path to ssl certificate file 684 | # If using Let's Encrypt replace this path with the location of the fullchain.pem file for your domain 685 | # eg. 686 | # ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; 687 | ssl_certificate /etc/nginx/ssl/homebridge.local.crt; 688 | 689 | # path to ssl private key file 690 | # If using Let's Encrypt replace this path with the location of the privkey.pem file for your domain 691 | # eg. 692 | # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; 693 | ssl_certificate_key /etc/nginx/ssl/homebridge.local.key; 694 | 695 | ssl_session_cache builtin:1000 shared:SSL:10m; 696 | ssl_protocols TLSv1.2 TLSv1.3; 697 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 698 | ssl_prefer_server_ciphers off; 699 | # ssl_stapling on; 700 | # ssl_stapling_verify on; 701 | 702 | # large upload size to allow for uploading 3rd party backup files 703 | client_max_body_size 2G; 704 | 705 | error_page 502 /custom_502.html; 706 | 707 | location /startup-status.json { 708 | alias /usr/share/nginx/html/status.json; 709 | } 710 | 711 | location / { 712 | proxy_pass http://127.0.0.1:$(cat /etc/hb-ui-port); 713 | proxy_http_version 1.1; 714 | proxy_buffering off; 715 | proxy_set_header Host \$host; 716 | proxy_set_header Upgrade \$http_upgrade; 717 | proxy_set_header Connection \"Upgrade\"; 718 | proxy_set_header X-Real-IP \$remote_addr; 719 | proxy_set_header X-Forward-For \$proxy_add_x_forwarded_for; 720 | } 721 | 722 | location = /custom_502.html { 723 | root /usr/share/nginx/html; 724 | internal; 725 | } 726 | } 727 | $NGINX_REDIRECT_HTTP_TO_HTTPS 728 | " 729 | 730 | echo "$NGINX_TEMPLATE" > /etc/nginx/sites-available/homebridge.local 731 | /usr/local/sbin/nginx-homebridge-self-signed-cert 732 | systemctl enable nginx 733 | 734 | if systemctl is-active --quiet nginx.service; then 735 | echo "Restarting Nginx..." 736 | systemctl restart nginx 737 | else 738 | echo "Starting Nginx..." 739 | systemctl start nginx 740 | fi 741 | 742 | if [ $? -eq 0 ]; then 743 | whiptail --title "Success" --msgbox "NGINX has been configured successfully." 20 60 1 744 | else 745 | whiptail --title "Error" --msgbox "An error occured while attempting to reload the NGINX config." 20 60 1 746 | fi 747 | 748 | fi 749 | fi 750 | } 751 | 752 | do_configure_raspbian() { 753 | raspi-config 754 | } 755 | 756 | do_update() { 757 | tempDir=$(mktemp -d) 758 | scriptUrl=https://raw.githubusercontent.com/homebridge/homebridge-raspbian-image/latest/stage3_homebridge/01-homebridge/files/hb-config 759 | 760 | # download the update to a temporary directory 761 | scriptUrl=$scriptUrl?$(date --iso-8601) 762 | printf "Downloading hb-config update...\n$scriptUrl\n" 763 | curl -fL# $scriptUrl > $tempDir/hb-config 764 | 765 | # check the download was a success 766 | RET=$? 767 | if [ $RET -ne 0 ]; then 768 | whiptail --title "Error" --msgbox "An error occured while attempting to update the hb-config tool." 20 60 1 769 | else 770 | # syntax check the script is a valid bash script 771 | if bash -n $tempDir/hb-config; then 772 | chmod +x $tempDir/hb-config 773 | mv $tempDir/hb-config /usr/local/sbin/hb-config 774 | whiptail --title "Success" --msgbox "The hb-config tool has been updated and will now exit.\n\nIf you like the Homebridge Raspberry Pi image please show your support by starring the project on GitHub:\n\nhttps://github.com/homebridge/homebridge-raspbian-image" 20 60 1 775 | exit 0 776 | else 777 | whiptail --title "Error" --msgbox "The downloaded script was corrupted and did not pass the syntax check." 20 60 1 778 | fi 779 | fi 780 | } 781 | 782 | do_about() { 783 | IMAGE_VERSION=$(cat /etc/hb-release) 784 | 785 | whiptail --title "About" --msgbox "\ 786 | This tool provides a straight-forward way of managing Homebridge on a Raspberry Pi. Although it can be run at any time, some of the options may have difficulties if you have heavily customised your installation. 787 | 788 | Image Version: $IMAGE_VERSION 789 | 790 | Homebridge - @nfarina 791 | https://github.com/homebridge/homebridge 792 | 793 | Homebridge Config UI X - @oznu 794 | https://github.com/oznu/homebridge-config-ui-x 795 | 796 | Homebridge Raspbian Image - @oznu 797 | https://github.com/homebridge/homebridge-raspbian-image 798 | 799 | 800 | \ 801 | " 20 80 1 802 | } 803 | 804 | # Everything else needs to be run as root 805 | if [ $(id -u) -ne 0 ]; then 806 | printf "Script must be run as root. Try 'sudo hb-config'\n" 807 | exit 1 808 | fi 809 | 810 | # 811 | # Interactive use loop 812 | # 813 | if [ "$INTERACTIVE" = True ]; then 814 | [ -e $CONFIG ] || touch $CONFIG 815 | calc_wt_size 816 | while true; do 817 | if is_pi ; then 818 | FUN=$(whiptail --title "Homebridge Raspbian Configuration Tool (hb-config)" --backtitle "$(cat /proc/device-tree/model)" --menu "Setup Options" $WT_HEIGHT $WT_WIDTH $WT_MENU_HEIGHT --cancel-button Finish --ok-button Select \ 819 | "1 Upgrade Node.js" "Upgrade Node.js to the latest LTS version" \ 820 | "2 Update Homebridge" "Update Homebridge to the latest version" \ 821 | "3 Restore Config" "Restores the Homebridge config to the factory default" \ 822 | "4 Extra Packages" "Optional packages, eg. Pi-Hole, Node-RED, UniFi Controller" \ 823 | "5 Nginx Options" "Configure Homebridge Nginx settings" \ 824 | "6 Configure OS" "Open the Raspbian Configuration Tool" \ 825 | "7 Networking" "Open the NetworkManager UI" \ 826 | "8 Update" "Update this tool to the latest version" \ 827 | "9 About" "Information about this configuration tool" \ 828 | 3>&1 1>&2 2>&3) 829 | else 830 | FUN=$(whiptail --title "Homebridge Raspbian Configuration Tool (hb-config)" --menu "Setup Options" $WT_HEIGHT $WT_WIDTH $WT_MENU_HEIGHT --cancel-button Finish --ok-button Select \ 831 | "1 Upgrade Node.js" "Upgrade Node.js to the latest LTS version" \ 832 | "2 Update Homebridge" "Update Homebridge to the latest version" \ 833 | "3 Restore Config" "Restores the Homebridge config to the factory default" \ 834 | "4 Extra Packages" "Optional packages, eg. Pi-Hole, Node-RED, UniFi Controller" \ 835 | "5 Nginx Options" "Configure Homebridge Nginx settings" \ 836 | "6 Configure OS" "Open the Raspbian Configuration Tool" \ 837 | "7 Networking" "Open the NetworkManager UI" \ 838 | "8 Update" "Update this tool to the latest version" \ 839 | "9 About" "Information about this configuration tool" \ 840 | 3>&1 1>&2 2>&3) 841 | fi 842 | RET=$? 843 | if [ $RET -eq 1 ]; then 844 | do_finish 845 | elif [ $RET -eq 0 ]; then 846 | if is_pi ; then 847 | case "$FUN" in 848 | 1\ *) do_nodejs_update ;; 849 | 2\ *) do_homebridge_update ;; 850 | 3\ *) do_restore_config ;; 851 | 4\ *) do_extra_packages ;; 852 | 5\ *) do_configure_nginx ;; 853 | 6\ *) do_configure_raspbian ;; 854 | 7\ *) nmtui ;; 855 | 8\ *) do_update ;; 856 | 9\ *) do_about ;; 857 | *) whiptail --msgbox "Programmer error: unrecognized option" 20 60 1 ;; 858 | esac || whiptail --msgbox "There was an error running option $FUN" 20 60 1 859 | else 860 | case "$FUN" in 861 | 1\ *) do_nodejs_update ;; 862 | 2\ *) do_homebridge_update ;; 863 | 3\ *) do_restore_config ;; 864 | 4\ *) do_extra_packages ;; 865 | 5\ *) do_configure_nginx ;; 866 | 6\ *) do_configure_raspbian ;; 867 | 7\ *) nmtui ;; 868 | 8\ *) do_update ;; 869 | 9\ *) do_about ;; 870 | *) whiptail --msgbox "Programmer error: unrecognized option" 20 60 1 ;; 871 | esac || whiptail --msgbox "There was an error running option $FUN" 20 60 1 872 | fi 873 | else 874 | exit 1 875 | fi 876 | done 877 | fi 878 | --------------------------------------------------------------------------------