docs: mark APK distribution as deployed
This commit is contained in:
@@ -0,0 +1,449 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# yaris-obd — one-shot installer for a freshly flashed Raspberry Pi OS.
|
||||
#
|
||||
# Takes a blank Raspberry Pi OS Bookworm (Lite or desktop) install all the way
|
||||
# to a running yaris-obd deployment:
|
||||
# * base packages + NetworkManager
|
||||
# * the `yaris` service user (with sudo + serial/GPIO groups)
|
||||
# * repo clone at /home/yaris/app/yaris-obd
|
||||
# * RTL8812BU driver for the two TP-Link Archer T3U dongles (wlan1/wlan2)
|
||||
# * NetworkManager profiles: home-wifi, Hotspot-local, optional phone-hotspot
|
||||
# * every deploy/install_*.sh in the correct order
|
||||
#
|
||||
# Run ON THE PI as root (or via sudo):
|
||||
# sudo bash auto_install.sh
|
||||
#
|
||||
# The only questions it asks are for the WiFi credentials it needs. You can
|
||||
# instead pass them through the environment for fully non-interactive runs:
|
||||
#
|
||||
# HOME_WIFI_SSID="MyWiFi" \
|
||||
# HOME_WIFI_PSK="secret" \
|
||||
# HOTSPOT_SSID="yaris-obd" \
|
||||
# HOTSPOT_PSK="in-car-password" \
|
||||
# sudo -E bash auto_install.sh --non-interactive
|
||||
#
|
||||
# Optional flags:
|
||||
# --non-interactive fail instead of prompting for missing credentials
|
||||
# --skip-t3u-driver do not build the RTL8812BU driver (wlan1/wlan2)
|
||||
# --no-watchdog do not enable the hardware watchdog
|
||||
# --no-app-updater do not install the Android APK mirror timer
|
||||
# --with-rtc configure the DS3231 RTC (I2C + overlay + fake-hwclock off)
|
||||
# --with-sim868 configure the SIM868 UART (enable_uart, disable-bt,
|
||||
# no serial login shell, hciuart off)
|
||||
# -h|--help print this help
|
||||
#
|
||||
# Env vars honoured (all optional unless marked required):
|
||||
# GIT_URL / GIT_BRANCH repo source (defaults to GitHub default branch)
|
||||
# HOME_WIFI_SSID / HOME_WIFI_PSK home WiFi (required for netwatch)
|
||||
# HOTSPOT_SSID / HOTSPOT_PSK in-car AP (SSID defaults to "yaris-obd")
|
||||
# PHONE_HOTSPOT_SSID / PHONE_HOTSPOT_PSK optional phone hotspot uplink
|
||||
# YARIS_PASSWORD set a password for the yaris user (else disabled)
|
||||
# OWNER_NAME / CAR_MODEL / DEVICE_NAME prefill config/device.json
|
||||
# YARIS_SERVER_URL / YARIS_DEVICE_NAME / YARIS_API_KEY prefill db/upload_creds.env
|
||||
set -euo pipefail
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
REPO_DIR="/home/yaris/app/yaris-obd"
|
||||
REPO_URL="${GIT_URL:-https://github.com/quoije/yaris-obd.git}"
|
||||
REPO_BRANCH="${GIT_BRANCH:-}"
|
||||
|
||||
# ---- flag defaults (env-overridable) ---------------------------------------
|
||||
NONINTERACTIVE=${NONINTERACTIVE:-0}
|
||||
SKIP_T3U_DRIVER=${SKIP_T3U_DRIVER:-0}
|
||||
INSTALL_WATCHDOG=${INSTALL_WATCHDOG:-1}
|
||||
INSTALL_APP_UPDATER=${INSTALL_APP_UPDATER:-1}
|
||||
WITH_RTC=${WITH_RTC:-0}
|
||||
WITH_SIM868=${WITH_SIM868:-0}
|
||||
|
||||
# ---- logging ----------------------------------------------------------------
|
||||
info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[1;33m!!>\033[0m %s\n' "$*" >&2; }
|
||||
err() { printf '\033[1;31m!!>\033[0m %s\n' "$*" >&2; }
|
||||
|
||||
usage() {
|
||||
sed -n '2,40p' "$0"
|
||||
}
|
||||
|
||||
# ---- small helpers ----------------------------------------------------------
|
||||
config_txt_path() {
|
||||
if [ -f /boot/firmware/config.txt ]; then
|
||||
printf '%s' /boot/firmware/config.txt
|
||||
elif [ -f /boot/config.txt ]; then
|
||||
printf '%s' /boot/config.txt
|
||||
else
|
||||
printf '%s' /boot/firmware/config.txt
|
||||
fi
|
||||
}
|
||||
|
||||
# Idempotently set a KEY=... style line in config.txt, replacing any existing
|
||||
# (possibly commented) occurrence of the key, appending otherwise.
|
||||
set_config_line() {
|
||||
local file="$1" key="$2" line="$3"
|
||||
if [ ! -f "$file" ]; then
|
||||
warn "$file does not exist; skipping '$line'."
|
||||
return 0
|
||||
fi
|
||||
if grep -qE "^[[:space:]]*#?[[:space:]]*${key}" "$file"; then
|
||||
sed -i -E "s|^[[:space:]]*#?[[:space:]]*${key}.*|${line}|" "$file"
|
||||
else
|
||||
printf '%s\n' "$line" >> "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Return the value of the named env var, an explicit default, or prompt (unless
|
||||
# non-interactive). Prints the value to stdout.
|
||||
prompt_value() {
|
||||
local var="$1" prompt="$2" default="${3:-}"
|
||||
local val="${!var:-}"
|
||||
if [ -n "$val" ]; then
|
||||
printf '%s' "$val"
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$default" ]; then
|
||||
printf '%s' "$default"
|
||||
return 0
|
||||
fi
|
||||
if [ "$NONINTERACTIVE" = "1" ]; then
|
||||
err "Missing required value: $var ($prompt). Pass it via the environment or drop --non-interactive."
|
||||
exit 1
|
||||
fi
|
||||
read -rp "$prompt: " val
|
||||
printf '%s' "$val"
|
||||
}
|
||||
|
||||
# Same as prompt_value but reads the answer without echoing it.
|
||||
prompt_secret() {
|
||||
local var="$1" prompt="$2" default="${3:-}"
|
||||
local val="${!var:-}"
|
||||
if [ -n "$val" ]; then
|
||||
printf '%s' "$val"
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$default" ]; then
|
||||
printf '%s' "$default"
|
||||
return 0
|
||||
fi
|
||||
if [ "$NONINTERACTIVE" = "1" ]; then
|
||||
err "Missing required value: $var ($prompt). Pass it via the environment or drop --non-interactive."
|
||||
exit 1
|
||||
fi
|
||||
read -rsp "$prompt: " val
|
||||
printf '\n' >&2
|
||||
printf '%s' "$val"
|
||||
}
|
||||
|
||||
# ---- install steps ----------------------------------------------------------
|
||||
install_base_packages() {
|
||||
info "Installing base packages..."
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
git curl python3 python3-venv python3-pip \
|
||||
network-manager nftables avahi-daemon dnsmasq-base iw
|
||||
systemctl unmask NetworkManager 2>/dev/null || true
|
||||
systemctl enable --now NetworkManager
|
||||
}
|
||||
|
||||
create_yaris_user() {
|
||||
info "Creating the 'yaris' service user..."
|
||||
if ! id -u yaris >/dev/null 2>&1; then
|
||||
if [ -n "${YARIS_PASSWORD:-}" ]; then
|
||||
useradd --create-home --shell /bin/bash \
|
||||
--password "$(openssl passwd -6 "$YARIS_PASSWORD")" yaris
|
||||
else
|
||||
useradd --create-home --shell /bin/bash --disabled-password yaris
|
||||
fi
|
||||
fi
|
||||
usermod -aG sudo,dialout,gpio,i2c yaris
|
||||
info "yaris user ready (groups: $(id -nG yaris | tr ' ' ','))"
|
||||
}
|
||||
|
||||
clone_repo() {
|
||||
info "Cloning $REPO_URL into $REPO_DIR..."
|
||||
mkdir -p "$(dirname "$REPO_DIR")"
|
||||
chown yaris:yaris "$(dirname "$REPO_DIR")"
|
||||
if [ ! -d "$REPO_DIR/.git" ]; then
|
||||
local branch_args=()
|
||||
if [ -n "$REPO_BRANCH" ]; then
|
||||
branch_args+=(--branch "$REPO_BRANCH")
|
||||
fi
|
||||
sudo -u yaris git clone "${branch_args[@]}" "$REPO_URL" "$REPO_DIR"
|
||||
else
|
||||
warn "$REPO_DIR already exists — attempting git pull."
|
||||
sudo -u yaris git -C "$REPO_DIR" pull --ff-only || warn "git pull failed; continuing with the existing checkout."
|
||||
fi
|
||||
}
|
||||
|
||||
install_t3u_driver() {
|
||||
if [ "$SKIP_T3U_DRIVER" = "1" ]; then
|
||||
warn "Skipping RTL8812BU driver build (--skip-t3u-driver). wlan1/wlan2 will not exist."
|
||||
return 0
|
||||
fi
|
||||
info "Building the RTL8812BU driver for the TP-Link Archer T3U dongles..."
|
||||
apt-get install -y dkms build-essential bc raspberrypi-kernel-headers
|
||||
rm -rf /tmp/88x2bu-20210702
|
||||
git clone --depth 1 https://github.com/morrownr/88x2bu-20210702.git /tmp/88x2bu-20210702
|
||||
( cd /tmp/88x2bu-20210702 && printf 'n\n' | ./install-driver.sh ) || {
|
||||
warn "RTL8812BU driver build failed. The in-car AP/uplink on wlan1/wlan2 will be unavailable until fixed."
|
||||
return 0
|
||||
}
|
||||
if [ -e /sys/class/net/wlan1 ]; then
|
||||
info "wlan1 is present."
|
||||
else
|
||||
warn "wlan1 still not present after the driver build — check the dongle / USB ports."
|
||||
fi
|
||||
}
|
||||
|
||||
create_home_wifi() {
|
||||
local ssid psk
|
||||
ssid="$(prompt_value HOME_WIFI_SSID "Home WiFi SSID")"
|
||||
psk="$(prompt_secret HOME_WIFI_PSK "Home WiFi password")"
|
||||
info "Configuring NetworkManager profile 'home-wifi'..."
|
||||
if nmcli connection show home-wifi >/dev/null 2>&1; then
|
||||
nmcli connection modify home-wifi \
|
||||
802-11-wireless.ssid "$ssid" \
|
||||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$psk" \
|
||||
connection.autoconnect yes connection.autoconnect-priority 30
|
||||
else
|
||||
nmcli connection add type wifi con-name home-wifi ifname wlan2 \
|
||||
ssid "$ssid" \
|
||||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$psk" \
|
||||
connection.autoconnect yes connection.autoconnect-priority 30
|
||||
fi
|
||||
}
|
||||
|
||||
create_hotspot() {
|
||||
local ssid psk
|
||||
ssid="$(prompt_value HOTSPOT_SSID "In-car hotspot SSID" "yaris-obd")"
|
||||
psk="$(prompt_secret HOTSPOT_PSK "In-car hotspot password")"
|
||||
info "Configuring NetworkManager profile 'Hotspot-local' (5GHz AP on wlan1)..."
|
||||
if nmcli connection show Hotspot-local >/dev/null 2>&1; then
|
||||
nmcli connection modify Hotspot-local \
|
||||
802-11-wireless.ssid "$ssid" \
|
||||
802-11-wireless.mode ap \
|
||||
802-11-wireless.band a \
|
||||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$psk" \
|
||||
ipv4.method shared ipv4.addresses 10.42.0.1/24 \
|
||||
connection.autoconnect yes connection.autoconnect-priority 10
|
||||
else
|
||||
nmcli connection add type wifi con-name Hotspot-local ifname wlan1 \
|
||||
ssid "$ssid" \
|
||||
802-11-wireless.mode ap \
|
||||
802-11-wireless.band a \
|
||||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$psk" \
|
||||
ipv4.method shared ipv4.addresses 10.42.0.1/24 \
|
||||
connection.autoconnect yes connection.autoconnect-priority 10
|
||||
fi
|
||||
}
|
||||
|
||||
create_phone_hotspot() {
|
||||
local ssid="${PHONE_HOTSPOT_SSID:-}"
|
||||
if [ -z "$ssid" ]; then
|
||||
if [ "$NONINTERACTIVE" = "1" ]; then
|
||||
info "No phone-hotspot credentials provided; skipping phone hotspot."
|
||||
return 0
|
||||
fi
|
||||
read -rp "Phone hotspot SSID (leave empty to skip): " ssid
|
||||
if [ -z "$ssid" ]; then
|
||||
info "Skipping phone hotspot."
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
local psk
|
||||
psk="$(prompt_secret PHONE_HOTSPOT_PSK "Phone hotspot password")"
|
||||
info "Configuring NetworkManager profile 'phone-hotspot'..."
|
||||
if nmcli connection show phone-hotspot >/dev/null 2>&1; then
|
||||
nmcli connection modify phone-hotspot \
|
||||
802-11-wireless.ssid "$ssid" \
|
||||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$psk" \
|
||||
connection.autoconnect yes connection.autoconnect-priority 20 \
|
||||
ipv4.dhcp-send-hostname yes ipv4.dhcp-hostname yaris \
|
||||
802-11-wireless.mtu 1400
|
||||
else
|
||||
nmcli connection add type wifi con-name phone-hotspot ifname wlan2 \
|
||||
ssid "$ssid" \
|
||||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$psk" \
|
||||
connection.autoconnect yes connection.autoconnect-priority 20 \
|
||||
ipv4.dhcp-send-hostname yes ipv4.dhcp-hostname yaris \
|
||||
802-11-wireless.mtu 1400
|
||||
fi
|
||||
}
|
||||
|
||||
enable_rtc() {
|
||||
if [ "$WITH_RTC" != "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
info "Configuring the DS3231 RTC (--with-rtc)..."
|
||||
apt-get install -y i2c-tools
|
||||
local cfg
|
||||
cfg="$(config_txt_path)"
|
||||
set_config_line "$cfg" "dtparam=i2c_arm" "dtparam=i2c_arm=on"
|
||||
set_config_line "$cfg" "dtoverlay=i2c-rtc,ds3231" "dtoverlay=i2c-rtc,ds3231"
|
||||
printf '%s\n' "i2c-dev" > /etc/modules-load.d/i2c-dev.conf
|
||||
systemctl disable --now fake-hwclock 2>/dev/null || true
|
||||
warn "RTC overlay enabled — reboot for it to take effect, then verify with: i2cdetect -y 1 (expect UU at 0x68)."
|
||||
}
|
||||
|
||||
enable_sim868() {
|
||||
if [ "$WITH_SIM868" != "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
info "Configuring the SIM868 UART (--with-sim868)..."
|
||||
local cfg
|
||||
cfg="$(config_txt_path)"
|
||||
set_config_line "$cfg" "enable_uart" "enable_uart=1"
|
||||
set_config_line "$cfg" "dtoverlay=disable-bt" "dtoverlay=disable-bt"
|
||||
if command -v raspi-config >/dev/null 2>&1; then
|
||||
raspi-config nonint do_serial_cons 1 || true
|
||||
fi
|
||||
systemctl disable --now hciuart 2>/dev/null || true
|
||||
warn "SIM868 UART enabled — reboot for it to take effect (serial0 frees the full PL011 UART)."
|
||||
}
|
||||
|
||||
prefill_device_config() {
|
||||
if [ -z "${OWNER_NAME:-}${CAR_MODEL:-}${DEVICE_NAME:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
info "Prefilling config/device.json..."
|
||||
python3 - "$REPO_DIR/config/device.json" "${OWNER_NAME:-}" "${CAR_MODEL:-}" "${DEVICE_NAME:-}" <<'PY'
|
||||
import json, sys
|
||||
path, owner, car, device = sys.argv[1:5]
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
if owner:
|
||||
data["owner_name"] = owner
|
||||
if car:
|
||||
data["car_model"] = car
|
||||
if device:
|
||||
data["device_name"] = device
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
f.write("\n")
|
||||
PY
|
||||
}
|
||||
|
||||
prefill_upload_creds() {
|
||||
if [ -z "${YARIS_SERVER_URL:-}${YARIS_DEVICE_NAME:-}${YARIS_API_KEY:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
info "Prefilling db/upload_creds.env..."
|
||||
local creds="$REPO_DIR/db/upload_creds.env"
|
||||
python3 - "$creds" \
|
||||
"YARIS_SERVER_URL" "${YARIS_SERVER_URL:-}" \
|
||||
"YARIS_DEVICE_NAME" "${YARIS_DEVICE_NAME:-}" \
|
||||
"YARIS_API_KEY" "${YARIS_API_KEY:-}" <<'PY'
|
||||
import re, sys
|
||||
path = sys.argv[1]
|
||||
pairs = list(zip(sys.argv[2::2], sys.argv[3::2]))
|
||||
with open(path) as f:
|
||||
text = f.read()
|
||||
for key, value in pairs:
|
||||
if value == "":
|
||||
continue
|
||||
pattern = re.compile(rf"^{re.escape(key)}\s*=.*$", re.M)
|
||||
replacement = f'{key} = "{value}"'
|
||||
if pattern.search(text):
|
||||
text = pattern.sub(replacement, text, count=1)
|
||||
else:
|
||||
text += replacement + "\n"
|
||||
with open(path, "w") as f:
|
||||
f.write(text)
|
||||
PY
|
||||
}
|
||||
|
||||
run_installers() {
|
||||
info "Running deploy installers (as root; sudo inside them is a no-op)..."
|
||||
|
||||
info "install_service.sh"
|
||||
bash "$REPO_DIR/deploy/install_service.sh"
|
||||
|
||||
info "install_port_redirect.sh"
|
||||
bash "$REPO_DIR/deploy/install_port_redirect.sh"
|
||||
|
||||
info "install_netwatch.sh"
|
||||
bash "$REPO_DIR/deploy/install_netwatch.sh"
|
||||
|
||||
info "install_video_tools.sh"
|
||||
bash "$REPO_DIR/deploy/install_video_tools.sh"
|
||||
|
||||
if [ "$INSTALL_WATCHDOG" = "1" ]; then
|
||||
info "install_watchdog.sh"
|
||||
bash "$REPO_DIR/deploy/install_watchdog.sh" || warn "watchdog install failed (non-fatal)."
|
||||
else
|
||||
warn "Skipping watchdog install (--no-watchdog)."
|
||||
fi
|
||||
|
||||
if [ "$INSTALL_APP_UPDATER" = "1" ]; then
|
||||
info "install_app_updater.sh"
|
||||
bash "$REPO_DIR/deploy/install_app_updater.sh" || warn "APK updater install failed (non-fatal)."
|
||||
else
|
||||
warn "Skipping APK updater install (--no-app-updater)."
|
||||
fi
|
||||
}
|
||||
|
||||
fix_ownership_and_data_dirs() {
|
||||
info "Fixing repo ownership and creating writable data directories..."
|
||||
mkdir -p "$REPO_DIR/db/sql" "$REPO_DIR/dash/videos" "$REPO_DIR/static/app"
|
||||
chown -R yaris:yaris "$REPO_DIR"
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
echo
|
||||
info "Installation complete."
|
||||
echo
|
||||
echo "What was set up:"
|
||||
echo " - yaris user (sudo, dialout, gpio, i2c)"
|
||||
echo " - repo at $REPO_DIR"
|
||||
echo " - yaris-obd.service (enabled + running on boot)"
|
||||
echo " - nftables port 80 -> 5000 redirect"
|
||||
echo " - yaris-netwatch.timer (home-wifi / phone-hotspot / Hotspot-local switching)"
|
||||
echo " - ffmpeg thumbnails, hardware watchdog, APK mirror timer"
|
||||
echo
|
||||
echo "Verify:"
|
||||
echo " sudo systemctl status yaris-obd.service --no-pager"
|
||||
echo " curl -I http://127.0.0.1/"
|
||||
echo
|
||||
if [ "$WITH_RTC" = "1" ] || [ "$WITH_SIM868" = "1" ]; then
|
||||
echo "A reboot is required for the RTC / SIM868 boot-config changes to apply."
|
||||
fi
|
||||
echo "After reboot, open the web UI at http://yaris.local (or http://10.42.0.1 from the car hotspot)."
|
||||
}
|
||||
|
||||
main() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--non-interactive) NONINTERACTIVE=1 ;;
|
||||
--skip-t3u-driver) SKIP_T3U_DRIVER=1 ;;
|
||||
--no-watchdog) INSTALL_WATCHDOG=0 ;;
|
||||
--no-app-updater) INSTALL_APP_UPDATER=0 ;;
|
||||
--with-rtc) WITH_RTC=1 ;;
|
||||
--with-sim868) WITH_SIM868=1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) err "Unknown option: $1"; usage; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
err "This script must be run as root (or via sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_base_packages
|
||||
create_yaris_user
|
||||
clone_repo
|
||||
enable_rtc
|
||||
enable_sim868
|
||||
install_t3u_driver
|
||||
create_home_wifi
|
||||
create_hotspot
|
||||
create_phone_hotspot
|
||||
run_installers
|
||||
prefill_device_config
|
||||
prefill_upload_creds
|
||||
fix_ownership_and_data_dirs
|
||||
print_summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user