├── overlay ├── filesystem │ ├── etc │ │ ├── locale.gen │ │ ├── ld.so.conf.d │ │ │ └── ubnt.conf │ │ ├── fstab │ │ ├── systemd │ │ │ ├── system │ │ │ │ ├── systemd-networkd-wait-online.service.d │ │ │ │ │ └── override.conf │ │ │ │ ├── ulcmd-reboot-hook.service │ │ │ │ ├── mock-ubnt-api.service │ │ │ │ ├── ubnt-init.service │ │ │ │ ├── ulcmd-shutdown-hook.service │ │ │ │ ├── unvr-fan-daemon.service │ │ │ │ └── ulcmd.service │ │ │ └── network │ │ │ │ ├── first-boot-enp0s1.network │ │ │ │ └── first-boot-enp0s2.network │ │ ├── default │ │ │ └── chrony │ │ ├── apt │ │ │ └── sources.list │ │ ├── initramfs-tools │ │ │ └── update-initramfs.conf │ │ └── cloud │ │ │ ├── cloud.cfg.d │ │ │ └── 99-none-omv-setup.cfg │ │ │ └── cloud.cfg │ └── usr │ │ ├── bin │ │ ├── ubnt-tools │ │ ├── ubnt-systool │ │ ├── mock-ubnt-api │ │ ├── unvr-fan-daemon │ │ └── ustorage │ │ └── lib │ │ ├── init │ │ └── boot │ │ │ ├── ubnt-ulcmd.sh │ │ │ ├── ubnt-init.sh │ │ │ └── ubnt-bt.sh │ │ ├── systemd │ │ └── system-shutdown │ │ │ └── unifi-shutdown │ │ └── udev │ │ └── rules.d │ │ └── 60-persistent-storage-ubnt-boot.rules └── kernel │ └── arch │ └── arm64 │ └── configs │ └── alpine_v2_defconfig ├── tools ├── ubnt-mtd-lock │ ├── Kbuild │ ├── README.md │ └── ubnt-mtd-lock.c └── ubnteeprom │ ├── README.md │ └── main.go ├── .gitignore ├── genimage_final.cfg ├── genimage_initial.cfg ├── unifi-firmware └── README.md ├── scripts ├── docker │ ├── run_mkimage_initial.sh │ ├── extract_firmware.sh │ ├── setup_mkimage.sh │ ├── run_mkimage_final.sh │ ├── build_packages.sh │ ├── build_kernel.sh │ ├── bootstrap │ │ └── 001-bootstrap │ └── run_debootstrap.sh ├── 01_pre_docker.sh ├── 04_post_docker.sh ├── 02_download_dependencies.sh ├── 00_prereq_check.sh ├── vars.sh ├── 03_docker.sh └── ubnt-fw-parse.py ├── Makefile ├── Dockerfile └── README.md /overlay/filesystem/etc/locale.gen: -------------------------------------------------------------------------------- 1 | en_US.UTF-8 UTF-8 -------------------------------------------------------------------------------- /tools/ubnt-mtd-lock/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m := ubnt-mtd-lock.o -------------------------------------------------------------------------------- /overlay/filesystem/etc/ld.so.conf.d/ubnt.conf: -------------------------------------------------------------------------------- 1 | # Used on UNVRPRO for ulcmd 2 | /usr/lib/ubnt-fw 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Unifi firmware 2 | unifi-firmware/*.bin 3 | BuildEnv 4 | output 5 | downloads 6 | tools/ubnteeprom/test-files -------------------------------------------------------------------------------- /overlay/filesystem/etc/fstab: -------------------------------------------------------------------------------- 1 | PARTLABEL=boot /boot ext4 defaults,ro 0 1 2 | PARTLABEL=rootfs / ext4 defaults 0 1 3 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/systemd/system/systemd-networkd-wait-online.service.d/override.conf: -------------------------------------------------------------------------------- 1 | [Service] 2 | ExecStart= 3 | ExecStart=/lib/systemd/systemd-networkd-wait-online --any 4 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/bin/ubnt-tools: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" == "id" ]; then 4 | ubnteeprom -tools 5 | else 6 | echo "Unknown ubnt-tools cmd: $@" >> /tmp/ubnt-tools-unknown.log 7 | fi 8 | -------------------------------------------------------------------------------- /genimage_final.cfg: -------------------------------------------------------------------------------- 1 | image emmc.img { 2 | hdimage { 3 | partition-table-type = "gpt" 4 | } 5 | 6 | partition boot { 7 | bootable = "true" 8 | image = "boot.ext4" 9 | } 10 | 11 | partition rootfs { 12 | image = "rootfs.ext4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/bin/ubnt-systool: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" == "poweroff" ]; then 4 | poweroff --halt 5 | elif [ "$1" == "reboot" ]; then 6 | reboot 7 | else 8 | echo "Unknown ubnt-systool cmd: $@" >> /tmp/ubnt-systool-unknown.log 9 | fi 10 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/systemd/network/first-boot-enp0s1.network: -------------------------------------------------------------------------------- 1 | [Match] 2 | Name=enp0s1 3 | 4 | [Network] 5 | DHCP=ipv4 6 | LinkLocalAddressing=ipv6 7 | IPv6AcceptRA=yes 8 | IPv6PrivacyExtensions=yes 9 | 10 | [DHCP] 11 | RouteMetric=100 12 | UseMTU=true 13 | UseDomains=true 14 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/systemd/network/first-boot-enp0s2.network: -------------------------------------------------------------------------------- 1 | [Match] 2 | Name=enp0s2 3 | 4 | [Network] 5 | DHCP=ipv4 6 | LinkLocalAddressing=ipv6 7 | IPv6AcceptRA=yes 8 | IPv6PrivacyExtensions=yes 9 | 10 | [DHCP] 11 | RouteMetric=100 12 | UseMTU=true 13 | UseDomains=true 14 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/default/chrony: -------------------------------------------------------------------------------- 1 | # This is a configuration file for /etc/init.d/chrony and 2 | # /lib/systemd/system/chrony.service; it allows you to pass various options to 3 | # the chrony daemon without editing the init script or service file. 4 | 5 | # Options to pass to chrony. 6 | DAEMON_OPTS="-F 0" 7 | -------------------------------------------------------------------------------- /genimage_initial.cfg: -------------------------------------------------------------------------------- 1 | image boot.ext4 { 2 | ext4 { 3 | label = "boot" 4 | use-mke2fs = true 5 | } 6 | size = 255M 7 | } 8 | 9 | image rootfs.ext4 { 10 | name = "rootfs" 11 | ext4 { 12 | label = "rootfs" 13 | use-mke2fs = true # Needed to prevent resize issues... 14 | } 15 | size = 3G 16 | } 17 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/systemd/system/ulcmd-reboot-hook.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ulcmd reboot hook 3 | DefaultDependencies=no 4 | Before=reboot.target 5 | 6 | [Service] 7 | ExecStart=/usr/bin/ulcmd --sender system-hook --command restart 8 | Type=oneshot 9 | 10 | [Install] 11 | WantedBy=reboot.target 12 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/systemd/system/mock-ubnt-api.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Mock Unifi API to make ulcmd happy 3 | 4 | [Service] 5 | Type=simple 6 | ExecStart=/usr/bin/mock-ubnt-api 7 | KillMode=process 8 | Restart=on-failure 9 | RestartSec=2s 10 | TimeoutStopSec=2s 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/systemd/system/ubnt-init.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=UBNT bootup init script 3 | 4 | [Service] 5 | User=root 6 | Type=oneshot 7 | ExecStart=/usr/lib/init/boot/ubnt-init.sh start 8 | ExecStop=/usr/lib/init/boot/ubnt-init.sh stop 9 | RemainAfterExit=yes 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/systemd/system/ulcmd-shutdown-hook.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ulcmd shutdown hook 3 | DefaultDependencies=no 4 | Before=shutdown.target halt.target poweroff.target 5 | 6 | [Service] 7 | ExecStart=/usr/bin/ulcmd --sender system-hook --command poweroff 8 | Type=oneshot 9 | 10 | [Install] 11 | WantedBy=shutdown.target halt.target poweroff.target 12 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/systemd/system/unvr-fan-daemon.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Fan Controller daemon for the UNVR/UNVRPRO 3 | Requires=ubnt-init.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/bin/unvr-fan-daemon 8 | KillMode=process 9 | Restart=on-failure 10 | RestartSec=2s 11 | TimeoutStopSec=2s 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/systemd/system/ulcmd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Unifi ULCMD contoller 3 | Requires=ubnt-init.service 4 | Requires=mock-ubnt-api.service 5 | 6 | [Service] 7 | Type=simple 8 | ExecStart=/usr/lib/init/boot/ubnt-ulcmd.sh 9 | KillMode=process 10 | Restart=on-failure 11 | RestartSec=2s 12 | TimeoutStopSec=2s 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/apt/sources.list: -------------------------------------------------------------------------------- 1 | deb http://ftp.us.debian.org/debian bookworm main contrib non-free-firmware 2 | deb-src http://ftp.us.debian.org/debian bookworm main contrib non-free-firmware 3 | deb http://ftp.us.debian.org/debian bookworm-updates main contrib non-free-firmware 4 | deb-src http://ftp.us.debian.org/debian bookworm-updates main contrib non-free-firmware 5 | deb http://security.debian.org/debian-security bookworm-security main 6 | deb-src http://security.debian.org/debian-security bookworm-security main 7 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/initramfs-tools/update-initramfs.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration file for update-initramfs(8) 3 | # 4 | 5 | # 6 | # update_initramfs [ yes | all | no ] 7 | # 8 | # Default is yes 9 | # If set to all update-initramfs will update all initramfs 10 | # If set to no disables any update to initramfs beside kernel upgrade 11 | 12 | update_initramfs=no 13 | 14 | # 15 | # backup_initramfs [ yes | no ] 16 | # 17 | # Default is no 18 | # If set to no leaves no .bak backup files. 19 | 20 | backup_initramfs=no 21 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/lib/init/boot/ubnt-ulcmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ULCMD wrapper, used to start ulcmd on the UNVR Pro 4 | 5 | # Ensure our tmp file with info is generated for our patched ulcmd 6 | ubnteeprom -systeminfo > /tmp/.ubnthal_system_info 7 | 8 | # Is ulcmd running already? if so, assume it was not done via systemd so let's 9 | # kill and respawn as this is our systemd entry script for the service, and we 10 | # need to have it foregrounded as we act as the "daemon" here. 11 | if pidof -q ulcmd; then 12 | killall ulcmd 13 | fi 14 | 15 | # Start ulcmd 16 | exec ulcmd 17 | -------------------------------------------------------------------------------- /tools/ubnt-mtd-lock/README.md: -------------------------------------------------------------------------------- 1 | # ubnt-mtd-lock 2 | 3 | A stupid basic kernel module to ensure we set /dev/mtd* as RO (fully) in our firmware. This is done to prevent users/bad actors from wiping your bootloader/Unifi EEPROM, which are required for your device to function! 4 | 5 | ## Building 6 | 7 | make -C ${kernel_source_dir} M=$PWD 8 | 9 | ## License 10 | 11 | This code is licensed under the GNU General Public License, version 2. A copy of said license can be found at [https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html#SEC1](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html#SEC1) 12 | -------------------------------------------------------------------------------- /unifi-firmware/README.md: -------------------------------------------------------------------------------- 1 | # unifi-firmware 2 | 3 | Please place the UNVR/UNVR Pro firmware in this directory. 4 | 5 | * UNVR: 6 | * Name: [0592-UNVR-4.1.9-6ea55371-e18f-4de9-a67e-a5c63bb0fc2f.bin](https://community.ui.com/releases/UniFi-OS-Network-Video-Recorders-4-1-9/951963ff-a06d-4895-8aac-4bcff43489c1) 7 | * MD5Sum: 7222b70f9383781133e40a134eb0fc06 8 | 9 | 10 | * UNVR Pro: 11 | * Name: [ca04-UNVRPRO-4.1.9-0e3e9c30-7e9a-48ba-975d-2ad0ff8f8eee.bin](https://community.ui.com/releases/UniFi-OS-Network-Video-Recorders-4-1-9/951963ff-a06d-4895-8aac-4bcff43489c1) 12 | * MD5Sum: ae41e9f61246877958c394f57b3618ca 13 | -------------------------------------------------------------------------------- /scripts/docker/run_mkimage_initial.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" 5 | . ${scripts_path}/vars.sh 6 | 7 | # Tempdir for root 8 | GENIMAGE_ROOT=$(mktemp -d) 9 | 10 | # "fake" file so generation is happy 11 | touch ${GENIMAGE_ROOT}/placeholder 12 | 13 | # Generate our boot and rootfs disk images 14 | genimage \ 15 | --rootpath "${GENIMAGE_ROOT}" \ 16 | --tmppath "/tmp/genimage-initial-tmppath" \ 17 | --inputpath "${build_path}" \ 18 | --outputpath "${build_path}" \ 19 | --config "${root_path}/genimage_initial.cfg" 20 | -------------------------------------------------------------------------------- /scripts/docker/extract_firmware.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" 5 | . ${scripts_path}/vars.sh 6 | 7 | # Extract the goodies 8 | ${scripts_path}/ubnt-fw-parse.py "${root_path}/unifi-firmware/${firmware_filename}" "${build_path}/fw-extract/${firmware_filename%.bin}" 9 | 10 | # Extract the squashfs rootfs 11 | if ! [ -d "${build_path}/fw-extract/${BOARD}-rootfs" ]; then 12 | echo "Extracting unifi rootfs as root..." 13 | unsquashfs -f -d "${build_path}/fw-extract/${BOARD}-rootfs" "${build_path}/fw-extract/${firmware_filename%.bin}/rootfs.bin" 14 | fi 15 | -------------------------------------------------------------------------------- /scripts/01_pre_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Source our common vars 5 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | . ${scripts_path}/vars.sh 7 | 8 | debug_msg "Starting 01_pre_docker.sh" 9 | 10 | # Make sure our BuildEnv dir exists 11 | if [ -d ${build_path} ]; then 12 | error_msg "BuildEnv already exists, this isn't a clean build! Things might fail, but we're going to try!" 13 | else 14 | mkdir ${build_path} 15 | fi 16 | 17 | # Always build to pickup changes/updates/improvements 18 | debug_msg "Building ${docker_tag}" 19 | docker build -t ${docker_tag} ${root_path} 20 | 21 | debug_msg "Finished 01_pre_docker.sh" 22 | -------------------------------------------------------------------------------- /scripts/docker/setup_mkimage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # setup_mkimage is special, as it's copied to the dockerfile and ran 5 | # same with vars.sh which we put in place 6 | . /vars.sh 7 | 8 | cd /usr/src 9 | echo "Downloading genimage..." 10 | wget ${genimage_src} -O /usr/src/${genimage_filename} 11 | 12 | echo "Extracting genimage..." 13 | tar -xJf /usr/src/${genimage_filename} 14 | 15 | echo "Building genimage..." 16 | cd ./${genimage_repopath} 17 | ./configure 18 | make 19 | 20 | echo "Installing genimage..." 21 | make install 22 | 23 | echo "Cleaning up..." 24 | rm -rf /vars.sh # We wanna use it n burn it 25 | rm -rf /usr/src/${genimage_repopath}* 26 | 27 | exit 0 28 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/cloud/cloud.cfg.d/99-none-omv-setup.cfg: -------------------------------------------------------------------------------- 1 | # configure cloud-init for None 2 | datasource_list: [ None ] 3 | datasource: 4 | None: 5 | metadata: 6 | local-hostname: "unvr-nas" 7 | userdata_raw: | 8 | #cloud-config 9 | hostname: unvr-nas 10 | 11 | # Setup for OMV 12 | runcmd: 13 | - DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get install -f -y 14 | - usermod -a -G _ssh debian 15 | - rm -rf /etc/systemd/network/first-boot-*.network 16 | - omv-salt deploy run hosts systemd-networkd 17 | - systemctl restart systemd-networkd 18 | 19 | # We will have OMV manage the nics 20 | network: 21 | config: disabled 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := build 2 | CONTAINER_NAME = unvr-nas:builder 3 | 4 | setup: 5 | sudo modprobe loop; \ 6 | sudo modprobe binfmt_misc 7 | 8 | build: setup 9 | @set -e; \ 10 | for file in `ls ./scripts/[0-99]*.sh`; \ 11 | do \ 12 | bash $${file}; \ 13 | done \ 14 | 15 | clean: mountclean 16 | sudo rm -rf $(CURDIR)/BuildEnv; \ 17 | docker ps -a | awk '{ print $$1,$$2 }' | grep $(CONTAINER_NAME) | awk '{print $$1 }' | xargs -I {} docker rm {}; 18 | 19 | distclean: clean 20 | docker rmi $(CONTAINER_NAME) -f; \ 21 | rm -rf $(CURDIR)/downloads $(CURDIR)/output 22 | 23 | mountclean: 24 | sudo umount $(CURDIR)/BuildEnv/rootfs/boot; \ 25 | sudo umount $(CURDIR)/BuildEnv/rootfs; \ 26 | sudo losetup -D 27 | -------------------------------------------------------------------------------- /scripts/04_post_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Source our common vars 5 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | . ${scripts_path}/vars.sh 7 | 8 | debug_msg "Starting 04_post_docker.sh" 9 | 10 | if [ -d ${build_path}/final ]; then 11 | debug_msg "WARNING: final builddir already exists! Cleaning up..." 12 | rm -rf ${build_path}/final 13 | fi 14 | mkdir -p ${build_path}/final 15 | 16 | # Kick off the docker to do the magics for us, since we need genimage 17 | docker run --rm -v "${root_path}:/repo:Z" -e BOARD="${BOARD}" -it ${docker_tag} /repo/scripts/docker/run_mkimage_final.sh 18 | 19 | # Just create our final dir and move bits over 20 | TIMESTAMP=`date +%Y%m%d-%H%M` 21 | mkdir -p ${root_path}/output/${TIMESTAMP} 22 | mv ${build_path}/final/debian*.img.gz ${root_path}/output/${TIMESTAMP}/ 23 | sudo rm -rf ${build_path} # Be gone, we done buildin! :) 24 | 25 | debug_msg "Finished 04_post_docker.sh" 26 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/lib/init/boot/ubnt-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | case "$1" in 4 | start) 5 | # Load our kernel modules 6 | /usr/sbin/modprobe ubnt-mtd-lock # Force our /dev/mtd* as RO 7 | /usr/sbin/modprobe btrfs 8 | 9 | # Set our kernel panic timeout SUPER short so we reboot on crash 10 | echo 2 > /proc/sys/kernel/panic 11 | 12 | # Setup bluetooth hci0 device 13 | /usr/lib/init/boot/ubnt-bt.sh hci0 14 | 15 | # If UNVR, turn on LED 16 | if [ -f "/sys/class/leds/ulogo_ctrl/pattern" ]; then 17 | # Set boot LED to blue 18 | # 2=white, 1=blue, 0=off, needs a value set with :x for ms 19 | echo 1:500 > /sys/class/leds/ulogo_ctrl/pattern 20 | fi 21 | ;; 22 | stop) 23 | # Tear down BT 24 | hciconfig hci0 down 25 | # LED shutdown on UNVR4 is done via systemd/system-shutdown/unifi-shutdown 26 | ;; 27 | *) 28 | echo "Invalid command $1" 29 | ;; 30 | esac 31 | -------------------------------------------------------------------------------- /scripts/02_download_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Source our common vars 5 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | . ${scripts_path}/vars.sh 7 | 8 | debug_msg "Starting 02_download_dependencies.sh" 9 | 10 | # Make sure our BuildEnv dir exists 11 | if [ ! -d ${root_path}/downloads ]; then 12 | mkdir ${root_path}/downloads 13 | fi 14 | 15 | # Toolchain 16 | if [ ! -f ${root_path}/downloads/${toolchain_filename} ]; then 17 | debug_msg "Downloading toolchain..." 18 | wget ${toolchain_url} -P ${root_path}/downloads 19 | fi 20 | 21 | # Kernel 22 | if [ ! -f ${root_path}/downloads/${kernel_filename} ]; then 23 | debug_msg "Downloading Kernel..." 24 | wget ${kernel_src} -O ${root_path}/downloads/${kernel_filename} 25 | fi 26 | 27 | # Bluez 28 | if [ ! -f ${root_path}/downloads/${bluez_filename} ]; then 29 | debug_msg "Downloading Package Bluez..." 30 | wget ${bluez_src} -O ${root_path}/downloads/${bluez_filename} 31 | fi 32 | 33 | debug_msg "Finished 02_download_dependencies.sh" 34 | -------------------------------------------------------------------------------- /scripts/docker/run_mkimage_final.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" 5 | . ${scripts_path}/vars.sh 6 | 7 | # Tempdir for root 8 | GENIMAGE_ROOT=$(mktemp -d) 9 | 10 | # setup and move bits 11 | mkdir -p ${build_path}/final 12 | cp ${build_path}/boot.ext4 ${build_path}/final/ 13 | cp ${build_path}/rootfs.ext4 ${build_path}/final/ 14 | cp ${root_path}/genimage_final.cfg ${build_path}/genimage.cfg 15 | 16 | # We get to gen an image per board, YAY! 17 | echo "Generating disk image" 18 | genimage \ 19 | --rootpath "${GENIMAGE_ROOT}" \ 20 | --tmppath "/tmp/genimage-initial-tmppath" \ 21 | --inputpath "${build_path}/final" \ 22 | --outputpath "${build_path}/final" \ 23 | --config "${build_path}/genimage.cfg" 24 | mv ${build_path}/final/emmc.img ${build_path}/final/debian-${BOARD}.img 25 | gzip ${build_path}/final/debian-${BOARD}.img 26 | rm -rf /tmp/genimage-initial-tmppath # Cleanup 27 | 28 | # Cleanup 29 | rm ${build_path}/final/boot.ext4 ${build_path}/final/rootfs.ext4 ${build_path}/genimage.cfg 30 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/bin/mock-ubnt-api: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | 4 | from flask import Flask, jsonify 5 | from functools import lru_cache 6 | import socket 7 | 8 | 9 | def __get_system_temp(): 10 | try: 11 | with open("/tmp/.unvr_temp", "r") as f: 12 | return float(f.read()) 13 | except (IOError, OSError, PermissionError) as e: 14 | print(f"Warning: Unable to get device temp!") 15 | return None 16 | 17 | 18 | @lru_cache(None) 19 | def __get_omv_version(): 20 | return os.popen("dpkg-query -W -f='${Version}' openmediavault").read() 21 | 22 | 23 | app = Flask(__name__) 24 | 25 | 26 | @app.route("/api/info") 27 | def api_info(): 28 | payload = { 29 | "isSetup": True, 30 | "hostname": socket.gethostname(), 31 | "hardware": { 32 | "firmwareVersion": f"OMV {__get_omv_version()}", # OMV version 33 | }, 34 | "cpu": { 35 | "temperature": __get_system_temp(), 36 | }, 37 | } 38 | return jsonify(payload) 39 | 40 | 41 | # No controllers for you 42 | @app.route("/api/controllers") 43 | def api_controllers(): 44 | return jsonify([]) 45 | 46 | 47 | if __name__ == "__main__": 48 | app.run(host="0.0.0.0", port=11081) 49 | -------------------------------------------------------------------------------- /tools/ubnteeprom/README.md: -------------------------------------------------------------------------------- 1 | # ubnteeprom 2 | 3 | A userspace tool to parse/read/render the EEPROM MTD partition on Unifi UNVR/UNVR Pro, and possibly other Dream Machine devices in the future. 4 | 5 | ## Purpose 6 | 7 | This tool was created as a userspace replacement for the functions Unifi's ubnthal proprietary kernel module provides, by reporting most of the same information out as `/proc/ubnthal/*` as well as some output from `ubnt-tools id`. 8 | 9 | The idea behind this is so we can get this repo off of using proprietary Unifi code as much as possible, so replacements for things are required. All code for this was reverse engineered and no unifi proprietary code was copied/used in the creation of this tool. 10 | 11 | ## Usage 12 | 13 | Get similar output to `/proc/ubnthal/board`: 14 | 15 | ubnteeprom -board 16 | 17 | Get similar output to `/proc/ubnthal/system.info`: 18 | 19 | ubnteeprom -systeminfo 20 | 21 | Get similar output to `ubnt-tools id`: 22 | 23 | ubnteeprom -tools 24 | 25 | Get a specfic value for a selected key in output, for example, `boardid`: 26 | 27 | ubnteeprom -board -key boardid 28 | 29 | ## Building 30 | 31 | ``` 32 | env GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ubnteeprom main.go 33 | ``` 34 | 35 | ## License 36 | 37 | This code is licensed under the GNU General Public License, version 2. A copy of said license can be found at [https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html#SEC1](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html#SEC1) 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Our AIO builder docker file 2 | FROM debian:12 3 | 4 | RUN mkdir /repo 5 | COPY ./scripts/vars.sh /vars.sh 6 | COPY ./scripts/docker/setup_mkimage.sh /setup_mkimage.sh 7 | 8 | RUN apt-get update && apt-get install -yq \ 9 | autoconf \ 10 | bc \ 11 | binfmt-support \ 12 | bison \ 13 | bsdextrautils \ 14 | build-essential \ 15 | cpio \ 16 | curl \ 17 | debootstrap \ 18 | debhelper \ 19 | device-tree-compiler \ 20 | dosfstools \ 21 | dwarves \ 22 | fakeroot \ 23 | flex \ 24 | genext2fs \ 25 | git \ 26 | kmod \ 27 | kpartx \ 28 | libconfuse-common \ 29 | libconfuse-dev \ 30 | libdbus-1-dev \ 31 | libelf-dev \ 32 | libglib2.0-dev \ 33 | libical-dev \ 34 | libncurses-dev \ 35 | libreadline-dev \ 36 | libssl-dev \ 37 | libudev-dev \ 38 | lvm2 \ 39 | mtools \ 40 | parted \ 41 | pkg-config \ 42 | python3-dev \ 43 | python3-pyelftools \ 44 | python3-setuptools \ 45 | qemu-utils \ 46 | qemu-user-static \ 47 | rsync \ 48 | squashfs-tools \ 49 | swig \ 50 | u-boot-tools \ 51 | unzip \ 52 | uuid-runtime \ 53 | wget \ 54 | && apt-get clean \ 55 | && rm -rf /var/lib/apt/lists/* \ 56 | && /setup_mkimage.sh \ 57 | && rm /setup_mkimage.sh \ 58 | && curl -fsSL "https://go.dev/dl/go1.22.4.linux-amd64.tar.gz" -o golang.tar.gz \ 59 | && tar -C /usr/local -xzf golang.tar.gz \ 60 | && rm golang.tar.gz \ 61 | && for bin in `ls /usr/local/go/bin/`; do \ 62 | update-alternatives --install "/usr/bin/$bin" "$bin" "/usr/local/go/bin/$bin" 1; \ 63 | update-alternatives --set "$bin" "/usr/local/go/bin/$bin"; \ 64 | done -------------------------------------------------------------------------------- /scripts/00_prereq_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Source our common vars 5 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | . ${scripts_path}/vars.sh 7 | 8 | debug_msg "Starting 00_prereq_check.sh" 9 | 10 | # Check for required utils 11 | for bin in losetup docker wget sudo unsquashfs; do 12 | if ! which ${bin} > /dev/null; then 13 | error_msg "${bin} is missing! Exiting..." 14 | exit 1 15 | fi 16 | done 17 | 18 | # Make sure loop module is loaded 19 | if [ ! -d /sys/module/loop ]; then 20 | error_msg "Loop module isn't loaded into the kernel! This is REQUIRED! Exiting..." 21 | exit 1 22 | fi 23 | 24 | # Did we have a board set? 25 | if [ -z "${BOARD}" ]; then 26 | echo "Error: BOARD is not set, so we don't know what we are building for! Exiting..." 27 | echo "Please review the README.md on usage!" 28 | exit 1 29 | elif [ -z "${firmware_filename}" ]; then 30 | # Board is set, make sure it's a board we support 31 | echo "Error: Invalid BOARD value of ${BOARD}. Please review the README.md on usage!" 32 | exit 1 33 | fi 34 | 35 | # Validate FW is downloaded 36 | if ! [ -f "${root_path}/unifi-firmware/${firmware_filename}" ]; then 37 | echo "Error: File ${firmware_filename} does not exist in ./unifi-firmware! Exiting..." 38 | exit 1 39 | fi 40 | 41 | # Does the checksum match? 42 | file_md5=$(md5sum ${root_path}/unifi-firmware/${firmware_filename} | awk '{print $1}') 43 | if [ "$file_md5" != "${firmware_md5}" ]; then 44 | echo "Error: File ${firmware_filename} does not have the expected checksum! This is either the wrong file, or it's corrupted. Exiting..." 45 | exit 1 46 | fi 47 | 48 | debug_msg "Finished 00_prereq_check.sh" 49 | -------------------------------------------------------------------------------- /scripts/docker/build_packages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" 5 | . ${scripts_path}/vars.sh 6 | 7 | # Exports baby 8 | export PATH=${build_path}/toolchain/${toolchain_bin_path}:${PATH} 9 | export GCC_COLORS=auto 10 | export CROSS_COMPILE=${toolchain_cross_compile} 11 | export ARCH=arm64 12 | 13 | # Make our temp builddir for bluez so we can make bccmd 14 | bluez_builddir=$(mktemp -d) 15 | tar -xzf ${root_path}/downloads/${bluez_filename} -C ${bluez_builddir} 16 | 17 | # Start with Bluez 18 | cd ${bluez_builddir}/${bluez_repopath} 19 | 20 | # If we have patches, apply them 21 | if [[ -d ${root_path}/patches/bluez/ ]]; then 22 | for file in ${root_path}/patches/bluez/*.patch; do 23 | echo "Applying bluez patch ${file}" 24 | patch -p1 < ${file} 25 | done 26 | fi 27 | 28 | # Build bccmd from bluez 29 | ./bootstrap 30 | ./configure --disable-systemd \ 31 | --enable-deprecated \ 32 | --disable-library \ 33 | --disable-cups \ 34 | --disable-datafiles \ 35 | --disable-manpages \ 36 | --disable-pie \ 37 | --disable-client \ 38 | --disable-obex \ 39 | --disable-udev \ 40 | --build=x86_64-linux-gnu \ 41 | --host=aarch64-none-linux-gnu \ 42 | --target=aarch64-none-linux-gnu 43 | make lib/bluetooth/hci.h lib/bluetooth/bluetooth.h lib/libbluetooth-internal.la tools/bccmd -j`getconf _NPROCESSORS_ONLN` 44 | 45 | # Save our binary 46 | mkdir -p ${build_path}/packages/bluez 47 | mv ./tools/bccmd ${build_path}/packages/bluez/ 48 | 49 | # Build ubnteeprom (our own tool) 50 | mkdir -p ${build_path}/packages/ubnteeprom 51 | env GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ${build_path}/packages/ubnteeprom/ubnteeprom ${root_path}/tools/ubnteeprom/main.go 52 | 53 | # Cleanup 54 | cd - > /dev/null 55 | rm -rf ${bluez_builddir} 56 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/lib/systemd/system-shutdown/unifi-shutdown: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ensure all mounts in /srv from OMV are cleaned up 4 | for dir in `ls /srv | grep dev-disk`; do 5 | umount -q /srv/${dir}; 6 | done 7 | 8 | # Give it a sec... 9 | sleep 1 10 | 11 | # Now for all disks, delete from bus 12 | for dsk in sd{a..z}; do 13 | if [ -d "/sys/block/${dsk}/device" ]; then 14 | echo 1 > "/sys/block/${dsk}/device/delete" 15 | fi 16 | done 17 | 18 | # Now for all disks, remove them with ui-hdd-pwrctl-v2 if we have it 19 | if [ -d "/sys/bus/platform/drivers/ui-hdd-pwrctl-v2" ]; then 20 | for bay in `seq 0 7`; do 21 | echo ${bay} > "$(realpath /sys/bus/platform/drivers/ui-hdd-pwrctl-v2/*hdd_pwrctl-v2)/hdd_force_poweroff" 22 | done 23 | fi 24 | 25 | # Let the disks spool down a sec... 26 | sleep 2 27 | 28 | # turn off fans for shutdown 29 | if [ -d "/sys/class/hwmon/hwmon0/device" ]; then 30 | for pwm in `ls /sys/class/hwmon/hwmon0/device/pwm[0-6]`; do 31 | echo 0 > "${pwm}" 32 | done 33 | elif [ -f "/sys/class/hwmon/hwmon0/pwm1" ]; then 34 | # Different hwmon devices may have this path instead 35 | for pwm in `ls /sys/class/hwmon/hwmon0/pwm[0-6]`; do 36 | echo 0 > "${pwm}" 37 | done 38 | fi 39 | 40 | # If UNVR, turn off LED 41 | if [ -f "/sys/class/leds/ulogo_ctrl/pattern" ]; then 42 | echo 0:500 > /sys/class/leds/ulogo_ctrl/pattern 43 | fi 44 | 45 | # Do a sync before we do abusive things 46 | sync 47 | 48 | # Finally, the UNVR's are known to not properly shutdown, due to either a kernel or u-boot thing we can't control, so 49 | # we need to hijack shutdowns and turn them into halts to actually keep the system from restarting. This isn't a watchdog 50 | # thing as far as I can tell, so ugh. This is why it would be nice to have GPL kernel/u-boot sources but alas. 51 | if [[ "$1" == "poweroff" ]]; then 52 | halt & 53 | sleep 30 # Let the halt bring us to the darkness we deserve 54 | fi 55 | -------------------------------------------------------------------------------- /scripts/vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | root_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" 4 | build_path="${root_path}/BuildEnv" 5 | 6 | # Docker image name 7 | docker_tag=unvr-nas:builder 8 | 9 | # Expected UNVR Firmware(s) and hash(s) 10 | UNVR_firmware_filename="0592-UNVR-4.1.9-6ea55371-e18f-4de9-a67e-a5c63bb0fc2f.bin" 11 | UNVR_firmware_md5="7222b70f9383781133e40a134eb0fc06" 12 | UNVRPRO_firmware_filename="ca04-UNVRPRO-4.1.9-0e3e9c30-7e9a-48ba-975d-2ad0ff8f8eee.bin" 13 | UNVRPRO_firmware_md5="ae41e9f61246877958c394f57b3618ca" 14 | 15 | # Render our board out 16 | fwfnvar="${BOARD}_firmware_filename" 17 | firmware_filename="${!fwfnvar}" 18 | fwmd5var="${BOARD}_firmware_md5" 19 | firmware_md5="${!fwmd5var}" 20 | 21 | # Toolchain 22 | toolchain_url="https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz" 23 | toolchain_filename="$(basename ${toolchain_url})" 24 | toolchain_bin_path="${toolchain_filename%.tar.xz}/bin" 25 | toolchain_cross_compile="aarch64-none-linux-gnu-" 26 | 27 | # Kernel 28 | kernel_src="https://github.com/fabianishere/udm-kernel/archive/refs/heads/master.tar.gz" 29 | kernel_filename="udm-kernel-master.tar.gz" 30 | kernel_config="alpine_v2_defconfig" 31 | kernel_overlay_dir="kernel" 32 | 33 | # Genimage 34 | genimage_src="https://github.com/pengutronix/genimage/releases/download/v16/genimage-16.tar.xz" 35 | genimage_filename="$(basename ${genimage_src})" 36 | genimage_repopath="${genimage_filename%.tar.xz}" 37 | 38 | # bluez 39 | bluez_src="https://github.com/bluez/bluez/archive/refs/tags/5.55.tar.gz" 40 | bluez_filename="bluez-$(basename ${bluez_src})" 41 | bluez_repopath="${bluez_filename%.tar.gz}" 42 | 43 | # Distro 44 | distrib_name="debian" 45 | deb_mirror="http://ftp.us.debian.org/debian" 46 | deb_release="bookworm" 47 | deb_arch="arm64" 48 | fs_overlay_dir="filesystem" 49 | 50 | debug_msg () { 51 | BLU='\033[0;32m' 52 | NC='\033[0m' 53 | printf "${BLU}${@}${NC}\n" 54 | } 55 | 56 | error_msg () { 57 | BLU='\033[0;31m' 58 | NC='\033[0m' 59 | printf "${BLU}${@}${NC}\n" 60 | } 61 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/lib/udev/rules.d/60-persistent-storage-ubnt-boot.rules: -------------------------------------------------------------------------------- 1 | # Support for Ubiquiti /dev/boot devices with udev 2 | 3 | ACTION=="remove", GOTO="persistent_emmc" 4 | 5 | SUBSYSTEM!="block", GOTO="persistent_emmc" 6 | KERNEL!="boot*", GOTO="persistent_emmc" 7 | 8 | # For partitions import parent disk ID_* information, except ID_FS_*. 9 | # 10 | # This is particularly important on media where a filesystem superblock and 11 | # partition table are found on the same level, e.g. common Linux distro ISO 12 | # installation media. 13 | # 14 | # In the case where a partition device points to the same filesystem that 15 | # was detected on the parent disk, the ID_FS_* information is already 16 | # present on the partition devices as well as the parent, so no need to 17 | # propagate it. In the case where the partition device points to a different 18 | # filesystem, merging the parent ID_FS_ properties would lead to 19 | # inconsistencies, so we avoid doing so. 20 | ENV{DEVTYPE}=="partition", \ 21 | IMPORT{parent}="ID_[!F]*", IMPORT{parent}="ID_", \ 22 | IMPORT{parent}="ID_F[!S]*", IMPORT{parent}="ID_F", \ 23 | IMPORT{parent}="ID_FS[!_]*", IMPORT{parent}="ID_FS" 24 | 25 | # probe filesystem metadata of disks 26 | KERNEL!="sr*|mmcblk[0-9]boot[0-9]", IMPORT{builtin}="blkid" 27 | 28 | # by-label/by-uuid links (filesystem metadata) 29 | ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}" 30 | ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_LABEL_ENC}=="?*", SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}" 31 | 32 | # by-path 33 | ENV{DEVTYPE}=="disk", DEVPATH!="*/virtual/*", IMPORT{builtin}="path_id" 34 | ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}" 35 | ENV{DEVTYPE}=="partition", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-part%n" 36 | 37 | # by-partlabel/by-partuuid links (partition metadata) 38 | IMPORT{builtin}="blkid" 39 | ENV{ID_PART_ENTRY_UUID}=="?*", SYMLINK+="disk/by-partuuid/$env{ID_PART_ENTRY_UUID}" 40 | ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}" 41 | 42 | LABEL="persistent_emmc" 43 | -------------------------------------------------------------------------------- /tools/ubnt-mtd-lock/ubnt-mtd-lock.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024 Chris Blake 3 | * 4 | * Inspired by mtd-rw: https://github.com/jclehner/mtd-rw/tree/master 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; version 2 of the License. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifndef MODULE 22 | #error "uvnt-mtd-lock must be compiled as a module." 23 | #endif 24 | 25 | #define MOD_INFO KERN_INFO "ubnt-mtd-lock: " 26 | #define MOD_ERR KERN_ERR "ubnt-mtd-lock: " 27 | 28 | static int set_readonly(unsigned n) 29 | { 30 | struct mtd_info *mtd = get_mtd_device(NULL, n); 31 | int err; 32 | 33 | if (IS_ERR(mtd)) { 34 | if (PTR_ERR(mtd) != -ENODEV) { 35 | printk(MOD_ERR "error probing mtd%d %ld\n", n, PTR_ERR(mtd)); 36 | } 37 | return PTR_ERR(mtd); 38 | } 39 | 40 | err = -EEXIST; 41 | 42 | if (mtd->flags & MTD_WRITEABLE) { 43 | printk(MOD_INFO "setting mtd%d \"%s\" readonly\n", n, mtd->name); 44 | mtd->flags &= ~MTD_WRITEABLE; 45 | err = 0; 46 | } 47 | 48 | put_mtd_device(mtd); 49 | return err; 50 | } 51 | 52 | int ubnt_mtd_lock_init(void) 53 | { 54 | int i, err; 55 | 56 | /* For all MTD partitions, go RO. Assume <15 for UNVR/UNVRPRO, as UNVR OG has 10 */ 57 | for (i = 0; i < 15; ++i) { 58 | err = set_readonly(i); 59 | if (err == -ENODEV) { 60 | break; 61 | } 62 | } 63 | 64 | return 0; 65 | } 66 | 67 | void ubnt_mtd_lock_exit(void) 68 | { 69 | /* Do nothing, we wanna keep mtd locked!!! */ 70 | } 71 | 72 | module_init(ubnt_mtd_lock_init); 73 | module_exit(ubnt_mtd_lock_exit); 74 | 75 | MODULE_LICENSE("GPL"); 76 | MODULE_AUTHOR("Chris Blake "); 77 | MODULE_DESCRIPTION("Unifi UNVR/UNVRPRO driver to force MTD partitions RO"); 78 | MODULE_VERSION("1"); 79 | -------------------------------------------------------------------------------- /scripts/docker/build_kernel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" 5 | . ${scripts_path}/vars.sh 6 | 7 | # Make our temp builddir outside of the world of mounts for SPEEDS 8 | kernel_builddir=$(mktemp -d) 9 | tar -xzf "${root_path}/downloads/${kernel_filename}" -C ${kernel_builddir} 10 | 11 | # Exports baby 12 | export PATH="${build_path}/toolchain/${toolchain_bin_path}":${PATH} 13 | export GCC_COLORS=auto 14 | export CROSS_COMPILE=${toolchain_cross_compile} 15 | export ARCH=arm64 16 | 17 | # Here we go 18 | cd ${kernel_builddir}/${kernel_filename%.tar.gz} 19 | 20 | # If we have patches, apply them 21 | if [[ -d "${root_path}/patches/kernel/" ]]; then 22 | for file in ${root_path}/patches/kernel/*.patch; do 23 | echo "Applying kernel patch ${file}" 24 | patch -p1 < ${file} 25 | done 26 | fi 27 | 28 | # Apply overlay if it exists 29 | if [[ -d "${root_path}/overlay/${kernel_overlay_dir}/" ]]; then 30 | echo "Applying ${kernel_overlay_dir} overlay" 31 | cp -R ${root_path}/overlay/${kernel_overlay_dir}/* ./ 32 | fi 33 | 34 | # Normally we would build a full kernel, but the old GPL doesn't work right with NICs and I don't 35 | # want to debug a 2+ year old GPL source. Waiting for Unifi to release the lastest GPL code, and 36 | # then we can fully move to our own custom kernel but for now we just use the old GPL to strip out 37 | # some modules. This is why some lines below are commented out. 38 | 39 | # Build as normal, with our extra version set to a timestamp 40 | make ${kernel_config} 41 | make -j`getconf _NPROCESSORS_ONLN` EXTRAVERSION=-alpine-unvr # Build kernel and modules 42 | #make -j`getconf _NPROCESSORS_ONLN` EXTRAVERSION=-alpine-unvr Image.gz # makes gzip image (we should just do this ourselves, skip using make) 43 | make INSTALL_MOD_PATH=./modules-dir -j`getconf _NPROCESSORS_ONLN` EXTRAVERSION=-alpine-unvr modules_install # installs modules to dir 44 | #mkimage -A arm64 -O linux -T kernel -C gzip -a 04080000 -e 04080000 -n "Linux-UNVR-NAS-$(date +%Y%m%d-%H%M%S)" -d ./arch/arm64/boot/Image.gz uImage 45 | 46 | # Save our config 47 | mkdir -p ${build_path}/kernel 48 | make savedefconfig 49 | mv defconfig ${build_path}/kernel/kernel_config 50 | 51 | # Save our kernel(s) and libs 52 | #cp ./arch/arm64/boot/Image.gz ${build_path}/kernel 53 | #mv uImage ${build_path}/kernel 54 | mv ./modules-dir ${build_path}/kernel/kernel-modules 55 | 56 | # Now that the kernel is done, build our out of tree modules! :) 57 | module_builddir=$(mktemp -d) 58 | cp ${root_path}/tools/ubnt-mtd-lock/* "${module_builddir}" 59 | cd "${module_builddir}" 60 | make -C ${kernel_builddir}/${kernel_filename%.tar.gz} M=$PWD 61 | cp "${module_builddir}/ubnt-mtd-lock.ko" ${build_path}/kernel 62 | -------------------------------------------------------------------------------- /overlay/filesystem/etc/cloud/cloud.cfg: -------------------------------------------------------------------------------- 1 | # The top level settings are used as module 2 | # and system configuration. 3 | # A set of users which may be applied and/or used by various modules 4 | # when a 'default' entry is found it will reference the 'default_user' 5 | # from the distro configuration specified below 6 | users: 7 | - default 8 | 9 | # If this is set, 'root' will not be able to ssh in and they 10 | # will get a message to login instead as the default $user 11 | disable_root: true 12 | 13 | # This will cause the set+update hostname module to not operate (if true) 14 | preserve_hostname: false 15 | 16 | apt: 17 | # This prevents cloud-init from rewriting apt's sources.list file, 18 | # which has been a source of surprise. 19 | preserve_sources_list: true 20 | 21 | # If you use datasource_list array, keep array items in a single line. 22 | # If you use multi line array, ds-identify script won't read array items. 23 | # Example datasource config 24 | # datasource: 25 | # Ec2: 26 | # metadata_urls: [ 'blah.com' ] 27 | # timeout: 5 # (defaults to 50 seconds) 28 | # max_wait: 10 # (defaults to 120 seconds) 29 | 30 | # The modules that run in the 'init' stage 31 | cloud_init_modules: 32 | - migrator 33 | - seed_random 34 | - bootcmd 35 | - write-files 36 | - growpart 37 | - resizefs 38 | - disk_setup 39 | - mounts 40 | - set_hostname 41 | - update_hostname 42 | - update_etc_hosts 43 | - ca-certs 44 | - rsyslog 45 | - users-groups 46 | - ssh 47 | 48 | # The modules that run in the 'config' stage 49 | cloud_config_modules: 50 | - snap 51 | - ssh-import-id 52 | - keyboard 53 | - locale 54 | - set-passwords 55 | - grub-dpkg 56 | - apt-pipelining 57 | - apt-configure 58 | - ntp 59 | - timezone 60 | - disable-ec2-metadata 61 | - runcmd 62 | - byobu 63 | 64 | # The modules that run in the 'final' stage 65 | cloud_final_modules: 66 | - package-update-upgrade-install 67 | - fan 68 | - landscape 69 | - lxd 70 | - write-files-deferred 71 | - puppet 72 | - chef 73 | - mcollective 74 | - salt-minion 75 | - reset_rmc 76 | - refresh_rmc_and_interface 77 | - rightscale_userdata 78 | - scripts-vendor 79 | - scripts-per-once 80 | - scripts-per-boot 81 | - scripts-per-instance 82 | - scripts-user 83 | - ssh-authkey-fingerprints 84 | - keys-to-console 85 | - install-hotplug 86 | - phone-home 87 | - final-message 88 | - power-state-change 89 | 90 | # System and/or distro specific settings 91 | # (not accessible to handlers/transforms) 92 | system_info: 93 | # This will affect which distro class gets used 94 | distro: debian 95 | # Default user name + that default users groups (if added/used) 96 | default_user: 97 | name: debian 98 | plain_text_passwd: 'debian' 99 | lock_passwd: False 100 | gecos: Debian 101 | groups: [adm, audio, cdrom, dialout, dip, floppy, netdev, plugdev, sudo, video] 102 | sudo: ["ALL=(ALL) NOPASSWD:ALL"] 103 | shell: /bin/bash 104 | # systemd networking 105 | network: 106 | renderers: ['networkd'] 107 | activators: ['networkd'] 108 | # Other config here will be given to the distro class and/or path classes 109 | paths: 110 | cloud_dir: /var/lib/cloud/ 111 | templates_dir: /etc/cloud/templates/ 112 | package_mirrors: 113 | - arches: [default] 114 | failsafe: 115 | primary: https://deb.debian.org/debian 116 | security: https://deb.debian.org/debian-security 117 | ssh_svcname: ssh 118 | -------------------------------------------------------------------------------- /scripts/docker/bootstrap/001-bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Starting 001-bootstrap within chroot!" 5 | 6 | export DEBIAN_FRONTEND=noninteractive 7 | export APT_LISTCHANGES_FRONTEND=none 8 | 9 | # Conf debconf 10 | debconf-set-selections /debconf.set 11 | rm -f /debconf.set 12 | 13 | # Run depmod for our kernel so we pick up btrfs + more 14 | depmod -a 4.19.152-alpine-unvr 15 | 16 | # Initial package install 17 | apt-get clean 18 | apt-get update 19 | apt-mark hold linux-image-* # We do not want these, as we run our own kernel! 20 | 21 | # Setup our services 22 | systemctl enable ubnt-init 23 | systemctl enable unvr-fan-daemon 24 | 25 | # Do we have ulcmd? if so, we are UNVRPRO so enable ulcmd services 26 | if [ -f "/usr/bin/ulcmd" ]; then 27 | systemctl enable mock-ubnt-api 28 | systemctl enable ulcmd 29 | systemctl enable ulcmd-reboot-hook 30 | systemctl enable ulcmd-shutdown-hook 31 | fi 32 | 33 | # Now that we have our wanted kernel in place, do the rest of our installs 34 | apt-get -o Dpkg::Options::="--force-confold" -y --allow-downgrades \ 35 | --allow-remove-essential --allow-change-held-packages install cloud-init \ 36 | bsdextrautils git binutils ca-certificates e2fsprogs haveged parted curl \ 37 | locales console-common openssh-server less vim net-tools wireguard-tools \ 38 | ntpsec u-boot-tools wget initramfs-tools python3-flask gnupg libc-ares2 \ 39 | dfu-util bluez 40 | 41 | # Enable bluetooth 42 | systemctl enable bluetooth 43 | 44 | # Locale gen 45 | locale-gen 46 | 47 | # Setup OMV repo 48 | wget --quiet --output-document=- https://packages.openmediavault.org/public/archive.key | gpg --dearmor --yes --output "/usr/share/keyrings/openmediavault-archive-keyring.gpg" 49 | cat <> /etc/apt/sources.list.d/openmediavault.list 50 | deb [signed-by=/usr/share/keyrings/openmediavault-archive-keyring.gpg] https://packages.openmediavault.org/public sandworm main 51 | # deb [signed-by=/usr/share/keyrings/openmediavault-archive-keyring.gpg] https://downloads.sourceforge.net/project/openmediavault/packages sandworm main 52 | ## This software is not part of OpenMediaVault, but is offered by third-party 53 | ## developers as a service to OpenMediaVault users. 54 | deb [signed-by=/usr/share/keyrings/openmediavault-archive-keyring.gpg] https://packages.openmediavault.org/public sandworm partner 55 | # deb [signed-by=/usr/share/keyrings/openmediavault-archive-keyring.gpg] https://downloads.sourceforge.net/project/openmediavault/packages sandworm partner 56 | EOF 57 | 58 | # Install OMV 59 | apt-get update 60 | apt-get --yes --auto-remove --show-upgraded \ 61 | --allow-downgrades --allow-change-held-packages \ 62 | --no-install-recommends \ 63 | --option DPkg::Options::="--force-confdef" \ 64 | --option DPkg::Options::="--force-confold" \ 65 | install openmediavault openmediavault-md || true # We "fail" all apt cmds from here on til we boot on HW 66 | 67 | # Setup NICs for OMV to manage 68 | jq --null-input --compact-output \ 69 | "{uuid: \"fa4b1c66-ef79-11e5-87a0-0002b3a176b4\", devicename: \"enp0s1\", method: \"dhcp\", method6: \"auto\"}" | \ 70 | omv-confdbadm update "conf.system.network.interface" - 71 | jq --null-input --compact-output \ 72 | "{uuid: \"fa4b1c66-ef79-11e5-87a0-0002b3a176b4\", devicename: \"enp0s2\", method: \"dhcp\", method6: \"auto\"}" | \ 73 | omv-confdbadm update "conf.system.network.interface" - 74 | 75 | # Set hostname 76 | omv-confdbadm update "conf.system.network.dns" "{\"hostname\": \"unvr-nas\"}" 77 | 78 | # Cleanup stuff we don't want floating around 79 | apt-get autoclean || true 80 | apt-get --purge -y autoremove || true 81 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /etc/resolv.conf 82 | rm -rf /var/lib/dbus/machine-id /etc/machine-id # Nuke machine IDs 83 | -------------------------------------------------------------------------------- /scripts/03_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Source our common vars 5 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | . ${scripts_path}/vars.sh 7 | 8 | debug_msg "Starting 03_docker.sh" 9 | 10 | # Start with things we can do now 11 | if [ ! -d ${build_path}/toolchain ]; then 12 | debug_msg "Setting up the toolchain for docker..." 13 | mkdir -p ${build_path}/toolchain 14 | tar -xf ${root_path}/downloads/${toolchain_filename} -C ${build_path}/toolchain 15 | fi 16 | 17 | # Do our firmware extraction 18 | debug_msg "Docker: Extracting Firmware..." 19 | docker run --ulimit nofile=1024 --rm -v "${root_path}:/repo:Z" -e BOARD="${BOARD}" -it ${docker_tag} /repo/scripts/docker/extract_firmware.sh 20 | 21 | if [ ! -d ${build_path}/kernel ]; then 22 | debug_msg "Docker: Building Kernel..." 23 | docker run --ulimit nofile=1024 --rm -v "${root_path}:/repo:Z" -it ${docker_tag} /repo/scripts/docker/build_kernel.sh 24 | fi 25 | if [ ! -d ${build_path}/packages ]; then 26 | debug_msg "Docker: Building Packages..." 27 | docker run --ulimit nofile=1024 --rm -v "${root_path}:/repo:Z" -it ${docker_tag} /repo/scripts/docker/build_packages.sh 28 | fi 29 | 30 | debug_msg "Doing safety checks... please enter your password for sudo if prompted..." 31 | # Before we do anything, make our dirs, and validate they are not mounted atm. If they are, exit! 32 | if mountpoint -q ${build_path}/rootfs/boot; then 33 | error_msg "ERROR: ${build_path}/rootfs/boot is mounted before it should be! Cleaning up..." 34 | sudo umount ${build_path}/rootfs/boot 35 | fi 36 | if mountpoint -q ${build_path}/rootfs; then 37 | error_msg "ERROR: ${build_path}/rootfs is mounted before it should be! Cleaning up..." 38 | sudo umount ${build_path}/rootfs 39 | fi 40 | 41 | # Validate we don't have image files yet, because if we do, they may be mounted, and 42 | # that would be REAL BAD if we overwrite em 43 | if [ -f ${build_path}/boot.ext4 ]; then 44 | error_msg "ERROR: ${build_path}/boot.ext4 exists already! Cleaning up..." 45 | rm -f ${build_path}/boot.ext4 46 | fi 47 | if [ -f ${build_path}/rootfs.ext4 ]; then 48 | error_msg "ERROR: ${build_path}/rootfs.ext4 exists already! Cleaning up..." 49 | rm -f ${build_path}/rootfs.ext4 50 | fi 51 | 52 | debug_msg "Docker: Generating rootfs and boot partitions..." 53 | docker run --ulimit nofile=1024 --rm -v "${root_path}:/repo:Z" -it ${docker_tag} /repo/scripts/docker/run_mkimage_initial.sh 54 | 55 | debug_msg "Note: You might be asked for your password for losetup and mounting of said loopback devices since sudo is used..." 56 | 57 | debug_msg "Mounting generated block files for use with docker..." 58 | # Mount our loopbacks 59 | boot_loop_dev=$(sudo losetup -f --show ${build_path}/boot.ext4) 60 | rootfs_loop_dev=$(sudo losetup -f --show ${build_path}/rootfs.ext4) 61 | 62 | # And now mount them to the dirs :) 63 | mkdir -p ${build_path}/rootfs 64 | sudo mount -t ext4 ${rootfs_loop_dev} ${build_path}/rootfs 65 | sudo mkdir -p ${build_path}/rootfs/boot 66 | sudo mount -t ext4 ${boot_loop_dev} ${build_path}/rootfs/boot 67 | 68 | # Remove stupid placeholder files -_- 69 | sudo rm -f ${build_path}/rootfs/placeholder ${build_path}/rootfs/boot/placeholder 70 | 71 | # SAFETY NET - trap it, even tho we have makefile with set -e 72 | debug_msg "Docker: debootstraping..." 73 | trap "sudo umount ${build_path}/rootfs/boot; sudo umount ${build_path}/rootfs; sudo losetup -d ${boot_loop_dev}; sudo losetup -d ${rootfs_loop_dev}" SIGINT SIGTERM 74 | docker run --ulimit nofile=1024 --rm --privileged --cap-add=ALL -h unvr-nas -v /dev:/dev -v "${root_path}:/repo:Z" -e BOARD="${BOARD}" -it ${docker_tag} /repo/scripts/docker/run_debootstrap.sh 75 | 76 | debug_msg "Note: You might be asked for your password for losetup and umount since we are cleaning up mounts..." 77 | debug_msg "Cleaning up..." 78 | sudo umount ${build_path}/rootfs/boot 79 | sudo umount ${build_path}/rootfs 80 | sudo losetup -d ${boot_loop_dev} 81 | sudo losetup -d ${rootfs_loop_dev} 82 | rm -rf ${build_path}/rootfs 83 | 84 | debug_msg "Finished 03_docker.sh" 85 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/lib/init/boot/ubnt-bt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function log_error() { 4 | echo "<3> ${*}" 1>&2 5 | } 6 | 7 | function get_bt_mac() { 8 | local bt_device_num="$1" 9 | if [ -z "${bt_device_num}" ]; then 10 | log_error "get_bt_mac: Unknown device num" 11 | return 1 12 | fi 13 | 14 | local hw_addr_base=$(ubnteeprom -board -key "hwaddrbbase") 15 | local eth_count=$(ubnteeprom -board -key "EthMACAddrCount") 16 | local wifi_count=$(ubnteeprom -board -key "WiFiMACAddrCount") 17 | local bt_count=$(ubnteeprom -board -key "BtMACAddrCount") 18 | 19 | if [ -z "${hw_addr_base}" ] || [ -z "${eth_count}" ] || [ -z "${wifi_count}" ] || [ -z "${bt_count}" ]; then 20 | log_error "Unexpected contents in $UBNTHAL_BOARD" 21 | return 2 22 | fi 23 | 24 | if [ ${bt_device_num} -ge ${bt_count} ]; then 25 | log_error "Unsupported device number (bt_device_num[${bt_device_num}] >= bt_count[${bt_count}])" 26 | return 3 27 | fi 28 | 29 | local mac=$(echo "${hw_addr_base}" | sed s/":"//g) 30 | local mac_dec=$(printf '%d\n' 0x${mac}) 31 | local bt_mac_dec=$(expr ${mac_dec} + ${eth_count} + ${wifi_count} + ${bt_device_num}) 32 | 33 | printf '%012X\n' "${bt_mac_dec}" | tr A-Z a-z 34 | } 35 | 36 | function main(){ 37 | BT_DEVICE="$1" 38 | [ -z "$BT_DEVICE" ] && return 2 39 | 40 | BT_DEVICE_NUM=$(echo "${BT_DEVICE}" | sed s/"hci"//g) 41 | if [[ ! "${BT_DEVICE_NUM}" =~ ^[0-9]+$ ]]; then 42 | log_error "Invalid bluetooth device number [${BT_DEVICE_NUM}]" 43 | return 3 44 | fi 45 | 46 | BT_MAC=$(get_bt_mac "${BT_DEVICE_NUM}") 47 | [ $? -eq 0 ] || return 4 48 | 49 | local board_id=$(ubnteeprom -board -key "boardid") 50 | case ${board_id} in 51 | ea16) 52 | # unvr: nothing to do here 53 | ;; 54 | ea1a) 55 | # unvr 56 | usb_based_init 57 | ;; 58 | ea20) 59 | # unvr-pro 60 | gpio_num=$(find_gpio_on_expander 0 0020 8) 61 | if [ $gpio_num -lt 0 ]; then 62 | return 5 63 | fi 64 | gpio_reset $gpio_num 65 | uart_based_init /dev/ttyS3 "/lib/firmware/csr8x11/csr8x11-a12-bt4.2-patch-2018_uart.psr" 66 | ;; 67 | *) 68 | return 4 69 | ;; 70 | esac 71 | } 72 | 73 | function find_gpio_on_expander() { 74 | local bus=$1 75 | local addr=$2 76 | local pin=$3 77 | local gpiochip_dir="/sys/bus/i2c/devices/$bus-$addr/gpio" 78 | local base 79 | 80 | for chip in $(find $gpiochip_dir -maxdepth 1 -name 'gpiochip*' -printf "%f\n"); do 81 | base=$(echo $chip | sed 's/gpiochip//g') 82 | echo $((base + pin)) 83 | return 84 | done 85 | 86 | echo -1 87 | } 88 | 89 | function gpio_reset(){ 90 | local gpio=$1 91 | 92 | if [ ! -d /sys/class/gpio/gpio${gpio} ]; then 93 | echo ${gpio} > /sys/class/gpio/export 94 | fi 95 | echo out > /sys/class/gpio/gpio${gpio}/direction 96 | echo 1 > /sys/class/gpio/gpio${gpio}/value 97 | sleep 1 98 | echo 0 > /sys/class/gpio/gpio${gpio}/value 99 | sleep 1 100 | echo 1 > /sys/class/gpio/gpio${gpio}/value 101 | sleep 1 102 | echo ${gpio} > /sys/class/gpio/unexport 103 | } 104 | 105 | function uart_based_init(){ 106 | local bt_serial_dev="$1" 107 | local bt_serial_speed="115200" 108 | local bt_fw="$2" 109 | local bt_option="$3" 110 | local bt_proto="bcsp" 111 | local loop_no=10 112 | local i=0 113 | 114 | for i in $(seq 0 ${loop_no}); do 115 | if [ ${i} -gt 0 ]; then 116 | if [ ${i} -eq ${loop_no} ]; then 117 | log_error "Failed to initialize bluetooth (BT is not operational)" 118 | break 119 | fi 120 | log_error "Unable to initialize bluetooth. Give it another try (${i})" 121 | fi 122 | 123 | #load psr file 124 | bccmd -t "${bt_proto}" -b "${bt_serial_speed}" -d "${bt_serial_dev}" psload -r ${bt_fw} || continue 125 | 126 | #set bt address: 0x00 0x00 127 | bccmd -t "${bt_proto}" -d "${bt_serial_dev}" -b "${bt_serial_speed}" psset -r \ 128 | 0x$((BT_DEVICE_NUM+1)) \ 129 | 0x${BT_MAC:6:2} \ 130 | 0x00 \ 131 | 0x${BT_MAC:10:2} \ 132 | 0x${BT_MAC:8:2} \ 133 | 0x${BT_MAC:4:2} \ 134 | 0x00 \ 135 | 0x${BT_MAC:2:2} \ 136 | 0x${BT_MAC:0:2} \ 137 | 2>&1 138 | 139 | # attach UART interface 140 | hciattach -s "${bt_serial_speed}" "${bt_serial_dev}" "${bt_proto}" "${bt_serial_speed}" "${bt_option}" 141 | sleep 0.5 142 | 143 | # check if the device exists 144 | [ -d "/sys/class/bluetooth/${BT_DEVICE}" ] && break 145 | 146 | done 147 | 148 | hciconfig ${BT_DEVICE} up 149 | } 150 | 151 | main "$@" 152 | -------------------------------------------------------------------------------- /scripts/ubnt-fw-parse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import argparse 3 | import mmap 4 | import sys 5 | import zlib 6 | 7 | from pathlib import Path 8 | 9 | 10 | def main(fwfile: str, savedir: str): 11 | # Does the file exist? 12 | if not Path(fwfile).is_file(): 13 | print(f"Error: {fwfile} is not a file! Exiting...") 14 | sys.exit(1) 15 | 16 | # Does our save dir exist? 17 | if not Path(savedir).is_dir(): 18 | print(f"Warning: {savedir} is not a directory, attempting to create it...") 19 | Path(savedir).mkdir(parents=True) 20 | 21 | # Start parsing the OTA file 22 | with open(fwfile, "rb+") as f: 23 | # Read from disk, not memory 24 | mm = mmap.mmap(f.fileno(), 0) 25 | 26 | ubntheader = mm.read(0x104) # Read header in 27 | ubntheadercrc = int.from_bytes(mm.read(0x4)) # Read header CRC, convert to int 28 | 29 | # Do we have the UBNT header? 30 | if ubntheader[0:4].decode("utf-8") != "UBNT": 31 | print( 32 | f"Error: {fwfile} is missing the UBNT header! Is this the right firmware file?" 33 | ) 34 | sys.exit(1) 35 | 36 | # Is the header CRC valid? 37 | if zlib.crc32(ubntheader) != ubntheadercrc: 38 | print( 39 | f"Error: {fwfile} has in incorrect CRC for it's header! Please re-download the file!" 40 | ) 41 | sys.exit(1) 42 | 43 | # If we are here, that's a great sign :) 44 | ubnt_fw_ver_string = ubntheader[4:].decode("utf-8") 45 | print(f"Loaded in firmware file {ubnt_fw_ver_string}") 46 | 47 | # Start parsing out all of the files in the OTA 48 | fcount = 1 49 | while True: 50 | file_header_offset = mm.find(b"\x46\x49\x4C\x45") # FILE in hex bye string 51 | # Are we done scanning the file? 52 | if file_header_offset == -1: 53 | break 54 | 55 | # We found one, seek to it 56 | mm.seek(file_header_offset) 57 | file_header = mm.read(0x38) # Entire header 58 | file_position = file_header[ 59 | 39 60 | ] # Increments with files read, can be used to validate this is a file for us 61 | file_location = ( 62 | file_header_offset + 0x38 63 | ) # header location - header = data :) 64 | 65 | # Is this a VALID file?! 66 | if fcount == file_position: 67 | file_name = ( 68 | file_header[4:33].decode("utf-8").rstrip("\x00") 69 | ) # Name/type of FILE 70 | file_length = int(file_header[48:52].hex(), 16) 71 | # Disabled because it's debug info and older python can't handle it 72 | #print( 73 | # f"{file_name} is at offset {"0x%0.2X" % file_location}, {file_length} bytes" 74 | #) 75 | # print(int(file_header[52:56].hex(), 16)) # Maybe reserved memory or partition size? We don't use this tho 76 | fcount = fcount + 1 # Increment on find! 77 | 78 | fcontents = mm.read(file_length) # Read into memory 79 | file_footer_crc32 = mm.read(0x8)[ 80 | 0:4 81 | ] # Read in tailing 8 bytes (crc32) footer, but we only want the first 4 82 | 83 | # Does our calculated crc32 match the unifi footer in the img? 84 | if hex(zlib.crc32(file_header + fcontents)).lstrip( 85 | "0x" 86 | ) != file_footer_crc32.hex().lstrip("0"): 87 | print( 88 | f"Error: Contents of {file_name} does not match the Unifi CRC! Please re-download the file!" 89 | ) 90 | sys.exit(1) 91 | 92 | # Write file out since our mmap position is now AT the data, and we parsed the length 93 | with open(f"{savedir}/{file_name}.bin", "wb") as wf: 94 | wf.write(fcontents) # Write out file 95 | print(f"{file_name} has been written to {savedir}/{file_name}.bin") 96 | 97 | del fcontents # Cleanup memory for next run 98 | 99 | print(f"Finished extracting the contents of {fwfile}, enjoy!") 100 | 101 | 102 | if __name__ == "__main__": 103 | parser = argparse.ArgumentParser("ubnt-fw-parse for Dream Machines") 104 | parser.add_argument("file", help="The Ubiquiti firmware file to parse", type=str) 105 | parser.add_argument( 106 | "savedir", help="The directory to save the parsed files to", type=str 107 | ) 108 | args = parser.parse_args() 109 | main(args.file, args.savedir) 110 | -------------------------------------------------------------------------------- /scripts/docker/run_debootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | docker_scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" 6 | . ${scripts_path}/vars.sh 7 | 8 | # Exports 9 | export PATH=${build_path}/toolchain/${toolchain_bin_path}:${PATH} 10 | export GCC_COLORS=auto 11 | export CROSS_COMPILE=${toolchain_cross_compile} 12 | export ARCH=arm64 13 | export DEBIAN_FRONTEND=noninteractive 14 | export DEBCONF_NONINTERACTIVE_SEEN=true 15 | 16 | # CD into our rootfs mount, and starts the fun! 17 | cd "${build_path}/rootfs" 18 | debootstrap --no-check-gpg --foreign --arch=${deb_arch} --include=apt-transport-https ${deb_release} "${build_path}/rootfs" ${deb_mirror} 19 | cp /usr/bin/qemu-aarch64-static usr/bin/ 20 | chroot "${build_path}/rootfs" /debootstrap/debootstrap --second-stage 21 | 22 | # Copy over our kernel modules and kernel from the FS image 23 | # Note that in the future, we wanna use our own kernel, but the current GPL is way too old!!!!! 24 | mv -f "${build_path}/fw-extract/${BOARD}-rootfs/lib/modules" "${build_path}/rootfs/lib" 25 | cp "${build_path}/fw-extract/${firmware_filename%.bin}/kernel.bin" "${build_path}/rootfs/boot/uImage" 26 | 27 | # We can burn ubnt's proprietary modules, so we don't bundle in this IP! 28 | rm "${build_path}/rootfs/lib/modules/4.19.152-alpine-unvr/extra/ubnt_common.ko" \ 29 | "${build_path}/rootfs/lib/modules/4.19.152-alpine-unvr/extra/ubnthal.ko" 30 | 31 | # Now, for the old kernel we built, pull in our extra modules we need! (depmod is done in bootstrap) 32 | cp "${build_path}/kernel/kernel-modules/lib/modules/4.19.152-alpine-unvr/kernel/lib/zstd/zstd_compress.ko" "${build_path}/rootfs/lib/modules/4.19.152-alpine-unvr/extra/" 33 | cp "${build_path}/kernel/kernel-modules/lib/modules/4.19.152-alpine-unvr/kernel/fs/btrfs/btrfs.ko" "${build_path}/rootfs/lib/modules/4.19.152-alpine-unvr/extra/" 34 | cp "${build_path}/kernel/kernel-modules/lib/modules/4.19.152-alpine-unvr/kernel/fs/fuse/fuse.ko" "${build_path}/rootfs/lib/modules/4.19.152-alpine-unvr/extra/" 35 | cp "${build_path}/kernel/kernel-modules/lib/modules/4.19.152-alpine-unvr/kernel/fs/fuse/cuse.ko" "${build_path}/rootfs/lib/modules/4.19.152-alpine-unvr/extra/" 36 | cp "${build_path}/kernel/ubnt-mtd-lock.ko" "${build_path}/rootfs/lib/modules/4.19.152-alpine-unvr/extra/" 37 | 38 | # Copy over our overlay if we have one 39 | if [[ -d ${root_path}/overlay/${fs_overlay_dir}/ ]]; then 40 | echo "Applying ${fs_overlay_dir} overlay" 41 | cp -R ${root_path}/overlay/${fs_overlay_dir}/* ./ 42 | fi 43 | 44 | # Hostname 45 | echo "unvr-nas" > "${build_path}/rootfs/etc/hostname" 46 | echo "127.0.1.1 unvr-nas" >> "${build_path}/rootfs/etc/hosts" 47 | 48 | # Console settings 49 | echo "console-common console-data/keymap/policy select Select keymap from full list 50 | console-common console-data/keymap/full select us 51 | " > "${build_path}/rootfs/debconf.set" 52 | 53 | # Copy over stuff for ulcmd on UNVRPRO. this is hacky, but that's this ENTIRE repo for you 54 | if [ "${BOARD}" == "UNVRPRO" ]; then 55 | mv "${build_path}/fw-extract/${BOARD}-rootfs/usr/bin/ulcmd" "${build_path}/rootfs/usr/bin/ulcmd" # LCD controller 56 | mv "${build_path}/fw-extract/${BOARD}-rootfs/usr/share/firmware" "${build_path}/rootfs/usr/share/" # LCD panel firmwares 57 | mkdir -p "${build_path}/rootfs/usr/lib/ubnt-fw/" # Home for ulcmd libraries 58 | for file in libgrpc++.so.1 libgrpc.so.10 libprotobuf.so.23 \ 59 | libssl.so.1.1 libcrypto.so.1.1 libabsl*.so.20200923 libatomic.so.1; do 60 | cp -H ${build_path}/fw-extract/${BOARD}-rootfs/usr/lib/aarch64-linux-gnu/${file} "${build_path}/rootfs/usr/lib/ubnt-fw/" 61 | done 62 | # Now for the REAL JANK! patch ulcmd so it doesn't rely on /proc/ubnthal, so we can use our userspace tool ubnteeprom 63 | sed -i 's|/proc/ubnthal/system.info|/tmp/.ubnthal_system_info|g' "${build_path}/rootfs/usr/bin/ulcmd" 64 | else 65 | # Remove UNVRPRO specific files/services 66 | rm "${build_path}/rootfs/etc/ld.so.conf.d/ubnt.conf" \ 67 | "${build_path}/rootfs/etc/systemd/system/mock-ubnt-api.service" \ 68 | "${build_path}/rootfs/etc/systemd/system/ulcmd.service" \ 69 | "${build_path}/rootfs/etc/systemd/system/ulcmd-reboot-hook.service" \ 70 | "${build_path}/rootfs/etc/systemd/system/ulcmd-shutdown-hook.service" \ 71 | "${build_path}/rootfs/usr/bin/mock-ubnt-api" \ 72 | "${build_path}/rootfs/usr/bin/ubnt-systool" \ 73 | "${build_path}/rootfs/usr/bin/ubnt-tools" \ 74 | "${build_path}/rootfs/usr/bin/ustorage" \ 75 | "${build_path}/rootfs/usr/lib/init/boot/ubnt-ulcmd.sh" 76 | fi 77 | 78 | # Copy over bluetooth firmware files 79 | mkdir -p "${build_path}/rootfs/lib/firmware" 80 | cp -R "${build_path}/fw-extract/${BOARD}-rootfs/lib/firmware/csr8x11" "${build_path}/rootfs/lib/firmware/" # LCD panel firmwares 81 | 82 | # Install our bccmd we compiled (less we use from unifi the better) 83 | cp -R "${build_path}/packages/bluez/bccmd" "${build_path}/rootfs/usr/bin" 84 | chmod +x "${build_path}/rootfs/usr/bin/bccmd" 85 | 86 | # Install our ubnteeprom tool 87 | cp -R "${build_path}/packages/ubnteeprom/ubnteeprom" "${build_path}/rootfs/usr/bin" 88 | chmod +x "${build_path}/rootfs/usr/bin/ubnteeprom" 89 | 90 | # Kick off bash setup script within chroot 91 | cp "${docker_scripts_path}/bootstrap/001-bootstrap" "${build_path}/rootfs/bootstrap" 92 | chroot "${build_path}/rootfs" /bootstrap 93 | rm "${build_path}/rootfs/bootstrap" 94 | 95 | # Final cleanup 96 | rm "${build_path}/rootfs/usr/bin/qemu-aarch64-static" 97 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/bin/unvr-fan-daemon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import re 4 | import time 5 | 6 | from functools import lru_cache 7 | 8 | SMARTCTL_PATH = "/usr/sbin/smartctl" 9 | 10 | THERMAL_SYS_PATHS = { 11 | "UNVRPRO": { 12 | "thermal": [ 13 | "/sys/devices/virtual/thermal/thermal_zone0/temp", 14 | "/sys/class/hwmon/hwmon0/device/temp1_input", 15 | "/sys/class/hwmon/hwmon0/device/temp2_input", 16 | "/sys/class/hwmon/hwmon0/device/temp3_input", 17 | ], 18 | "fan_pwms": [ 19 | "/sys/class/hwmon/hwmon0/device/pwm1", 20 | "/sys/class/hwmon/hwmon0/device/pwm2", 21 | "/sys/class/hwmon/hwmon0/device/pwm3", 22 | ], 23 | }, 24 | "UNVR4": { 25 | "thermal": [ 26 | "/sys/devices/virtual/thermal/thermal_zone0/temp", 27 | "/sys/class/hwmon/hwmon0/device/temp1_input", 28 | "/sys/class/hwmon/hwmon0/device/temp2_input", 29 | "/sys/class/hwmon/hwmon0/device/temp3_input", 30 | ], 31 | "fan_pwms": [ 32 | "/sys/class/hwmon/hwmon0/device/pwm1", 33 | "/sys/class/hwmon/hwmon0/device/pwm2", 34 | "/sys/class/hwmon/hwmon0/device/pwm3", 35 | ], 36 | }, 37 | } 38 | 39 | 40 | @lru_cache(None) 41 | def get_ubnt_shortname() -> str: 42 | return os.popen("ubnteeprom -systeminfo -key shortname").read().rstrip("\n") 43 | 44 | 45 | def __get_board_temps(): 46 | # Are we supported? 47 | if get_ubnt_shortname() not in THERMAL_SYS_PATHS: 48 | raise Exception( 49 | f"Error: Your Unifi device of {get_ubnt_shortname()} is not yet supported by unvr-fan-daemon! Exiting..." 50 | ) 51 | # For each of our paths, get the temps, and append to single return list 52 | board_temps = [] 53 | for path in THERMAL_SYS_PATHS[get_ubnt_shortname()]["thermal"]: 54 | try: 55 | with open(path, "r") as f: 56 | board_temps.append(int(f.readline().rstrip("\n"))) 57 | except FileNotFoundError: 58 | # If we are here, either it doesn't exist, OR, we need to change paths from 59 | # /sys/class/hwmon/hwmon0/device/temp*_input to /sys/class/hwmon/hwmon0/temp*_input 60 | # since there could be a different thermal sensor/controller being used on the board 61 | try: 62 | with open(path.replace('/device',''), "r") as f: 63 | board_temps.append(int(f.readline().rstrip("\n"))) 64 | except FileNotFoundError: 65 | print(f"Warning: Unable to open {path}; ignoring and continuing...") 66 | continue 67 | 68 | 69 | # Did we get ANY temps?!? 70 | if len(board_temps) == 0: 71 | raise Exception( 72 | "Error: Unable to parse out any board temps for your device, something is really wrong! Exiting..." 73 | ) 74 | 75 | return board_temps 76 | 77 | 78 | def __get_disk_temps(): 79 | # Find the list of all devices, which could be none 80 | devices = re.findall( 81 | r"^[/a-z]+", 82 | os.popen(f"{SMARTCTL_PATH} -n standby --scan").read(), 83 | re.MULTILINE, 84 | ) 85 | 86 | # For each disk, get the temp, and append to our list 87 | disk_temps = [] 88 | for dev in devices: 89 | # Sadly this is slow, SMART data pulls are not fast... 90 | dev_temp = re.search( 91 | r"^194 [\w-]+\s+0x\d+\s+\d+\s+\d+\s+\d+\s+[\w-]+\s+\w+\s+\S+\s+(\d+)(?:\s[\(][^)]*[\)])?$", 92 | os.popen(f"{SMARTCTL_PATH} -A {dev}").read(), 93 | re.MULTILINE, 94 | ) 95 | if dev_temp: 96 | disk_temps.append(int(f"{dev_temp.group(1)}000")) # Append zeros 97 | 98 | return disk_temps 99 | 100 | 101 | def __calculate_fan_speed(temp): 102 | # our basic fancurve logic 103 | match temp: 104 | case _ if temp < 40: 105 | fanspeed = 25 106 | case _ if temp >= 40 and temp < 50: 107 | fanspeed = 75 108 | case _ if temp >= 50 and temp < 55: 109 | fanspeed = 100 110 | case _ if temp >= 55 and temp < 60: 111 | fanspeed = 130 112 | case _ if temp >= 60 and temp < 65: 113 | fanspeed = 160 114 | case _ if temp >= 65 and temp < 70: 115 | fanspeed = 200 116 | case _: 117 | fanspeed = 255 118 | 119 | return fanspeed 120 | 121 | 122 | def __set_fan_speed(speed: int): 123 | # Set the fans 124 | for fan in THERMAL_SYS_PATHS[get_ubnt_shortname()]["fan_pwms"]: 125 | try: 126 | with open(fan, "w") as f: 127 | f.write(str(speed)) 128 | except FileNotFoundError: 129 | # If we are here, either it doesn't exist, OR, we need to change paths from 130 | # /sys/class/hwmon/hwmon0/device/pwm* to /sys/class/hwmon/hwmon0/pwm* since 131 | # there could be a different thermal sensor/controller being used on the board 132 | try: 133 | with open(fan.replace('/device',''), "w") as f: 134 | f.write(str(speed)) 135 | except FileNotFoundError: 136 | print( 137 | f"Error: Unable to write to PWM at {fan}! Why can't we set fan speed!?" 138 | ) 139 | raise 140 | 141 | 142 | def __write_out_temp(temp: int, path: str = "/tmp/.unvr_temp"): 143 | try: 144 | with open(path, "w+") as f: 145 | f.write(str(temp)) 146 | except (IOError, OSError, PermissionError) as e: 147 | print( 148 | f"Warning: Unable to write to temp file at {path}; ulcmd won't get the system temp! Error was: {e}" 149 | ) 150 | 151 | 152 | if __name__ == "__main__": 153 | # Cache so we only write to PWMs if this changes 154 | last_fanspeed = 0 155 | 156 | print("unvr-fan-daemon starting...") 157 | 158 | # Start with debug write to max speed so we hear it :) 159 | __set_fan_speed(255) 160 | time.sleep(0.5) 161 | 162 | # Start our main loop 163 | while True: 164 | # Get the fanspeed we wanna set based on temps 165 | temp = ( 166 | sorted(__get_board_temps() + __get_disk_temps(), reverse=True)[0] / 1000.0 167 | ) # Move temp to C 168 | 169 | fanspeed = __calculate_fan_speed(temp) 170 | 171 | # Write out for consumption by ulcmd via mock-ubnt-api 172 | __write_out_temp(temp) 173 | 174 | # If there's a change in calculated fan speed, set it 175 | if last_fanspeed != fanspeed: 176 | print(f"Setting fan PWMs to {fanspeed} due to temp of {temp}C") 177 | __set_fan_speed(fanspeed) 178 | last_fanspeed = fanspeed 179 | 180 | # Sleep and run again 181 | time.sleep(10) 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UNVR-NAS 2 | 3 | Firmware builder to convert your Unifi NVR/Unifi NVR Pro into an OpenMediaVault NAS appliance. 4 | 5 | **This repo is still under heavy development and should be considered alpha!** 6 | 7 | ## Supported Devices 8 | 9 | * UNVR 10 | * UNVR Pro 11 | 12 | ## Disclaimer 13 | 14 | Note that since prebuilt Ubiquiti software is currently required for this firmware, this repo doesn't have prebuilt images available. This is to prevent redistribution of Ubiquiti's IP, so please DO NOT ASK! Also, by using this repo you accept all risk associated with it including but not limited to voiding your warranty and releasing all parties from any liability associated with your device and this software. PROCEED AT YOUR OWN RISK! 15 | 16 | ## Usage 17 | 18 | 1. Download the required UNVR firmware for your device, and place it in the unifi-firmware directory. Please see the README.md in that directory for more information. 19 | 2. Make sure your linux system has the required packages installed for this repo, which are: 20 | 21 | `docker-ce losetup wget sudo make qemu-user-static squashfs-tools` 22 | 23 | Note that building from OSX/Windows is not supported. A Linux host is **REQUIRED**. 24 | 25 | 3. Run make with your board name set, and sit back and wait for the firmware image to build. Depending on your computer, this may take around an hour or so. 26 | 27 | For the UNVR: `BOARD=UNVR make` 28 | 29 | For the UNVR Pro: `BOARD=UNVRPRO make` 30 | 31 | * Also note that near the end of the image build process there will be some red text errors, but this is expected. This is due to the openmediavault install not being able to talk to systemd, as it's a debootstrap environment. 32 | 33 | 4. Once done, you will have a compressed disk image in ./output 34 | 35 | ## Installation 36 | 37 | Note that currently the install process requires UART to modify the u-boot env for booting. In the future, if I can get the latest kernel GPL source, this will not be required. 38 | 39 | 1. MAKE SURE your UNVR/UNVR Pro is running the same Unifi firmware as referenced in the README.md in the unifi-firmware directory. 40 | * **Failure to do this can cause issues from the installation process not working, to the touch screen not working!** 41 | 42 | 2. Build the firmware image (follow the Usage section), and then throw it on an HDD/SSD formatted to ext4. Put said HDD in the UNVR/UNVR Pro as the only hard drive. 43 | 44 | 3. Hook up UART to the UNVR/UNVR Pro: 45 | 46 | On the UNVR, UART is located on the PCB behind the SFP+ cage, near the middle of the board (4 pins). 47 | 48 | On the UNVR Pro, UART is located on the PCB near the DC Power Backup port (4 pins). 49 | 50 | 4. Boot the UNVR/UNVR Pro, and in your UART console press Escape (Esc) twice when prompted to get to the u-boot shell. You only have 2 seconds to do this! 51 | 5. Run the following commands to update the kernel cmdline and save the changes: 52 | 53 | ``` 54 | setenv rootfs PARTLABEL=rootfs 55 | setenv bootargsextra boot=local rw 56 | saveenv 57 | ``` 58 | 59 | 6. Boot into recovery. This can be done using the command below, or by unplugging the UNVR/UNVR Pro, and holding the reset button for 10~ seconds as you power it back up. 60 | 61 | `run bootcmdrecovery` 62 | 63 | 7. Once recovery is booted, login with `ubnt:ubnt` or `root:ubnt`. Note this can be done either via UART shell, or if you want you can telnet into the IP address of your UNVR/UNVR Pro in recovery if you have it networked. 64 | 65 | 8. Mount your HDD with the firmware image and then flash our custom firmware to the EMMC/Storage. (Note the examples below expect your HDD with the firmware to be at /dev/sda) 66 | 67 | * UNVR: 68 | 69 | * Mount your disk to /mnt 70 | 71 | ``` 72 | mount /dev/sda1 /mnt 73 | ``` 74 | 75 | * Write the UNVR-NAS firmware image to the EMMC/Storage 76 | 77 | Note that if you have an older UNVR with the internal USB drive, you will need to replace `/dev/boot` with the path of your USB drive! 78 | 79 | ``` 80 | gunzip /mnt/debian-UNVR.img.gz 81 | dd if=/mnt/debian-UNVR.img of=/dev/boot bs=4M 82 | sync 83 | reboot 84 | ``` 85 | 86 | * UNVR Pro: 87 | 88 | * Mount your disk to /mnt 89 | 90 | ``` 91 | mount /dev/sda1 /mnt 92 | ``` 93 | 94 | * Write the UNVR-NAS firmware image to the EMMC/Storage 95 | 96 | ``` 97 | gunzip /mnt/debian-UNVRPRO.img.gz 98 | dd if=/mnt/debian-UNVRPRO.img of=/dev/boot bs=4M 99 | sync 100 | reboot 101 | ``` 102 | 103 | 9. At this point you can remove the HDD/SSD you used, and enjoy Debian 12 with OpenMediaVault on your UNVR/UNVR Pro! Default login for OpenMediaVault is `admin:openmediavault`. SSH login information is `debian:debian`. Please note that first boot may take a bit as cloud-init runs to finish the setup. 104 | 105 | ## Removal 106 | 107 | To restore back to the factory UNVR/UNVR Pro firmware, you can do the following steps: 108 | 109 | 1. Hold the "reset" button on the front while powering on to boot into recovery 110 | 2. Once the device is in recovery mode, telnet to the IP address if the device (the UNVR Pro will display this on the touch screen). At the login prompt, login with `ubnt:ubnt` or `root:ubnt`. 111 | 3. Erase the uboot env, to remove our custom boot commands. This SHOULD be mtd1/mtd2, but **PLEASE VERIFY** first with `cat /proc/mtd` to prevent bricking your device! **DO NOT SKIP THIS STEP!** The output should match below, if not, **PLEASE DO NOT CONTINUE!** 112 | 113 | ``` 114 | $ cat /proc/mtd 115 | dev: size erasesize name 116 | mtd0: 001c0000 00001000 "u-boot" 117 | mtd1: 00010000 00001000 "u-boot env" 118 | mtd2: 00010000 00001000 "u-boot env redundant" 119 | mtd3: 00010000 00001000 "Factory" 120 | mtd4: 00010000 00001000 "EEPROM" 121 | mtd5: 01000000 00001000 "recovery kernel" 122 | mtd6: 00e00000 00001000 "config" 123 | ``` 124 | 125 | 4. Once the uboot env's are identified, erase them to remove the setting overrides we added during install: 126 | 127 | ``` 128 | dd if=/dev/zero of=/dev/mtd1 129 | dd if=/dev/zero of=/dev/mtd2 130 | ``` 131 | 132 | 5. Next, erase the EMMC so all partitions are wiped: 133 | 134 | Note that if you have an older UNVR with the internal USB drive, you will need to replace `/dev/boot` with the path of your USB drive! 135 | 136 | ``` 137 | /sbin/parted -s -- /dev/boot mklabel gpt 138 | ``` 139 | 140 | 6. Now you can use the Unifi Recovery WebUI to upload the firmware file, and restore your device. 141 | 142 | ## Known Issues 143 | 144 | * Installation is Hard 145 | * Need to simplify the install process, this should be much easier once I can get latest GPL kernel source (no more uboot env stuff) 146 | * Reset Button 147 | * Only works to reboot the system, may wire this up to reset the WebUI password in OpenMediaVault down the road 148 | -------------------------------------------------------------------------------- /overlay/filesystem/usr/bin/ustorage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import datetime 3 | import json 4 | import os 5 | import re 6 | import sys 7 | 8 | from functools import lru_cache 9 | 10 | @lru_cache(None) 11 | def get_ubnt_shortname() -> str: 12 | return os.popen("ubnteeprom -systeminfo -key shortname").read().rstrip("\n") 13 | 14 | DEVICE_DISK_INFO = { 15 | "UNVRPRO": { 16 | "scsi_map": { 17 | 1: "7:0:0:0", 18 | 2: "5:0:0:0", 19 | 3: "3:0:0:0", 20 | 4: "6:0:0:0", 21 | 5: "4:0:0:0", 22 | 6: "0:0:0:0", 23 | 7: "2:0:0:0", 24 | } 25 | }, 26 | } 27 | 28 | CACHE_FILE = "/tmp/.ustorage_cache" 29 | 30 | SMARTCTL_PATH = "/usr/sbin/smartctl" 31 | 32 | 33 | class UNVRDiskInfo: 34 | def __init__(self, disk_slot: int, scsi_id: str): 35 | self.disk_slot = disk_slot 36 | self.scsi_id = scsi_id 37 | self.blk_device = None 38 | self.__smartctl_output = None 39 | # Default no disk response 40 | self.__resp = { 41 | "healthy": "none", 42 | "reason": [], 43 | "slot": self.disk_slot, 44 | "state": "nodisk", 45 | } 46 | 47 | # Trigger a disk scan, and update smartctl if a disk exists 48 | if self.__scan_for_disk(): 49 | self.__smartctl_output = self.__get_smartctl_data() 50 | # Since we exist, build our return payload :) 51 | self.__resp = { 52 | "bad_sector": self.__parse_bad_sector(), 53 | "estimate": None, # Not sure what this is used for sadly 54 | "model": self.__read_file( 55 | f"/sys/class/scsi_disk/{self.scsi_id}/device/model" 56 | ), 57 | "node": self.blk_device, 58 | "size": self.__parse_disk_size(), 59 | "slot": self.disk_slot, 60 | "state": self.__parse_disk_state(), 61 | "temperature": self.__parse_disk_temp(), 62 | "type": self.__parse_disk_type(), 63 | "life_span": self.__parse_ssd_life_span(), 64 | } 65 | 66 | def __scan_for_disk(self): 67 | # Our path for the SCSI ID should always exist, but play it safe 68 | if os.path.exists(f"/sys/class/scsi_disk/{self.scsi_id}"): 69 | # Now, do we have a block device attached? 70 | blkdirlist = os.listdir(f"/sys/class/scsi_disk/{self.scsi_id}/device/block") 71 | if len(blkdirlist) > 0 and blkdirlist[0].startswith("sd"): 72 | # We found our disk, it has a /dev/sd* entry 73 | self.blk_device = blkdirlist[0] 74 | return True 75 | # No disk, return false 76 | return False 77 | 78 | def __get_smartctl_data(self): 79 | # Get our response from smartctl for the device for us to parse later 80 | return os.popen(f"{SMARTCTL_PATH} -iHA /dev/{self.blk_device}").read() 81 | 82 | def __parse_smartctl(self, input: str, regex: str): 83 | # Used to assist in parsing smartctl output 84 | search = re.search( 85 | regex, 86 | input, 87 | re.MULTILINE, 88 | ) 89 | if bool(search): 90 | return search.group(1) 91 | else: 92 | return None 93 | 94 | def __read_file(self, path: str): 95 | with open(path) as f: 96 | s = f.read().rstrip("\n").rstrip() 97 | return s 98 | 99 | def __parse_bad_sector(self): 100 | try: 101 | return int( 102 | self.__parse_smartctl( 103 | self.__smartctl_output, 104 | r"^ 5 [\w-]+\s+0x\d+\s+\d+\s+\d+\s+\d+\s+[\w-]+\s+\w+\s+\S+\s+(\d+)(?:\s[\(][^)]*[\)])?$", 105 | ) 106 | ) 107 | except: 108 | return None 109 | 110 | def __parse_disk_size(self): 111 | try: 112 | return int(self.__read_file(f"/sys/block/{self.blk_device}/size")) * int( 113 | self.__read_file( 114 | f"/sys/block/{self.blk_device}/queue/logical_block_size" 115 | ) 116 | ) 117 | except: 118 | return None 119 | 120 | def __parse_disk_state(self): 121 | # Do we pass SMART testing? 122 | if "PASSED" in self.__parse_smartctl( 123 | self.__smartctl_output, 124 | r"SMART overall-health self-assessment test result:\s*(.*)", 125 | ): 126 | return "normal" 127 | else: 128 | return "failed" 129 | 130 | def __parse_disk_temp(self): 131 | try: 132 | try: 133 | # First try the expected 194 134 | return int( 135 | self.__parse_smartctl( 136 | self.__smartctl_output, 137 | r"^194 [\w-]+\s+0x\d+\s+\d+\s+\d+\s+\d+\s+[\w-]+\s+\w+\s+\S+\s+(\d+)(?:\s[\(][^)]*[\)])?$", 138 | ) 139 | ) 140 | except: 141 | # Some other SSDs (cough, samsung) use 190 for airflow temp -_- 142 | return int( 143 | self.__parse_smartctl( 144 | self.__smartctl_output, 145 | r"^190 [\w-]+\s+0x\d+\s+\d+\s+\d+\s+\d+\s+[\w-]+\s+\w+\s+\S+\s+(\d+)(?:\s[\(][^)]*[\)])?$", 146 | ) 147 | ) 148 | except: 149 | return None 150 | 151 | def __parse_disk_type(self): 152 | if self.__parse_smartctl( 153 | self.__smartctl_output, r"Rotation Rate:\s+Solid State Device*(.)" 154 | ): 155 | return "SSD" 156 | else: 157 | return "HDD" 158 | 159 | def __parse_ssd_life_span(self): 160 | if self.__parse_disk_type() == "SSD": 161 | disk_span_raw = self.__parse_smartctl( 162 | self.__smartctl_output, 163 | r"^231 [\w-]+\s+0x\d+\s+\d+\s+\d+\s+\d+\s+[\w-]+\s+\w+\s+\S+\s+(\d+)(?:\s[\(][^)]*[\)])?$", 164 | ) 165 | # Did we have SMART value 231? 166 | if disk_span_raw: 167 | return 100 - int(disk_span_raw) 168 | 169 | # Return None if we are HDD, or can't get SSD life 170 | return None 171 | 172 | def get_payload(self): 173 | # Return our disk info 174 | return self.__resp 175 | 176 | 177 | def run_main(): 178 | # Are we supported? 179 | if get_ubnt_shortname() not in DEVICE_DISK_INFO: 180 | raise Exception( 181 | f"Error: Your Unifi device of {get_ubnt_shortname()} is not yet supported by ustorage! Exiting..." 182 | ) 183 | 184 | # Before we do all this work, have we ran before? If so, load in last run data and see if we can use it 185 | cache_data = None 186 | if os.path.isfile(CACHE_FILE): 187 | with open(CACHE_FILE, "r") as f: 188 | cache_data = json.loads(f.read()) 189 | 190 | # Get current list of block devices 191 | current_block_devs = ( 192 | os.popen( 193 | f"{SMARTCTL_PATH}" 194 | + " --scan | grep 'dev' | awk '{print $1}' | sed -e 's|/dev/||'" 195 | ) 196 | .read() 197 | .splitlines() 198 | ) 199 | 200 | # If we have a cache, do block devices match, and are we not expired? if so, return 201 | # the cached result instead of regenerating. 202 | if cache_data: 203 | if sorted(cache_data["block_devices"]) == sorted(current_block_devs) and ( 204 | datetime.datetime.now().timestamp() < cache_data["expiration"] 205 | ): 206 | return json.dumps(cache_data["response"]) 207 | 208 | # For each of our scsi IDs/ports, get disk info using our class and stash it in our response list 209 | ustorage_response = [] 210 | for port, scsi_id in DEVICE_DISK_INFO[get_ubnt_shortname()]["scsi_map"].items(): 211 | # Load and append our data 212 | ustorage_response.append(UNVRDiskInfo(port, scsi_id).get_payload()) 213 | 214 | # Now build our data to save 215 | save_data = { 216 | "block_devices": current_block_devs, 217 | # TODO: Figure out the right timeframe for this expiration 218 | "expiration": ( 219 | datetime.datetime.now() + datetime.timedelta(minutes=2) 220 | ).timestamp(), 221 | "response": ustorage_response, 222 | } 223 | 224 | # Save before we return... 225 | with open(CACHE_FILE, "w") as f: 226 | f.write(json.dumps(save_data)) 227 | 228 | # And were done here! 229 | return json.dumps(save_data["response"]) 230 | 231 | 232 | if __name__ == "__main__": 233 | # Only work if disk inspect is called 234 | if len(sys.argv) == 3 and sys.argv[1] == "disk" and sys.argv[2] == "inspect": 235 | print(run_main()) 236 | -------------------------------------------------------------------------------- /overlay/kernel/arch/arm64/configs/alpine_v2_defconfig: -------------------------------------------------------------------------------- 1 | # CONFIG_LOCALVERSION_AUTO is not set 2 | CONFIG_SYSVIPC=y 3 | CONFIG_POSIX_MQUEUE=y 4 | CONFIG_AUDIT=y 5 | CONFIG_NO_HZ_IDLE=y 6 | CONFIG_HIGH_RES_TIMERS=y 7 | CONFIG_PREEMPT_VOLUNTARY=y 8 | CONFIG_IRQ_TIME_ACCOUNTING=y 9 | CONFIG_BSD_PROCESS_ACCT=y 10 | CONFIG_BSD_PROCESS_ACCT_V3=y 11 | CONFIG_TASKSTATS=y 12 | CONFIG_TASK_DELAY_ACCT=y 13 | CONFIG_TASK_XACCT=y 14 | CONFIG_TASK_IO_ACCOUNTING=y 15 | CONFIG_IKCONFIG=y 16 | CONFIG_IKCONFIG_PROC=y 17 | CONFIG_CGROUPS=y 18 | CONFIG_MEMCG=y 19 | CONFIG_MEMCG_SWAP=y 20 | CONFIG_BLK_CGROUP=y 21 | CONFIG_CGROUP_SCHED=y 22 | CONFIG_CFS_BANDWIDTH=y 23 | CONFIG_RT_GROUP_SCHED=y 24 | CONFIG_CGROUP_FREEZER=y 25 | CONFIG_CPUSETS=y 26 | CONFIG_CGROUP_DEVICE=y 27 | CONFIG_CGROUP_CPUACCT=y 28 | CONFIG_USER_NS=y 29 | CONFIG_CHECKPOINT_RESTORE=y 30 | CONFIG_BLK_DEV_INITRD=y 31 | CONFIG_KALLSYMS_ALL=y 32 | CONFIG_DEBUG_PERF_USE_VMALLOC=y 33 | CONFIG_PROFILING=y 34 | CONFIG_ARCH_ALPINE=y 35 | CONFIG_PCI=y 36 | CONFIG_PCI_IOV=y 37 | CONFIG_PCI_INTERNAL_ALPINE=y 38 | CONFIG_PCI_EXTERNAL_ALPINE=y 39 | CONFIG_PCI_EXTERNAL_ERR_ALPINE=y 40 | CONFIG_NR_CPUS=4 41 | CONFIG_HZ_100=y 42 | CONFIG_CRASH_DUMP=y 43 | # CONFIG_ARM64_PAN is not set 44 | # CONFIG_ARM64_LSE_ATOMICS is not set 45 | # CONFIG_ARM64_VHE is not set 46 | # CONFIG_ARM64_SVE is not set 47 | CONFIG_CPU_IDLE=y 48 | CONFIG_CPU_IDLE_GOV_LADDER=y 49 | CONFIG_ARM_CPUIDLE=y 50 | # CONFIG_EFI_ARMSTUB_DTB_LOADER is not set 51 | # CONFIG_STACKPROTECTOR_STRONG is not set 52 | # CONFIG_VMAP_STACK is not set 53 | CONFIG_MODULES=y 54 | CONFIG_MODULE_UNLOAD=y 55 | CONFIG_BLK_DEV_THROTTLING=y 56 | CONFIG_BLK_CGROUP_IOLATENCY=y 57 | CONFIG_CFQ_GROUP_IOSCHED=y 58 | # CONFIG_MQ_IOSCHED_DEADLINE is not set 59 | # CONFIG_MQ_IOSCHED_KYBER is not set 60 | CONFIG_TRANSPARENT_HUGEPAGE=y 61 | CONFIG_NET=y 62 | CONFIG_PACKET=y 63 | CONFIG_UNIX=y 64 | CONFIG_XFRM_USER=y 65 | CONFIG_NET_KEY=m 66 | CONFIG_INET=y 67 | CONFIG_IP_MULTICAST=y 68 | CONFIG_IP_PNP=y 69 | CONFIG_IP_PNP_BOOTP=y 70 | CONFIG_INET_AH=m 71 | CONFIG_INET_ESP=m 72 | CONFIG_INET_IPCOMP=m 73 | CONFIG_INET_XFRM_MODE_TRANSPORT=m 74 | CONFIG_INET_XFRM_MODE_TUNNEL=m 75 | CONFIG_INET_XFRM_MODE_BEET=m 76 | # CONFIG_INET_DIAG is not set 77 | CONFIG_INET6_AH=m 78 | CONFIG_INET6_ESP=m 79 | CONFIG_INET6_IPCOMP=m 80 | CONFIG_INET6_XFRM_MODE_TRANSPORT=m 81 | CONFIG_INET6_XFRM_MODE_TUNNEL=m 82 | CONFIG_INET6_XFRM_MODE_BEET=m 83 | CONFIG_IPV6_SIT=m 84 | CONFIG_IPV6_MULTIPLE_TABLES=y 85 | CONFIG_IPV6_SUBTREES=y 86 | CONFIG_NET_DSA=m 87 | CONFIG_VLAN_8021Q=m 88 | CONFIG_BPF_JIT=y 89 | CONFIG_BT=y 90 | CONFIG_BT_RFCOMM=y 91 | CONFIG_BT_BNEP=y 92 | CONFIG_BT_HIDP=y 93 | CONFIG_BT_NORDIC_QUIRK_LOOKUP_FIX=y 94 | CONFIG_BT_HCIBTUSB=y 95 | CONFIG_BT_HCIUART=y 96 | CONFIG_BT_HCIUART_H4=y 97 | CONFIG_BT_HCIUART_BCSP=y 98 | CONFIG_FAILOVER=y 99 | CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" 100 | CONFIG_DEVTMPFS=y 101 | CONFIG_DEVTMPFS_MOUNT=y 102 | CONFIG_MTD=y 103 | CONFIG_MTD_BLOCK=y 104 | CONFIG_MTD_M25P80=y 105 | CONFIG_MTD_NAND=m 106 | CONFIG_MTD_NAND_AL=m 107 | CONFIG_MTD_SPI_NOR=y 108 | CONFIG_OF_OVERLAY=y 109 | CONFIG_BLK_DEV_LOOP=m 110 | CONFIG_BLK_DEV_RAM=m 111 | CONFIG_BLK_DEV_RAM_COUNT=1 112 | CONFIG_EEPROM_AT24=y 113 | CONFIG_EEPROM_AT25=m 114 | # CONFIG_SCSI_PROC_FS is not set 115 | CONFIG_BLK_DEV_SD=y 116 | CONFIG_CHR_DEV_SG=y 117 | # CONFIG_SCSI_SAS_HOST_SMP is not set 118 | CONFIG_SCSI_HISI_SAS=y 119 | CONFIG_SCSI_HISI_SAS_PCI=y 120 | CONFIG_SCSI_MPT2SAS=y 121 | CONFIG_SCSI_UFSHCD=m 122 | CONFIG_SCSI_UFSHCD_PLATFORM=m 123 | CONFIG_ATA=y 124 | # CONFIG_SATA_PMP is not set 125 | CONFIG_SATA_AHCI=y 126 | CONFIG_SATA_AHCI_PLATFORM=y 127 | CONFIG_AHCI_ALPINE=y 128 | CONFIG_SATA_SIL24=y 129 | # CONFIG_ATA_SFF is not set 130 | CONFIG_MD=y 131 | CONFIG_BLK_DEV_MD=m 132 | CONFIG_MD_LINEAR=m 133 | CONFIG_MD_RAID0=m 134 | CONFIG_MD_RAID1=m 135 | CONFIG_MD_RAID10=m 136 | CONFIG_MD_RAID456=m 137 | CONFIG_BLK_DEV_DM=y 138 | CONFIG_DM_FLAKEY=m 139 | CONFIG_DM_LOG_WRITES=m 140 | CONFIG_NETDEVICES=y 141 | # CONFIG_NET_VENDOR_3COM is not set 142 | # CONFIG_NET_VENDOR_ADAPTEC is not set 143 | # CONFIG_NET_VENDOR_AGERE is not set 144 | # CONFIG_NET_VENDOR_ALACRITECH is not set 145 | # CONFIG_NET_VENDOR_ALTEON is not set 146 | # CONFIG_NET_VENDOR_AMAZON is not set 147 | # CONFIG_NET_VENDOR_AMD is not set 148 | # CONFIG_NET_VENDOR_AQUANTIA is not set 149 | # CONFIG_NET_VENDOR_ARC is not set 150 | # CONFIG_NET_VENDOR_ATHEROS is not set 151 | # CONFIG_NET_VENDOR_AURORA is not set 152 | # CONFIG_NET_VENDOR_BROADCOM is not set 153 | # CONFIG_NET_VENDOR_BROCADE is not set 154 | # CONFIG_NET_VENDOR_CADENCE is not set 155 | # CONFIG_NET_VENDOR_CAVIUM is not set 156 | # CONFIG_NET_VENDOR_CHELSIO is not set 157 | # CONFIG_NET_VENDOR_CISCO is not set 158 | # CONFIG_NET_VENDOR_CORTINA is not set 159 | # CONFIG_NET_VENDOR_DEC is not set 160 | # CONFIG_NET_VENDOR_DLINK is not set 161 | # CONFIG_NET_VENDOR_EMULEX is not set 162 | # CONFIG_NET_VENDOR_EZCHIP is not set 163 | # CONFIG_NET_VENDOR_HISILICON is not set 164 | # CONFIG_NET_VENDOR_HP is not set 165 | # CONFIG_NET_VENDOR_HUAWEI is not set 166 | # CONFIG_NET_VENDOR_INTEL is not set 167 | # CONFIG_NET_VENDOR_MARVELL is not set 168 | # CONFIG_NET_VENDOR_MELLANOX is not set 169 | # CONFIG_NET_VENDOR_MICREL is not set 170 | # CONFIG_NET_VENDOR_MICROCHIP is not set 171 | # CONFIG_NET_VENDOR_MICROSEMI is not set 172 | # CONFIG_NET_VENDOR_MYRI is not set 173 | # CONFIG_NET_VENDOR_NATSEMI is not set 174 | # CONFIG_NET_VENDOR_NETERION is not set 175 | # CONFIG_NET_VENDOR_NETRONOME is not set 176 | # CONFIG_NET_VENDOR_NI is not set 177 | # CONFIG_NET_VENDOR_NVIDIA is not set 178 | # CONFIG_NET_VENDOR_OKI is not set 179 | # CONFIG_NET_VENDOR_PACKET_ENGINES is not set 180 | # CONFIG_NET_VENDOR_QLOGIC is not set 181 | # CONFIG_NET_VENDOR_QUALCOMM is not set 182 | # CONFIG_NET_VENDOR_RDC is not set 183 | # CONFIG_NET_VENDOR_REALTEK is not set 184 | # CONFIG_NET_VENDOR_RENESAS is not set 185 | # CONFIG_NET_VENDOR_ROCKER is not set 186 | # CONFIG_NET_VENDOR_SAMSUNG is not set 187 | # CONFIG_NET_VENDOR_SEEQ is not set 188 | # CONFIG_NET_VENDOR_SOLARFLARE is not set 189 | # CONFIG_NET_VENDOR_SILAN is not set 190 | # CONFIG_NET_VENDOR_SIS is not set 191 | # CONFIG_NET_VENDOR_SMSC is not set 192 | # CONFIG_NET_VENDOR_SOCIONEXT is not set 193 | # CONFIG_NET_VENDOR_STMICRO is not set 194 | # CONFIG_NET_VENDOR_SUN is not set 195 | # CONFIG_NET_VENDOR_SYNOPSYS is not set 196 | CONFIG_NET_AL_ETH=y 197 | # CONFIG_NET_VENDOR_TEHUTI is not set 198 | # CONFIG_NET_VENDOR_TI is not set 199 | # CONFIG_NET_VENDOR_VIA is not set 200 | # CONFIG_NET_VENDOR_WIZNET is not set 201 | CONFIG_AR8033_DISABLE_EEE=y 202 | CONFIG_AT8033_SEL_1P8=y 203 | CONFIG_AT803X_PHY=y 204 | CONFIG_MARVELL_PHY=m 205 | CONFIG_MARVELL_10G_PHY=m 206 | CONFIG_MICREL_PHY=y 207 | CONFIG_MICROCHIP_PHY=m 208 | CONFIG_REALTEK_PHY=y 209 | CONFIG_ROCKCHIP_PHY=y 210 | # CONFIG_USB_NET_DRIVERS is not set 211 | # CONFIG_WLAN is not set 212 | # CONFIG_INPUT_LEDS is not set 213 | CONFIG_INPUT_POLLDEV=m 214 | CONFIG_INPUT_MATRIXKMAP=y 215 | CONFIG_INPUT_EVDEV=y 216 | # CONFIG_KEYBOARD_ATKBD is not set 217 | CONFIG_KEYBOARD_GPIO=y 218 | # CONFIG_INPUT_MOUSE is not set 219 | # CONFIG_SERIO_SERPORT is not set 220 | CONFIG_SERIO_AMBAKMI=y 221 | CONFIG_VT_HW_CONSOLE_BINDING=y 222 | CONFIG_LEGACY_PTY_COUNT=16 223 | CONFIG_SERIAL_8250=y 224 | CONFIG_SERIAL_8250_CONSOLE=y 225 | CONFIG_SERIAL_8250_EXTENDED=y 226 | CONFIG_SERIAL_8250_SHARE_IRQ=y 227 | CONFIG_SERIAL_8250_DW=y 228 | CONFIG_SERIAL_OF_PLATFORM=y 229 | CONFIG_HW_RANDOM=y 230 | CONFIG_HW_RANDOM_CAVIUM=m 231 | CONFIG_I2C=y 232 | CONFIG_I2C_CHARDEV=y 233 | CONFIG_I2C_MUX=y 234 | CONFIG_I2C_MUX_PCA954x=y 235 | CONFIG_I2C_DESIGNWARE_PLATFORM=y 236 | CONFIG_I2C_DESIGNWARE_PCI=y 237 | CONFIG_SPI=y 238 | CONFIG_SPI_DESIGNWARE=y 239 | CONFIG_SPI_DW_MMIO=y 240 | CONFIG_SPI_SPIDEV=y 241 | CONFIG_SPMI=y 242 | CONFIG_PTP_1588_CLOCK=y 243 | CONFIG_GPIOLIB=y 244 | CONFIG_GPIO_SYSFS=y 245 | CONFIG_GPIO_GENERIC_PLATFORM=y 246 | CONFIG_GPIO_PL061=y 247 | CONFIG_GPIO_AL_SGPO=y 248 | CONFIG_GPIO_PCA953X=y 249 | CONFIG_GPIO_PCA953X_IRQ=y 250 | CONFIG_POWER_RESET_ALPINE=y 251 | CONFIG_SENSORS_ADT7475=y 252 | CONFIG_SENSORS_LM63=y 253 | CONFIG_THERMAL=y 254 | CONFIG_AL_THERMAL_V2=y 255 | CONFIG_AL_THERMAL_V3=y 256 | CONFIG_MFD_SYSCON=y 257 | CONFIG_VGA_ARB_MAX_GPUS=0 258 | # CONFIG_USB_HID is not set 259 | CONFIG_USB=y 260 | CONFIG_USB_XHCI_HCD=y 261 | CONFIG_USB_XHCI_PLATFORM=y 262 | CONFIG_USB_EHCI_HCD=y 263 | CONFIG_USB_EHCI_ROOT_HUB_TT=y 264 | CONFIG_USB_EHCI_HCD_PLATFORM=y 265 | CONFIG_USB_OHCI_HCD=y 266 | CONFIG_USB_OHCI_HCD_PLATFORM=y 267 | CONFIG_USB_ACM=y 268 | CONFIG_USB_STORAGE=y 269 | CONFIG_USB_SERIAL=y 270 | CONFIG_USB_SERIAL_CP210X=y 271 | CONFIG_USB_SERIAL_FTDI_SIO=y 272 | CONFIG_NEW_LEDS=y 273 | CONFIG_LEDS_CLASS=y 274 | CONFIG_LEDS_GPIO=y 275 | CONFIG_LEDS_TRIGGERS=y 276 | CONFIG_LEDS_TRIGGER_EXTERNAL=y 277 | CONFIG_EDAC=y 278 | CONFIG_RTC_CLASS=y 279 | # CONFIG_RTC_NVMEM is not set 280 | CONFIG_RTC_DRV_DS1307=y 281 | CONFIG_RTC_DRV_S35390A=y 282 | CONFIG_DMADEVICES=y 283 | CONFIG_ASYNC_TX_DMA=y 284 | CONFIG_AL_DMA=y 285 | CONFIG_AL_DMA_STATS=y 286 | CONFIG_AL_DMA_PCI_IOV=y 287 | CONFIG_SYNC_FILE=y 288 | # CONFIG_VIRTIO_MENU is not set 289 | CONFIG_COMMON_CLK_VERSATILE=y 290 | CONFIG_CLK_SP810=y 291 | # CONFIG_COMMON_CLK_XGENE is not set 292 | # CONFIG_FSL_ERRATUM_A008585 is not set 293 | # CONFIG_HISILICON_ERRATUM_161010101 is not set 294 | # CONFIG_ARM64_ERRATUM_858921 is not set 295 | # CONFIG_IOMMU_SUPPORT is not set 296 | CONFIG_ALPINE_PLATFORM="ALPINE_V2" 297 | CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y 298 | CONFIG_MEMORY=y 299 | CONFIG_ALPINE_IOFIC=y 300 | CONFIG_GENERIC_PHY=y 301 | CONFIG_ARM_CCI_PMU=y 302 | # CONFIG_ARM_CCI5xx_PMU is not set 303 | # CONFIG_ARM_PMU is not set 304 | CONFIG_RAS=y 305 | CONFIG_EXT4_FS=y 306 | CONFIG_EXT4_FS_POSIX_ACL=y 307 | CONFIG_BTRFS_FS=m 308 | CONFIG_BTRFS_FS_POSIX_ACL=y 309 | # CONFIG_MANDATORY_FILE_LOCKING is not set 310 | CONFIG_QUOTA=y 311 | # CONFIG_PRINT_QUOTA_WARNING is not set 312 | CONFIG_FUSE_FS=m 313 | CONFIG_CUSE=m 314 | CONFIG_OVERLAY_FS=y 315 | CONFIG_PROC_KCORE=y 316 | CONFIG_TMPFS=y 317 | CONFIG_TMPFS_POSIX_ACL=y 318 | CONFIG_HUGETLBFS=y 319 | CONFIG_CONFIGFS_FS=y 320 | CONFIG_EFIVAR_FS=y 321 | CONFIG_ECRYPT_FS=y 322 | CONFIG_SQUASHFS=m 323 | CONFIG_SQUASHFS_FILE_DIRECT=y 324 | CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU=y 325 | CONFIG_SQUASHFS_XATTR=y 326 | CONFIG_SQUASHFS_ZSTD=y 327 | CONFIG_PSTORE=y 328 | CONFIG_PSTORE_CONSOLE=y 329 | CONFIG_PSTORE_RAM=m 330 | CONFIG_NFS_FS=y 331 | CONFIG_NFS_V3_ACL=y 332 | CONFIG_NFS_V4=y 333 | CONFIG_NFS_SWAP=y 334 | CONFIG_NFS_V4_1=y 335 | CONFIG_NFS_V4_2=y 336 | CONFIG_NFS_V4_1_MIGRATION=y 337 | CONFIG_ROOT_NFS=y 338 | CONFIG_NFSD=y 339 | CONFIG_NFSD_V3_ACL=y 340 | CONFIG_NFSD_V4=y 341 | CONFIG_NFSD_BLOCKLAYOUT=y 342 | CONFIG_NFSD_SCSILAYOUT=y 343 | CONFIG_NFSD_FLEXFILELAYOUT=y 344 | CONFIG_NFSD_FAULT_INJECTION=y 345 | CONFIG_SUNRPC_DEBUG=y 346 | CONFIG_CIFS=y 347 | # CONFIG_CIFS_DEBUG is not set 348 | CONFIG_NLS_ISO8859_1=y 349 | CONFIG_ENCRYPTED_KEYS=y 350 | CONFIG_CRYPTO_RSA=y 351 | # CONFIG_CRYPTO_MANAGER_DISABLE_TESTS is not set 352 | CONFIG_CRYPTO_GF128MUL=y 353 | CONFIG_CRYPTO_CRYPTD=y 354 | CONFIG_CRYPTO_AUTHENC=y 355 | CONFIG_CRYPTO_ECHAINIV=y 356 | CONFIG_CRYPTO_SHA1=y 357 | CONFIG_CRYPTO_LZO=y 358 | CONFIG_CRYPTO_ANSI_CPRNG=y 359 | CONFIG_CRC64=y 360 | CONFIG_LIBCRC32C=y 361 | CONFIG_PRINTK_TIME=y 362 | CONFIG_CONSOLE_LOGLEVEL_DEFAULT=4 363 | CONFIG_MAGIC_SYSRQ=y 364 | CONFIG_DEBUG_KERNEL=y 365 | CONFIG_SOFTLOCKUP_DETECTOR=y 366 | CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=y 367 | CONFIG_PANIC_ON_OOPS=y 368 | CONFIG_PANIC_TIMEOUT=5 369 | CONFIG_STACKTRACE=y 370 | # CONFIG_RCU_TRACE is not set 371 | # CONFIG_UPROBE_EVENTS is not set 372 | # CONFIG_TRACING_EVENTS_GPIO is not set 373 | # CONFIG_RUNTIME_TESTING_MENU is not set 374 | # CONFIG_STRICT_DEVMEM is not set 375 | -------------------------------------------------------------------------------- /tools/ubnteeprom/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Copyright (C) 2024 Chris Blake 4 | // Licensed under the GNU Public License, version 2 5 | 6 | import ( 7 | "encoding/hex" 8 | "flag" 9 | "fmt" 10 | "net" 11 | "os" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | // Type for device map 17 | type UBNTSysMap struct { 18 | name, shortname, sysshortname, cpu, sysid string 19 | } 20 | 21 | var UBNTDeviceMap = []UBNTSysMap{ 22 | { 23 | name: "UniFi Network Video Recorder Pro", 24 | shortname: "UNVRPRO", 25 | sysshortname: "UNVRPRO", 26 | cpu: "AL324V2", 27 | sysid: "ea20", 28 | }, 29 | { 30 | name: "UniFi Network Video Recorder", 31 | shortname: "UNVR", 32 | sysshortname: "UNVR4", 33 | cpu: "AL324V2", 34 | sysid: "ea16", // USB drive (non emmc) variant 35 | }, 36 | { 37 | name: "UniFi Network Video Recorder", 38 | shortname: "UNVR", 39 | sysshortname: "UNVR4", 40 | cpu: "AL324V2", 41 | sysid: "ea1a", 42 | }, 43 | } 44 | 45 | type UBNT_Return_Values struct { 46 | name, value string 47 | } 48 | 49 | // Store our board vars 50 | var UBNT_Board_Vars = []string{ 51 | "format", 52 | "version", 53 | "boardid", 54 | "vendorid", 55 | "bomrev", 56 | "hwaddrbbase", 57 | "EthMACAddrCount", 58 | "WiFiMACAddrCount", 59 | "BtMACAddrCount", 60 | "regdmn[0]", 61 | "regdmn[1]", 62 | "regdmn[2]", 63 | "regdmn[3]", 64 | "regdmn[4]", 65 | "regdmn[5]", 66 | "regdmn[6]", 67 | "regdmn[7]", 68 | } 69 | 70 | // Store our systeminfo vars 71 | var UBNT_SystemInfo_Vars = []string{ 72 | // "cpu", 73 | // "cpuid", 74 | // "flashSize", 75 | // "ramsize", 76 | "vendorid", 77 | "systemid", 78 | // "shortname", 79 | "boardrevision", 80 | "serialno", 81 | // "manufid", 82 | // "mfgweek", 83 | // "qrid", 84 | // eth*.macaddr (generated mac's for all eth interfaces) 85 | // "device.hashid", 86 | // "device.anonid", 87 | // "bt0.macaddr", 88 | "regdmn[]", 89 | // "cpu_rev_id", 90 | } 91 | 92 | // Store our ubnt-tools id vars 93 | var UBNT_Tools_vars = []string{ 94 | "board.sysid", 95 | "board.serialno", 96 | "board.bom", 97 | } 98 | 99 | // Type for EEPROM structure 100 | type EEPROM_Value struct { 101 | name, description, vtype string 102 | offset, length int64 103 | } 104 | 105 | // Build our BOARD eeprom structure 106 | var EEPROM = []EEPROM_Value{ 107 | // START BOARD DATA 108 | { 109 | name: "format", 110 | description: "EEPROM Format", 111 | offset: 0x800C, 112 | length: 0x2, 113 | vtype: "hex", 114 | }, 115 | { 116 | name: "version", 117 | description: "EEPROM Version", 118 | offset: 0x800E, 119 | length: 0x2, 120 | vtype: "hex", 121 | }, 122 | { 123 | name: "boardid", 124 | description: "Board Identifier (bomrev+model identifier)", 125 | offset: 0x8012, 126 | length: 0x2, 127 | vtype: "hex", 128 | }, 129 | { 130 | name: "vendorid", 131 | description: "Vendor Identifier", 132 | offset: 0x8010, 133 | length: 0x2, 134 | vtype: "hex", 135 | }, 136 | { 137 | name: "bomrev", 138 | description: "Bill of Materials Revision", 139 | offset: 0x8014, 140 | length: 0x4, 141 | vtype: "hex", 142 | }, 143 | { 144 | name: "hwaddrbbase", 145 | description: "Base Mac Address for Device", 146 | offset: 0x8018, 147 | length: 0x6, 148 | vtype: "hexmac", 149 | }, 150 | { 151 | name: "EthMACAddrCount", 152 | description: "Number of MAC addresses for ethernet interfaces", 153 | offset: 0x801E, 154 | length: 0x1, 155 | vtype: "hexint", 156 | }, 157 | { 158 | name: "WiFiMACAddrCount", 159 | description: "Number of MAC addresses for WiFi interfaces", 160 | offset: 0x801F, 161 | length: 0x1, 162 | vtype: "hexint", 163 | }, 164 | { 165 | name: "BtMACAddrCount", 166 | description: "Number of MAC addresses for Bluetooth interfaces", 167 | offset: 0x8070, 168 | length: 0x1, 169 | vtype: "hexint", 170 | }, 171 | { 172 | name: "regdmn[0]", 173 | description: "Region Domain 0", 174 | offset: 0x8020, 175 | length: 0x2, 176 | vtype: "hex", 177 | }, 178 | { 179 | name: "regdmn[1]", 180 | description: "Region Domain 1", 181 | offset: 0x8022, 182 | length: 0x2, 183 | vtype: "hex", 184 | }, 185 | { 186 | name: "regdmn[2]", 187 | description: "Region Domain 2", 188 | offset: 0x8024, 189 | length: 0x2, 190 | vtype: "hex", 191 | }, 192 | { 193 | name: "regdmn[3]", 194 | description: "Region Domain 3", 195 | offset: 0x8026, 196 | length: 0x2, 197 | vtype: "hex", 198 | }, 199 | { 200 | name: "regdmn[4]", 201 | description: "Region Domain 4", 202 | offset: 0x8028, 203 | length: 0x2, 204 | vtype: "hex", 205 | }, 206 | { 207 | name: "regdmn[5]", 208 | description: "Region Domain 5", 209 | offset: 0x802A, 210 | length: 0x2, 211 | vtype: "hex", 212 | }, 213 | { 214 | name: "regdmn[6]", 215 | description: "Region Domain 6", 216 | offset: 0x802C, 217 | length: 0x2, 218 | vtype: "hex", 219 | }, 220 | { 221 | name: "regdmn[7]", 222 | description: "Region Domain 7", 223 | offset: 0x802E, 224 | length: 0x2, 225 | vtype: "hex", 226 | }, 227 | // END BOARD DATA 228 | // START SYSTEM INFO 229 | // cpu 230 | // cpuid 231 | // flashSize (this is static in unifi's kernel module -_-) 232 | // ramsize 233 | { 234 | name: "vendorid", 235 | description: "Vendor Identifier", 236 | offset: 0x8010, 237 | length: 0x2, 238 | vtype: "hex", 239 | }, 240 | { 241 | name: "systemid", 242 | description: "Device Model/Revision Identifier", 243 | offset: 0xC, 244 | length: 0x2, 245 | vtype: "hex", 246 | }, 247 | // shortname (we may wanna map boardid for this) 248 | { 249 | name: "boardrevision", 250 | description: "Board Revision for the device", 251 | offset: 0x13, 252 | length: 0x1, 253 | vtype: "hextobase10", 254 | }, 255 | { 256 | name: "serialno", 257 | description: "Serial Number", 258 | offset: 0x0, 259 | length: 0x6, 260 | vtype: "hex", 261 | }, 262 | // manufid 263 | // mfgweek 264 | // qrid 265 | // eth*.macaddr (generated mac's for all eth interfaces) 266 | // device.hashid 267 | // device.anonid 268 | // bt0.macaddr (generated bt mac) 269 | { 270 | name: "regdmn[]", 271 | description: "Region Domain", 272 | offset: 0x8020, 273 | length: 0x10, 274 | vtype: "hex", 275 | }, 276 | // cpu_rev_id 277 | // END SYSTEM INFO 278 | // START UBNT TOOLS ID 279 | { 280 | name: "board.sysid", 281 | description: "Device Model/Revision Identifier", 282 | offset: 0xC, 283 | length: 0x2, 284 | vtype: "hex", 285 | }, 286 | // board.name (handled by UBNTDeviceMap) 287 | // board.shortname (handled by UBNTDeviceMap) 288 | // board.subtype 289 | // board.reboot # Time (s) 290 | // board.upgrade # Time (s) 291 | // board.cpu.id 292 | // board.uuid 293 | { 294 | name: "board.bom", 295 | description: "Board Bill of Materials Revision Code", 296 | offset: 0xD024, 297 | length: 0xC, 298 | vtype: "str", 299 | }, 300 | // board.hwrev 301 | { 302 | name: "board.serialno", 303 | description: "Serial Number", 304 | offset: 0x0, 305 | length: 0x6, 306 | vtype: "hex", 307 | }, 308 | // board.qrid 309 | // END UBNT TOOLS ID 310 | } 311 | 312 | func check(e error) { 313 | if e != nil { 314 | if strings.Contains(e.Error(), "encoding/hex: invalid byte:") { 315 | fmt.Printf("Error: Unable to parse hex, please check your input.\n") 316 | } else if strings.Contains(e.Error(), "EOF") { 317 | fmt.Printf("Error: Unable to read contents from EEPROM/file.\n") 318 | } else if strings.Contains(e.Error(), "encoding/hex: odd length hex string") { 319 | fmt.Printf("Error: Incorrect length of input. Please try again.\n") 320 | } else { 321 | fmt.Printf("Fatal Error!!! ") 322 | panic(e) 323 | } 324 | os.Exit(1) 325 | } 326 | } 327 | 328 | func eeprom_read(vl []EEPROM_Value, f *os.File, key string) string { 329 | var offs, leng int64 330 | var vtype string 331 | 332 | // Lookup our item 333 | for _, v := range vl { 334 | if v.name == key { 335 | offs = v.offset 336 | leng = v.length 337 | vtype = v.vtype 338 | break 339 | } 340 | } 341 | 342 | // Make sure we found it 343 | if (offs == 0) && (leng == 0) { 344 | fmt.Printf("Error: Invalid key %s!\n", key) 345 | os.Exit(1) 346 | } 347 | 348 | // Seek our file 349 | _, err := f.Seek(offs, 0) 350 | check(err) 351 | 352 | // Make var and read into it 353 | b2 := make([]byte, leng) 354 | _, err = f.Read(b2) 355 | check(err) 356 | 357 | // Format as needed depending on type 358 | switch vtype { 359 | case "hexmac": 360 | // Mac is stored in hex, but we need to format the return 361 | macstr := net.HardwareAddr(b2[:]).String() 362 | return macstr 363 | case "hexint": 364 | // Int is stored in hex 365 | if b2[0]&0x0 == 0x0 { 366 | // We start with 0, so strip 367 | return strings.TrimPrefix(hex.EncodeToString(b2), `0`) 368 | } else { 369 | // We do not start with 0, so carry on 370 | return hex.EncodeToString(b2) 371 | } 372 | case "hextobase10": 373 | // Hex value needs to be moved to base 10 374 | base_conversion, err := strconv.ParseInt(hex.EncodeToString(b2), 16, 64) 375 | check(err) 376 | return strconv.Itoa(int(base_conversion)) 377 | case "str": 378 | // String value 379 | return string(b2) 380 | default: 381 | // Default is to assume hex 382 | return hex.EncodeToString(b2) 383 | } 384 | } 385 | 386 | func return_values(rtype string, file string, filter string) { 387 | var okeys []string 388 | var ourboard UBNTSysMap 389 | var ret_data []UBNT_Return_Values 390 | 391 | // Open EEPROM for read only 392 | f, err := os.Open(file) 393 | defer f.Close() 394 | check(err) 395 | 396 | // Start with our sysid for extra vars, pull it out of our list 397 | board_sysid := eeprom_read(EEPROM, f, "systemid") 398 | for _, board := range UBNTDeviceMap { 399 | if board.sysid == board_sysid { 400 | ourboard = board 401 | break 402 | } 403 | } 404 | 405 | // did we find our device? 406 | if ourboard.name == "" { 407 | fmt.Printf("Error: Unknown board sysid of %s! This device is not yet supported.\n", board_sysid) 408 | os.Exit(1) 409 | } 410 | 411 | // Load in the right list of keys to itterate over, add static keys if we got em 412 | if rtype == "board" { 413 | okeys = UBNT_Board_Vars 414 | } else if rtype == "system" { 415 | okeys = UBNT_SystemInfo_Vars 416 | ret_data = append(ret_data, UBNT_Return_Values{name: "cpu", value: ourboard.cpu}) 417 | ret_data = append(ret_data, UBNT_Return_Values{name: "shortname", value: ourboard.sysshortname}) 418 | } else if rtype == "tools" { 419 | okeys = UBNT_Tools_vars 420 | ret_data = append(ret_data, UBNT_Return_Values{name: "board.name", value: ourboard.name}) 421 | ret_data = append(ret_data, UBNT_Return_Values{name: "board.shortname", value: ourboard.shortname}) 422 | } 423 | 424 | // Populate our eeprom data for the rest of the data 425 | for _, v := range okeys { 426 | ret_data = append(ret_data, UBNT_Return_Values{name: v, value: eeprom_read(EEPROM, f, v)}) 427 | } 428 | 429 | // Do we have a filter? if so select and return single item 430 | if len(filter) > 0 { 431 | // Does our filter item exist in our ret object? 432 | for _, v := range ret_data { 433 | if v.name == filter { 434 | fmt.Printf("%s\n", v.value) 435 | return 436 | } 437 | } 438 | // If we are here, our key didn't exist 439 | fmt.Printf("Error: Invalid key %s!\n", filter) 440 | os.Exit(1) 441 | } else { 442 | // Return all items because we have no filter 443 | for _, v := range ret_data { 444 | fmt.Printf("%s=%s\n", v.name, v.value) 445 | } 446 | } 447 | } 448 | 449 | func main() { 450 | // Start by defining our args 451 | argfile := flag.String("file", "/dev/mtd4", "The path to the EEPROM for the device") 452 | argboard := flag.Bool("board", false, "Print the board values") 453 | argsystem := flag.Bool("systeminfo", false, "Print the system info values") 454 | argtools := flag.Bool("tools", false, "Print similar values to ubnt-tools id") 455 | argfilter := flag.String("key", "", "Used to select a specific EEPROM value") 456 | flag.Parse() 457 | 458 | // Verify we can read our eeprom file 459 | if _, err := os.Stat(*argfile); os.IsNotExist(err) { 460 | fmt.Printf("Error: Unable to access %s.\n", *argfile) 461 | os.Exit(1) 462 | } 463 | 464 | // Is board set? 465 | if *argboard { 466 | return_values("board", *argfile, *argfilter) 467 | } else if *argsystem { 468 | return_values("system", *argfile, *argfilter) 469 | } else if *argtools { 470 | return_values("tools", *argfile, *argfilter) 471 | } else { 472 | // Tell user noting was submitted 473 | fmt.Fprintf(os.Stderr, "Error Invalid usage of %s:\n", os.Args[0]) 474 | flag.PrintDefaults() 475 | os.Exit(1) 476 | } 477 | 478 | // We be done 479 | os.Exit(0) 480 | } 481 | --------------------------------------------------------------------------------