1
0
mirror of https://github.com/bestnite/quadlet-migrator-skill.git synced 2026-04-04 00:13:28 +00:00
This commit is contained in:
2026-04-03 20:09:42 +11:00
commit 0bdcd9a2e1
14 changed files with 3549 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
# Compose Mapping
Use this file when converting `docker-compose.yml` or `compose.yaml` into Quadlet units.
## General approach
- Model each service first, then decide how to group all resulting containers into one or more `.pod` units.
- Prefer maintainable Quadlet output over mechanical one-to-one translation.
- Keep names stable and predictable. Generated filenames should use a shared project prefix.
## Field strategy
### `name`
- Use as an application prefix when it improves unit naming clarity.
- Do not force a top-level project name into every filename if the user prefers shorter units.
### `services.<name>.image`
- Map to `Image=` in `[Container]`.
- Prefer fully qualified image names when normalizing output.
- If the source omits a registry and is using Docker Hub semantics, normalize it explicitly for Podman.
- Use these rules when filling in Docker Hub references:
- `redis:7` -> `docker.io/library/redis:7`
- `nginx` -> `docker.io/library/nginx`
- `langgenius/dify-api:latest` -> `docker.io/langgenius/dify-api:latest`
- Do not guess `docker.io/library/...` for images that already include a namespace.
### `services.<name>.build`
- Prefer upstream published images over local builds when the project already documents supported registry images.
- If the user wants declarative builds, create a `.build` unit and reference it from `Image=`.
- If the build semantics are too custom, or if an equivalent upstream image is clearly available, keep this as a manual follow-up item instead of guessing.
### `container_name`
- Drop it.
- Do not generate `ContainerName=`. Let Podman and systemd derive the runtime name.
### `ports`
- For a standalone service, map to `PublishPort=` on the `.container`.
- For a pod-based topology, prefer `PublishPort=` on the `.pod` when the published ports belong to the pod boundary rather than one child container.
### `volumes`
- Bind mounts become `Volume=HOST:CONTAINER[:OPTIONS]`.
- Normalize relative host paths against the Compose file directory and emit absolute paths in the final Quadlet output.
- Named volumes can remain referenced by name, but when the user wants explicit infrastructure-as-code, create matching `.volume` units.
- Ask the user which volume mode they want when the source does not make the intended persistence model obvious.
### `networks`
- If the source uses a default network only, you often do not need a `.network` unit.
- If a custom network is intentional and needs to be managed declaratively, create a `.network` unit and reference it explicitly.
- Containers in the same `.pod` can communicate over `127.0.0.1` / `localhost` because they share a network namespace.
- Containers in different pods must not be treated as reachable via `127.0.0.1` / `localhost`.
- When splitting services across multiple pods, use container networking and service-level addressing, or publish ports to the host boundary when that is the intended access pattern.
### `environment`
- Small stable values can become one or more `Environment=` lines.
- Sensitive values should be moved to `EnvironmentFile=` unless the user explicitly wants them inline.
### `env_file`
- Prefer `EnvironmentFile=`.
- If there are multiple env files, preserve order and explain precedence if the user asks.
### `.env` interpolation
- Resolve only when you have the actual source values.
- If variables are missing, surface a missing-variable list.
- Never silently replace unknown values with blanks.
### `profiles`
- Decide first which profiles are actually part of the desired deployment.
- Do not try to preserve Compose profiles as a direct Quadlet concept.
- Treat profiles as source selection inputs that decide which services become units.
### `depends_on`
- Translate to `Requires=` and `After=` when that reflects intent.
- State clearly that this controls startup ordering, not application readiness.
### `restart`
- Map common cases to `[Service] Restart=`.
- If the source policy has no direct systemd equivalent, choose the nearest safe mapping and explain it.
### `healthcheck`
- Prefer dedicated Quadlet health fields such as `HealthCmd=`, `HealthInterval=`, `HealthTimeout=`, `HealthRetries=` when representable.
- If the Compose healthcheck is only partially representable, preserve the command intent and call out missing knobs.
### `command` and `entrypoint`
- `entrypoint` typically maps to `Entrypoint=`.
- `command` typically maps to `Exec=`.
### `user`
- Map to `User=` and `Group=` in `[Container]` only when it is a container runtime user mapping, not a systemd service user.
- Do not use systemd `User=` to try to make a rootless Quadlet run as another login user.
### unsupported or risky fields
Handle these conservatively and usually as migration notes:
- `deploy`
- `profiles`
- `extends`
- advanced Compose merge behavior
- readiness semantics hidden behind `depends_on`
## Minimal examples
### Single service to standalone container
Source intent:
```yaml
services:
web:
image: nginx:latest
ports:
- "8080:80"
```
Reasonable result shape:
```ini
[Container]
Image=docker.io/library/nginx:latest
PublishPort=8080:80
```
Use this when the deployment is truly a simple single-service case and pod semantics add little value.
### Small multi-service app to one pod
Source intent:
```yaml
services:
api:
image: ghcr.io/example/api:1.0
depends_on:
- db
db:
image: postgres:16
```
Reasonable result shape:
- one `.pod` for the application boundary
- one container unit for `api`
- one container unit for `db`
- `api` may reach `db` over `127.0.0.1` / `localhost` because both containers share the pod network namespace
- ordering hints for startup, while explicitly noting that `depends_on` does not guarantee readiness
Use this when shared networking and a grouped lifecycle make the deployment easier to operate.
## Pod decision rule
Choose the simplest topology that preserves the source deployment intent.
Prefer a single `.pod` for multi-container applications when practical.
If the project is a simple single-container deployment with no real need for pod semantics, a standalone `.container` is acceptable.
If shared networking, shared published ports, or tightly coupled service lifecycle make pod semantics useful, prefer one or more `.pod` units.
If one pod is not practical because of port conflicts or clearly incompatible groupings, split the result into a small number of pods rather than forcing an awkward topology.
For large application stacks with optional services, ask the user to choose the desired service set before generating a minimized result.

