# 1panel App Adapter

> Adapt, generate, migrate, and validate 1Panel App Store (AppStore/appstore) packages from Docker Compose, AppSpec, and aaPanel/Baota sources. Use when users ask for a 1Panel app skill, 1Panel application adaptation, appstore packaging or submission preparation, v1-to-v2 migration, Docker Compose conversion, localized metadata, strict-store validation, or batch app adaptation on Linux and GitHub.

- Skill: `okxlin/1panel-app-adapter` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add okxlin/1panel-app-adapter`
- Raw SKILL.md: https://api.skillmd.com/api/skills/okxlin/1panel-app-adapter/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: okxlin (https://skillmd.com/u/okxlin)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/okxlin/1panel-app-adapter

---


# 1Panel AppStore Adapter

Adapt Docker applications into reviewable 1Panel AppStore packages, then validate their structure, metadata, Compose configuration, localization, and upgrade behavior against source-backed rules.

## Submission Target and Delivery Defaults

Use **`third-party` by default**. Select **`official`** only for an official 1Panel appstore submission, and pass `--submission-profile official` to generation, migration, import, and validation. Read [references/submission-profiles.md](references/submission-profiles.md) for the exact file and form contract before choosing a route.

- Deliver only `<out-dir>/<app-key>/`. Keep source, license-review, registry, and test evidence outside it at `<out-dir>/.evidence/<app-key>/source-evidence.json`; pass `--source-evidence <path>` when validating a copied package.
- Third-party packages retain `.env.sample` and reviewed path choices. Official packages omit `.env.sample`; any existing directory form fields keep their source-backed defaults with `disabled: true` and `edit: false`. Neither mode changes the selected service graph or mount mechanism.
- Write Chinese `README.md` and English `README_en.md` with product features and usage. Follow `references/readme-style.md`. Do not put the selected application version, audit records, or generator diagnostics into product prose; document a necessary upgrade boundary only when it changes a user action.
- Add a lifecycle hook only for a concrete setup, reconciliation, migration, or cleanup operation. Prefer Compose and upstream entrypoints for ordinary startup. Do not build an extra startup framework, runtime package installer, secret generator, or generic supervisor without an exact upstream requirement. Preserve source-backed ownership and path confinement when they are needed.
- Do not generate application license text merely because an image is referenced. Ship only notices or sources required by material actually copied into the package. The fallback icon's MIT notice is included in the README, without an extra license text or SVG.

## Start Here: Choose One Route

Work from the skill directory so every `scripts/...` and `references/...` path resolves. Read this `SKILL.md` through its final line. First classify the input, then follow exactly one route below. Open and read every reference named by that route through its final line before running its command, and state how the references affected the plan or artifact. Every route ends at **Completion Gates**. If official Docker deployment evidence is unavailable, stop and report the missing evidence; do not guess a package. Publishing is outside this skill.

1. **New app from official Docker/Compose**: Read `references/source-policy.md`, `references/topology-preflight.md`, and `references/lifecycle-safety.md`, then record the preflight decision before scaffolding. Stop for `platform_stack_terminal`; stop for `specialized_conditional` until every named prerequisite is proven. `--out-dir` is a parent: for example, run `bash scripts/scaffold-v2.sh --app-key <app-key> --title <title> --image <image> --version <version> --out-dir "$RUN_ROOT/artifact" --source-repository <url> --source-docker-docs <url> --source-compose-file <url>`, then require `"$RUN_ROOT/artifact/<app-key>"` to directly contain `data.yml`, `README.md`, `README_en.md`, and `<version>/`; external `source-evidence.json` is a sibling sidecar; reject `<app-key>/<app-key>`. Review every generated file against the authoritative Compose and replace all placeholders. For callback, origin, or other public URL fields, never synthesize `localhost` or `127.0.0.1`: expose a required public URL as a required form field; leave an optional public URL empty or omit it and document the affected features.
2. **AppSpec input**: Read `references/appspec.md`, `references/source-policy.md`, `references/topology-preflight.md`, and `references/lifecycle-safety.md`. Run `python3 scripts/generate-from-appspec.py --spec <appspec.json> --out-dir <out-dir> --validate --require-validate`. Review the generated topology, variables, metadata, translations, lifecycle ledger, and validation report against the AppSpec and official sources.
3. **Existing v1 or mixed package**: Read `references/source-policy.md`, `references/topology-preflight.md`, `references/upgrade-maintenance.md`, and `references/lifecycle-safety.md`. Run `bash scripts/migrate-v1-to-v2.sh --src <app-dir> --out <out-dir> [--version <source-version>] [--target-version <target-version>] --source-repository <url> --source-docker-docs <url> --source-compose-file <url>`. Review the migrated root/version metadata, Compose, `.env.sample`, lifecycle scripts, and upgrade compatibility; source URL flags may be omitted only when the source package has valid external or historical in-package `source-evidence.json`.
4. **aaPanel/Baota input**: Read `references/baota-migration-workflow.md`, `references/baota-app-format.md`, `references/baota-to-1panel-mapping.md`, `references/source-policy.md`, `references/topology-preflight.md`, and `references/lifecycle-safety.md`. Precheck the complete prepared input with `python3 scripts/import-baota-app.py --input <baota-app-dir> --precheck-only --report <report.json>`; for a batch add `--batch`. Then convert one selected version per invocation with `python3 scripts/import-baota-app.py --input <baota-app-dir> --out-dir <out-dir> --version <exact-version> --validate --require-validate`. Review every output as `converted_candidate` against official upstream evidence; never infer version order from Baota metadata.
5. **Update an existing v2 app**: Read `references/upgrade-maintenance.md`, `references/source-policy.md`, `references/topology-preflight.md`, and `references/lifecycle-safety.md`. Compare the old and new package before editing; use only the needed helper commands below. Review image lineage, persisted data, changed variables, dependencies, lifecycle scripts, and direct-upgrade behavior, then run final validation.
6. **Validate only**: Read `references/source-policy.md`, `references/topology-preflight.md`, and `references/lifecycle-safety.md`; also read `references/upgrade-maintenance.md` when several versions or an update are involved. Start with `bash scripts/validate-v2.sh --dir <app-dir>` and review every failure and warning before strict validation. Validation does not authorize guessing or silently patching unknown semantics.
7. **PHP runtime**: Read `references/php-runtime.md`, `references/source-policy.md`, `references/topology-preflight.md`, and `references/lifecycle-safety.md` before choosing a generator. Follow the runtime-specific package shape, review picker metadata and actual runtime integration, then use the applicable helper commands and final validation below. Do not treat a PHP runtime as an ordinary website/tool app.

### Exact Helper Commands

Use scripts for their named job instead of manually recreating their behavior. Review each diff after a mutating helper; patch helpers normalize structure but cannot prove application semantics.

| Job | Command |
| --- | --- |
| New v2 scaffold | `bash scripts/scaffold-v2.sh --app-key <key> --title <title> --image <image> --version <version> --out-dir <out-dir> --source-repository <url> --source-docker-docs <url> --source-compose-file <url>` |
| AppSpec generation | `python3 scripts/generate-from-appspec.py --spec <appspec.json> --out-dir <out-dir> --validate --require-validate` |
| v1 migration | `bash scripts/migrate-v1-to-v2.sh --src <app-dir> --out <out-dir> [--version <source-version>] [--target-version <target-version>] --source-repository <url> --source-docker-docs <url> --source-compose-file <url>` |
| Single Baota precheck | `python3 scripts/import-baota-app.py --input <baota-app-dir> --precheck-only --report <report.json>` |
| Batch Baota precheck | `python3 scripts/import-baota-app.py --input <apphub-dir> --batch --precheck-only --report <report.json>` |
| Explicit-version Baota import | `python3 scripts/import-baota-app.py --input <baota-app-dir> --out-dir <out-dir> --version <exact-version> --validate --require-validate --report <report.json>` |
| Root metadata patch | `python3 scripts/patch_root_data_yml.py <app-dir>/data.yml [app-key] [architectures]` |
| Version metadata patch | `python3 scripts/patch_version_data_yml.py <app-dir>/<version>/data.yml` |
| Compose patch | `python3 scripts/patch_compose_yml.py <app-dir>/<version>/docker-compose.yml [app-type]` |
| Regenerate env sample | `bash scripts/gen-env-sample.sh <app-dir>/<version>/data.yml <app-dir>/<version>/.env.sample` |
| Finalize required directory initialization | `bash scripts/finalize_runtime_scripts.sh <app-dir> <app-dir>/<version>` |
| Apply a proven non-root directory owner | `bash scripts/finalize_runtime_scripts.sh <app-dir> <app-dir>/<version> --dir-owner APP_DATA_DIR=<uid>:<gid>:0750 --replace-init` |
| Normalize logo | `bash scripts/normalize-logo.sh <app-dir>/logo.png` |
| Baseline validation | `bash scripts/validate-v2.sh --dir <app-dir> [--version <version>]` |
| Delivery validation | `bash scripts/validate-v2.sh --dir <app-dir> [--version <version>] --strict-store --i18n-mode strict --source-evidence-mode required --require-delivery-evidence` |

### Completion Gates

- Confirm authoritative repository, Docker documentation, Compose/image evidence, license, and topology; record unsupported facts instead of inventing them, and stop when the selected preflight route says to stop.
- Preserve the selected upstream service graph, dependencies, internal networks, persistence, and security controls. Give every Compose service `labels.createdBy: "Apps"` and a unique `container_name` based on `${CONTAINER_NAME}` unless current 1Panel runtime evidence requires another shape.
- Build an authoritative control inventory from the selected official launch command, Compose, image, and entrypoint before editing. Record services, image commands and users, environment variables and fixed values, healthchecks, dependencies, ports, networks, mounts and mount options, capabilities, security options, privileged mode, devices, and host namespaces. Compare the final Compose against that inventory, preserve every source-backed control, and justify every omission or change with official source or target-platform evidence. A fixed upstream hardening value is not an optional install-form setting; keep it fixed unless evidence supports changing it.
- Build a capability contract for each official deployment shape. Record its user-visible capabilities and the exact service, image, environment, and dependency combination that enables each one. Preserve the full upstream capability set by selecting either the full upstream default or an officially/target-platform-proven capability-equivalent alternative. Select a reduced-capability profile only when the user explicitly requests it; disclosure does not substitute for capability preservation. For an authorized reduced profile, prove the remaining combination is supported, list the exact unavailable features in the README and report, and use any required compatible image variant; do not call it the default or source-equivalent topology. Missing compatibility evidence blocks scaffolding.
- Before assigning any route, write a deployment-shape census. Start from official install guides and their indexes or README, then enumerate every referenced sample or Compose root in the application and companion deployment repositories. For each discovered root, trace its include/template chain and record whether it is selected or rejected and why. A route conclusion that considers only the most obvious sample is incomplete.
- After the census, evaluate each deployment shape independently in a deployment-shape decision ledger. For each shape, record its exact root and release, support scope, default capability set, external dependency ownership, persistence boundary, migration and upgrade boundary, and disposition as selected, conditional, or rejected with evidence. Treat a documented operator-managed dependency as an external-service boundary, not a missing service. A more complex production topology does not invalidate a smaller official topology, and a production-ready label is scoped evidence, not an exclusivity claim. Invalidate a smaller shape only with exact-version evidence in one of these categories: officially required, exclusive, deprecated, or capability-incomplete. Assign platform_stack_terminal only after every capability-complete official shape has been evaluated and none can enter `ordinary_candidate` or `specialized_conditional` with satisfiable named prerequisites.
- The default capability set excludes optional features that are not enabled by default. Do not pre-publish optional listeners, mounts, devices, capabilities, or host permissions. Keep one only when the selected profile explicitly enables and fully configures it and the capability is validated; "for later configuration" is not evidence that it belongs in the selected profile.
- Apply the historical official source fallback before stopping for missing deployment evidence. A single 404 or moved page is not a terminal condition: inspect the exact release tree, version history, official documentation repositories, pinned historical Compose, and official image/startup sources, then record every attempted official source. When deployment evidence comes from a companion repository, pin its exact commit and trace the selected root through its actual include/template chain; an alternative file on a moving default branch is not evidence that the selected topology uses it. Stop only after those paths are exhausted and a concrete unsafe unknown remains; name the missing contract fact and the unsafe guessed decision it blocks.
- A terminal route does not waive OCI inspection. Inspect the OCI manifest and OCI config for each published image used by every viable official shape, including base, bootstrap, or runtime images named by launchers. If no immutable published registry artifact exists or registry access remains unavailable after the approved checks, record the unavailable registry fact and limit negative security or runtime claims; do not infer image controls from YAML alone.
- Build a startup configuration contract by inspecting every available exact-version authority among the official install examples, configuration reference, image defaults, entrypoint, and startup source; record unavailable authorities instead of requiring all of them to exist. Do not treat the selected official Compose as an exhaustive startup configuration list. Scope the ledger to required, startup-fatal, stability-bearing, or coupled inputs for the selected topology, plus any selected or default-dependent optional inputs. Record each value's default, empty-value rule, consumer, validation rule, secret format, stability requirement, and cross-field constraints, and prove the consumer and startup behavior from source or exact-image/runtime evidence. Reconcile every required value into the final Compose, `data.yml`, `.env.sample`, or source-backed lifecycle logic, then trace it to the application consumer. Whenever a form `envKey`, `label`, or `type` changes, re-review its `default`, `rule`, and `values`; keep every non-empty, non-secret, Compose-consumed form default consistent with `.env.sample` as defined in `references/lifecycle-safety.md`. Treat validated form values as literal data during template generation, never as regular-expression or replacement-program input. `required: true` or a generic `paramComplexity` rule does not prove the application-specific validation contract; enforce the full rule before Compose starts or stop. A successful Compose render does not prove that the panel default passes application startup validation; an unresolved or omitted required value blocks a pass claim.
- Record `source-evidence.json.images[]` for every selected version's Compose service image, including database, cache, browser, migration, and helper services. Bind the version, exact service, resolved image reference, and matching registry digest; prefer a digest-pinned runtime reference, but do not claim that a tag-only reference is immutable. The legacy single `imageEvidence` covers only a one-image Compose. For a multi-platform image, record the index digest separately from each platform child digest. Associate a platform child digest only from that same registry descriptor; do not infer it from descriptor order, the current host architecture, or a separate query. Recheck every declared platform-to-digest mapping before reporting it.
- For every service with a writable bind, add `runtimeIdentity` to its selected `images[]` entry (or legacy `imageEvidence`) with numeric `startupUid`, `startupGid`, `steadyStateUid`, `steadyStateGid`, an exact-version HTTPS `source`, and `writableBindOwner`. Use `host-init` only with generated `--dir-owner`/`--fixed-dir-owner` coverage for every bind, `image-managed` only with a separate HTTPS `ownerEvidence`, and `root-runtime` only when `steadyStateUid` is `0`. Delivery validation rejects missing identity evidence or an unmatched non-root owner plan.
- Keep a minimal install form: expose only settings users need for the selected default topology. Do not mirror every optional upstream profile or environment variable; remove disabled profiles or resolve reviewed package defaults while maintaining Compose/form/`.env.sample` closure.
- For callback, origin, and externally visible base URLs, use the exact current upstream variable and expose one full public URL when user input is required. Do not reconstruct it from host, protocol, or port fields unless version-matched official evidence proves that contract equivalent. Keep the external URL separate from the internal listener: for a reverse proxy, preserve the documented internal scheme/port, TLS termination boundary, and trusted-proxy or hop settings. Do not set an internal listener to HTTPS without the application's required certificates and source-backed configuration.
- Treat `.env` as untrusted data in lifecycle scripts. Never `source` or `eval` it; parse only exact known keys, strip quotes, validate values, and resolve relative paths from the app root.
- Complete the `references/lifecycle-safety.md` path and mount ledger, including each source-backed mount mechanism, mount options, and operator-access contract. Treat source, target, read/write mode, propagation, and security options in authoritative Compose as required delivery defaults; preserve them exactly unless target-platform evidence proves incompatibility and a reviewed replacement preserves the same contract. A named volume is not a drop-in replacement for a configuration bind used by official host-side editing, backup, restore, or support procedures; preserve that fixed package-local bind and do not add an `APP_DATA_DIR` form unless a selectable host path is actually required. Derive startup and steady-state runtime UID/GID separately from the published OCI configuration, Compose `user`, and verified entrypoint/process behavior; keep mutable host paths package-local and confined before creation, permission changes, or cleanup. When the selected authoritative deployment uses a named volume for a non-root writable path, preserve that volume. When it uses a bind, use the explicit `--dir-owner`/`--fixed-dir-owner` helper with a source-backed identity on a direct child of the trusted version root, then verify the exact owner, mode, and a write probe as that identity. Change the mechanism only through the same target-platform incompatibility and equivalent-replacement gate. A plain root-created `0755` bind directory blocks delivery. Do not recursively change ownership on an unconfined or symlinked path.
- Report an observed owner or mode as an observation tied to the invoking UID/GID and umask. Claim a portable guarantee only when the delivered lifecycle script explicitly enforces that owner or mode and the exact-artifact test verifies it.
- Create or validate the exact source file for every file bind before Compose starts. Prove each generated secret format against the application contract, keep stable secrets across upgrade, URL-encode URL credentials, and apply the official escaping rules to every other connection-string grammar.
- Replace placeholders with real product metadata and meaningful translations in all required locales. English fields must contain English. Record the application and asset licenses; when exact redistribution terms require attribution, copyright or license text, source disclosure, or NOTICE delivery, include that required material in the package instead of relying on a link. When a license has material use restrictions, name and link it in the README instead of keeping it only in machine-readable evidence. Verify an asset's redistribution basis separately from the application code license; for an unresolved asset license or trademark permission, use the neutral placeholder immediately rather than shipping the asset with a future-confirmation note.
- Render and validate the exact delivered artifact without creating then removing a file it needs. Keep only lifecycle hooks that perform required work, and retain executable mode for every delivered hook. Missing no-op hooks are valid; preserve any source-backed setup, upgrade, or cleanup operation. Run baseline validation first, then `bash scripts/validate-v2.sh --dir <app-dir> [--version <version>] --strict-store --i18n-mode strict --source-evidence-mode required --require-delivery-evidence`; unresolved failures block a pass claim.
- Before finalizing the README or report, build a configuration claim ledger and compare each statement with the exact Compose, `data.yml`, `.env.sample`, and lifecycle scripts. Classify every described value as fixed, defaulted, generated, optional, or user-configurable. If an editable form field controls it, describe it as user-configurable with that default rather than fixed; contradictions block a delivery-ready claim.
- For every form field, record its install-time and steady-state consumer before choosing `edit`. A value generated once, written into persistent configuration, or identity-bearing must use `edit: false` unless an idempotent reconciliation or migration applies later edits to the real persisted consumer. Do not expose a control that only changes `.env` while the application continues using an older persisted value.
- Test in a real 1Panel development/test instance: clean install, application-specific readiness, restart, upgrade when applicable, uninstall, and task-owned cleanup. Report artifact paths, evidence, checks, risk-bearing permissions, assumptions, warnings, and every unexecuted runtime gate; static validation or HTTP 200 alone is insufficient. Distinguish files in the delivered AppStore package from run-only evidence caches. Do not claim that a run-only cache path is present in the delivered package.

## Rule Priority

Judge every rule in this order:

1. Runtime and source-code hard rules from `1Panel-dev/1Panel`
2. Official 1Panel wiki and official docs
3. Official appstore repository conventions
4. External references and practical articles

Only rules backed by runtime behavior or explicit official documentation should block generation or validation. Repository conventions are guidance unless validator mode explicitly upgrades them.

## Source Policy (Evidence First)

Before adapting a new candidate, read `references/source-policy.md`, `references/topology-preflight.md`, and `references/lifecycle-safety.md`. Complete the topology decision before scaffolding or deployment testing: ordinary candidates may proceed, specialized conditional candidates need their recorded prerequisites satisfied, and platform-stack terminal candidates stop unless the user opens a separately scoped project.

Collect evidence according to source priority:
- User-provided official repository/documentation > official image docs > official appstore structure facts > third-party examples
- Without official Docker evidence, stop expanding; do not guess images, ports, volumes, UID/GID, or dependency injection methods
- When using third-party images, user must explicitly accept, and record source and risk in delivery notes
- Preserve high-risk runtime permissions when official compose, documentation, or source evidence shows that a core application feature requires them. This includes Docker or Podman Socket mounts, `privileged: true`, `cap_add`, host networking, host PID/IPC namespaces, device mappings, and relaxed `security_opt` settings. Do not remove a required permission merely to reduce scanner findings.
- Keep required high-risk access no broader than upstream needs, test the feature that depends on it, and document the exact permission, feature dependency, and host impact in the app README and delivery notes. A risk label is required; deletion is not the default remediation.

For existing app updates, version additions, image lineage changes, dependency changes, volume/env rewrites, or lifecycle script edits, also read `references/upgrade-maintenance.md` before changing files. Treat upgrade safety as part of the adaptation contract, not a post-submit note.

For PHP runtime work, especially when converting a historical package such as `php-unofficial` into a real 1Panel PHP runtime, also read `references/php-runtime.md`. PHP runtimes are not packaged like ordinary website/tool apps, and the runtime picker behavior is source-backed rather than guessable from generic appstore patterns.

## Recommended Directory Structure (Store-Aligned)

### Field Hierarchy Constraints (Aligned with Official apps/ Facts)

> Note: In the official repository (1Panel-dev/appstore dev branch), the `data.yml` field hierarchy under v2 structure is very stable; this skill's `validate-v2.sh --strict-store` performs strict validation according to this.

> - **Language codes**: Application metadata uses runtime keys `zh-hant`, `pt-br`, and `es-es`. UI locale names such as `zh-Hant` are accepted as historical input aliases, then normalized for generated output. See the pinned source evidence in `references/1panel-sources.md`.

**Application-level**: `apps/<app>/data.yml`
- Top-level only allows: `name` / `tags` / `title` / `description` / `additionalProperties`
- Top-level `type` appearing: **WARN only** (historical/non-standard field; official regular write doesn't place at top level)
- `architectures`: Must be in `additionalProperties.architectures` (100% present in official samples)

**strict-store additionalProperties required (missing = FAIL):**
- `key` / `name` / `tags` / `type` / `website` / `document` / `architectures`
- `github` / `shortDescZh` / `shortDescEn` / `crossVersionUpdate` / `limit`
- (Others like `recommend` / `description` / `memoryRequired` can maintain WARN by occurrence rate)

**Version-level**: `apps/<app>/<ver|latest|stable>/data.yml`
- Top-level only allows: `additionalProperties`
  - Can be `null` or `object` (but under strict-store, recommend object with required fields filled)
- `formFields` must be in `additionalProperties.formFields` (top-level `formFields` not allowed)
- **Prohibit `architectures`**: Version-level `data.yml` (same level as `docker-compose.yml`) should not have `architectures` (neither top-level nor inside `additionalProperties`)

> Convention supplement (this skill's default artifact style):
> - root `data.yml` top-level `description` uses single-line string (not map).
> - root `data.yml` `additionalProperties.shortDesc` uses `shortDescZh/shortDescEn` (not map).
> - `additionalProperties.description` uses i18n map, **complete all 12 supported languages for delivery**: `en/zh/zh-hant/ja/ko/ru/ms/pt-br/tr/es-es/fa/lo`.
> - root `data.yml` uses hierarchical structure: top-level `tags` and `additionalProperties.tags` both exist and are semantically consistent (allow redundant expression).
> - **Descriptive summaries**: `title` may be the product name. Top-level `description` and `additionalProperties.shortDescZh/shortDescEn` must explain the application's purpose in a short sentence; a product name alone fails strict-store validation. Keep their meaning consistent with the multilingual description. `patch_root_data_yml.py` and `appstore_i18n.py --normalize` can repair missing/name-only summaries from supplied Chinese and English descriptions while preserving valid prose.
> - **Translation constraint (strong constraint)**: `additionalProperties.description` must be the multilingual translation of the above `shortDescZh` (not repetition of project name/title).
> - `architectures` (only root `data.yml`) represents Docker image supported architecture list; should be in `additionalProperties.architectures`, using hierarchical array (e.g., `- amd64` / `- arm64`); if cannot reliably obtain (e.g., no manifest info/offline), default only fill `- amd64`.

### formFields Structure Facts (version-level)

Location: `additionalProperties.formFields: [ ... ]`

### `formFields[]` item common fields
- `envKey` (required)
- `type` (required)
- `required` (required)
- `default` (common)
- `rule` (optional; common set below)
- `labelZh` / `labelEn` (high-frequency; recommend providing both)
- `label` (multi-language map, high-frequency)
- `child` (appears when `type: apps`)

### `formFields[].type` allowed set (based on official dev/apps real samples)
- `number`
- `password`
- `select`
- `text`
- `apps`
- `service`

Notes:
- Official repo has **both patterns**:
  - `type: apps` + `child.type: service`
  - Direct `type: service`
- Therefore validator should allow `service` in `formFields[].type`, but adapted artifacts can prefer `apps + child.service` to express "dependency selection → service instance selection" two-step semantics.

### `rule` common set (for this skill validation)
- `paramPort` (most common, for `PANEL_APP_PORT_*`)
- `paramExtUrl`
- `paramCommon`
- `paramComplexity`

Treat this as an allowed set, not a requirement to populate `rule`. Normally only externally published port fields use `rule: paramPort`; omit `rule` from secrets, URLs, selectors, domains, and ordinary text unless a demonstrated 1Panel parser or application-format requirement needs it.

### `formFields[].edit` (consumer-backed editability)

Based on official v2 app library `docker-compose.yml` and same-directory version-level `data.yml` actual write:
- `edit` is bool in DTO (default equivalent to false), but official library explicitly writes `edit: true` for many fields.
- Choose `edit` from the value lifecycle, not from a blanket editable default:
  - Use `edit: true` only when the steady-state consumer reads the changed value directly, or an idempotent reconciliation or migration safely applies it to persisted state.
  - Use `edit: false` for a value generated once, identity-bearing configuration, panel injection, or any value whose persisted consumer is intentionally not regenerated. Typical injection keys include `PANEL_DB_*`, `PANEL_REDIS_*`, and `PANEL_MINIO_*`.
  - Dependency selection fields (`type: apps/service`) may use `edit: true` only when the linked-resource lifecycle supports changing the selection; enumeration alone is not migration evidence.
- Validation strategy: `validate-v2.sh --strict-store` will **WARN** when a required non-selector field lacks an explicit `edit` decision (not FAIL, to avoid rejecting official historical output). Resolve the warning from the consumer lifecycle; do not silence it mechanically with `edit: true`.

## Database / Redis Dependency Injection (Panel Fixed envKey)

Before preserving an upstream-bundled database/cache sidecar, inspect the target appstore and a current 1Panel store for reusable runtime applications and service registration. When a compatible 1Panel-managed runtime exists and the application supports an external service, **prefer the panel runtime selector path** and panel fixed envKeys; do not wait for a later review reminder to replace the bundled dependency.

Dependency preference order:
1. A compatible, selectable 1Panel-managed runtime exposed through `/apps/services/<key>` and the corresponding resource records.
2. A documented external service configuration when no reusable panel runtime is available.
3. The upstream-bundled database/cache sidecar only when the runtime selector path is unavailable, unregistered, incompatible, or the upstream application requires the bundled topology.

For applications supporting multiple database engines, keep one app key and expose the engines that have independent panel/runtime evidence through one `type: apps` plus `child.type: service` selector. Map engine-specific ports through selector `params`. Do not create database-specific app keys or version directories merely to represent engine choice, and do not advertise an engine that only passes static compose validation.

Common fixed envKeys (recommend using as needed):
- Database: `PANEL_DB_TYPE`, `PANEL_DB_HOST`, `PANEL_DB_NAME`, `PANEL_DB_USER`, `PANEL_DB_USER_PASSWORD`
- Redis: `REDIS_HOST`, `REDIS_PORT`, `PANEL_REDIS_ROOT_PASSWORD`, `REDIS_DB`

If users are expected to choose a reusable 1Panel-managed dependency from the install UI, do not leave the dependency host field as plain `type: text`.
- For database-family selectors, prefer `type: apps` plus `child.type: service`.
- For single-step selectors such as Redis service reuse, prefer `type: service` with the dependency `key` (for example `key: redis`) on the host envKey field.
- A package that only accepts manual host input is not equivalent to a package whose UI can actually select a store/local dependency app.

Scaffold supports optional injection template:
- When running `scripts/scaffold-v2.sh`, add `--with-panel-deps` (or alias `--with-panel-db-redis`), will automatically add above DB/Redis related formFields in generated `<version>/data.yml` (including `labelEn/labelZh` + a `label` map using runtime locale keys).

Key points for adaptation:
- Treat store-runtime discovery as an adaptation preflight step: search existing app definitions for the dependency key, query the panel store metadata, and verify that an installed instance appears in `/apps/services/<key>` before deciding the final service topology.
- Treat the selector value as a 1Panel app key, not a display-label alias. `mysql` and `localmysql` are distinct values and must be verified independently through `/apps/services/mysql` and `/apps/services/localmysql`; do not advertise one because the other works.
- `version/data.yml` uses `type: apps` + `child.type: service` to inject `PANEL_DB_HOST` (reference 1Panel store app common dependency injection pattern).
- Keep service enumeration separate from database lifecycle integration. A running option returned by `/apps/services/<key>` proves the selector can enumerate that instance, but not that 1Panel created or linked an application database. When the form also requests `PANEL_DB_NAME`, `PANEL_DB_USER`, and `PANEL_DB_USER_PASSWORD`, require install and upgrade evidence that the host envKey is present in the `services` payload and the installed app reports `linkDB: true` or the expected `resourceKeys`; then verify schema/user creation and cleanup behavior separately.
- For packages that require a runtime administrator password to initialize several upstream-defined schemas, use the actual password of the selected installed runtime. A form default is only a template and must not be treated as the current runtime credential. Do not change a shared runtime's authentication mode to make a smoke test pass.
- App uninstall does not universally own external database data. Confirm whether selector-created linked resources are removed by 1Panel, and explicitly clean only task-owned schemas/users during tests. Preserve manually managed external databases unless the user separately authorizes their removal.
- When converting an existing package from manual host input to a selector-backed dependency, keep the effective runtime envKey stable when possible. For example, changing `REDIS_HOST` from `type: text` to `type: service` is usually upgrade-safe because existing `.env` values still map to the same compose/app variable.
- If the selector conversion requires a renamed envKey or adds a new selector-driving field such as `PANEL_DB_TYPE`, treat that as an upgrade migration item and backfill it in `scripts/upgrade.sh` when possible.
- PostgreSQL-only rule: if the app relies on panel-side PostgreSQL provisioning (`CreateDatabase` in install task logs), runtime validation should use a real 1Panel-installed PostgreSQL app in the same panel. Pointing the service field at an arbitrary external hostname can bypass the intended provisioning path and create misleading failures.
- For that PostgreSQL-only path, keep the application PostgreSQL user (`PANEL_DB_USER`) distinct from the PostgreSQL service admin/root account. Reusing the admin username can make a correct package fail during install with `User already exists`.
- Do not automatically generalize those PostgreSQL-specific behaviors to MySQL; verify MySQL-linked adaptations from their own 1Panel task/runtime evidence before carrying the rule over.
- For format-sensitive secrets, do not assume a generic random password is a valid application value. Examples: Laravel `APP_KEY` expects Laravel-compatible key material, while Mastodon `ACTIVE_RECORD_*`, `SECRET_KEY_BASE`, `OTP_SECRET`, and `VAPID_*` have upstream-specific generator commands and formats.
- When official docs expose a generator helper, prefer `scripts/init.sh` / `scripts/upgrade.sh` to generate or normalize those values from the official image/helper command instead of shipping a fixed sample secret in `data.yml` or trusting a generic panel-generated random string.
- If `scripts/init.sh` or `scripts/upgrade.sh` replaces a panel-provided secret with a normalized/generated value, persist that final value under the app's configurable data path and restore it during later upgrades. Real 1Panel upgrades can replay the original install form value instead of the mutated `.env`, which can break apps that silently rotate `APP_KEY`, `DB_PASSWORD`, or similar persisted secrets.
- Keep that secret-persistence rule distinct from the PostgreSQL-only provisioning notes above: the replay problem can affect MySQL-, PostgreSQL-, or non-DB secret fields, even though the dependency-provisioning behavior is not shared across engines.
- When simplifying forms, retain documented security controls such as user registration, anonymous access, and administrator exposure, or provide verified configuration instructions with the effective default. Review the before/after behavior so removing a switch does not silently inherit a more permissive upstream default.
- If the compose uses `network_mode: host`, its listener occupies the host port directly even without a `ports:` block. Keep the install-form envKey as `PANEL_APP_PORT_*` so 1Panel performs its port-occupation check, map the upstream listener variable from that key (for example, `NETDATA_LISTENER_PORT=${PANEL_APP_PORT_HTTP}`), and verify the application consumes it. A disabled/fixed port field must still match the real built-in listener instead of being treated as a free-to-randomize published port.
- For host-network adaptations tested from a containerized smoke runner, runtime probing may need the Docker host gateway (or another host-reachable address) rather than `127.0.0.1` inside the panel container.
- `docker-compose.yml` if application uses `DATABASE_*` variables, need to map in compose:
  - `DATABASE_HOST: ${PANEL_DB_HOST}`
  - `DATABASE_USER: ${PANEL_DB_USER}`
  - `DATABASE_PASSWORD: ${PANEL_DB_USER_PASSWORD}`
  - `DATABASE_DBNAME: ${PANEL_DB_NAME}`
- Redis password similarly: `REDIS_PASSWORD: ${PANEL_REDIS_ROOT_PASSWORD}` (if application field name differs, map as needed).
- Validation must prove the selector path, not only connectivity. A smoke/install report should show the d

…(truncated)
