Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 49 additions & 122 deletions .agents/skills/switching-ftw-deploy-mode/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,126 +1,53 @@
---
name: switching-ftw-deploy-mode
description: Use when the user wants to flip an FTW host between the official container image and a host-built development binary, in either direction. Detect and preserve legacy service, directory, and binary aliases rather than assuming them.
description: Use when the user wants to switch an FTW host between a release and a development build, or asks how to update an existing host. Identify native release slots, new Docker packaging and retired Docker before choosing a path. Never use old Docker latest as a route to current FTW.
---

# Switching FTW deploy mode (official image ↔ development binary)

## Safety contract

Both modes use the existing Compose project and the same persistent `/app/data`
bind. Never create a second install directory, rename the Compose project, or
copy the data directory as part of this switch. Back up an override before
changing it, and use `docker compose up -d <main-service>` rather than
`down`.

The canonical layout is:

| Surface | Canonical | Supported legacy |
|---|---|---|
| Install directory | `~/ftw` | `~/forty-two-watts` |
| Compose service | `ftw` | `forty-two-watts` |
| Container binary | `/app/ftw` | `/app/forty-two-watts` |
| Official image | `ghcr.io/srcfl/ftw:latest` | mirrored `ghcr.io/frahlg/forty-two-watts:latest` |
| Dev binary | `~/ftw-dev/bin/ftw` | `~/ftw-dev/bin/forty-two-watts` |

## Required input

The SSH target (`user@host`) must come from the user. Do not guess or reuse a
host from unrelated context. The default development directory is
`~/ftw-dev`; confirm any different path before writing an override.

## Detect the installed layout first

Run a read-only probe:

```sh
ssh "$HOST" 'set -eu
if [ -d "$HOME/ftw" ]; then dir="$HOME/ftw"
elif [ -d "$HOME/forty-two-watts" ]; then dir="$HOME/forty-two-watts"
else echo "no FTW Compose directory found" >&2; exit 1
fi
cd "$dir"
services="$(docker compose config --services)"
count="$(printf "%s\n" "$services" | grep -Ec "^(ftw|forty-two-watts)$" || true)"
[ "$count" -eq 1 ] || { echo "expected exactly one FTW main service" >&2; exit 1; }
service="$(printf "%s\n" "$services" | grep -E "^(ftw|forty-two-watts)$")"
docker compose config "$service" | grep -q "/app/data" ||
{ echo "main service does not map persistent /app/data" >&2; exit 1; }
cid="$(docker compose ps -q "$service")"
printf "dir=%s service=%s container=%s\n" "$dir" "$service" "$cid"
docker inspect "$cid" --format "image={{.Config.Image}}"
ls -1 docker-compose.override.yml* 2>/dev/null || true'
```

Stop if both main service names own `/app/data`, neither does, or the data bind
is absent. Those layouts are ambiguous and must not be recreated automatically.

## Switch to the official image

Disable the development override by renaming it, then pull and recreate only the
detected main service:

```sh
ssh "$HOST" 'set -eu
if [ -d "$HOME/ftw" ]; then cd "$HOME/ftw"; else cd "$HOME/forty-two-watts"; fi
service="$(docker compose config --services | grep -E "^(ftw|forty-two-watts)$")"
if [ -f docker-compose.override.yml ]; then
mv docker-compose.override.yml "docker-compose.override.yml.dev-$(date +%Y%m%d-%H%M%S).bak"
fi
docker compose pull "$service"
docker compose up -d "$service"
cid="$(docker compose ps -q "$service")"
docker inspect "$cid" --format "image={{.Config.Image}} version={{index .Config.Labels \"org.opencontainers.image.version\"}}"'
```

The effective image after removing the override must be the canonical Sourceful
image or its published compatibility mirror. If it is still a local,
hard-coded developer tag, stop: the base Compose file itself was customized and
needs an explicit reviewed migration. Do not silently replace the whole file.

## Switch to a development binary

First locate a canonical or legacy host binary and verify its architecture:

```sh
ssh "$HOST" 'set -eu
for bin in "$HOME/ftw-dev/bin/ftw" "$HOME/ftw-dev/bin/forty-two-watts"; do
if [ -x "$bin" ]; then file "$bin"; exit 0; fi
done
echo "no executable FTW dev binary found" >&2
exit 1'
```

