diff --git a/skills/mittwald-migrate/playbooks/discover-source.md b/skills/mittwald-migrate/playbooks/discover-source.md index 3a7594b..9ae965c 100644 --- a/skills/mittwald-migrate/playbooks/discover-source.md +++ b/skills/mittwald-migrate/playbooks/discover-source.md @@ -79,6 +79,18 @@ For each persistent path, capture: mount path, real size, file count, "hot" (con **Watch out for "index" volumes** that are populated on demand (Solr, Elasticsearch, generated caches — Pitfall #11). If `du` shows them small or empty, do not waste time copying them; let the target rebuild on first start. +### Release-symlink layouts (Deployer / Capistrano) + +PHP/Ruby sources deployed with **Deployer** or **Capistrano** use a release-rotation layout: `current/` is a symlink to the active `releases//`, and persistent data (uploads, `.env`, logs) lives in `shared/`, symlinked into each release. Spot it during the file inventory — `ls -l` on the app root shows `current -> releases/`. + +Migrate the **active state only**, not the machinery: + +- **Don't** copy the whole tree — `releases/` holds N old deploys and multiplies the data. +- **Don't** ship the symlinks as-is — they'd land as broken links on the target. +- **Do** transfer from `current/` with `tar -h` (dereference): the active release *and* the `shared/` content it links to come across as one flat tree, into the target's single document root. You do **not** rebuild the `current`/`releases`/`shared` scaffolding on mStudio. Mechanics: [`migrate-files.md`](migrate-files.md). + +(If the operator wants to keep deploying *to* mStudio with Deployer afterwards, that's a separate post-migration setup — out of scope for the data move.) + ### External object storage / S3 If the app uses S3 / MinIO / Backblaze / equivalent: capture bucket names, region, total size, number of objects. Decision point: keep on the original provider (often the cheapest path) or move to Mittwald-attached storage / a project-mounted bucket. Often: **keep**. @@ -145,7 +157,7 @@ Same discipline for the data plane. Full procedure in [`../references/database-e For **each DB engine** in the inventory from §2: -1. If the engine is not MySQL and not Redis → **container in the stack**. Stop. +1. If the engine is not MySQL and not Redis → **container in the stack**. Stop. This includes **MariaDB**: mirror it to a `mariadb:` container — don't route a MariaDB source to managed MySQL by default (engine swap = explicit operator opt-in; see [`../references/database-engines.md`](../references/database-engines.md) "Default: mirror the source engine"). 2. If it is MySQL or Redis → live-query the supported versions: - MySQL: `mw database mysql versions` / `mcp__mittwald__mittwald_database_mysql_versions` / `GET /v2/mysql-versions` - Redis: `mw database redis versions -p ` (CLI quirk — projectId required) / `mcp__mittwald__mittwald_database_redis_versions` / `GET /v2/redis-versions` @@ -192,6 +204,7 @@ Produce a markdown summary and present it to the operator. Template: | Source engine | Source version | Target | Reason | |---|---|---|---| | MySQL | 5.7 | managed MySQL 5.7 | exact match, non-disabled | +| MariaDB | 10.11 | container `mariadb:10.11` | mirror source engine — MariaDB ≠ managed MySQL | | Redis | 7.0 | container `redis:7.0` | 7.0 is `disabled: true`, source operator doesn't want to upgrade | | Postgres | 15.6 | container `library/postgres:15.6` | no managed Postgres on mStudio | diff --git a/skills/mittwald-migrate/playbooks/migrate-mysql.md b/skills/mittwald-migrate/playbooks/migrate-mysql.md index 4cf0fec..16a6961 100644 --- a/skills/mittwald-migrate/playbooks/migrate-mysql.md +++ b/skills/mittwald-migrate/playbooks/migrate-mysql.md @@ -6,6 +6,8 @@ Decision was made during Discovery's DB-engines self-check ([`discover-source.md`](discover-source.md) §7b; full rules in [`../references/database-engines.md`](../references/database-engines.md)). One of two paths: +> **Default = mirror the source engine.** A **MariaDB** source goes to a **`mariadb:` container**, not managed MySQL — importing MariaDB into managed MySQL is an engine swap that only happens on explicit operator opt-in (see [`database-engines.md`](../references/database-engines.md) "Default: mirror the source engine"). The managed-vs-container choice below applies to genuine **MySQL** sources. + | Choice | Pros | Cons | |---|---|---| | Mittwald-managed MySQL | Mittwald owns backups, upgrades, monitoring | Limited version flexibility, not in the same stack network | diff --git a/skills/mittwald-migrate/playbooks/provision-target.md b/skills/mittwald-migrate/playbooks/provision-target.md index 1050e84..9cb06c9 100644 --- a/skills/mittwald-migrate/playbooks/provision-target.md +++ b/skills/mittwald-migrate/playbooks/provision-target.md @@ -237,6 +237,8 @@ The decision was already made in Discovery's DB-engines self-check ([`discover-s ### Managed MySQL +Only for a genuine **MySQL** source. Mirror a **MariaDB** source to a container instead (see "Container DBs" below) — don't import MariaDB into managed MySQL by default ([`../references/database-engines.md`](../references/database-engines.md) "Default: mirror the source engine"). + ```text mcp__mittwald__mittwald_database_mysql_versions # confirm chosen version is offered and not disabled mcp__mittwald__mittwald_database_mysql_create diff --git a/skills/mittwald-migrate/references/database-engines.md b/skills/mittwald-migrate/references/database-engines.md index b2ff1f0..552f6bb 100644 --- a/skills/mittwald-migrate/references/database-engines.md +++ b/skills/mittwald-migrate/references/database-engines.md @@ -25,6 +25,16 @@ curl -sS -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $MITTWALD_AP https://api.mittwald.de/v2/postgres-versions # expect 404 ``` +## Default: mirror the source engine + +For an autonomous migration the **default is to reproduce the source faithfully** — same engine, same major version. Switching engines is a behaviour change whose breakage often surfaces only in production, so it is an **explicit operator decision, never the agent's default**. + +- **Source is MariaDB → target is a MariaDB *container*** (matching major, e.g. `mariadb:10.11`). Do **not** import a MariaDB dump into managed MySQL by default. MariaDB and MySQL have diverged — auth plugins, `utf8mb4` collations, system tables, JSON handling, sequences, virtual-column syntax — so schemas usually port but app-level expectations can break after cutover. +- **Source is MySQL → target is MySQL** (managed or container — decided below). +- A MariaDB→MySQL (or any cross-engine) swap is only appropriate when the operator explicitly asks for it and accepts the compatibility risk. Surface it as a question; don't bake it into the plan. + +A managed or runtime app can still talk to a container DB by service name (apps and containers share the project network — see "App ↔ container DB networking" below), so **"managed app + container MariaDB" is a fully supported, first-class shape**, not a workaround. + ## Live-query primitives | Operation | MCP | CLI | API | @@ -91,11 +101,21 @@ Result: upgrade-required from 5.5 to ≥5.6 — propose 5.7 for closest semantic ## When the managed engine is the right call +(Assumes the source engine is **MySQL** — for a **MariaDB** source, mirror to a container; see "Default: mirror the source engine".) + - App is a Managed App (e.g. WordPress) where Mittwald is already responsible for the runtime — pair it with a managed DB for consistency and to let Mittwald do the upgrades. - Source's DB version is well-supported, with no exotic config. - Operator explicitly wants Mittwald to handle backups, point-in-time recovery, and security patches. - The DB is a small, well-bounded thing (a Redis cache, a single-app MySQL instance) — managed gives you less surface to operate. +## App ↔ container DB networking + +Within a project, **apps and container stacks share the same network**. A managed or runtime app reaches a stack service by its **service name** as hostname (the service's map key in the compose — e.g. `mariadb`), the same way containers reach each other (Pitfall #16). Per the mittwald docs: *"Managed applications and containers are connected to the same network. […] you can access managed applications from your containers and vice versa"* ([containers platform docs](https://developer.mittwald.de/docs/v2/platform/workloads/containers/)). + +Consequence: a PHP / Node.js / Python runtime app (or a Managed App) can use a **container database** with `DB_HOST=` — no port-forward, no public exposure. This is the recommended shape when mirroring a container-only engine (MariaDB, Postgres, Mongo). + +Contrast with **managed** MySQL/Redis, which live *outside* the stack network: reach them via the host from `database_mysql_get` / `database_redis_get`, not a service name (Pitfall #16 does not apply to managed DBs). + ## Cross-references - Provisioning details, including the managed-host vs service-name distinction: [`../playbooks/provision-target.md`](../playbooks/provision-target.md) §7. diff --git a/skills/mittwald-migrate/references/mittwald-surfaces.md b/skills/mittwald-migrate/references/mittwald-surfaces.md index 2a07682..fac543a 100644 --- a/skills/mittwald-migrate/references/mittwald-surfaces.md +++ b/skills/mittwald-migrate/references/mittwald-surfaces.md @@ -110,7 +110,7 @@ Identifier convention below: `{projectId}` etc. are UUIDs. CLI accepts short-IDs | Operation | MCP | CLI | API | |---|---|---|---| | List projects | `project_list` | `mw project list -o json` | `GET /v2/projects` | -| Get project | `project_get` | `mw project get -p {projectId} -o json` | `GET /v2/projects/{projectId}` | +| Get project | `project_get` | `mw project get {projectId} -o json` ⚠ project is a **positional** arg, not `-p` | `GET /v2/projects/{projectId}` | | Create project | `project_create` | `mw project create ...` | `POST /v2/servers/{serverId}/projects` | | Delete project (destructive) | `project_delete` | `mw project delete {projectId} -f` | `DELETE /v2/projects/{projectId}` | | Assemble SSH connection info | `project_ssh` (returns ready-to-use ssh command) | `mw project ssh ` ⚠ takes the project as a **positional** arg, not `-p` — opens an interactive shell directly | composed from `clusterId` + `clusterDomain` + `shortId` on the project object, plus an app or container shortId. No single endpoint; see [`ssh-modes.md`](ssh-modes.md) §"Assembling the SSH command from API fields". | @@ -167,6 +167,7 @@ Managed engines on mStudio: **MySQL and Redis only**. Everything else → contai | List / get domain | `domain_list` / `_get` | `mw domain list -o json` / `mw domain get {id}` | `GET /v2/projects/{projectId}/domains[/{id}]` | | List virtualhosts | `domain_virtualhost_list` | `mw domain virtualhost list -o json` | `GET /v2/projects/{projectId}/ingresses` | | Create / delete virtualhost | `domain_virtualhost_create` / `_delete` | `mw domain virtualhost create/delete` | `POST` / `DELETE /v2/projects/{projectId}/ingresses[/{id}]` | +| Update virtualhost **paths** (re-route) | — | — (no CLI command yet; delete+recreate, or use API) | `PATCH /v2/ingresses/{ingressId}/paths` — **resource-scoped**, not under `…/projects/{id}/…` | | Get DNS zone | `domain_dnszone_get` / `_list` | `mw domain dns get/list` | `GET /v2/projects/{projectId}/dns-zones[/{id}]` | | Update DNS zone | `domain_dnszone_update` | `mw domain dns update {zoneId}` | `PUT /v2/projects/{projectId}/dns-zones/{id}/records` | @@ -181,10 +182,12 @@ Managed engines on mStudio: **MySQL and Redis only**. Everything else → contai | Operation | MCP | CLI | API | |---|---|---|---| -| One-shot backup | `backup_create` | `mw project backup create --expires 30d` | `POST /v2/projects/{projectId}/backups` | -| List / get / delete | `backup_list` / `_get` / `_delete` | `mw project backup list/get/delete` | `GET` / `DELETE /v2/projects/{projectId}/backups[/{id}]` | -| Download | — | `mw project backup download {id}` | dedicated endpoint via signed URL | -| Schedules | `backup_schedule_*` | `mw project backupschedule list` (+ create/update/delete) | `…/backup-schedules…` | +| One-shot backup | `backup_create` | `mw backup create --expires 30d` | `POST /v2/projects/{projectId}/backups` | +| List / get / delete | `backup_list` / `_get` / `_delete` | `mw backup list/get/delete` | `GET` / `DELETE /v2/projects/{projectId}/backups[/{id}]` | +| Download | — | `mw backup download {id}` | dedicated endpoint via signed URL | +| Schedules | `backup_schedule_*` | `mw backup schedule list` (+ create/update/delete) | `…/backup-schedules…` | + +> ⚠ The `mw project backup …` / `mw project backupschedule …` forms are **deprecated** — the CLI now nests these under `mw backup …` / `mw backup schedule …`. ### Registry, SSH users, volumes diff --git a/skills/mittwald-migrate/references/pitfalls.md b/skills/mittwald-migrate/references/pitfalls.md index 1ae2df3..e505879 100644 --- a/skills/mittwald-migrate/references/pitfalls.md +++ b/skills/mittwald-migrate/references/pitfalls.md @@ -249,6 +249,8 @@ Update the compose to reference the new path. **Fix.** Rewrite source-side DB hostnames to bare service names: `DB_HOST=postgresql`, `REDIS_HOST=redis`. Capture this in the env-var rewrite map during Discovery. +This reach extends **across the app↔stack boundary**: a managed or runtime app in the same project resolves a container service by the same service name (apps and containers share the project network), so "runtime app + container DB" works with `DB_HOST=`. See [`database-engines.md`](database-engines.md) § "App ↔ container DB networking". (Managed MySQL/Redis are the exception — they live outside the stack network and are reached by host, not service name.) + --- ## #17 — Default `.project.space` is your smoke-test surface diff --git a/skills/mittwald-migrate/references/ssh-modes.md b/skills/mittwald-migrate/references/ssh-modes.md index 5e764c9..a11fe15 100644 --- a/skills/mittwald-migrate/references/ssh-modes.md +++ b/skills/mittwald-migrate/references/ssh-modes.md @@ -22,6 +22,14 @@ Where: Both modes go through the same SSH gateway (`ssh..`). +> **Local SSH multiplexing breaks mStudio connections.** If your `~/.ssh/config` enables connection sharing (`ControlMaster auto` with a `ControlPath`), an mStudio connection can fail with a misleading `could not connect to project` even though the key is registered and the app is `ready` — the shared control socket is keyed on the gateway host and collides with the multi-`@` routing identity. Disable it per connection: +> +> ``` +> ssh -o ControlMaster=no -o ControlPath=none @@ssh..project.host +> ``` +> +> Apply the same two flags to any `scp` / `rsync -e ssh` / `tar … | ssh …` pipeline against mStudio. + ## Two SSH user models — which one are you using? This decides where the SSH key lives and what the `` looks like in the URL.