diff --git a/install/install.sh b/install/install.sh new file mode 100644 index 0000000..ddd0e31 --- /dev/null +++ b/install/install.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash +set -euo pipefail + +## +# toes installer +# Usage: curl -sSL https://toes.space/install | bash +# Must be run as the 'toes' user. + +DEST=~/toes +REPO="https://git.nose.space/defunkt/toes" + +quiet() { "$@" > /dev/null 2>&1; } + +echo "" +echo " ╔══════════════════════════════════╗" +echo " ║ toes - personal web appliance ║" +echo " ╚══════════════════════════════════╝" +echo "" + +# Must be running as toes +if [ "$(whoami)" != "toes" ]; then + echo "ERROR: This script must be run as the 'toes' user." + echo "Create the user during Raspberry Pi OS setup." + exit 1 +fi + +# Must have passwordless sudo (can't prompt when piped from curl) +if ! sudo -n true 2>/dev/null; then + echo "ERROR: This script requires passwordless sudo." + echo "On Raspberry Pi OS, the default user has this already." + exit 1 +fi + +# -- Hostname -- + +CURRENT_HOSTNAME=$(hostname) +if [ "$CURRENT_HOSTNAME" != "toes" ]; then + echo ">> Setting hostname to 'toes' (was '$CURRENT_HOSTNAME')" + sudo hostnamectl set-hostname toes + sudo sed -i "s/$CURRENT_HOSTNAME/toes/g" /etc/hosts +fi + +# -- System packages -- + +echo ">> Updating system packages" +quiet sudo apt-get update +quiet sudo apt-get install -y git libcap2-bin avahi-utils fish unzip + +echo ">> Setting fish as default shell" +if [ "$(getent passwd toes | cut -d: -f7)" != "/usr/bin/fish" ]; then + quiet sudo chsh -s /usr/bin/fish toes +fi + +# -- Bun -- + +BUN_REAL="$HOME/.bun/bin/bun" +BUN_SYMLINK="/usr/local/bin/bun" + +if [ ! -x "$BUN_REAL" ]; then + echo ">> Installing bun" + curl -fsSL https://bun.sh/install | bash > /dev/null 2>&1 + if [ ! -x "$BUN_REAL" ]; then + echo "ERROR: bun installation failed" + exit 1 + fi +fi + +if [ ! -x "$BUN_SYMLINK" ]; then + sudo ln -sf "$BUN_REAL" "$BUN_SYMLINK" +fi + +echo ">> Setting CAP_NET_BIND_SERVICE on bun" +sudo setcap 'cap_net_bind_service=+ep' "$BUN_REAL" + +# -- Clone -- + +if [ ! -d "$DEST" ]; then + echo ">> Cloning toes" + git clone "$REPO" "$DEST" +else + echo ">> Updating toes" + cd "$DEST" && git pull origin main +fi + +# -- Directories -- + +mkdir -p ~/data ~/apps + +# -- Dependencies & build -- + +echo ">> Installing dependencies" +cd "$DEST" && bun install + +echo ">> Building client" +cd "$DEST" && bun run build + +# -- Bundled apps -- + +echo ">> Installing bundled apps" +BUNDLED_APPS="clock code cron env stats versions" +for app in $BUNDLED_APPS; do + if [ -d "$DEST/apps/$app" ]; then + echo " $app" + cp -r "$DEST/apps/$app" ~/apps/ + version_dir=$(ls -1 ~/apps/$app | grep -E '^[0-9]{8}-[0-9]{6}$' | sort -r | head -1) + if [ -n "$version_dir" ]; then + ln -sfn "$version_dir" ~/apps/$app/current + (cd ~/apps/$app/current && bun install --frozen-lockfile) > /dev/null 2>&1 || true + fi + fi +done + +# -- Systemd -- + +echo ">> Installing toes service" +sudo install -m 644 -o root -g root "$DEST/scripts/toes.service" /etc/systemd/system/toes.service +sudo systemctl daemon-reload +sudo systemctl enable toes + +echo ">> Starting toes" +sudo systemctl restart toes + +# -- Kiosk mode -- + +echo ">> Enabling kiosk mode" +sudo raspi-config nonint do_boot_behaviour B4 2>/dev/null || true + +mkdir -p ~/.config/labwc +cat > ~/.config/labwc/autostart <<'EOF' +chromium --noerrdialogs --disable-infobars --kiosk http://localhost +EOF + +WAYFIRE_CONFIG="$HOME/.config/wayfire.ini" +if [ -f "$WAYFIRE_CONFIG" ]; then + sed -i '/^chromium = /d' "$WAYFIRE_CONFIG" + if grep -q '^\[autostart\]' "$WAYFIRE_CONFIG"; then + sed -i '/^\[autostart\]/a chromium = chromium --noerrdialogs --disable-infobars --kiosk http://localhost' "$WAYFIRE_CONFIG" + else + cat >> "$WAYFIRE_CONFIG" <<'EOF' + +[autostart] +chromium = chromium --noerrdialogs --disable-infobars --kiosk http://localhost +EOF + fi +fi + +# -- Done -- + +echo "" +echo " toes is installed! Rebooting in 5 seconds..." +echo " After reboot, visit: http://toes.local" +echo "" +sleep 5 +sudo nohup reboot >/dev/null 2>&1 & +exit 0 diff --git a/install/server.ts b/install/server.ts new file mode 100644 index 0000000..8e62548 --- /dev/null +++ b/install/server.ts @@ -0,0 +1,17 @@ +import { resolve } from "path" + +const script = await Bun.file(resolve(import.meta.dir, "install.sh")).text() + +Bun.serve({ + port: parseInt(process.env.PORT || "3000"), + fetch(req) { + if (new URL(req.url).pathname === "/install") { + return new Response(script, { + headers: { "content-type": "text/plain" }, + }) + } + return new Response("toes", { status: 404 }) + }, +}) + +console.log(`Serving /install on :${Bun.env.PORT || 3000}`)