2026-10-03 16:12:25 +08:00
#!/usr/bin/env bash
set -euo pipefail
# Run from a committed, published release checkout on an x86_64 Ubuntu host.
2026-10-03 16:21:00 +08:00
# VISION_BIND is a comma-separated list of explicit private/Tailscale IPv4s.
2026-10-03 16:12:25 +08:00
# MEDIA_ARCHIVE optionally points to an already downloaded pinned release archive.
[[ $( id -u) == 0 ]] || { echo 'Run as root' ; exit 1; }
[[ $( uname -m) == x86_64 ]] || { echo 'This installer targets x86_64' ; exit 1; }
command -v python3 >/dev/null
2026-10-03 16:49:48 +08:00
if ! command -v ffmpeg >/dev/null; then
apt-get update
DEBIAN_FRONTEND = noninteractive apt-get install -y --no-install-recommends ffmpeg
fi
2026-10-03 16:58:04 +08:00
if [[ -f /sys/class/drm/renderD128/device/vendor ]] && [[ $( cat /sys/class/drm/renderD128/device/vendor) == 0x8086 ]] ; then
if ! dpkg-query -W -f= '${Status}' intel-media-va-driver-non-free 2>/dev/null | grep -q 'install ok installed' ; then
apt-get update
DEBIAN_FRONTEND = noninteractive apt-get install -y --no-install-recommends intel-media-va-driver-non-free
fi
fi
2026-10-03 16:12:25 +08:00
release_dir = $( cd -- " $( dirname -- " $0 " ) /.." && pwd )
bind_address = ${ VISION_BIND :- 127 .0.0.1 }
python3 - " $bind_address " <<'PY'
import ipaddress,sys
2026-10-03 16:21:00 +08:00
for value in sys.argv[1].split(','):
ip=ipaddress.ip_address(value.strip())
assert ip.version==4 and not ip.is_unspecified and not ip.is_multicast and (ip.is_private or ip in ipaddress.ip_network('100.64.0.0/10')), 'Use explicit private or Tailscale addresses'
2026-10-03 16:12:25 +08:00
PY
getent passwd zhaovision >/dev/null || useradd --system --home-dir /var/lib/zhaovision --shell /usr/sbin/nologin zhaovision
2026-10-03 16:58:04 +08:00
if getent group render >/dev/null; then
usermod -a -G render zhaovision
fi
2026-10-03 16:12:25 +08:00
install -d -o zhaovision -g zhaovision -m 700 /var/lib/zhaovision /var/lib/zhaovision/recordings
install -d -m 755 /opt/zhaovision /usr/local/lib/zhaovision
archive = ${ MEDIA_ARCHIVE :- /tmp/mediamtx_v1.21.1_linux_amd64.tar.gz }
if [[ ! -f " $archive " ]] ; then
curl -fL --retry 3 'https://github.com/bluenviron/mediamtx/releases/download/v1.21.1/mediamtx_v1.21.1_linux_amd64.tar.gz' -o " $archive "
fi
python3 - " $archive " <<'PY'
import hashlib,sys
expected='653abc672a3e693f8d3b2717752492fdcfb8072291ec108d03d3dd857411b0ee'
assert hashlib.file_digest(open(sys.argv[1],'rb'),'sha256').hexdigest()==expected, 'MediaMTX checksum mismatch'
PY
tar -xzf " $archive " -C /usr/local/lib/zhaovision mediamtx LICENSE
chmod 755 /usr/local/lib/zhaovision/mediamtx
if [[ -e /opt/zhaovision/current && ! -L /opt/zhaovision/current ]] ; then
echo '/opt/zhaovision/current is not a symlink; refusing to replace it' ; exit 1
fi
ln -sfn " $release_dir " /opt/zhaovision/current
if [[ ! -f /etc/zhaovision.env ]] ; then
umask 077
printf 'VISION_BIND=%s\nVISION_PORT=8790\nVISION_DATA=/var/lib/zhaovision\nVISION_RECORDINGS=/var/lib/zhaovision/recordings\n' " $bind_address " > /etc/zhaovision.env
fi
install -m 644 " $release_dir /deploy/zhaovision.service" /etc/systemd/system/zhaovision.service
install -m 644 " $release_dir /deploy/zhaovision-media.service" /etc/systemd/system/zhaovision-media.service
systemctl daemon-reload
systemctl enable zhaovision.service zhaovision-media.service
systemctl restart zhaovision.service
for attempt in { 1..20} ; do
[[ -s /var/lib/zhaovision/mediamtx.yml ]] && break
sleep 1
done
systemctl restart zhaovision-media.service
echo 'Services started. Initial setup code: /var/lib/zhaovision/setup-code.txt'
echo "Tailscale entry: http:// ${ bind_address } :8790/"