View File

@@ -0,0 +1,55 @@
# Deployment Notes
Use this file when the user wants deployment-ready instructions alongside generated Quadlet units.
## Directory choice
### Rootless
- primary default: `~/.config/containers/systemd/`
- user-scoped management commands use `systemctl --user`
### Rootful
- primary default: `/etc/containers/systemd/`
- system-scoped management commands use `systemctl`
See `podman-systemd.unit.5.md` for the full search-path matrix.
## Rootless operational notes
- Bind mounts may hit UID/GID mismatches.
- For pod-based deployments that should preserve host ownership semantics, consider `UserNS=keep-id` on `[Pod]` when appropriate.
- If the service must survive logout, mention lingering:
```bash
sudo loginctl enable-linger <username>
```
## Paths and bind mounts
- Ensure bind-mount source directories exist before first start.
- Normalize relative source paths against the source Compose file directory or the directory the user specifies.
- Emit absolute host paths in generated Quadlet files when using bind mounts.
- Explain the resolved absolute path if the source used `./...`.
## Recommended service defaults
Depending on the workload, consider adding:
```ini
[Service]
Restart=always
TimeoutStartSec=900
```
Use the timeout especially when first start may need to pull large images or build locally.
## Useful optional enhancements
- `AutoUpdate=registry` for opt-in automatic image refresh workflows
- explicit `.volume` or `.network` units when the user wants declarative infrastructure instead of implicit Podman objects
## Output language
If you generate a README, deployment note, or operator-facing document as part of the migration, write it in the user's language unless the user explicitly asks for another language.

165
references/env-strategy.md Normal file
View File

@@ -0,0 +1,165 @@
# Environment Strategy
Use this file whenever the migration includes `.env`, `env_file`, Compose interpolation, or inline `-e` flags.
## Goals
- preserve source-of-truth for variables
- avoid leaking secrets into generated Quadlet files by default
- keep resulting units readable
- report missing variables explicitly
- reduce large upstream env templates into a small set of user decisions
## Default rules
The agent should actively interpret the env template and its comments. Do not offload the entire env review back to the user.
### Prefer `Environment=` when
- there are only a few variables
- values are stable and not sensitive
- keeping everything in one file materially improves readability
### Prefer `EnvironmentFile=` when
- the source already uses `.env` or `env_file`
- variables are numerous
- variables contain secrets or deployment-specific values
- the same env file is shared by multiple services
## Sensitive-value heuristic
Treat names containing these substrings as sensitive unless the user tells you otherwise:
- `PASSWORD`
- `TOKEN`
- `SECRET`
- `KEY`
- `PRIVATE`
- `PASS`
Default behavior:
- do not inline them in `Environment=`
- keep them in `EnvironmentFile=` or generate a placeholder sample file instead
## Compose interpolation
Common forms:
- `${VAR}`
- `${VAR:-default}`
- `${VAR-default}`
Strategy:
- if the actual value source is present, resolve it and document where it came from
- if only a default is available, note that the value is default-derived
- if the variable is missing, list it as unresolved
Do not fabricate values.
## Agent responsibility
When the source project ships a large `.env.example` or multiple env templates:
- read the comments and deployment docs yourself
- determine which values can safely stay at documented defaults
- determine which values are true deployment decisions and must be confirmed with the user
- prepare a candidate `.env` or env delta instead of asking the user to read the whole template manually
The user should only need to answer the small number of high-impact questions that cannot be discovered locally.
## Minimal examples
### Inline stable values
Source intent:
```yaml
environment:
APP_ENV: production
APP_PORT: "8080"
```
Reasonable result shape:
```ini
[Container]
Environment=APP_ENV=production
Environment=APP_PORT=8080
```
Use this when there are only a few non-sensitive structural values.
### Preserve env-file workflow
Source intent:
```yaml
env_file:
- .env
```
Reasonable result shape:
```ini
[Container]
EnvironmentFile=/opt/myapp/.env
```
Use this when the source already relies on an env file, especially for secrets or many variables.
### Large template reduced to a candidate env
Source intent:
- upstream ships `.env.example`
- only a few values are true deployment decisions
Recommended behavior:
- keep documented defaults that are safe to preserve
- ask the user only for high-impact values such as domain, storage path, or database password
- generate a candidate `.env` or env delta with clear placeholders for the unresolved items
## Output patterns
### Small inline set
```ini
[Container]
Environment=APP_ENV=production
Environment=APP_PORT=8080
```
### External env file
```ini
[Container]
EnvironmentFile=/opt/myapp/myapp.env
```
### Mixed pattern
Use this when a few values are structural and the rest are secret or deployment-specific.
```ini
[Container]
Environment=APP_ENV=production
EnvironmentFile=/opt/myapp/myapp.env
```
## Missing-variable reporting
When variables cannot be resolved, report them as a concrete checklist.
Example:
- missing `DB_PASSWORD`
- missing `IMMICH_VERSION`
- missing `UPLOAD_LOCATION`
If the user asks for scaffolding, generate a sample env file with obvious placeholders.
Even when the user does not explicitly ask for scaffolding, produce a candidate env result when that materially advances the migration.