Prefer restoring the newest saved development override. It preserves the
user's actual service name and paths:

```sh
ssh "$HOST" 'set -eu
if [ -d "$HOME/ftw" ]; then cd "$HOME/ftw"; else cd "$HOME/forty-two-watts"; fi
service="$(docker compose config --services | grep -E "^(ftw|forty-two-watts)$")"
saved="$(ls -1t docker-compose.override.yml.dev-*.bak 2>/dev/null | head -n1 || true)"
[ -n "$saved" ] || {
echo "no saved dev override; review service and host paths before creating one" >&2
exit 1
}
cp "$saved" docker-compose.override.yml
docker compose up -d "$service"'
```

Only create a new override after the user confirms the detected service, host
binary path, and container target (`/app/ftw` for canonical images,
`/app/forty-two-watts` for a legacy image). A wrong target can leave the
container running the bundled official binary and make the switch look
successful when it was not.

## Verification

Verify all three independently:

1. `docker compose ps <service>` reports one healthy/running container.
2. `docker inspect <container>` shows the intended image and bind mount.
3. The dashboard version/about field or startup log matches the intended
official tag or development revision.

Do not treat a successful `docker compose up` alone as proof that the mounted
binary is running.
# Choose the FTW deployment and update path

Read [Install and update FTW](../../../docs/native-beta.md) before changing a
host. It is the common guide for people and agents. 2.x and 3.x receive no
more updates. All new releases use the new 0.x line, starting with
`v0.131.0-beta.1`; old 0.x through 0.130.x belongs to the retired line too.
Do not install 3.x beta as an intermediate step.

## Identify the host and layout

Use the host authorized for this task. Record its running version, native unit
or Compose project and files, data paths, image or binary, overrides and start
rules. `ftw.service`, a working dashboard and `ftw status` do not identify the
layout by themselves. A diagnosis stays read-only. An authorized update needs
no repeat approval for the same action.

| Installed layout | Action |
|---|---|
| New native with launcher and release slots | Follow the native update section: `ftw status`, `ftw update --channel beta`, then verify. Refresh launcher/CLI/unit only when the release requires it. |
| New Docker from a release package | Keep the project and data, set the exact `FTW_VERSION` in its existing `.env`, then `docker compose up -d --build`. Native update/rollback commands do not apply. |
| Old Docker, Pi image or Home Assistant app | Follow the switch guide. Use a separate new setup and data; guided transfer of old data is not ready. |
| Older direct native or custom development build | Inspect the actual binary and override first. Use the guide's older-native path or a checked recovery plan for that site. Do not infer a safe binary swap from the service name. |

## Returning from a development build

Preserve the current config, data and override before changing the running
process. Check that the chosen release can read those data. Use the update or
recovery steps for the identified layout, within the owner's authorized scope.
If the development build changed the data format, establish its recovery path
before replacing it.

