Files
Jan Nedbal 22f67a0589 docs: document m2pack subcommands
Add a Phase 4 'm2pack commands' section to docs/cli.md with each
subcommand's flags and a pointer at the m2pack-secure repo for
installation. Update README.md with a short m2pack paragraph and
append the Phase 4 entry to the Unreleased CHANGELOG section.
2026-04-14 22:31:16 +02:00

5.9 KiB

metin-release — CLI reference

Phase 1 release … commands and Phase 4 m2pack … commands. All subcommands share the top-level flags.

Top-level flags

Flag Description
--version Print package version and exit.
--json Emit only the JSON envelope on stdout (stderr may still carry logs).
-v, --verbose Verbose stderr logging.
-q, --quiet Suppress stderr logging entirely.

Output envelope

Every command writes a JSON envelope on stdout:

{
  "ok": true,
  "command": "release inspect",
  "status": "inspected",
  "stats": { "...": "..." }
}

On failure:

{
  "ok": false,
  "command": "release inspect",
  "status": "failed",
  "error": { "code": "source_not_found", "message": "..." }
}

Exit codes

Code Meaning
0 Success
1 Operator or validation error
2 Remote / network error
3 Signing or integrity error
4 Reserved (ERP sync, Phase 2+)

release inspect

Scan a client source root and report file counts plus launcher/main-exe detection.

metin-release release inspect --source /path/to/client

No writes. JSON stats: source_path, file_count, total_bytes, launcher_present, main_exe_present.

release build-manifest

Wraps make-manifest.py to produce manifest.json for a source tree.

metin-release release build-manifest \
    --source /path/to/client \
    --version 2026.04.14-1 \
    --out /tmp/release/manifest.json \
    [--previous 2026.04.13-3] \
    [--notes release-notes.md] \
    [--launcher Metin2Launcher.exe] \
    [--created-at 2026-04-14T12:00:00Z]

Override the wrapped script path via the METIN_RELEASE_MAKE_MANIFEST env var.

release sign

Wraps sign-manifest.py. Requires an absolute path to a chmod-600 raw 32-byte Ed25519 key.

metin-release release sign \
    --manifest /tmp/release/manifest.json \
    --key /home/you/.config/metin/launcher-signing-key \
    [--out /tmp/release/manifest.json.sig]

Override the wrapped script path via METIN_RELEASE_SIGN_MANIFEST.

release diff-remote

HEADs every unique blob hash from a manifest against <base-url>/files/<hh>/<hash>.

metin-release release diff-remote \
    --manifest /tmp/release/manifest.json \
    --base-url https://updates.example.com

release upload-blobs

rsyncs the release directory (excluding manifest.json and .sig) to a target.

metin-release release upload-blobs \
    --release-dir /tmp/release \
    --rsync-target user@host:/var/www/updates/ \
    [--dry-run] [--yes]

release promote

Pushes only manifest.json + manifest.json.sig to the target top-level — makes the new release live.

metin-release release promote \
    --release-dir /tmp/release \
    --rsync-target user@host:/var/www/updates/ \
    [--dry-run] [--yes]

release verify-public

GETs manifest.json + manifest.json.sig from a public URL and verifies the Ed25519 signature. Optionally spot-checks random blobs with --sample-blobs N.

metin-release release verify-public \
    --base-url https://updates.example.com \
    --public-key <hex-or-path-to-hex-file> \
    [--sample-blobs 5]

release publish

Composite: build-manifest → sign → stage blob tree → upload-blobs → promote → verify-public. Short-circuits on the first failure; the JSON envelope includes a stages array with {name, status, duration_ms, error?} per step.

metin-release release publish \
    --source /path/to/client \
    --version 2026.04.14-1 \
    --out /tmp/release \
    --key /home/you/.config/metin/launcher-signing-key \
    --rsync-target user@host:/var/www/updates/ \
    --base-url https://updates.example.com \
    --public-key <hex-or-path-to-hex-file> \
    [--previous ...] [--notes ...] [--launcher ...] \
    [--created-at ...] [--sample-blobs N] \
    [--yes] [--force] [--dry-run-upload]

m2pack commands

Phase 4 subcommands wrap the m2pack-secure binary and translate its JSON envelopes into the standard metin-release result envelope. The binary is not shipped with this CLI — build it from metin-server/m2pack-secure and either put it on PATH or point at it via the M2PACK_BINARY environment variable.

All m2pack commands pass through --json to the real tool, so the raw m2pack envelope is always available under data.m2pack. When m2pack exits non-zero or emits non-JSON output the wrapper raises a subprocess error with m2pack_failed / m2pack_invalid_json / m2pack_empty_output error codes.

m2pack build

Build a signed .m2p archive from a client asset directory.

metin-release m2pack build \
    --input /path/to/client-assets \
    --output /path/to/out.m2p \
    --key /path/to/content.key \
    --sign-secret-key /path/to/signing.sk \
    [--key-id N]

m2pack verify

Verify an .m2p archive's signature (and optionally full-decrypt it).

metin-release m2pack verify \
    --archive /path/to/a.m2p \
    [--public-key /path/to/signing.pub] \
    [--key /path/to/content.key]

Passing --key enables full-decrypt verification; omitting it only checks manifest structure and signature.

m2pack diff

Diff two directories and/or .m2p archives. Either side can be a directory or an archive; m2pack figures it out.

metin-release m2pack diff --left /old --right /new.m2p

The wrapper promotes m2pack's added/removed/changed/unchanged counts into data.stats when available.

m2pack export-runtime-key

Export a launcher runtime-key payload (json or raw blob) from a master content key + signing public key. Used to seed the launcher's bundled runtime-key file during release workflows.

metin-release m2pack export-runtime-key \
    --key /path/to/content.key \
    --public-key /path/to/signing.pub \
    --output /path/to/runtime-key.json \
    [--key-id N] [--format json|blob]

See docs/key-rotation.md in m2pack-secure for when to re-export runtime keys.