View File

@@ -0,0 +1,57 @@
# GitHub Repository Intake
Use this file when the user provides a GitHub repository URL and expects you to find the deployment inputs yourself.
## Goal
Discover the canonical self-hosting assets before attempting any Quadlet conversion.
## Discovery order
1. Read the repository `README.md`.
2. Look for self-hosting instructions and explicit references to Compose files.
3. If documentation names a path, follow that path first.
4. Search the repository tree for likely deployment files:
- `docker-compose.yaml`
- `docker-compose.yml`
- `compose.yaml`
- `compose.yml`
- `.env.example`
- `.env.sample`
- `env.example`
- `middleware.env.example`
5. Inspect likely deployment directories when needed:
- `docker/`
- `deploy/`
- `ops/`
- `infra/`
- `examples/`
- `.devcontainer/`
6. Read deployment-specific README files in those directories.
7. Identify helper scripts that generate or sync compose files.
## What to extract
- canonical compose file path
- companion env template path
- additional compose files used for middleware or optional services
- whether the compose file is generated
- whether the source relies on profiles
- whether startup requires preparatory steps such as copying `.env.example` to `.env`
## Heuristics
- Prefer the path explicitly linked from the main README over a randomly discovered file.
- Do not hardcode assumptions like "the deployment entry point is always under `docker/`".
- If the repo has both a template and a generated compose file, treat the generated file as the runnable source and the template as explanatory context.
- If profiles control optional databases or vector stores, decide which profile set the user actually wants before generating Quadlet.
- If env management is mandatory, preserve that pattern rather than flattening hundreds of variables into inline `Environment=` values.
- If several candidate compose files exist, explain which one you selected and why.
## Migration posture for GitHub-sourced projects
When converting a GitHub-hosted project, report:
- which files you chose as source of truth
- which optional files or profiles you ignored
- which variables still require user decisions

File diff suppressed because it is too large Load Diff

70
references/validation.md Normal file
View File

@@ -0,0 +1,70 @@
# Validation
Use this file when the user asks how to verify or troubleshoot generated Quadlet units.
## Basic deployment flow
### Rootless
```bash
systemctl --user daemon-reload
systemctl --user start <unit>
systemctl --user status <unit>
```
### Rootful
```bash
systemctl daemon-reload
systemctl start <unit>
systemctl status <unit>
```
## Generator debugging
Use the Podman systemd generator dry run when units fail to appear or options look unsupported.
```bash
/usr/lib/systemd/system-generators/podman-system-generator --dryrun
```
For rootless debugging:
```bash
/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun
```
## Systemd verification
```bash
systemd-analyze verify <unit>.service
```
For user units:
```bash
systemd-analyze --user verify <unit>.service
```
## Common failure causes
- unsupported Quadlet option for the installed Podman version
- bind-mount source directory missing
- wrong rootless or rootful unit directory
- unresolved env file path
- permissions on rootless bind mounts
- readiness assumptions hidden behind `depends_on`
## Troubleshooting posture
When validation fails, report:
- what generated successfully
- what failed to generate or start
- whether the issue is syntax, unsupported feature, path resolution, or permissions
## Relationship to execution phase
Validation belongs after the files are written in the execution phase.
Before execution, the skill should already have completed planning and finalize review with the user. Do not treat validation as a substitute for design review.