The old container distribution, `ghcr.io/srcfl/ftw` and its compatibility
mirror, is retired. Do not pull `:latest` or restore a saved Compose override
to obtain new 0.x. The
[previous skill](https://github.com/srcfl/ftw/blob/5d811a937b6085df7d5494022c615d1f611198fb/.agents/skills/switching-ftw-deploy-mode/SKILL.md)
is historical context, not a current host recipe.

For local development with separate test data, use
[the development guide](../../../docs/development.md).

## Verify the result

Follow the common guide's checks: expected running release or revision,
healthy devices, advancing measurements, plan, history writes and recovery.
Confirm that only one Core controls the equipment and that old Core/updater
start rules cannot bring another instance back. Preserve required MQTT.
Repeat after a planned reboot; report any checks not done. A successful
Compose command or one visible dashboard does not prove a complete switch.
7 changes: 7 additions & 0 deletions .changeset/clear-update-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"ftw": patch
---

Installer messages now direct users to the new 0.x setup paths and explain that 2.x and 3.x receive no more updates. The scripts still exit without changing the site. Docker's missing-version message asks for an exact published new 0.x tag instead of suggesting an old example.

Block retired Docker release workflow dispatches before checkout or registry writes, while keeping native Changesets version PRs running.
171 changes: 49 additions & 122 deletions .claude/skills/switching-ftw-deploy-mode/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,126 +1,53 @@
---
name: switching-ftw-deploy-mode
description: Use when the user wants to flip an FTW host between the official container image and a host-built development binary, in either direction. Detect and preserve legacy service, directory, and binary aliases rather than assuming them.
description: Use when the user wants to switch an FTW host between a release and a development build, or asks how to update an existing host. Identify native release slots, new Docker packaging and retired Docker before choosing a path. Never use old Docker latest as a route to current FTW.
---

# Switching FTW deploy mode (official image ↔ development binary)

## Safety contract

Both modes use the existing Compose project and the same persistent `/app/data`
bind. Never create a second install directory, rename the Compose project, or
copy the data directory as part of this switch. Back up an override before
changing it, and use `docker compose up -d <main-service>` rather than
`down`.

The canonical layout is:

| Surface | Canonical | Supported legacy |
|---|---|---|
| Install directory | `~/ftw` | `~/forty-two-watts` |
| Compose service | `ftw` | `forty-two-watts` |
| Container binary | `/app/ftw` | `/app/forty-two-watts` |
| Official image | `ghcr.io/srcfl/ftw:latest` | mirrored `ghcr.io/frahlg/forty-two-watts:latest` |
| Dev binary | `~/ftw-dev/bin/ftw` | `~/ftw-dev/bin/forty-two-watts` |

## Required input

The SSH target (`user@host`) must come from the user. Do not guess or reuse a
host from unrelated context. The default development directory is
`~/ftw-dev`; confirm any different path before writing an override.

## Detect the installed layout first

Run a read-only probe:

```sh
ssh "$HOST" 'set -eu
if [ -d "$HOME/ftw" ]; then dir="$HOME/ftw"
elif [ -d "$HOME/forty-two-watts" ]; then dir="$HOME/forty-two-watts"
else echo "no FTW Compose directory found" >&2; exit 1
fi
cd "$dir"
services="$(docker compose config --services)"
count="$(printf "%s\n" "$services" | grep -Ec "^(ftw|forty-two-watts)$" || true)"
[ "$count" -eq 1 ] || { echo "expected exactly one FTW main service" >&2; exit 1; }
service="$(printf "%s\n" "$services" | grep -E "^(ftw|forty-two-watts)$")"
docker compose config "$service" | grep -q "/app/data" ||
{ echo "main service does not map persistent /app/data" >&2; exit 1; }
cid="$(docker compose ps -q "$service")"
printf "dir=%s service=%s container=%s\n" "$dir" "$service" "$cid"
docker inspect "$cid" --format "image={{.Config.Image}}"
ls -1 docker-compose.override.yml* 2>/dev/null || true'
```

Stop if both main service names own `/app/data`, neither does, or the data bind
is absent. Those layouts are ambiguous and must not be recreated automatically.

## Switch to the official image

Disable the development override by renaming it, then pull and recreate only the
detected main service:

```sh
ssh "$HOST" 'set -eu
if [ -d "$HOME/ftw" ]; then cd "$HOME/ftw"; else cd "$HOME/forty-two-watts"; fi
service="$(docker compose config --services | grep -E "^(ftw|forty-two-watts)$")"
if [ -f docker-compose.override.yml ]; then
mv docker-compose.override.yml "docker-compose.override.yml.dev-$(date +%Y%m%d-%H%M%S).bak"
fi
docker compose pull "$service"
docker compose up -d "$service"
cid="$(docker compose ps -q "$service")"
docker inspect "$cid" --format "image={{.Config.Image}} version={{index .Config.Labels \"org.opencontainers.image.version\"}}"'
```

The effective image after removing the override must be the canonical Sourceful
image or its published compatibility mirror. If it is still a local,
hard-coded developer tag, stop: the base Compose file itself was customized and
needs an explicit reviewed migration. Do not silently replace the whole file.

## Switch to a development binary

First locate a canonical or legacy host binary and verify its architecture:

```sh
ssh "$HOST" 'set -eu
for bin in "$HOME/ftw-dev/bin/ftw" "$HOME/ftw-dev/bin/forty-two-watts"; do
if [ -x "$bin" ]; then file "$bin"; exit 0; fi
done
echo "no executable FTW dev binary found" >&2
exit 1'
```

Prefer restoring the newest saved development override. It preserves the
user's actual service name and paths:

```sh
ssh "$HOST" 'set -eu
if [ -d "$HOME/ftw" ]; then cd "$HOME/ftw"; else cd "$HOME/forty-two-watts"; fi
service="$(docker compose config --services | grep -E "^(ftw|forty-two-watts)$")"
saved="$(ls -1t docker-compose.override.yml.dev-*.bak 2>/dev/null | head -n1 || true)"
[ -n "$saved" ] || {
echo "no saved dev override; review service and host paths before creating one" >&2
exit 1
}
cp "$saved" docker-compose.override.yml
docker compose up -d "$service"'
```

Only create a new override after the user confirms the detected service, host
binary path, and container target (`/app/ftw` for canonical images,
`/app/forty-two-watts` for a legacy image). A wrong target can leave the
container running the bundled official binary and make the switch look
successful when it was not.

## Verification

Verify all three independently:

1. `docker compose ps <service>` reports one healthy/running container.
2. `docker inspect <container>` shows the intended image and bind mount.
3. The dashboard version/about field or startup log matches the intended
official tag or development revision.

Do not treat a successful `docker compose up` alone as proof that the mounted
binary is running.
# Choose the FTW deployment and update path

Read [Install and update FTW](../../../docs/native-beta.md) before changing a
host. It is the common guide for people and agents. 2.x and 3.x receive no
more updates. All new releases use the new 0.x line, starting with
`v0.131.0-beta.1`; old 0.x through 0.130.x belongs to the retired line too.
Do not install 3.x beta as an intermediate step.

## Identify the host and layout

Use the host authorized for this task. Record its running version, native unit
or Compose project and files, data paths, image or binary, overrides and start
rules. `ftw.service`, a working dashboard and `ftw status` do not identify the
layout by themselves. A diagnosis stays read-only. An authorized update needs
no repeat approval for the same action.

| Installed layout | Action |
|---|---|
| New native with launcher and release slots | Follow the native update section: `ftw status`, `ftw update --channel beta`, then verify. Refresh launcher/CLI/unit only when the release requires it. |
| New Docker from a release package | Keep the project and data, set the exact `FTW_VERSION` in its existing `.env`, then `docker compose up -d --build`. Native update/rollback commands do not apply. |
| Old Docker, Pi image or Home Assistant app | Follow the switch guide. Use a separate new setup and data; guided transfer of old data is not ready. |
| Older direct native or custom development build | Inspect the actual binary and override first. Use the guide's older-native path or a checked recovery plan for that site. Do not infer a safe binary swap from the service name. |

## Returning from a development build

Preserve the current config, data and override before changing the running
process. Check that the chosen release can read those data. Use the update or
recovery steps for the identified layout, within the owner's authorized scope.
If the development build changed the data format, establish its recovery path
before replacing it.

The old container distribution, `ghcr.io/srcfl/ftw` and its compatibility
mirror, is retired. Do not pull `:latest` or restore a saved Compose override
to obtain new 0.x. The
[previous skill](https://github.com/srcfl/ftw/blob/5d811a937b6085df7d5494022c615d1f611198fb/.agents/skills/switching-ftw-deploy-mode/SKILL.md)
is historical context, not a current host recipe.

For local development with separate test data, use
[the development guide](../../../docs/development.md).

## Verify the result

Follow the common guide's checks: expected running release or revision,
healthy devices, advancing measurements, plan, history writes and recovery.
Confirm that only one Core controls the equipment and that old Core/updater
start rules cannot bring another instance back. Preserve required MQTT.
Repeat after a planned reboot; report any checks not done. A successful
Compose command or one visible dashboard does not prove a complete switch.
11 changes: 9 additions & 2 deletions .github/workflows/beta.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
name: beta release
name: retired Docker beta release

# Publication is blocked before checkout or credentials. Use native-release.yml.

on:
workflow_dispatch:
inputs:
version:
description: "Immutable prerelease tag, for example v0.128.0-beta.1"
description: "Retired input; publication is blocked. Use native-release.yml."
required: true
type: string

Expand All @@ -23,6 +25,11 @@ jobs:
name: verify package write access
runs-on: ubuntu-latest
steps:
- name: Refuse retired Docker publication
run: |
echo "::error::2.x and 3.x releases are retired. Use native-release.yml for new 0.x."
exit 1

- name: Checkout release checks
uses: actions/checkout@v7
with:
Expand Down
Loading
Loading