diff --git a/.changeset/native-fresh-install.md b/.changeset/native-fresh-install.md new file mode 100644 index 000000000..44f7e74be --- /dev/null +++ b/.changeset/native-fresh-install.md @@ -0,0 +1,7 @@ +--- +"ftw": minor +--- + +Install a verified native 0.x release on a fresh Linux host from an exact tag. +The old one-line Docker installer is retired; existing installations remain +untouched until the guided migration is ready. diff --git a/Makefile b/Makefile index fbac79870..aedaa9b58 100644 --- a/Makefile +++ b/Makefile @@ -94,6 +94,7 @@ container-boundary-test: release-workflow-test bash scripts/test-container-boundaries.sh release-workflow-test: + bash scripts/test-install-native.sh PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s scripts -p 'test_package_linux.py' PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s scripts -p 'test_check_native_release_order.py' bash scripts/test-upload-release-assets.sh diff --git a/README.md b/README.md index 53301bc2a..be00901a8 100644 --- a/README.md +++ b/README.md @@ -90,27 +90,33 @@ driver actually changed. ## Install on Linux -The installer supports Raspberry Pi OS, Debian and Ubuntu: +Native 0.x is in beta. On a fresh 64-bit Raspberry Pi OS, Debian or Ubuntu +host that is part of the beta test, install one exact published tag: ```bash -curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh -o /tmp/ftw-install.sh +bash /tmp/ftw-install.sh --fresh-host --tag v0.131.0-beta.1 ``` -It installs Docker when needed, creates `~/ftw`, downloads the Compose file -and starts core, updater and the local MQTT broker. Open -`http://:8080/setup` on the LAN. +Use the tag chosen for that test site. `--fresh-host` confirms there is no +existing FTW site, even a stopped Docker site in a custom directory. The +installer checks the package and checksum, creates native release slots under +`/opt/ftw`, and starts the local +Core service. It does not install Docker. Open `http://:8080/setup` on +the LAN, then check storage health and live device readings. If the first +install is interrupted, use `--resume --tag` with the same tag after checking +the service log; it keeps any data the first attempt created. Give the FTW machine a DHCP reservation (a fixed IP) in your router. Devices that dial in to FTW — OCPP chargers store their backend URL at commissioning, and some hardware whitelists which addresses may talk to it — silently lose the connection if DHCP later hands the host a different address. -Existing Forty Two Watts or older FTW deployments must use the -[legacy upgrade guide](docs/upgrade-from-legacy.md) so configuration and state -are preserved. A 2.x Compose site that already runs `ghcr.io/srcfl/ftw` -and wants 3.x uses [upgrade-paired-release.md](docs/upgrade-paired-release.md), -not orange Update. Raspberry Pi image installation is covered by -[docs/rpi-image.md](docs/rpi-image.md). +Existing 1.x, 2.x, 3.x and earlier native sites stay on their current version +until the guided 0.x migration is ready. The fresh installer refuses them; do +not use Update or old Docker migration scripts to cross release lines. The +published [Raspberry Pi image](docs/rpi-image.md) still uses the older Docker +path and is not a way to start a new native 0.x site. The on-box dashboard remains local. The optional [FTW webapp](https://github.com/srcfl/ftw-webapp) connects through an encrypted diff --git a/docs/linux-packages.md b/docs/linux-packages.md index c290d0abc..f36c8bebc 100644 --- a/docs/linux-packages.md +++ b/docs/linux-packages.md @@ -5,16 +5,18 @@ Raspberry Pi/Linux hosts and `ftw-linux-amd64.tar.gz` for x86-64 Linux hosts. Each archive has a matching `.sha256` file. New releases do not build Windows packages. Existing published assets remain available. -This page describes package contents and a manual, fresh installation. It is -not the guided migration for an existing Docker or Home Assistant box. Those -boxes stay on their current version until the guided 0.x installer has been -tested and published. +This page describes package contents and manual recovery. Fresh native 0.x +beta sites use [`scripts/install.sh`](../scripts/install.sh) with an exact +published tag; it verifies the package, initializes release slots and starts +the native `ftw.service`. It is not the guided migration for an existing Docker, +Home Assistant or earlier native box. Those boxes stay on their current version +until the guided 0.x migration has been tested and published. The archive contains Core, `ftw-backup`, `ftw-launcher`, web files, the pinned recovery drivers, the compiled Energyplan bundle, license notices, an example config, `deploy/ftw.service` and `deploy/ftw-native.service`. Keep these files -together when changing versions. The native slot service is not yet the default -install path; it needs the migration work in ADR 0007. +together when changing versions. The native slot service is the path for new +0.x installs; existing sites still need the migration work in ADR 0007. Existing Linux download names remain aliases during the transition. Each package includes only the Energyplan executable for its Linux @@ -38,7 +40,11 @@ Use `amd64` instead on an x86-64 host. Extracting the archive does not install or start the systemd service. Keep configuration and data outside the directory replaced on update. -## First native installation +## Manual direct installation without 0.x self-update + +The fresh 0.x installer above is the path for beta sites. These older manual +steps keep the direct `/opt/ftw/ftw` layout available for recovery. They do +not set up the 0.x release slots or in-app native update. These steps target a fresh Debian 12 or Raspberry Pi OS Bookworm host with systemd and a 64-bit OS. The package needs no Go toolchain or Docker engine. diff --git a/docs/operations.md b/docs/operations.md index e74267123..7f090aaec 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -1,25 +1,35 @@ # Operations -FTW is normally deployed with Docker Compose on Linux. The core control loop -remains local; Energyplan ships with Core and Core falls back safely when -it is unavailable. +Existing sites may still use Docker Compose. New native 0.x installs run Core +under systemd. The core control loop remains local; Energyplan ships with Core +and Core falls back safely when it is unavailable. ## Install -Fresh Raspberry Pi OS, Debian or Ubuntu host: +The native 0.x installer is for a fresh 64-bit Raspberry Pi OS, Debian or +Ubuntu host and requires an exact published tag. During beta, use it only on +an agreed test site: ```bash -curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh -o /tmp/ftw-install.sh +bash /tmp/ftw-install.sh --fresh-host --tag v0.131.0-beta.1 ``` -The default directory is `~/ftw`; persistent data is under `~/ftw/data`. -Open `http://:8080/setup` on the LAN. - -Use `docker-compose.macos.yml` on macOS because Linux host networking is not -available through Docker Desktop. Existing installations must follow -[upgrade-from-legacy.md](upgrade-from-legacy.md), not the fresh installer. - -Common commands: +`--fresh-host` confirms that no FTW site exists, including a stopped Docker +site installed in a custom directory. Use the exact tag chosen for the site; +do not use GitHub `releases/latest`, which remains on 2.x for old boxes. The +installer checks the package and +checksum, creates `/opt/ftw` with version slots, and starts `ftw.service`. +Persistent data is under `/var/lib/ftw`. Open `http://:8080/setup` on +the LAN, then check health and live device readings. If setup stops partway, +inspect `journalctl -u ftw`, then rerun with `--resume --tag` and the same +tag. The installer checks its pending record and keeps any data already saved. + +Existing Docker, Home Assistant and earlier native installations must stay +on their current version until the guided 0.x migration is tested. The fresh +installer refuses an existing site. macOS uses a separate manual path. + +Common commands on an existing Docker site: ```bash cd ~/ftw diff --git a/docs/rpi-image.md b/docs/rpi-image.md index 2fabb28c7..8a12b1823 100644 --- a/docs/rpi-image.md +++ b/docs/rpi-image.md @@ -1,8 +1,9 @@ -# Raspberry Pi image +# Legacy Raspberry Pi image -The recommended Raspberry Pi 4/5 installation uses Raspberry Pi Imager and the -FTW image repository. Imager downloads the image and lets you set hostname, SSH -credentials and Wi-Fi before writing the card. +The published Raspberry Pi image uses the older Docker install path. It is +kept for existing sites and is not the new native 0.x install path. Do not +flash it to start a new 0.x site. The native image has not shipped. The steps +below describe the former Raspberry Pi Imager path and remain for reference. ## Install diff --git a/docs/setup-guide/de.md b/docs/setup-guide/de.md index 083db33aa..1ac51ae73 100644 --- a/docs/setup-guide/de.md +++ b/docs/setup-guide/de.md @@ -2,9 +2,9 @@ Diese Anleitung ist für dich, wenn du noch nie einen Raspberry Pi eingerichtet hast. Keine Sorge — es ist leichter, als es klingt. Folge den Schritten einfach einer nach dem anderen. -> **Alternativer manueller Weg:** empfohlen wird das fertige FTW-Image aus [`../rpi-image.md`](../rpi-image.md). Fahre hier nur fort, wenn du ausdrücklich Raspberry Pi OS + Docker selbst installieren möchtest. +> **Native 0.x-Beta:** Das alte FTW-Image und der Docker-Installer sind für neue Geräte eingestellt. Diese Anleitung zeigt die Pi-Einrichtung. Betatester mit einem neuen 64-Bit-Rechner nutzen den [nativen Linux-Installer](../operations.md#install). Bestehende Geräte warten auf die geführte Migration. -> **Kein Raspberry Pi?** Du kannst FTW auch auf einem NUC, einem alten Laptop oder anderer Hardware laufen lassen, die du herumliegen hast — solange sie Docker ausführen kann. Diese Anleitung konzentriert sich auf die Einrichtung mit einem Raspberry Pi; wenn du eine andere Maschine nutzt, überspringe die Hardware-Schritte und gehe direkt zu **Schritt 11 — FTW installieren** (das Installationsskript setzt Debian oder Ubuntu voraus). +> **Kein Raspberry Pi?** Ein neuer 64-Bit-Rechner mit Debian oder Ubuntu kann denselben nativen Beta-Installer nutzen. Überspringe die Pi-Hardware-Schritte und lies **Schritt 11 — FTW installieren**. ## Was du brauchst @@ -118,17 +118,14 @@ Gut gemacht — du bist jetzt "im" Raspberry Pi. ## Schritt 11 — FTW installieren -Kopiere diese Zeile GENAU so, wie sie ist: +Der alte Docker-Installer mit einem Befehl wird nicht mehr verwendet. Native +0.x wird noch getestet; diese Anleitung bietet derzeit keine allgemeine +Installation. Wenn du am Beta-Test teilnimmst und einen neuen 64-Bit-Pi hast, +folge der Anleitung mit festem Tag in den +[Installationsschritten](../operations.md#install). Läuft FTW schon auf dem +Pi, behalte die aktuelle Version bis zur geführten Migration. -``` -curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh | bash -``` - -Füge sie im Terminal oder in PuTTY ein (Rechtsklick fügt meistens ein) und drücke Enter. - -Gib dein Passwort noch einmal ein. Jetzt wird alles installiert. Das dauert ein paar Minuten. **Dann bist du fertig.** - -## Fertig +## Nach der Installation Öffne den Browser auf deinem normalen Computer und rufe die Web-Oberfläche auf: diff --git a/docs/setup-guide/en.md b/docs/setup-guide/en.md index 32d0ca270..0db692335 100644 --- a/docs/setup-guide/en.md +++ b/docs/setup-guide/en.md @@ -2,9 +2,9 @@ This guide is for you if you've never set up a Raspberry Pi before. Relax — it's easier than it sounds. Just follow the steps, one at a time. -> **Alternative manual path:** the recommended installation uses the ready-made FTW image and Raspberry Pi Imager repository in [`../rpi-image.md`](../rpi-image.md). Continue here only when you specifically want generic Raspberry Pi OS + Docker. +> **Native 0.x beta:** the old FTW image and Docker installer are retired for new sites. This guide covers Pi setup; beta testers with a fresh 64-bit host use the [native Linux installer](../operations.md#install). Existing sites wait for the guided migration. -> **Don't have a Raspberry Pi?** You can also run FTW on a NUC, an old laptop, or any other hardware you have lying around — as long as it can run Docker. This guide focuses on getting started with a Raspberry Pi; if you're on another box, skim the hardware steps and jump to **Step 11 — Install FTW** (the install script assumes Debian or Ubuntu). +> **Don't have a Raspberry Pi?** A fresh 64-bit Debian or Ubuntu host can use the same native beta installer. Skip the Pi hardware steps and read **Step 11 — Install FTW**. ## What you'll need @@ -118,17 +118,13 @@ Well done — you're now "inside" the Raspberry Pi. ## Step 11 — Install FTW -Copy this line EXACTLY as it is: +The old one-line Docker installer has been retired. Native 0.x is being tested; +this beginner guide does not yet offer a general install. If you are part of +the beta and this is a fresh 64-bit Pi, follow the exact-tag steps in the +[Linux install guide](../operations.md#install). If FTW already runs on this +Pi, leave it on its current version until the guided migration is ready. -``` -curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh | bash -``` - -Paste it into the terminal or PuTTY (right-click usually pastes) and press enter. - -Type your password one more time. Everything installs now. It takes a few minutes. **Then you're done.** - -## All done +## After installation Open the browser on your regular computer and go to the web interface: diff --git a/docs/setup-guide/es.md b/docs/setup-guide/es.md index d3a869691..5db0e62fe 100644 --- a/docs/setup-guide/es.md +++ b/docs/setup-guide/es.md @@ -2,9 +2,9 @@ Esta guía es para ti que nunca has configurado una Raspberry Pi antes. Tranquila — es más fácil de lo que parece. Basta con seguir los pasos, uno a uno. -> **Ruta manual alternativa:** la instalación recomendada usa la imagen FTW preparada que se describe en [`../rpi-image.md`](../rpi-image.md). Continúa aquí solo si quieres instalar Raspberry Pi OS + Docker manualmente. +> **Beta nativa 0.x:** la antigua imagen FTW y el instalador Docker ya no se usan en equipos nuevos. Esta guía cubre la preparación de la Pi. Quienes prueben la beta en un equipo nuevo de 64 bits deben seguir la [instalación nativa para Linux](../operations.md#install). Los equipos existentes esperan la migración guiada. -> **¿No tienes Raspberry Pi?** También puedes ejecutar FTW en un NUC, un portátil viejo o cualquier otro hardware que tengas por ahí — siempre que pueda ejecutar Docker. Esta guía se centra en empezar con una Raspberry Pi; si usas otra máquina, echa un vistazo rápido a los pasos de hardware y pasa directamente al **Paso 11 — Instalar FTW** (el script de instalación asume Debian o Ubuntu). +> **¿No tienes Raspberry Pi?** Un equipo nuevo de 64 bits con Debian o Ubuntu puede usar el mismo instalador de la beta nativa. Salta los pasos de la Pi y lee el **Paso 11 — Instalar FTW**. ## Lo que necesitas @@ -118,17 +118,13 @@ Bien hecho — ya estás "dentro" de la Raspberry Pi. ## Paso 11 — Instalar FTW -Copia esta línea EXACTAMENTE tal como está: +El instalador antiguo de Docker de una sola línea ya no se usa. Native 0.x +sigue en pruebas; esta guía aún no ofrece una instalación general. Si participas +en la beta y tienes una Pi nueva de 64 bits, sigue los pasos con una versión +exacta en la [guía de instalación](../operations.md#install). Si FTW ya se +ejecuta en tu Pi, conserva su versión hasta que esté lista la migración guiada. -``` -curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh | bash -``` - -Pégala en la terminal o PuTTY (el clic derecho suele pegar) y pulsa enter. - -Escribe tu contraseña una vez más. Ahora se instala todo. Tarda unos minutos. **Y ya está listo.** - -## Terminado +## Después de la instalación Abre el navegador en tu ordenador normal y ve a la interfaz web: diff --git a/docs/setup-guide/fr.md b/docs/setup-guide/fr.md index f59eee7b0..cb5d7b18f 100644 --- a/docs/setup-guide/fr.md +++ b/docs/setup-guide/fr.md @@ -2,9 +2,9 @@ Ce guide est fait pour vous qui n'avez jamais configuré un Raspberry Pi. Pas de panique — c'est plus simple qu'il n'y paraît. Il suffit de suivre les étapes, une par une. -> **Parcours manuel alternatif :** l'installation recommandée utilise l'image FTW prête à l'emploi décrite dans [`../rpi-image.md`](../rpi-image.md). Continuez ici uniquement si vous souhaitez installer vous-même Raspberry Pi OS + Docker. +> **Bêta native 0.x :** l'ancienne image FTW et l'installateur Docker ne servent plus aux nouveaux appareils. Ce guide couvre la préparation du Pi. Les testeurs ayant un nouvel hôte 64 bits suivent [l'installation Linux native](../operations.md#install). Les appareils existants attendent la migration guidée. -> **Pas de Raspberry Pi ?** Vous pouvez aussi faire tourner FTW sur un NUC, un vieux portable ou tout autre matériel qui traîne — du moment qu'il peut exécuter Docker. Ce guide se concentre sur la mise en route avec un Raspberry Pi ; si vous êtes sur une autre machine, survolez les étapes matérielles et passez directement à l'**Étape 11 — Installer FTW** (le script d'installation suppose Debian ou Ubuntu). +> **Pas de Raspberry Pi ?** Un nouvel hôte 64 bits sous Debian ou Ubuntu peut utiliser le même installateur natif bêta. Ignorez les étapes propres au Pi et lisez l'**Étape 11 — Installer FTW**. ## Ce dont vous avez besoin @@ -118,17 +118,13 @@ Bravo — vous êtes maintenant "à l'intérieur" du Raspberry Pi. ## Étape 11 — Installer FTW -Copiez cette ligne EXACTEMENT telle qu'elle est : +L'ancien installateur Docker en une ligne n'est plus utilisé. Native 0.x est +encore en test ; ce guide ne propose pas encore d'installation générale. Si +vous participez à la bêta avec un nouveau Pi 64 bits, suivez les étapes avec +un tag précis dans le [guide d'installation](../operations.md#install). Si FTW +tourne déjà sur votre Pi, gardez sa version jusqu'à la migration guidée. -``` -curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh | bash -``` - -Collez-la dans le terminal ou PuTTY (le clic droit colle généralement) et appuyez sur entrée. - -Tapez votre mot de passe une dernière fois. Tout s'installe maintenant. Cela prend quelques minutes. **Et c'est fini.** - -## Terminé +## Après l'installation Ouvrez le navigateur sur votre ordinateur habituel et allez à l'interface web : diff --git a/docs/setup-guide/sv.md b/docs/setup-guide/sv.md index 93c376010..09d7d8a08 100644 --- a/docs/setup-guide/sv.md +++ b/docs/setup-guide/sv.md @@ -2,9 +2,9 @@ Den här guiden är skriven för dig som aldrig har pillat med en Raspberry Pi förut. Lugn — det är lättare än det låter. Följ stegen ett i taget, så går det fint. -> **Alternativ manuell väg:** den rekommenderade installationen använder den färdiga FTW-imagen och Raspberry Pi Imager-katalogen i [`../rpi-image.md`](../rpi-image.md). Fortsätt här bara om du uttryckligen vill köra generell Raspberry Pi OS + Docker. +> **Native 0.x-beta:** den gamla FTW-imagen och Docker-installationen används inte för nya boxar. Den här guiden visar Pi-stegen. Betatestare med en ny 64-bitars värd följer [installationen för Linux](../operations.md#install). Befintliga boxar väntar på den styrda flytten. -> **Ingen Raspberry Pi?** Du kan lika gärna köra FTW på en NUC, en gammal bärbar, eller annan hårdvara du har liggande — så länge den kan köra Docker. Den här guiden fokuserar på att komma igång med en Raspberry Pi; är du på en annan burk kan du skumma hårdvarustegen och gå direkt till **Steg 11 — Installera FTW** (installationsskriptet förutsätter Debian eller Ubuntu). +> **Ingen Raspberry Pi?** En ny 64-bitars värd med Debian eller Ubuntu kan använda samma native beta. Hoppa över Pi-stegen och läs **Steg 11 — Installera FTW**. ## Vad du behöver @@ -118,17 +118,13 @@ Grattis — du är nu "inne" i Raspberry Pin. ## Steg 11 — Installera FTW -Kopiera denna rad EXAKT som den står: +Den gamla Docker-installationen med ett kommando är avslutad. Native 0.x +testas ännu och den här guiden ger därför ingen allmän installation just nu. +Om du deltar i betan och har en ny 64-bitars Pi, följ stegen med exakt tagg i +[installationsguiden](../operations.md#install). Kör FTW redan på din Pi, låt +den vara kvar på sin version tills den styrda flytten är klar. -``` -curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh | bash -``` - -Klistra in i terminalen eller PuTTY (högerklicka brukar fungera för att klistra in) och tryck enter. - -Skriv in ditt lösenord en gång till. Nu installeras allt. Det tar några minuter. **Sen är det klart.** - -## Klart +## Efter installationen Öppna webbläsaren på din vanliga dator och gå till webb-gränssnittet: diff --git a/docs/upgrade-from-legacy.md b/docs/upgrade-from-legacy.md index 714d96033..cd8bff088 100644 --- a/docs/upgrade-from-legacy.md +++ b/docs/upgrade-from-legacy.md @@ -1,11 +1,10 @@ # Upgrade an older installation to Sourceful FTW -Use this guide for an existing Linux Docker Compose installation with a -`docker-compose.yml` that still uses older image names. Sites that already run -`ghcr.io/srcfl/ftw` and want a published 3.x pair should follow -[upgrade-paired-release.md](upgrade-paired-release.md) instead. The migration -preserves its directory, Compose project, service name, configuration, database, history, hardware identities and -persistent `data/` bind. Choose [Svenska](#svenska) or [English](#english). +This page records the old Docker-to-Docker migration. It is not the path to +native 0.x and should not be run for an existing 1.x, 2.x or 3.x site. Leave +that site on its current version until the guided 0.x migration is ready. The +commands below remain as a record of the former procedure, which preserved +the Compose directory, service name, config and data bind. --- diff --git a/scripts/install.sh b/scripts/install.sh index 07902d1e0..47ca4cd90 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,225 +1,222 @@ #!/usr/bin/env bash -# FTW one-shot installer. -# -# Designed for a fresh Raspberry Pi OS (arm64) host but works on any -# Debian/Ubuntu-flavoured Linux with curl + sudo. Existing installations use -# scripts/migrate-legacy-compose.sh instead. -# -# Usage: -# curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/scripts/install.sh | bash -# -# What this does: -# 1. Installs Docker Engine + the `docker compose` plugin via -# get.docker.com (skipped if Docker is already present). -# 2. Adds your user to the `docker` group. -# 3. Creates ~/ftw with data/ owned by the in-container ftw user -# (uid 100 / gid 101). -# 4. Fetches docker-compose.yml from the repo. -# 5. Pulls the multi-arch image from GHCR and starts the container. -# -# Override via env vars (optional): -# FTW_DIR=/srv/ftw # explicit install location -# FTW_BRANCH=some-branch # pull docker-compose.yml from a non-master branch - +# Install one published native 0.x release on a fresh Linux host. +# Existing FTW installations need the separate guided migration. set -euo pipefail -# ---- Config (override via env) ---- -REPO="srcfl/ftw" -BRANCH="${FTW_BRANCH:-master}" -if [ -n "${FTW_DIR:-}" ]; then - INSTALL_DIR="$FTW_DIR" -elif [ -f "$HOME/ftw/docker-compose.yml" ]; then - INSTALL_DIR="$HOME/ftw" -elif [ -f "$HOME/forty-two-watts/docker-compose.yml" ]; then - INSTALL_DIR="$HOME/forty-two-watts" -elif [ -d "$HOME/ftw" ]; then - INSTALL_DIR="$HOME/ftw" -else - INSTALL_DIR="$HOME/ftw" -fi -COMPOSE_URL="${FTW_COMPOSE_URL:-https://raw.githubusercontent.com/${REPO}/${BRANCH}/docker-compose.yml}" -MIGRATION_URL="https://raw.githubusercontent.com/${REPO}/${BRANCH}/scripts/migrate-legacy-compose.sh" - -# Banner -cat <<'BANNER' - - ┌─────────────────────────────────────────────────┐ - │ FTW installer │ - │ Local-first home energy coordination. │ - └─────────────────────────────────────────────────┘ - -BANNER - -# ---- Platform guard ---- -# This installer is Linux-only (apt-get, get.docker.com, `hostname -I`, -# usermod, host networking). On macOS the deploy story is different — -# Docker Desktop + the dedicated macOS compose file. Bail early with a -# pointer instead of failing halfway through with cryptic errors. -if [ "$(uname -s)" = "Darwin" ]; then - cat >&2 <<'EOF' -This installer is for Linux only. - -On macOS, install Docker Desktop and use docker-compose.macos.yml: - - mkdir -p ~/ftw/data && cd ~/ftw - curl -fsSL https://raw.githubusercontent.com/srcfl/ftw/master/docker-compose.macos.yml -o docker-compose.macos.yml - docker compose -f docker-compose.macos.yml up -d - -Operational notes: https://github.com/srcfl/ftw/blob/master/docs/operations.md +usage() { + cat <<'EOF' +Usage: install.sh --fresh-host --tag v0.X.Y[-beta.N] + install.sh --resume --tag v0.X.Y[-beta.N] + +Install one exact published native 0.x release on a fresh 64-bit Linux host. +--fresh-host confirms that no FTW site exists on this host, including a +stopped Docker site installed in a custom directory. --resume only continues +an interrupted native install with the same tag and package checksum. +There is no default tag: GitHub releases/latest still serves old Docker boxes. +An existing FTW installation must wait for the guided 0.x migration. This +script will not replace it or update a Docker container. EOF - exit 1 -fi - -if [ -f "$INSTALL_DIR/docker-compose.yml" ]; then - cat >&2 <&2 exit 2 fi - -# ---- Prerequisites ---- -if ! command -v curl >/dev/null 2>&1; then - echo "ERROR: 'curl' is required. Install with:" >&2 - echo " sudo apt-get update && sudo apt-get install -y curl" >&2 - exit 1 +mode="$1" +tag="$3" +if [[ ! "$tag" =~ ^v0\.([1-9][0-9]*)\.(0|[1-9][0-9]*)(-beta\.([1-9][0-9]*))?$ ]] || + (( ${BASH_REMATCH[1]:-0} < 131 )); then + echo "A native release tag v0.131.0 or later is required: $tag" >&2 + exit 2 fi -# Docker install + chown need root. Prime sudo up-front so we don't -# interrupt the flow mid-way with a password prompt that the user -# might miss when the script is piped from curl. -if [ "$(id -u)" -eq 0 ]; then - SUDO="" +if [[ "$(uname -s)" != Linux ]]; then + echo "The native installer requires Linux and systemd." >&2 + exit 2 +fi +case "$(uname -m)" in + aarch64|arm64) arch=arm64 ;; + x86_64|amd64) arch=amd64 ;; + *) echo "A 64-bit ARM or AMD64 host is required." >&2; exit 2 ;; +esac +for tool in curl tar sha256sum mktemp systemctl; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "Missing required tool: $tool" >&2 + exit 2 + fi +done + +pending=/etc/ftw-native-install.pending +# These checks catch common layouts. --fresh-host also covers a stopped +# Docker site in a custom FTW_DIR that cannot be found from a fixed path. +legacy_paths=( + "$HOME/ftw/docker-compose.yml" + "$HOME/forty-two-watts/docker-compose.yml" +) +if [[ "$mode" == --fresh-host ]]; then + existing_paths=(/opt/ftw /var/lib/ftw /etc/systemd/system/ftw.service + /etc/systemd/system/forty-two-watts.service "$pending" "${legacy_paths[@]}") else - if ! command -v sudo >/dev/null 2>&1; then - echo "ERROR: 'sudo' is required when not running as root." >&2 - exit 1 + existing_paths=("${legacy_paths[@]}") + if [[ ! -f "$pending" ]]; then + echo "No interrupted native install found at $pending; --resume cannot start a new site." >&2 + exit 2 fi - SUDO="sudo" - echo "This installer needs sudo to install Docker. You may be prompted for your password." - echo "" - sudo -v +fi +for path in "${existing_paths[@]}"; do + if [[ -e "$path" || -L "$path" ]]; then + echo "Existing FTW installation found at $path; leave it running and use the guided 0.x migration when available." >&2 + exit 2 + fi +done +if systemctl is-active --quiet ftw.service >/dev/null 2>&1 || + systemctl is-active --quiet forty-two-watts.service >/dev/null 2>&1 || + { [[ "$mode" == --fresh-host ]] && + { systemctl cat ftw.service >/dev/null 2>&1 || + systemctl cat forty-two-watts.service >/dev/null 2>&1 || + id ftw >/dev/null 2>&1; }; }; then + echo "An FTW service or account already exists; refusing a fresh install." >&2 + exit 2 +fi +if command -v ss >/dev/null 2>&1 && + ss -ltnH | awk '$4 ~ /:8080$/ { found = 1 } END { exit !found }'; then + echo "Port 8080 is already in use; refusing a fresh install." >&2 + exit 2 fi -# ---- 1. Docker ---- -echo "==[1/5]== Installing Docker Engine + compose plugin" -if command -v docker >/dev/null 2>&1; then - echo " Docker is already installed — skipping." +if (( EUID == 0 )); then + as_root() { "$@"; } else - curl -fsSL https://get.docker.com | $SUDO sh + if ! command -v sudo >/dev/null 2>&1; then + echo "sudo is required to install the service." >&2 + exit 2 + fi + as_root() { sudo "$@"; } fi -# get.docker.com ships the compose plugin on current Debian/Raspbian, -# but older systems may need it installed separately. -if ! $SUDO docker compose version >/dev/null 2>&1; then - echo " Installing docker-compose-plugin separately..." - $SUDO apt-get update -qq - $SUDO apt-get install -y -qq docker-compose-plugin +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT +archive="ftw-linux-${arch}.tar.gz" +url="https://github.com/srcfl/ftw/releases/download/${tag}" +curl --fail --silent --show-error --location --retry 3 --proto '=https' \ + --proto-redir '=https' "${url}/${archive}" -o "${work}/${archive}" +curl --fail --silent --show-error --location --retry 3 --proto '=https' \ + --proto-redir '=https' "${url}/${archive}.sha256" -o "${work}/${archive}.sha256" + +checksum="$(cat "${work}/${archive}.sha256")" +expected="${checksum:0:64}" +if [[ ! "$expected" =~ ^[0-9a-f]{64}$ || + "${checksum:64:2}" != " " || "${checksum:66}" != "$archive" ]]; then + echo "The release checksum does not name the expected package." >&2 + exit 1 fi - -# uidmap (newuidmap/newgidmap) is required if the user later wants to -# switch to rootless Docker via `dockerd-rootless-setuptool.sh install`. -# Tiny package, harmless to have, saves a confusing second-step detour. -if ! command -v newuidmap >/dev/null 2>&1; then - echo " Installing uidmap (needed for rootless Docker)..." - $SUDO apt-get update -qq - $SUDO apt-get install -y -qq uidmap +actual="$(sha256sum "${work}/${archive}" | cut -d ' ' -f 1)" +if [[ "$actual" != "$expected" ]]; then + echo "The release package checksum does not match." >&2 + exit 1 fi -# ---- 2. Docker group ---- -echo "" -echo "==[2/5]== Adding $USER to the docker group" -if id -nG "$USER" 2>/dev/null | grep -qw docker; then - echo " $USER is already in the docker group — skipping." - NEED_RELOGIN=0 -else - $SUDO usermod -aG docker "$USER" - echo " Done. You'll need to run 'newgrp docker' or log out + back in" - echo " before 'docker' works without sudo in your shell." - NEED_RELOGIN=1 +# The package's launcher checks every archive entry, the embedded version, +# architecture and state schema before it makes a complete release visible. +tar -xOf "${work}/${archive}" ftw-launcher > "${work}/ftw-launcher" +chmod 0755 "${work}/ftw-launcher" +stage="${work}/slot-root" +mkdir -p "$stage" +"${work}/ftw-launcher" -root "$stage" install "$tag" \ + "${work}/${archive}" "${work}/${archive}.sha256" +"${work}/ftw-launcher" -root "$stage" init "$tag" +"${work}/ftw-launcher" -root "$stage" status >/dev/null + +# No host write occurs until the whole package has passed verification. +if (( EUID != 0 )) && ! sudo -n true 2>/dev/null; then sudo -v; fi +for path in "${existing_paths[@]}"; do + if as_root test -e "$path" || as_root test -L "$path"; then + echo "Existing FTW installation found at $path; leave it running and use the guided 0.x migration when available." >&2 + exit 2 + fi +done +if as_root systemctl is-active --quiet ftw.service >/dev/null 2>&1 || + as_root systemctl is-active --quiet forty-two-watts.service >/dev/null 2>&1; then + echo "An FTW service is running; refusing to install." >&2 + exit 2 fi - -# ---- 3. Install directory ---- -echo "" -echo "==[3/5]== Preparing install directory: $INSTALL_DIR" -mkdir -p "$INSTALL_DIR/data" -# The image runs as uid 100 / gid 101 — a bare numeric USER, no account is -# created in the image at all (see Dockerfile). A bind-mounted host dir must -# match those IDs so SQLite can create state.db inside it. -$SUDO chown -R 100:101 "$INSTALL_DIR/data" - -# ---- 4. docker-compose.yml ---- -echo "" -echo "==[4/5]== Preparing docker-compose.yml from $BRANCH" -COMPOSE_PATH="$INSTALL_DIR/docker-compose.yml" -curl -fsSL "$COMPOSE_URL" -o "$COMPOSE_PATH" - -# ---- 5. Pull + start ---- -# Run Docker as the invoking user when its current shell already has access. -# If the user was just added to the docker group, use sudo for this first run; -# the next login picks up group membership. NEED_RELOGIN from step 2 is authoritative: -# `id -nG "$USER"` reads /etc/group (already updated by usermod), not the -# current process's credentials, so it can't answer this question. -if [ "$(id -u)" -eq 0 ] || [ "$NEED_RELOGIN" = "0" ]; then - run_docker() { docker "$@"; } +if [[ "$mode" == --fresh-host ]] && + { as_root systemctl cat ftw.service >/dev/null 2>&1 || id ftw >/dev/null 2>&1; }; then + echo "An FTW service or account appeared during package verification; refusing to install." >&2 + exit 2 +fi +if command -v ss >/dev/null 2>&1 && + ss -ltnH | awk '$4 ~ /:8080$/ { found = 1 } END { exit !found }'; then + echo "Port 8080 is now in use; refusing to install." >&2 + exit 2 +fi +if [[ "$mode" == --resume ]]; then + if as_root test -L "$pending"; then + echo "The pending native install record is a symlink; refusing to resume." >&2 + exit 2 + fi + if [[ "$(as_root cat "$pending")" != "$tag $expected" ]]; then + echo "The interrupted install used another tag or checksum; refusing to replace it." >&2 + exit 2 + fi + if as_root test -L /opt/ftw || + { as_root test -e /opt/ftw && ! as_root test -d /opt/ftw; }; then + echo "The pending native install has an unsafe /opt/ftw path." >&2 + exit 2 + fi + if as_root test -L /etc/systemd/system/ftw.service; then + echo "The pending native service is a symlink; refusing to resume." >&2 + exit 2 + fi + if as_root test -e /etc/systemd/system/ftw.service && + ! as_root cmp -s /etc/systemd/system/ftw.service \ + "${stage}/releases/${tag}/deploy/ftw-native.service"; then + echo "The pending native service differs from the verified package; refusing to replace it." >&2 + exit 2 + fi + if as_root test -e /etc/systemd/system/forty-two-watts.service || + as_root test -L /etc/systemd/system/forty-two-watts.service; then + alias_target="$(as_root readlink /etc/systemd/system/forty-two-watts.service || true)" + if [[ "$alias_target" != ftw.service && "$alias_target" != /etc/systemd/system/ftw.service ]]; then + echo "An unrelated FTW service alias exists; refusing to resume." >&2 + exit 2 + fi + fi else - run_docker() { sudo docker "$@"; } + printf '%s %s\n' "$tag" "$expected" > "${work}/pending" + as_root install -m 0600 "${work}/pending" "$pending" fi - -echo "" -echo "==[5/5]== Pulling image + starting container" -cd "$INSTALL_DIR" -# Pull the newest images, but don't let a GitHub/GHCR outage block the install: -# GHCR is GitHub-hosted, so when GitHub is degraded `compose pull` fails, and -# under `set -e` that would abort the whole install before anything starts. -# Fall through to `up -d`, which starts from any locally-present image -# (last-known-good) instead — only a genuinely fresh host with no local image -# still needs GHCR reachable. -if ! run_docker compose pull; then - echo " ! image pull failed (GHCR/GitHub unreachable?) — starting with locally-present images if any" -fi -run_docker compose up -d - -# ---- Summary ---- -HOST_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" -[ -z "$HOST_IP" ] && HOST_IP="localhost" - -cat </dev/null 2>&1; then + as_root useradd --system --user-group --home-dir /var/lib/ftw --no-create-home ftw fi +as_root install -d -m 0755 /opt/ftw +as_root cp -a "${stage}/." /opt/ftw/ +as_root install -m 0755 "${work}/ftw-launcher" /opt/ftw/ftw-launcher +as_root chown -R ftw:ftw /opt/ftw +as_root /opt/ftw/ftw-launcher -root /opt/ftw init "$tag" +as_root /opt/ftw/ftw-launcher -root /opt/ftw status >/dev/null +as_root install -m 0644 \ + "${stage}/releases/${tag}/deploy/ftw-native.service" \ + /etc/systemd/system/ftw.service +as_root systemctl daemon-reload +if ! as_root systemctl enable --now ftw.service; then + as_root systemctl disable --now ftw.service || true + echo "FTW did not start. Inspect journalctl -u ftw, then rerun with --resume --tag $tag. Persistent data remains in place." >&2 + exit 1 +fi +for _ in 1 2 3; do + sleep 2 + if ! as_root systemctl is-active --quiet ftw.service; then + as_root systemctl disable --now ftw.service || true + echo "FTW stopped during startup. Inspect journalctl -u ftw, then rerun with --resume --tag $tag. Persistent data remains in place." >&2 + exit 1 + fi +done +as_root rm "$pending" -echo "──────────────────────────────────────────────────────────────────" +echo "Native FTW $tag is running. Open http://:8080/setup to finish setup." +echo "Check the reported version, storage health and live device readings before use." diff --git a/scripts/test-install-native.sh b/scripts/test-install-native.sh new file mode 100755 index 000000000..ebaee2882 --- /dev/null +++ b/scripts/test-install-native.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="$(cd "$(dirname "$0")/.." && pwd)" +installer="${root}/scripts/install.sh" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +bash -n "$installer" +bash "$installer" --help > "$work/help" +grep -q 'exact published native 0.x release' "$work/help" + +if bash "$installer" > "$work/out" 2>&1; then + echo "installer accepted a missing tag" >&2 + exit 1 +fi +if bash "$installer" --tag v0.131.0-beta.1 > "$work/out" 2>&1; then + echo "installer accepted a fresh install without acknowledgement" >&2 + exit 1 +fi +grep -q -- '--fresh-host' "$work/out" + +for args in '--tag v0.130.4' '--tag v3.8.0' '--tag v0.131.0-beta.0' \ + '--tag v0.131.0;touch /tmp/ftw-unexpected-install'; do + read -r -a words <<< "$args" + if bash "$installer" --fresh-host "${words[@]}" > "$work/out" 2>&1; then + echo "installer accepted invalid arguments: $args" >&2 + exit 1 + fi +done + +if [[ "$(uname -s)" == Linux ]]; then + if bash "$installer" --resume --tag v0.131.0-beta.1 > "$work/out" 2>&1; then + echo "installer resumed without a pending install" >&2 + exit 1 + fi + grep -q 'No interrupted native install found' "$work/out" + mkdir -p "$work/home/ftw" + ln -s /no-such-compose "$work/home/ftw/docker-compose.yml" + if HOME="$work/home" bash "$installer" --fresh-host --tag v0.131.0-beta.1 > "$work/out" 2>&1; then + echo "installer accepted an existing Compose path" >&2 + exit 1 + fi + grep -q 'Existing FTW installation found' "$work/out" + if grep -Eq 'sudo|download|checksum' "$work/out"; then + echo "installer did not stop before privilege or download" >&2 + exit 1 + fi +fi + +echo 'native fresh-installer guards passed'