This is the multi-page printable view of this section. .
Design Notes
- 1: From Human-Friendly to Agent-Native: PIG's CLI Contract
- 2: Compile, Validate, Then Commit: The Native sty conf Pipeline
- 3: The 70% Tuner: Defining the Boundary of pig pg tune
- 4: Why PIG Keeps the Cobra Command Layer Flat
- 5: Why PIG Builds Debug Packages by Default
- 6: One Grammar for Dangerous Work: PIG's Operations CLI Safety Contract
- 7: Edit the Declaration, Preserve the Document: Lossless Pigsty Inventory
- 8: Use the CMDB Pigsty Already Has
- 9: A Bounded Grafana Client Instead of Dashboard Shell Scripts
- 10: Let Patronictl Speak for Itself
- 11: PIG 2.0 Product Direction
- 12: Catalog v2 Proposal: Immutable Typed Snapshots
- 13: One-command Xray Clients and Servers
- 14: Bootstrapping a Pigsty Controller as a Recoverable Transaction
- 15: Install the Pigsty Repository Key Automatically
- 16: One Catalog Entry Count for the Website and CLI
Design notes explain the decisions and proposals behind PIG. Each note gives its date, status, scope, alternatives, and supporting evidence. Proposals describe directions under discussion; implemented and released records explain work that has reached those stages.
For command syntax and usage, use the PIG documentation. For changes in each version, use the release notes.
1 - From Human-Friendly to Agent-Native: PIG's CLI Contract
Decision date: 2026-02-12
Status: Released in pig v1.1.0 and refined by later command-layer work. The 2026-10-16 refinements below are Implemented in the v1.10.0 candidate, not yet released.
Current reference:pigcommand overview
Scope: PIG-owned commands and their machine-consumption contract; opaque passthrough commands keep their native interface.
Decision
PIG provides terminal text for people and structured results for automation. Commands that produce
stable data expose JSON or YAML and status codes. Supported workflows also expose execution plans.
Transparent passthroughs such as pig pt preserve the external tool’s output, prompts, and exit status.
Context
PIG began as a convenient package-management CLI. As it grew into PostgreSQL, Patroni, pgBackRest, Pigsty, and repository operations, a human-only interface created several problems:
- automation had to scrape colored prose;
- a zero process exit could hide a failed inner operation;
- destructive workflows could not be inspected before execution;
- an agent had to issue many discovery commands before understanding the host;
- wrappers could accidentally mix subprocess chatter with structured output.
The v1.1.0 design introduced global output selection, stable result objects, execution plans, and
the pig context snapshot. Later refactors narrowed these promises to commands that can actually
own them reliably.
Alternatives considered
Three tempting approaches were rejected:
- Parse human text. It is fragile across wording, localization, colors, and upstream tools.
- Capture every subprocess into JSON. This breaks interactive programs, streaming output, terminal control, and native exit semantics.
- Invent one universal result schema. Package transactions, recovery plans, metrics, and context snapshots have different stable data; flattening them loses meaning.
Contract
The durable contract is:
- text is the default interface for people;
- a command advertises structured output only when PIG owns a stable result;
- structured stdout contains one parseable result, while diagnostics and wrapped-tool output use stderr;
- a plan describes intended actions, scope, risk, and expected effects without performing them;
- destructive PIG-owned operations fail closed when confirmation is missing;
- status codes distinguish usage, confirmation, environment, dependency, and execution failures;
pig contextprovides a bounded environment snapshot rather than forcing consumers to infer it;- transparent passthroughs retain the upstream contract.
The 2026-10-16 refinements apply the same contract to pig ext:
- Availability checks and every summary format share matrix precedence, architecture aliases, and the metadata fallback. macOS reports declared PostgreSQL support without promising packages. This describes the catalog’s package snapshot. Default build targets retain their metadata and local-installation rules independently of the matrix.
- Partial imports return failure while preserving resolved, downloaded, and unresolved items. Completed downloads remain on disk; the operation does not promise an atomic transaction.
pig petreats HTTP 4xx/5xx as failure. Error details expose the numeric status and standard description, never the response body or server-supplied reason phrase.pig license issueexplicitly delivers a newly issued credential. Its successful structured result preserves the token indata.captured_output. Signing-key input is omitted from parameters, and raw key-loading errors are replaced with fixed messages at the command boundary. Parameter errors and safe signing-key diagnostics remain readable in every output format. Other sensitive command wrappers retain full output and error-detail redaction.
Consequences
This split makes scripts more reliable and lets agents choose commands based on risk and output capability. It also creates maintenance obligations: every structured field becomes compatibility surface, stdout purity needs tests, and a wrapper must not promise more stability than the tool it delegates to.
The output contract follows the command: PIG results for PIG operations, native output for forwarded tools.
Verification and evolution
Fixed source commits and regression tests for the committed v1.10.0 refinements cover: availability and counts, partial imports, exporter HTTP failures. These commits do not establish a release boundary.
The initial framework shipped with v1.1.0.
The command-layer consolidation in
fb93602 later removed duplicated wrappers and
centralized plan and output glue. Patroni subsequently became a transparent passthrough, an
example of reducing PIG-owned structure when the upstream interface is already authoritative.
Tests now cover structured stdout isolation, result rendering, plan behavior, confirmation gates, and context collection in the packages that own those contracts.
Current status
Use the output mode supported by each command. See the pig reference for structured
results and passthrough behavior.
2 - Compile, Validate, Then Commit: The Native sty conf Pipeline
Decision date: 2026-02-18; the production contract was finalized on 2026-08-14.
Status: Implemented and released in pig v1.8.0.
Current reference:pig sty conf
Scope: Generating one validated static Inventory from a trusted Pigsty template; not arbitrary YAML transformation.
Decision
pig sty conf should behave like a small compiler: resolve one safe template, parse it, apply a
bounded set of structural mutations, validate the complete candidate, and atomically commit the
output only after every required stage succeeds.
The command does not invoke the legacy configure script and does not fall back to raw shell
execution. Its structured result reports selected inputs, effective choices, applied change kinds,
and warnings without returning generated secret values.
Context
Template configuration looks simple until paths, symlinks, multiple IP placeholders, version-pinned templates, mirrors, proxy environments, generated credentials, and partially valid YAML interact. A text replacement pipeline can cascade IP substitutions, rewrite unrelated domains, leak secrets, or truncate the destination after a late validation failure.
The output Inventory may contain administrative credentials, so both file handling and result rendering are part of the security boundary.
Alternatives considered
- Call the existing shell configure script. Rejected because parsing, validation, and result semantics would remain outside PIG’s control.
- Use global search and replace. Rejected because IP and domain values need exact placeholder boundaries and simultaneous mapping.
- Write first and validate afterward. Rejected because a failed candidate could replace a usable Inventory.
- Accept arbitrary absolute templates. Rejected because the command should compile known Pigsty modes, not become a privileged file copier.
- Return generated passwords for convenience. Rejected because structured logs and agent traces are not secret-delivery channels.
Contract
- templates resolve below the Pigsty configuration tree through safe relative names;
- absolute paths, traversal, path escape, and direct, symlink, symlinked-parent, or hard-link source/output aliasing are rejected;
- parsing and IP-collision checks precede external preflight;
- placeholder IPs are mapped simultaneously and unrelated addresses remain unchanged;
- domain replacement matches the exact template token;
- profile, region, proxy, locale, and PostgreSQL-version changes are structural and bounded;
- generated credentials use one random value per known identifier and expose only identifiers in results;
- the complete candidate receives native validation and optional bounded Ansible parsing;
- any failure leaves the destination untouched;
- success writes atomically with mode
0600.
Consequences
Use sty conf to generate a new Inventory from a known template. Use the lossless
pig inventory workflow to edit an existing Inventory.
Version-pinned templates keep their version and warn when a conflicting version request cannot apply.
Verification and evolution
The native configure direction was first recorded on 2026-02-18. The production refinement landed with
74e084e, and the final contract synchronization
followed in adc4260. Tests cover traversal and
aliasing, simultaneous IP mapping, domain boundaries, interactive and closed-input selection,
version handling, proxy and region changes, secret generation and redaction, preflight ordering,
validation failures, permissions, and atomic writes.
Current status
Use pig sty conf to generate a new Inventory from a Pigsty template and
pig inventory to inspect or edit an existing declaration. Current flags, modes,
and preflight behavior live in the pig sty reference.
3 - The 70% Tuner: Defining the Boundary of pig pg tune
Decision date: 2026-03-21
Status: Released in pig v1.3.2, following implementation on 2026-03-23.
Current reference:pig pg tune
Scope: Generate a starting configuration from hardware, PostgreSQL version, and workload profile.
Decision
pig pg tune calculates core PostgreSQL parameters and prints them for review. It detects CPU,
memory, and disk capacity, accepts explicit overrides, and applies a selected workload profile.
Text output displays the generated parameters; JSON and YAML output include the inputs and parameters.
Review the values and select the settings to apply through your configuration system.
The original “70% tuner” name describes a practical starting point. It is not a measured accuracy score.
Context
A fresh installation needs an initial configuration before workload measurements are available. One static configuration cannot fit both a small development machine and a large database server. Hardware-based formulas provide a repeatable baseline that the operator can inspect and adjust.
Alternatives considered
- Ship one fixed configuration. Memory and parallelism settings need to scale with the host.
- Build an adaptive tuner. This would require workload data, experiments, and a feedback loop.
- Apply settings automatically. The operator needs to review the generated values and choose how to deploy them through the existing configuration workflow.
- Calculate every PostgreSQL parameter. Replication, security, and availability settings also depend on requirements that hardware detection cannot determine.
Contract
- Use the detected hardware or the operator’s explicit CPU, memory, and disk values.
- Select formulas through
oltp,olap,tiny, orcrit. - Produce the same parameters for the same resolved inputs and PostgreSQL version.
- Include the resolved profile, hardware, and PostgreSQL version in structured results.
- Print text parameters in a stable order and identify the SSD assumption in the header.
- Validate input ranges and omit parameters unavailable in the selected PostgreSQL version.
- Leave configuration files and the running server unchanged.
Consequences
Operators can use the output for a new installation or initial sizing. Review the values, apply them through the configuration system in use, then measure the workload. Query concurrency, checkpoint behavior, storage latency, and replication requirements guide later adjustments.
Verification and evolution
The initial implementation is
60eecfe.
The v1.3.2 release introduced parameter generation.
The current implementation
returns a result for rendering. Tests in
tune_test.go
cover workload formulas, resource limits, overrides, and version-dependent parameters.
Current status
Parameter generation is released.
See the pig pg reference for inputs and output examples.
4 - Why PIG Keeps the Cobra Command Layer Flat
Decision date: 2026-06-30
Status: Implemented; the repository uses this layout for new commands.
Current reference:pigcommand overview and the source repository
Scope: Go source ownership and command registration, not the public command taxonomy itself.
Decision
The cmd package stays flat. One top-level command belongs in one top-level Go file: pg.go,
pb.go, pt.go, pe.go, sty.go, do.go, repo.go, and their peers. Even large command trees
remain in that entry-point file unless there is an explicit decision to change the layout.
The file may be long, but it should contain Cobra concerns: names, aliases, annotations, flags,
argument validation, help, registration, and option mapping. Concrete work belongs in cli/*,
internal/*, or another implementation package.
Context
Earlier command growth produced many small files named after subcommands and several parallel implementations of confirmation, structured output, plan rendering, and legacy wrapping. It became difficult to answer simple questions: where is a top-level command registered, which file owns an alias, and whether two helpers implement the same policy.
PIG’s command families are large, but their public grammar is one surface. Keeping that grammar in one place makes review and collision detection easier, while implementation packages remain decomposed by responsibility.
Alternatives considered
- A directory per command under
cmd. Rejected for normal commands because it scatters one public grammar across many packages and encourages business logic near Cobra. - One file per subcommand. Rejected because registration, aliases, and inherited flags become hard to audit as one contract.
- Put everything in
cmd. Rejected because tests, reuse, and error handling suffer when operational logic depends on Cobra state. - Abstract every repeated line. Rejected because speculative frameworks can hide the command grammar; only stable, cross-command glue should be shared.
Contract
cmd/root.goowns root setup, global flags, and top-level registration;cmd/utils.goowns shared command-layer helpers;- each normal top-level command has one matching top-level source file; matching and focused test files may accompany it;
- Cobra code validates syntax and maps options, but does not perform the operation;
- reusable confirmation, annotation, structured-output, and plan helpers have one implementation;
- implementation packages accept ordinary options and return typed results or errors without depending on Cobra globals.
Consequences
Some command files are intentionally large. The trade-off is accepted because the public surface can be reviewed as a unit, while the implementation remains split below it. The rule also reduces file churn when aliases or flags move and gives agents a deterministic starting point.
Review the separation of responsibilities, not file length. Operational logic belongs in the implementation packages, including logic inside closures.
Verification and evolution
The convention was recorded in
9eb70db, followed by the large command-surface
consolidation in fb93602. Guard tests check alias
collisions and command registration, while package tests exercise the implementation beneath the
Cobra layer.
Current status
This remains the repository rule for new work. Public command documentation belongs on this site; source-layout enforcement remains close to the code so contributors and coding agents encounter it before editing.
5 - Why PIG Builds Debug Packages by Default
Decision date: 2026-09-03
Status: Released in v1.8.1.
Current reference:pig build
Scope: Debug-package policy forpig build extandpig build pkg; individual package recipes remain authoritative for their payload.
Decision
pig build ext and pig build pkg request the platform’s normal debug-package output by default.
PIG does not pass a suppression setting during an ordinary build. Users who need the smaller or
faster artifact set opt out explicitly with --nodbg.
On RPM systems, the ordinary build leaves the distribution’s debuginfo and debugsource
machinery enabled; --nodbg defines debug_package as empty. On Debian and Ubuntu,
--nodbg adds noautodbgsym to DEB_BUILD_OPTIONS while preserving existing options. The old
-s|--symbol spelling remains accepted as a deprecated compatibility no-op because debug output
no longer needs to be enabled.
Context
The earlier command made RPM debug output opt-in with --symbol and otherwise injected
debug_package %{nil}. That inverted the platform convention and made the default build less
useful for post-crash analysis. Debian’s Debhelper already creates automatic dbgsym packages by
default when eligible, and provides noautodbgsym as the explicit opt-out.
Debug packages increase build time, storage, and repository volume, but those costs are visible and manageable. Missing symbols are much harder to reconstruct after the exact source, compiler, and binary have moved on.
Alternatives considered
- Keep
--symbolas an opt-in. Rejected because release-quality artifacts should retain the matching diagnostic material by default. - Always require debug packages. Rejected because quick local builds and constrained artifact pipelines need an explicit opt-out.
- Rewrite package recipes from PIG. Rejected because spec files and
debian/rulesown package payload details; mutating them at execution time would be surprising and difficult to audit. - Use
nostripfor DEB opt-out. Rejected because it changes the main binary instead of merely suppressing the separate automatic debug package.
Contract
- without
--nodbg, PIG adds no debug-suppression macro or environment option; - on RPM,
--nodbgadds--define "debug_package %{nil}"torpmbuild; - on DEB,
--nodbgappendsnoautodbgsymtoDEB_BUILD_OPTIONSwithout discarding existing values such asparallel=Nornocheck; --symbolremains parseable for compatibility but is hidden, deprecated, and redundant;- structured command results expose
nodbgand retainsymbolas the effective inverse value; - the normal installation package remains the build-success authority; debug packages are additional artifacts, not substitutes for it;
- pure SQL,
noarch, or otherwise ineligible builds need not emit an empty debug package; - an explicit decision inside a package recipe or an operator-supplied build environment remains authoritative. The command default is a policy request, not permission to rewrite recipes.
Consequences
Compiled extensions normally produce more files and consume more build and repository space. In
return, a default build is suitable for stack traces, core analysis, and symbol servers without a
second reconstruction build. Existing scripts that pass --symbol keep working, while scripts
that intentionally want the old reduced artifact set must move to --nodbg.
Recipe repositories should remove blanket debug suppression where it does not represent a specific package constraint. That is a separate reviewed change because some packages are architecture independent, fail debug extraction, or use custom build systems.
Verification and evolution
The superseded opt-in behavior is pinned in the earlier
build flag registration
and
rpmbuild command construction.
The replacement was implemented in
1c6f524.
Command tests keep --nodbg visible and the compatibility flag deprecated. Builder tests verify
that the default RPM argv omits the suppression macro, the opt-out includes it, and the DEB
environment preserves existing options while adding noautodbgsym once.
Platform validation should sample a compiled extension on one EL builder and one DEB builder,
checking both default debug artifacts and their absence under --nodbg. Recipe-level exceptions
must be reported separately rather than treated as proof that the PIG command ignored its option.
Current status
Released in v1.8.1. See the current reference linked above for usage and platform requirements.
6 - One Grammar for Dangerous Work: PIG's Operations CLI Safety Contract
Decision date: 2026-07-02
Status: Released forpg,pb,pt, andpitrby v1.5.0; the 2026-08-29doandbuild proxyrefinements were released in v1.8.1. The acceptance refinements below are Implemented for v1.10.0; source evidence is listed under verification. They are not yet released.
Current reference:pig pg,pig pb,pig pitr,pig do, andpig build
Scope: PIG-owned operational commands; transparent upstream commands retain upstream confirmation and exit behavior.
Decision
Operational convenience must not blur operational meaning. PIG distinguishes low-level primitives from multi-stage orchestrators, makes destructive intent explicit, reserves aliases carefully, and requires plans and structured results to describe the same action that text mode will execute.
The most important example is recovery: pig pb restore is the pgBackRest primitive, while
pig pitr coordinates Patroni, PostgreSQL shutdown, restore, restart, and post-recovery guidance.
An alias must never make those two paths look interchangeable.
Context
The first generation of convenience aliases accumulated inconsistent positional arguments,
confirmation flags, output handling, and service semantics. Similar words such as restart,
restore, promote, and failover can refer to very different layers. A short alias that crosses
those layers can turn a harmless-looking invocation into an unmanaged destructive primitive.
Automation also exposed false-success risks when wrapper output, subprocess output, and result rendering used different definitions of success.
Alternatives considered
- Maximize shorthand aliases. Rejected because collisions and cross-layer synonyms are more dangerous than a few saved characters are valuable.
- Put confirmation on every risky-looking word. Rejected for passthrough commands because the upstream tool must own its prompt and semantics.
- Make the orchestrator call a convenience alias of the primitive. Rejected because recovery coordination has additional stop, verification, and restart invariants.
- Return success after launching the inner command. Rejected because the result must reflect the complete owned workflow.
- Restrict maintenance database names to ASCII identifiers. Rejected because spaces and non-ASCII names are valid inputs. Reject libpq connection-string syntax instead, and preserve the positional boundary when generating commands.
Contract
- sibling command names and aliases are unique;
- an alias cannot shadow a different top-level command;
- destructive PIG-owned operations require explicit confirmation and support non-mutating plans where a meaningful plan exists;
- command-layer validation rejects malformed or extra positional arguments before side effects;
- command-specific names mirror the downstream Pigsty contract, or use the narrowest documented safe boundary when the downstream playbook has no explicit grammar;
- structured output and text mode share one result and one success definition;
- credential-bearing values stay out of diagnostics, and readiness or connectivity failure remains a command failure rather than a logged warning followed by success;
- low-level restore does not claim to manage Patroni or HA routing;
- the PITR orchestrator stops the manager when required, proves PostgreSQL is stopped, restores, optionally starts PostgreSQL, and deliberately leaves Patroni stopped for operator verification;
- native tools receive extra arguments only through an explicit, documented boundary.
The 2026-10-16 to 2026-10-17 refinements apply the same boundary to the v1.10.0 candidate:
- Cross-user DBSU writes stage an exclusively created private sibling, use mode
0600and the executing DBSU’s ownership, reject symlink/non-regular destinations, replace the exact path, and clean staging on failure. Linux uses an exact-target rename; macOS uses the system Perlrenamesyscall because BSDmvhas directory-target semantics. repo rmvalidates the whole operand list before changes. Only zero operands mean full backup;allremainsnode,pgsql, andinfra.requested_modulesretains the original request;removed_reposdescribes the expanded actions.repo add,repo set, andrepo infoalso reject explicit empty names and comma-separated empty entries as a whole request. Only omitted operands retain the add/set default ofall. Add/set also validate module-name syntax before initializing the repository manager. Unsafe names reject the whole request; syntactically valid unknown names retain additive partial success, while replacement rejects them before changes.- With an explicit
pg -D, SQL operations and database enumeration bind to that directory’s recorded port and socket. Ambiguous metadata fails before execution. Omitting-Dpreserves native client connection defaults;/pg/dataremains the default for instance operations. Explicit empty or whitespace-only-Dvalues fail before configuration or operational work. Plans, confirmation retries, and verification commands preserve the same target and operation options, including an explicit-D /pg/data.VACUUM FULLplans require confirmation, as execution does. The 2026-10-17 maintenance refinement rejects connection-string database operands before plans, confirmation, or execution, without echoing the supplied value. A libpq connection string could otherwise override the selected host/port and expose credentials.vacuum,analyze, andfreezevalidate the complete enumerated database list before any maintenance starts. Generatedvacuumandrepackcommands place all options before--and the database name, preserving a name such as--allas a single-database operand. - Fork replacement requires matching metadata
with an explicit target for nonempty unmanaged directories, including non-PGDATA directories.
Empty destinations remain usable; managed forks must be replaced by name. Private paths are
resolved as DBSU, and unresolved aliases fail closed. The 2026-10-17 review removes the extra
/pg/dataprohibition: eligibility depends on target ownership, source/destination relationships, and runtime state. A conventional path cannot identify every primary and may be a legitimate fork target. Forced cleanup of managed orphan directories remains supported. For unmanaged removal, a permission-denied local path check retries as the selected DBSU; it must establish that the target is a directory and not a symlink before deletion can proceed. EPERMalone must not authorizepg init --forceor PITR restore. Independent executable identity may establish PID reuse; unavailable identity remains unknown. A live PID must identify PostgreSQL before lifecycle commands may signal it or connection metadata may be used for SQL. Unknown state carries no usable PID for force-stop escalation. Fork start must not persist success in that state; list output reportsunknownandruntime_unknown: true. Role detection also fails on an unknown running state; file-based role evidence cannot override a confirmed stopped state. Forced initialization reports probe failures separately from a running conflict. Plan preconditions report indeterminate runtime state asunknown, never a passed check with PID zero. Status queries distinguish permission failures (130201, exit3) from other probe failures (130808, exit1); partial data in either failure cannot establish that the instance is stopped.- Timeline reporting binds SQL to the selected data directory’s valid PID metadata and socket.
If that cannot be resolved, it uses that directory’s
pg_controldataor reports an unknown timeline. Promotion execution and the requirement to verify the primary role stay unchanged. - PITR cancellation and EOF return confirmation-required (
161001, exit7) before service stops, restore, or restart. Acceptance must execute the confirmation path and observe zero lifecycle side effects. PIG_NON_INTERACTIVEand structured execution disable prompt stdin and add sudo-n, including legacy structured wrappers. Native passthrough and explicit data stdin retain their contracts.- Sensitive captured output and error details remain private. Signing-key load errors never echo the supplied key. Explicit raw exports remain available on their documented text surfaces.
Consequences
Some historical shorthand disappeared and some scripts had to adopt cluster-first or explicit target syntax. In return, command names now preserve layer boundaries, plans correspond to real actions, and recovery automation cannot silently substitute a primitive for the orchestrator.
Plans show the affected resources and actions so operators can review recovery before execution.
Verification and evolution
Fixed source commits and regression tests for the committed v1.10.0 refinements cover:
runtime identity and SQL targets,
fork ownership and private paths,
PITR cancellation and unknown state,
plan target preservation,
status error classification. These commits do not establish a release boundary.
The later maintenance database-name and -- operand boundaries
include Cobra argument-reparse tests and a real libpq target-boundary regression test.
The repository operand preflight rejects
unsafe module names before key preparation or repository writes and preserves partial additive
results for syntactically valid unknown modules.
The normative command specifications entered the repository in
c62c0f5. Subsequent commits aligned aliases,
early validation, restore targets, service semantics, and role detection. Guard tests traverse the
Cobra tree to reject sibling and cross-layer alias collisions. Recovery tests cover plan,
confirmation, stop escalation, side restores, restart behavior, and structured failure results.
Maintenance regression checks must reparse generated commands with Cobra and exercise the real
libpq client against controlled local endpoints; checking for -h and -p in argv alone is insufficient.
Patroni later moved to transparent passthrough. That refinement keeps the same safety principle: PIG owns safeguards only for workflows it owns.
The same contract was applied to pig do name and cluster validation in
3e1603b,
with Ansible built-in targets closed in
a880485.
Package-backed, credential-safe, truthful build proxy setup entered in
220ef9c,
followed by structured-argument redaction and corrected machine annotations in
74cb128,
and optional operands were reflected in the machine grammar in
de7ffd0.
The changes were exercised on Ubuntu 24.04 and Rocky Linux 9 ARM64 Farrow guests, then released
from source commit 1c6f524
after the v1.8.1 CI run passed.
Current status
Use pig pb restore when you intentionally want the pgBackRest primitive and pig pitr
when you want the managed recovery workflow. See the command reference pages for current syntax and platform requirements.
7 - Edit the Declaration, Preserve the Document: Lossless Pigsty Inventory
Decision date: 2026-07-18
Status: Implemented and released in pig v1.6.0. The 2026-10-16 to 2026-10-17 refinements below are Implemented in the v1.10.0 candidate, not yet released.
Current reference:pig inventory
Scope: Static Pigsty Inventory inspection, scoped editing, validation, comparison, and safe writes.
Decision
PIG treats pigsty.yml as both a semantic declaration and a human-maintained source document.
Semantic parsers determine what the Inventory means; the original bytes remain authoritative for
how it is written. Scoped edits replace bounded source ranges and then reparse the complete
candidate before an atomic write.
This avoids a common YAML-tool failure: a logically correct edit that silently rewrites comments, key order, quoting, anchors, block scalars, or line endings across the entire file.
Context
Pigsty Inventories are long-lived operational assets. They contain topology, tuning, credentials, comments, examples, anchors, and locally meaningful ordering. A conventional parse-mutate-serialize cycle can produce a valid but unreviewable diff and may change constructs the operator never selected.
At the same time, raw text editing without semantic validation can put invalid or contradictory configuration on disk. The design needed source fidelity and whole-document correctness together.
Alternatives considered
- Round-trip through one YAML serializer. Rejected because no selected serializer preserved the full source contract byte-for-byte across the real Pigsty corpus.
- Use regular expressions for YAML. Rejected because quoting, comments, aliases, block scalars, and nested collections make text-only semantic decisions unsafe.
- Edit only a normalized generated copy. Rejected because the active Inventory is operator-owned and would still diverge from the generated representation.
- Allow every node type to be replaced. Rejected because anchors, aliases, tags, and block scalars need stricter handling than ordinary mappings and scalars.
Contract
- duplicate keys and multi-document YAML are rejected;
- selectors address one unambiguous declaration fragment;
- semantic decoding and source-range discovery are separate concerns;
- an edit starts from the exact source revision and fails if the file changes concurrently;
- the edited fragment is normalized only as required for its insertion context;
- the complete candidate is reparsed and validated before commit;
- writes use a same-directory temporary file, sync, rename, and directory sync;
- symlink and unsafe path changes are rejected;
- a successful edit removes group and other permissions, preserving the owner’s existing read and write permissions;
- diagnostics, diffs, plans, and structured results omit declaration values unless a command is explicitly a raw text surface.
The 2026-10-16 acceptance refinement preserves role-name strings and structured role grants in
pg_users[].roles. Editing, serialization, and CMDB export must preserve YAML 1.1 string semantics,
including "1:30"; "010" is a regression control, not a claim of an earlier defect.
The 2026-10-17 refinement also requires dates and timestamps supplied as strings to remain strings
when loaded by PyYAML. Existing timestamp values retain their source bytes as opaque scalars;
this does not add date arithmetic or normalize the document. Acceptance compares emitted types
with an independent YAML loader, rather than relying only on PIG’s own round trip.
sty deploy and do consume the configuration layer’s already resolved
Inventory path. Flag and environment relative paths use the caller’s cwd, quoted ~/ uses the
user’s home, and defaults remain explicit at execution. Missing sources do not fall back.
Static inspection still never executes dynamic sources; Ansible retains its execution boundary.
Consequences
The editor is more complex than ordinary YAML marshaling, and some syntactically valid fragments are deliberately refused when source fidelity cannot be proven. The benefit is a reviewable diff: unselected parts of the Inventory remain byte-for-byte stable, invalid YAML cannot be committed, and a concurrent edit cannot be overwritten silently.
Inventory can contain custom credential fields, so field names alone cannot reliably identify secrets.
For this reason, show prints the selected declaration as raw text. Metadata and validation commands omit declaration values.
Verification and evolution
Fixed source commits and regression tests for the committed v1.10.0 refinements cover: resolved sources and role grants, date-like string preservation, independent YAML consumer acceptance. These commits do not establish a release boundary.
The root Inventory contract and existing-CMDB boundary were established in
ba6e678, with the implementation landing in
ea43858. Tests exercise real Pigsty configuration
corpora, byte-identical round trips, scoped replacement, protected YAML forms, concurrent-change
rejection, atomic-write failures, selectors, validation, and secret-free diagnostics.
Later refactors removed legacy options and separated validation stages without changing the source-fidelity model.
Current status
The static Inventory remains the primary declaration surface. Use the current
pig inventory reference for selectors, validation profiles, structured output, and
the explicitly experimental CMDB bridge.
8 - Use the CMDB Pigsty Already Has
Decision date: 2026-07-18
Status: Released in v1.6.0 as an experimental adapter. The earlier revision-store design is superseded.
Current reference:pig inventory cmdb
Scope: Exchanging declarations with Pigsty’s existing CMDB, not designing another configuration database.
Decision
PIG must reuse the CMDB already provided by Pigsty. Its responsibility is a bounded adapter: validate a static Inventory, load declarations into the existing tables, dump the existing projection, check consistency, and switch Ansible between the static and dynamic sources safely.
PIG does not own a second schema, migration history, snapshot ledger, compare-and-swap revision store, three-way merge engine, or rollback database.
Context
An early design treated CMDB support as a greenfield backend. It proposed a separate schema,
immutable snapshots, revision tokens, merge and rollback operations, backup bundles, and source
switch records. The design was internally coherent but started from the wrong premise: Pigsty
already had the pigsty and pglog schemas, load scripts, dynamic Inventory projection, and source
switching behavior.
Building a parallel control plane would duplicate facts, create synchronization problems, and make PIG responsible for a data model owned by another project.
Alternatives considered
- Keep the new revision store as an advanced mode. Rejected because two authorities are still two authorities, even if one is optional.
- Mirror between the new and existing schemas. Rejected because conflict resolution and migration would become permanent product responsibilities.
- Hide the existing scripts behind a shell wrapper. Rejected because PIG needs bounded timeouts, safe connection handling, structured plans, and atomic source switching.
- Remove CMDB support entirely. Rejected because a small native adapter adds useful validation and automation without redefining the schema.
Contract
- Pigsty’s existing schema and projections are the data-model authority;
- PIG connects through an explicit database target, environment configuration, or
service=meta; - credentials, DSNs, SQL bodies, and declaration values never enter plans or diagnostics;
checkis read-only;initreplaces thepigstyandpglogschemas from the version-matched baseline and requires confirmation; back up an existing database first;loadreplaces declaration rows transactionally and requires explicit confirmation;dumprefuses an unexpected overwrite unless forced;enableanddisableedit only recognized Ansible Inventory source forms and write atomically;- unfamiliar executable Inventory sources are refused rather than rewritten;
- the entire command family remains labeled experimental.
Consequences
The correction removed the parallel revision-store implementation and its maintenance burden.
The remaining adapter is smaller, easier to audit, and compatible with existing Pigsty operations.
It also inherits the limits of that system: init needs an operator-managed backup, and loading a
declaration set is a replacement operation rather than collaborative version control.
Verification and evolution
The corrected boundary was recorded in
ba6e678. The abandoned implementation was removed
in e0f73ed, deleting the parallel schema, snapshot,
merge, revision, and rollback machinery. Tests for the retained path cover PostgreSQL compatibility,
connection redaction, transaction failures, digest-pinned confirmation, dump safety, and atomic
source switching.
Current status
The existing-CMDB adapter shipped in v1.6.0 but remains experimental.
Back up CMDB state before initialization or replacement. See the
pig inventory reference for the supported workflow.
9 - A Bounded Grafana Client Instead of Dashboard Shell Scripts
Decision date: 2026-07-18
Status: Released in v1.6.0; dashboard schema v2 support followed in v1.6.2.
Current reference:pig sty grafana
Scope: Dashboard import, export, cleanup, and selected organization and user preferences.
Decision
PIG uses a native HTTP client to manage Grafana dashboards. Local JSON paths select a dashboard, a folder, or a complete dashboard directory. This gives load, dump, and cleanup a common selection model and lets users preview destructive work.
The default directory is the Pigsty dashboard bundle. Users can select another compatible directory.
Cleanup normally selects dashboard UIDs declared by the local files; explicit clean --all selects
all dashboards and folders in the active Grafana organization.
Context
The previous shell scripts depended on local file layout and offered limited detail about partial failures. Moving these operations into PIG makes timeouts, authentication, path validation, and results consistent. A local declaration also provides a precise default cleanup scope.
Alternatives considered
- Keep shell scripts as the public interface. Network limits, redaction, and structured results would remain split across scripts.
- Expose arbitrary Grafana API calls. PIG only needs dashboard operations and a few preferences.
- Select cleanup targets by folder name. Names can collide. The selected JSON files provide dashboard UIDs; folders are removed only after they are empty.
- Embed a default password. Credentials belong in user configuration.
Contract
- Limit request duration and response size, and reject unsafe redirects.
- Check public health before authentication.
infocan return health alone when no password is set. - Resolve credentials from explicit inputs, environment, or Inventory; omit them and response bodies from diagnostics and structured results.
listlists all dashboards in the active organization.loadvalidates and imports the selected local files without deleting live dashboards.bootselects the existingpigstydashboard as home, stars it, and names the organization Pigsty.initloads a complete dashboard directory and runsbootonly after every load succeeds.dumpexports the selected remote scope as deterministic JSON with atomic file writes.cleandeletes locally declared UIDs and then empty folders.--allexplicitly selects the entire active organization. Both modes show a plan and require confirmation.langandstyleupdate the selected preference for the organization and current user. Styleautomaps to Grafana’ssystemvalue.- Normalize supported dashboard schema v1 and v2 representations when reading and writing them.
Consequences
Load and cleanup use the same local declarations, so users can operate on one dashboard or folder.
A matching UID identifies the target; it does not prove that PIG originally created it. Users must
review the cleanup plan, especially when selecting --all.
Datasource provisioning, plugin installation, and user administration remain in Grafana’s own tools.
Verification and evolution
The native workflow landed in
3060485. Dashboard schema v2 support followed in
67f6e3b.
The released tests
cover health-only operation, authentication, load scopes, bootstrap ordering, export stability,
preference updates, local UID cleanup, explicit full cleanup, and changed-plan rejection.
HTTP-client tests cover timeouts, redirects, response limits, and redaction.
Current status
The dashboard workflow is released. See pig sty grafana for path selection,
credentials, and cleanup examples.
10 - Let Patronictl Speak for Itself
Decision date: 2026-07-21
Status: Implemented and released in pig v1.6.0.
Current reference:pig pt
Scope: Patronictl-backed cluster commands plus PIG-owned configuration selection, settings sugar, service, status, and log helpers.
Decision
pig pt is a transparent launcher for the installed patronictl. PIG selects the configuration
and dispatches a small set of local helpers. Every other command token and all following arguments
are passed unchanged, with native prompts, terminal behavior, output formats, and exit codes.
PIG no longer maintains a copy of Patronictl’s evolving command tree.
Context
Mirroring Patronictl required PIG to reproduce commands, options, positional grammar, confirmation,
formatting, and version-dependent behavior. That surface changed upstream and PIG’s copy drifted.
Users could receive different semantics depending on whether they called patronictl directly or
through PIG.
The wrapper still adds value in a Pigsty environment: selecting the correct config as the database operating-system user, providing local service and log workflows, and translating a small settings operation into one native edit-config call.
Alternatives considered
- Continue mirroring every upstream command. Rejected because it guarantees lag and duplicates validation that Patronictl already owns.
- Allow only a tested command allowlist. Rejected because new upstream commands would remain unavailable until a PIG release.
- Capture native output into PIG JSON. Rejected because it breaks interactive editing, streaming, prompts, terminal fidelity, and upstream schemas.
- Remove
pig ptentirely. Rejected because deterministic config selection and local Pigsty helpers remain useful.
Contract
- the first non-option command token determines local dispatch or passthrough;
set, local service shortcuts,status, andlogare PIG-owned;- all other commands and remaining tokens are forwarded verbatim;
pig pt -- COMMAND ...bypasses a local-name collision explicitly;- wrapper-level options must precede the native command token;
- native help can run without resolving a local Patroni configuration;
- Patronictl owns interactive prompts, native
--format, and exit codes; - global PIG structured output is rejected where it would consume a native option ambiguously;
- the selected config is resolved predictably and the process runs as the database system user.
Consequences
Automation had to adopt Patronictl’s cluster-first positional grammar and native output flags. Some PIG-only aliases and result schemas disappeared. In exchange, new Patronictl features work without a PIG release and behavior no longer depends on a lagging wrapper implementation.
The local set helper remains intentionally small: it classifies scalar Patroni keys and
PostgreSQL parameters, then performs one native edit-config action.
Verification and evolution
The rewrite landed in
6cbc23b. Tests cover token-boundary parsing,
verbatim argv preservation, config precedence, database-user execution, native exit propagation,
help without configuration, output-mode rejection, the -- escape, and local-helper collisions.
The final help-path refinement landed before v1.6.0.
Current status
Use Patronictl’s own documentation for forwarded command grammar and pig pt for PIG’s
config selection and local helpers. For JSON output from a forwarded command, use Patronictl’s native --format json option.
11 - PIG 2.0 Product Direction
Decision date: 2026-08-13
Status: Proposal for review; not implemented.
Current reference: PIG documentation and release history
Scope: Candidate product boundaries and verification gates for a future PIG 2.0 / Pigsty 5.0 line.
Decision
The proposed direction makes PIG the stable onboarding front door from an empty controller to a validated, deployable Pigsty Inventory. PIG would own Catalog selection, resolution, plans, bounded execution orchestration, structured results, and redacted receipts. It would continue to delegate package transactions, configuration application, and infrastructure state to DNF/APT, Ansible, and future provider-specific tools.
The proposal deliberately preserves PIG’s standalone value: repo, ext, and install must work
without a Pigsty project. It also preserves explicit deployment consent: a future pig sty setup
may download, bootstrap, and configure, but it must stop before multi-node deploy unless the user
invokes deployment separately.
Context
By v1.8.0 PIG could download releases, bootstrap a controller natively, compile Inventory, manage repositories and extensions, and run selected operations. Several product seams remained:
- repository, package-alias, extension, route, and Pigsty metadata could change independently;
- projects had no explicit Catalog identity to protect later resolution from global updates;
- route choice and repository safety were not one visible product contract;
- execution results did not yet form a durable, redacted receipt across the onboarding path;
- compatibility among PIG, Pigsty, Catalog schema, operating systems, and Ansible needed one release matrix rather than separate assumptions.
The proposal focuses 2.0 work on metadata consistency and a repeatable setup workflow.
Alternatives considered
- Turn PIG into a monolithic configuration and state engine. Rejected because Inventory, Catalog authoring, package managers, Ansible, and providers already own different facts.
- Make Pigsty depend on a live PIG or pgext checkout. Rejected because a Pigsty release must remain independently usable from generated, versioned artifacts.
- Make setup deploy automatically. Rejected because creating and validating configuration is a different consent boundary from changing remote nodes.
- Reimplement DNF/APT failover or Ansible execution. Rejected because PIG should select inputs and explain results, not become another package manager or configuration engine.
- Block 2.0 on Vagrant/Terraform unification. Rejected because lab providers have different state semantics and do not determine the core onboarding path.
- Invent a universal
sty planimmediately. Rejected until at least two owned workflows prove that one reusable plan schema exists.
Contract
If accepted, the product direction would enforce these boundaries:
- each fact type has one authority: Inventory for cluster declarations, Catalog authoring sources for product metadata, project lock for selected snapshot identity, receipts for observed results, and providers for live state;
- PIG owns Catalog schema, validation, client, resolver, and selection, but not every authoring database;
sty setupcomposes existing init, boot, and configure use cases instead of duplicating them;- setup stops after a validated Inventory; deploy remains explicit;
- standalone commands follow a compatible Catalog channel, while a Pigsty project pins its selected snapshot after a successful setup or configuration commit;
- ordinary commands never rewrite an existing project lock implicitly;
- route selection is explicit or a bounded first-run decision, not a continuous GeoIP, cloud-IMDS, or background-latency service;
- package download retry and endpoint failover remain owned by DNF/APT;
- execution artifacts are versioned and redacted; raw upstream modes preserve native streams and exit behavior;
- doctor remains diagnostic and does not gain default repair authority;
- future lab support is a thin adapter and never makes PIG the owner of Terraform state.
Consequences
The proposal creates a clearer first-run story and makes metadata selection auditable. It also adds new durable contracts: snapshot identities, project locks, migration rules, trust policy, receipts, and compatibility matrices. Those contracts increase the testing and release burden and must not ship as loosely coupled features.
An Ansible event bridge depends on redaction and compatibility tests. Doctor/support bundles and lab adapters are deferred until after the initial release. The EL7 support level remains open.
Verification and evolution
This proposal requires evidence before it can become a release contract:
- a native onboarding VM matrix across the declared Linux targets;
- adversarial Catalog signature, expiry, rollback, mix-and-match, and offline tests;
- semantic-diff proof that generated Pig, Pigsty, and pgext consumers do not drift;
- an Ansible callback experiment with zero
no_logor secret leakage; - route-selection tests for global, China, proxy, and restricted-network environments;
- repository-signing tests before secure defaults are changed;
- rehearsed 1.x-to-2.0 layout, lock, and mixed-version migration;
- schema and structured-output fixtures tied to an explicit compatibility matrix.
The implementation baseline used for this proposal is
v1.8.0.
That release contains native boot and configure, but it does not implement the proposed Catalog v2,
project pin, setup command, event receipt, or 2.0 migration contract.
Current status
The proposal remains open. Decisions are still needed for Catalog security, overlays, paths, EL7 support, and the Ansible event bridge. Use the current documentation for released commands.
12 - Catalog v2 Proposal: Immutable Typed Snapshots
Decision date: 2026-08-13
Status: Proposal; security mechanism, overlays, packaging, and path layout remain open.
Current reference:pig extandpig repodescribe the v1 Catalog behavior.
Scope: A candidate PIG 2.0 publication and consumption model for product metadata, not Inventory or live system state.
Decision
Catalog v2 should be one immutable, verifiable snapshot composed of multiple typed targets. A manifest binds platform, repository, route, package-alias, extension, compatibility, Pigsty release, and public-key metadata to exact bytes. Candidate targets are validated together and activated through one pointer, preventing a new repository catalog from being mixed silently with an old extension matrix.
The snapshot digest is its identity. Package, system, user, portable, and project scopes store or select the same verified content; they do not merge unrelated base snapshots into a synthetic Catalog.
Context
The v1 Catalog is practical but spreads related facts across embedded CSV, repository YAML, Pigsty variables, generated sites, and reload paths. Some fields repeat derived information, and the extension matrix compresses several independent identities into one record. Independent updates make it difficult to prove that repository, package, extension, and compatibility data belong to the same publication event.
Catalog v2 must define how related data is published, verified, and activated together.
Alternatives considered
- Create a larger
extension.csvor one giant YAML file. Rejected because unrelated target types evolve differently and cannot be activated or streamed independently. - Use SQLite, protobuf, or a custom binary matrix immediately. Rejected because the current dataset has not demonstrated a performance need that justifies a new runtime and debugging cost.
- Merge system, user, and project base snapshots field by field. Rejected because the result has no single publisher, digest, compatibility statement, or signature.
- Store active or project-pinned content only in a cache. Rejected because deleting a cache must not destroy a durable user or project decision.
- Let a user file shadow the official trust root. Rejected because security policy is a constraint, not an ordinary last-writer-wins preference.
- Silently update and activate in the background. Rejected because metadata changes can alter package resolution and must be an observable operation.
Contract
The proposed snapshot contract is:
- deterministic UTF-8 JSON for manifests and small targets, and JSONL for large sparse targets;
- raw manifest bytes and target length/hash are verified without parse-and-reserialize ambiguity;
- manifests carry schema, monotonic security version, creation, expiry, channel, and PIG/Pigsty compatibility;
- an embedded rescue baseline is always available;
- package-owned baselines, durable snapshot stores, mutable state pointers, and purgeable download caches are separate paths;
- Linux follows FHS/XDG, macOS uses Application Support and Caches, and portable
PIG_HOMEkeeps config, data, state, cache, and run roles distinct; - a project lock records snapshot identity and ordered overlays, while the verified snapshot is materialized into durable project support data;
- selecting a digest and finding its bytes are separate algorithms;
- system security policy can only be tightened by lower scopes, not weakened silently;
- updates download into private staging, verify every layer, sync, move into a content-addressed store, and atomically replace the active pointer;
- failures retain the previous active snapshot;
- project pins do not move when user or system active channels update;
- offline export includes verification metadata and public trust material, never private keys;
- old
ext reloadandrepo reloadmay map to one whole-snapshot update for one compatibility period, but cannot activate targets independently.
The runtime Catalog excludes Inventory, credentials, live probes, installed-package state, Ansible events, provider state, confirmations, and metrics that change without a product decision.
Consequences
Typed targets make ownership and validation clearer, and content addressing makes rollback and airgap import auditable. Project materialization prevents a deployment from depending on one user’s global cache. Independent OS packages can refresh a read-only baseline without changing an active user selection.
The cost is a larger release protocol: key rotation, expiry, rollback protection, garbage collection, path permissions, migration, package-manager lifecycle, overlay conflicts, and cross-repository generators all become compatibility-sensitive work.
The extension model must also separate SQL extension identity, upstream project, distribution or
build unit, versioned release, OS package offer, target availability, and display policy. Derived
fields such as aggregate platform support or required_by should be generated, not maintained as a
second truth.
Verification and evolution
Before implementation, the proposal requires an ADR to choose between go-tuf and a minimal signed manifest. Both candidates must pass the same threat tests: bad signatures, expired metadata, rollback, target substitution, mix-and-match snapshots, truncated downloads, and offline verification. If neither passes, Catalog v2 cannot be a 2.0 release feature.
Additional gates cover FHS/XDG/macOS/portable paths, root and non-root permissions, read-only homes and projects, symlink replacement, disk-full and concurrent activation, package upgrade/remove semantics, project survival after global-cache deletion, and parity migration of the current extension and availability corpus.
The proposal was based on
v1.8.0.
v1.9.0 still uses the embedded and independently
reloadable v1 Catalog.
Current status
Catalog v2 is not implemented. Security, paths, and overlay rules need decisions and tests first.
If overlay conflict and trust rules are not ready, a complete custom signed channel can provide
the initial customization path. Current usage is documented under pig ext and
pig repo.
13 - One-command Xray Clients and Servers
Decision date: 2026-10-05
Status: Released in v1.9.0. The 2026-10-16 refinements below are Implemented in the v1.10.0 candidate, not yet released.
Current reference:pig build, operations safety, and release history
Scope: One-command Xray client and server setup; Linux clients and servers, plus a macOS client. Shared Nginx ingress remains a separate deployment boundary.
Decision
Extend pig build proxy with explicit client and server subcommands. Use VLESS over RAW,
REALITY, and xtls-rprx-vision for the new workflow. First server setup generates matching credentials;
repeated setup preserves the existing client connection contract and offers the same standard URI.
The client takes a URI or explicit command-line parameters,
installs Xray, configures the local listener, starts its service, and verifies the connection.
A URI can be pasted directly or read from a file or stdin.
Retain the existing command path and x alias. The zero-argument command installs or verifies
Xray. The historical id@host:port [local] form retains its VMess meaning and default port during
a compatibility period, through the existing V2Ray workflow.
Context
The previous implementation
installs vray, writes an HTTP-to-VMess configuration to /etc/v2ray.json, grants the v2ray
group access, writes shell aliases, restarts v2ray, and checks an HTTPS request. Existing tests
cover argument parsing, package-manager selection, and credential redaction.
The inspected reference pair uses Xray 26.3.27. The running macOS client exposes HTTP and SOCKS on one loopback port, 8888, and uses VLESS/RAW/REALITY/Vision with a Chrome fingerprint, disabled multiplexing, and ML-DSA-65 verification. Both HTTP and SOCKS requests succeeded during the review. The client UUID, short ID, and SNI match the server’s corresponding fields.
The reference Linux server listens on loopback port 9443. Nginx owns public port 443 and selects
the backend by SNI, forwarding a PROXY protocol header. Xray accepts that header, connects to a
REALITY target, sends allowed traffic through freedom, and blocks destinations matching
geoip:private. Nginx provides the public entry point in this deployment.
Inspected Pigsty RPM and DEB artifacts for both architectures provide /usr/bin/xray,
/etc/xray.json with mode 0640 and ownership root:xray, an xray:xray service account,
xray.service, and geodata in /usr/share/xray. Their installation scripts reload systemd without
automatically starting or enabling the service. Public repository indexes list version
26.3.27-1PGSTY. Runtime tests are listed below.
Alternatives considered
- Rename V2Ray paths and keep only VMess. Insufficient: it does not provide server setup or the working reference protocol. The old template passes Xray’s configuration validator, but Xray emits a VMess deprecation warning.
- Build a general proxy manager. Deferred: subscriptions, protocol catalogs, TUN, transparent routing, traffic accounting, and arbitrary configuration import are outside the requested pair.
- Create a new top-level command now. Deferred: the existing
build proxyentry can hold the two roles without changing the root grammar or creating a second implementation. - Copy the reference server verbatim. Rejected: a loopback listener requiring PROXY protocol cannot serve direct public connections, and existing Nginx port ownership must be respected.
- Require a connection file for client setup. Rejected: pasting one URI or supplying parameters directly is the primary workflow; a protected file or stdin remains useful for automation.
- Print generated credentials in ordinary success output. Rejected: structured results and routine logs must remain safe to retain. An explicit export can return a URI to the terminal.
Contract
The implemented role grammar is:
Server inputs and defaults. Require the advertised --host and a REALITY --target.
Public --port defaults to 443; the direct listener defaults to that port on all IPv4 interfaces.
On first setup with --proxy-protocol, the listener instead defaults to 127.0.0.1:9443.
An explicit --listen changes the bind endpoint without changing the advertised public port.
On repeated setup, omitting --listen preserves the existing supported listener rather than
replacing it with a first-setup default.
On first setup, derive SNI from the target hostname unless --sni is supplied. On repeated setup,
preserve the existing SNI instead of deriving a replacement. Validate the target’s TLS
compatibility before applying configuration; do not silently choose an unrelated camouflage host.
Generate a UUID, X25519 key pair, random eight-byte short ID, and a distinct ML-DSA-65 seed and
verification key. Reuse existing PIG-managed material on repeated setup; ordinary reruns do not
rotate credentials. Rotation and multi-user management require a later explicit contract.
Use warning logging, disabled access logging, decryption: none, the Vision flow, and an
explicit private-destination blocking rule. Set the geodata environment to the packaged asset path.
Server idempotence. Repeating setup with the same inputs must leave existing clients usable without changing their configuration. Preserve the UUID, X25519 private key and corresponding client authentication material, short ID, ML-DSA seed and verification key, SNI, and protocol defaults. With the same advertised host and public port, export the same canonical URI. Generate credentials only when creating a server configuration for the first time, never because setup runs again.
If the managed configuration and service settings already match the requested state and the service is running and enabled, perform checks without rewriting files or restarting the service. If only the service has stopped or become disabled, restore the requested service state using the existing credentials. Missing, malformed, or ambiguous existing authentication material is an error; do not repair it by silently regenerating credentials. Reject setup inputs that conflict with the existing authentication or SNI contract. Such changes need an explicit migration or rotation contract rather than an ordinary rerun.
Reuse an existing server. server --export-only --host HOST --export DESTINATION reads the
existing packaged server configuration and exports a connection URI using its current UUID, SNI,
short ID, and derived client verification material. The advertised public port still defaults to
443; never infer it from a loopback backend port. Require one unambiguous supported VLESS/REALITY
inbound, client, SNI, and short ID. Reject ambiguity or malformed material instead of selecting an
arbitrary entry. This mode does not install, rewrite server or service configuration, restart,
enable, or rotate credentials. An explicitly requested file export is its only persistent write.
Derive verification material in process so existing private keys and seeds do not enter subprocess
arguments. This branch performs a read-only query with explicit credential export; the shared server command retains its conservative action annotation.
Client inputs and defaults. Accept a single quoted vless:// positional URI as the primary
workflow. Also support direct parameter mode with --server host:port, --id, --sni,
--public-key, and --short-id; --pqv supplies ML-DSA verification when used by the server.
The direct mode supplies the connection fields while REALITY, RAW, Vision, and Chrome retain
the defaults below. All input forms feed the same validated connection model and renderer.
Keep --from FILE and --from - as optional file/stdin inputs. Reject mixed connection input
forms rather than silently overriding URI fields with flags. Local options such as --listen
remain available for every input form.
client.uri is an ordinary UTF-8 text file containing one standard vless:// URI and an optional
trailing newline; the filename is arbitrary. It is not a new PIG configuration format.
Support the reference combination only: REALITY, Vision, and TCP/RAW. Map URI type=tcp to JSON
network: raw, pbk to password, sid to shortId, and pqv to mldsa65Verify.
Preserve the ML-DSA verification field; never silently drop it. Reject duplicate or unsupported
parameters that would change connection meaning. The mapping follows the
upstream sharing specification.
On first setup, bind a socks inbound to 127.0.0.1:12345, retaining the existing PIG client
endpoint default. --listen can explicitly select 127.0.0.1:8888 to match the inspected client.
On repeated setup, omitting --listen preserves the existing managed client’s listener.
Provide HTTP support on the same port, UDP enabled, and
multiplexing disabled. Use encryption: none, the Vision flow, a Chrome fingerprint, and
spiderX: /. The upstream Socks reference
documents HTTP support. Validate HTTP and SOCKS independently; a TCP listener alone is insufficient.
The CLI fields map to the existing reference configuration as follows. Client paths below are relative to its VLESS outbound; server paths are relative to its VLESS inbound.
| CLI field | Client configuration | Server configuration or export |
|---|---|---|
Server --host, --port; client --server |
settings.vnext[0].address and .port |
Advertised URI endpoint; not necessarily the bind endpoint |
Role-specific --listen |
Local inbound listen and port |
VLESS inbound listen and port |
Client --id |
settings.vnext[0].users[0].id |
Matches settings.clients[0].id |
--sni |
streamSettings.realitySettings.serverName |
Member of streamSettings.realitySettings.serverNames |
Client --public-key |
streamSettings.realitySettings.password |
Derived from streamSettings.realitySettings.privateKey |
Client --short-id |
streamSettings.realitySettings.shortId |
Member of streamSettings.realitySettings.shortIds |
Client --pqv |
streamSettings.realitySettings.mldsa65Verify |
Derived from streamSettings.realitySettings.mldsa65Seed |
Server --target |
No direct client field; SNI must be compatible | streamSettings.realitySettings.target |
Server --proxy-protocol |
No client field | streamSettings.rawSettings.acceptProxyProtocol: true |
The reference server keeps realitySettings.xver: 0: accepting a PROXY header from Nginx and
forwarding a PROXY header to the REALITY target are different settings. Template defaults preserve
the reference flow, transport security, logging, disabled multiplexing, server private-destination
blocking, and client fingerprint. The public endpoint, bind endpoint, and target have separate roles.
Platform and service ownership. Linux uses the configured package repositories and the package
account, configuration path, and unit above. macOS clients use Homebrew Xray, a mode-0600 user
configuration at ~/.config/xray/pig-proxy.json, and a dedicated user LaunchAgent named
com.pigsty.xray-proxy; do not take over another Xray daemon or Homebrew service.
Linux setup requires root and a running systemd instance; macOS client setup requires a regular user and existing Homebrew. macOS server setup is outside this change.
New role setup explicitly starts and enables the managed service; install-only does not start or enable it.
On Linux, PIG owns a bounded pig-proxy.conf systemd drop-in for asset location and capabilities.
Direct binding to a privileged port requires only CAP_NET_BIND_SERVICE in the bounding and
ambient sets. Reset both sets before assignment because systemd combines positive directives
with the package unit. A loopback backend on an unprivileged port does not need that capability.
Do not depend on permissive host sysctl settings or run Xray as root to make port 443 work.
Application and recovery. Parse arguments and preflight paths, port ownership, service conflicts,
and existing configuration before mutations. Recognize managed configuration by its complete
supported shape, not merely an inbound tag. Replacing a different or unsupported client configuration requires --replace --yes. Unsupported
server configurations and service units using another account, an explicit different group, or
extra configuration arguments are refused. Preview supports --plan, with no installation, credential creation, file writes, or restarts.
Render JSON through Go serialization. Stage protected candidates, validate them with Xray under
the actual service identity, then atomically apply configuration and service changes. Compare
content, ownership, and permissions before treating files as unchanged. On macOS, track persistent
disablement separately from loaded state, enable the managed LaunchAgent on start, and restore both
states on failure. Save original
content, ownership, mode, service enablement, and running state for bounded rollback. Check service
health and connectivity before publishing shell aliases or connection exports. Client setup requires
HTTP 204 from https://www.google.com/generate_204 through both HTTP and SOCKS, so server outbound
access to this endpoint is part of the client acceptance condition. An application
failure returns nonzero and reports which recovery steps succeeded; an installed package may remain.
Never delete or automatically stop V2Ray. A port conflict stops preflight and produces a migration
instruction. Existing V2Ray configuration remains available for operator-controlled rollback.
Retain po, px, and pck for client convenience, including lowercase proxy variables needed by
build tools. Activating those variables in the calling shell still requires sourcing the generated
shell file and invoking po; a child process cannot modify its parent shell.
Secrets and exports. --export FILE writes the explicitly requested connection export with
mode 0600 and no server private key or ML-DSA seed. --export - explicitly delivers one complete
URI on stdout, with non-secret setup diagnostics on stderr. This credential-export mode requires
text output; reject JSON/YAML combinations before mutations instead of putting the URI inside a
structured result. Ordinary setup output remains redacted. Command-line input is supported even
though the caller’s shell history or process inspection can retain those supplied credentials;
file and stdin input remain available when that matters.
Treat the whole URI as a credential, including its UUID and authentication fields. Default logs, plans, JSON/YAML results, and failure details contain only redacted summaries and export paths. Generate and derive key material in process. Capture validator output privately: upstream configuration errors can include the invalid secret value. Never use a generic subprocess capture as a safe credential boundary. Re-exporting to an existing protected file containing the same canonical URI succeeds without rewriting it. A different existing export is a conflict; never silently overwrite it or print its contents in a diagnostic.
Ingress and verification boundaries. Direct mode is the default one-command server setup.
The explicit --proxy-protocol backend mode requires loopback binding and a trusted pre-existing
frontend. PIG does not rewrite Nginx or firewall/cloud rules in this change. A busy public port
causes a clear failure instead of stopping its owner. Backend readiness does not prove public ingress;
server setup reports local checks and leaves external reachability pending until a client handshake
and HTTPS request succeed.
Generating equivalent Xray configurations includes all of the reference pair’s protocol and authentication fields. Reproducing the entire reference deployment also requires Nginx’s SNI map, its PROXY-enabled website backend, and OS-specific service registration. This implementation manages Xray and its own service settings; it does not claim to recreate that Nginx composition from a server URI. Configuration equivalence does not imply byte-for-byte preservation of tags, field order, or installation-specific names.
The 2026-10-16 compatibility refinement accepts the complete marker-free macOS plist generated by the earlier renderer when its configuration and binary paths match. A management comment alone is insufficient. Edited or foreign files require explicit replacement. Linux recognition is also limited to the complete generated capability variants. After the one-time comment migration, identical execution must preserve the running service.
The marker-free fixture is generated by executing the earlier renderer. That source file is
byte-identical to the publicly available
v1.9.0 source at 9eb54888.
This is old-source-format evidence, not verification of a particular released binary.
Consequences
Users get two setup operations connected by one standard URI, or supply client parameters directly,
with matching generated parameters instead of manual JSON editing. No connection file is required.
Both new clients and the historical VMess form default to 127.0.0.1:12345 on first setup.
Direct servers default to the public port on all IPv4 interfaces; PROXY-protocol backends default
to 127.0.0.1:9443. Repeated setup preserves existing listeners when --listen is omitted.
Configuration and service management remain explicit about role, OS, ownership,
and external ingress.
The implementation uses cmd/build.go and focused files under cli/build. It reuses the
existing plan, output, confirmation, privileged-write, and logging helpers.
Verification and evolution
Fixed source commits and regression tests for the committed v1.10.0 refinements cover: complete legacy service recognition. These commits do not establish a release boundary. The v1.9.0 runtime evidence below covers the original implementation.
The implementation is recorded in
d090b7c,
with ownership and service-state fixes in
5d8660c.
Tests cover role syntax, equivalent input forms, redaction, listener defaults, credential reuse,
export conflicts, protected files, capability selection, and plans. Full Go tests, vet, static
analysis, dead-code and complexity checks, and repeated command/build race tests passed locally.
Runtime checks used Xray 26.3.27:
| Environment | Verified behavior |
|---|---|
| Fresh Debian 13 ARM64 client | Package installation; service running as xray; HTTP and SOCKS health checks; unchanged configuration and PID on repeat setup; rollback after a wrong UUID |
| Fresh Debian server | Direct privileged-port listener; 0640 configuration and 0600 export; stable credentials, files, and PID on repeat setup; rollback after export failure |
| Existing Homebrew Xray on macOS | Isolated LaunchAgent; HTTP/SOCKS connectivity; unchanged reruns; restoration of configuration and persistent disabled state after failure |
| Existing reference server | Read-only export and client tests left its configuration hash and PID unchanged |
The new server’s exported URI completed REALITY/Vision connections with ML-DSA verification. HTTP and SOCKS requests to a reachable TLS site returned HTTP 200. Its network could not reach the Google health endpoint, so full client setup for that pair failed and rolled back. Full client setup passed against the existing reference server, through its Nginx/PROXY ingress.
Runtime coverage did not include RPM installation, a fresh Homebrew installation, UDP forwarding, other Xray versions, or deployment of a new PROXY-protocol frontend.
Current status
Released in v1.9.0. Nginx ingress automation and credential rotation remain future work.
14 - Bootstrapping a Pigsty Controller as a Recoverable Transaction
Decision date: 2026-08-14
Status: Implemented and released in pig v1.8.0.
Current reference:pig sty boot
Scope: Preparing a Pigsty controller and its package sources; not deploying a database cluster.
Decision
pig sty boot should be one native, failure-aware controller bootstrap workflow. It resolves an
explicit source before privilege elevation, prepares online or offline repositories, installs only
the required controller packages, proves Ansible is usable, and performs bounded finishing checks.
Repository replacement is transactional: definitions are backed up and restored when package setup fails. Optional conveniences may warn, but invalid explicit input, package failures, and an unusable final Ansible environment are hard failures.
Context
The previous command delegated to a shell bootstrap script. That made source selection, download,
sudo boundaries, repository rollback, error classification, and structured automation difficult to
observe. A present ansible-playbook binary could also be mistaken for a usable environment even
when its Python dependencies were missing.
Offline installations added another ambiguity: an already ready controller may still need a local repository prepared for later nodes.
Alternatives considered
- Continue invoking the legacy script. Rejected because PIG could not own the transaction or explain partial failure reliably.
- Require the entire command to start as root. Rejected because explicit downloads and source validation do not need privilege and should happen before one bounded elevation.
- Treat an Ansible binary as readiness. Rejected because the executable may use a Python environment missing required modules.
- Skip repository work when Ansible is ready. Rejected because staging an explicit offline source is an independent requested effect.
- Make every finishing check fatal. Rejected because locale convenience, localhost SSH repair, or tree initialization can fail without invalidating an otherwise usable controller.
Contract
- explicit local paths and URLs are validated and never silently fall back to online mode;
- automatic offline sources must pass ownership and permission checks;
- a committed local repository can take precedence over a selected package;
- download and restricted archive extraction use native bounded implementations;
- offline bundles require a
pigsty/repo_completesentinel; additional roots are preflighted and renamed beforepigsty, while conflicts and interrupted residue fail closed for manual cleanup; - privilege elevation happens once after source resolution and can be disabled or made non-interactive;
- overwritten repository definitions are recoverable on setup failure;
- readiness executes Ansible and checks the Python modules it will use;
- the result distinguishes ready, offline, online, and existing modes;
- hard failures and finishing warnings are separate structured fields;
- bootstrap does not claim that Pigsty deployment has succeeded.
Consequences
The native workflow is larger than a script launcher, but its side effects and rollback state are visible. It can stage offline content on an already usable controller and gives automation a stable result without hiding optional problems.
After bootstrap, generate the Inventory, deploy the selected services, and check their status.
Verification and evolution
The native implementation landed in
222616e and was refined in
74e084e. Tests cover source precedence,
permission checks, restricted extraction, sudo re-exec, repository rollback, locale recovery,
Ansible/Python readiness, localhost SSH, initialization, warning classification, and structured
results. The release page records delivery in v1.8.0.
The offline-bundle refinement in
8fa3abb
preserves multiple validated top-level repositories and retains the repo_complete marker.
It shipped in v1.8.1.
Current status
pig sty boot prepares the controller. Run deployment separately after configuring the Inventory.
See pig sty for source modes, environment controls, and next steps.
15 - Install the Pigsty Repository Key Automatically
Decision date: 2026-10-04
Status: Released in v1.9.0.
Current reference:pig repo
Scope: Installing the existing embedded Pigsty public key during repository configuration.
Decision
When a selected module contains an available pigsty-infra or pigsty-pgsql repository,
prepare the embedded Pigsty public key before changing repository definitions. Apply this in
the shared configuration workflow so repo add, repo set, and callers that reuse it agree.
Reuse an existing key file. Automatic installation is best-effort; an explicit signing-key
reference in selected repository metadata makes installation failure fatal.
Context
The source baseline
installed the key only when a caller requested GPGCheck, as native sty boot does.
Ordinary repo add/set therefore omitted a public key already shipped inside PIG.
The key is needed by both Pigsty repositories. Their membership spans pigsty, infra,
pgsql, and the default all selection, so checking only the literal argument pigsty
would miss normal usage.
APT does not discover files under /etc/apt/keyrings automatically. A repository must reference
the prepared file with signed-by; otherwise a successful write still leaves NO_PUBKEY
warnings during cache refresh.
Removing signed-by during fallback can conflict with a retained definition of the same APT
source. Keep the default reference stable across successful preparation and fallback. Explicit
metadata names a required key of its own; preparing an unrelated default file cannot satisfy it.
Alternatives considered
- Download the default key at runtime. The embedded release input already supplies it and works offline, without adding a network or external-tool dependency.
- Install the key on every repository operation. Unrelated repositories should not cause a Pigsty key write. Resolve membership and platform availability first.
- Fail every repository operation when automatic key installation fails. Rejected because optional key preparation should not prevent the existing compatibility mode from working. Explicit signing-key metadata remains a strict boundary.
- Enable all signature checks at the same time. Publisher identity, RPM package signatures, and APT metadata authentication are separate contracts. Automatic key installation does not establish signing coverage for every package or mirror.
Contract
- Validate requested modules before installing the key. A rejected replacement does not install it or move repository files.
- Match the two Pigsty repository identifiers in resolved modules, including custom modules; the URL’s hostname alone does not select a signing identity.
- Write the existing embedded key to
/etc/pki/rpm-gpg/RPM-GPG-KEY-pigstyon EL or/etc/apt/keyrings/pigsty.ascon Debian/Ubuntu when installation is needed, with mode0644. - Check for an existing regular key file first, including a symlink resolving to one; reuse it without changing its contents or permissions. If the existence check fails, attempt installation.
- Create missing parent directories through the existing sudo fallback and use the atomic file writer. A denied destination check must still reach the privileged attempt, which checks the destination again. An occupied directory or dangling symlink is an installation error.
- Without explicit key metadata, installation failure warns and continues. Only selected Pigsty
definitions fall back to APT
trusted=yesor RPMgpgcheck=0andrepo_gpgcheck=0. Fill the defaultgpgkeyorsigned-byreference on both success and failure. Do not rewrite retained repository files to implement fallback. - Explicit key preparation applies to the actual reference. APT
signed-byaccepts absolute paths to existing regular keyring files, separated by commas; install the embedded key when the default path is explicitly named. EL prepares an explicitly named default file if needed and passesgpgkeypaths or literal URLs to nativerpm --import. - Failure to prepare an explicit key is fatal before backup, repository writes, or cache refresh. Preserve its reference and never downgrade that definition. A valid custom key does not depend on the default path; an implicit-key failure in another selected repository affects only that implicit definition.
- Successful ordinary operations preserve signature-checking settings.
sty bootrequests signing on successful key preparation and follows the same implicit-key fallback on failure. - Put fallback warnings on stderr and in
repo add/set’sdata.warnings;sty bootretains them in its own warnings. Implicit installation does not import into RPM’s database; explicit EL metadata does. Neither path changes APT’s global trust store. - Repository rollback restores definitions; already installed or imported public keys remain.
Consequences
Adding a Pigsty repository also attempts to prepare its public-key file, including on offline hosts. Ordinary key failures no longer prevent repository setup. Falling back disables signature verification for those Pigsty definitions and is reported explicitly. A successful existence check preserves the administrator’s existing file; it does not validate that file’s contents. Third-party repository trust remains unchanged. APT may retain previously authenticated indexes with a warning when a key becomes unavailable. Successful repository configuration therefore does not prove that every index was freshly downloaded. The maintained repository reference describes the resulting defaults and paths.
Verification and evolution
The automatic-key implementation
and repository regression tests
are recorded in source commit 56f7353. Local regression tests exercise EL, Debian, and Ubuntu on both supported architectures,
default and composite selections, existing-key reuse, denied existence checks, custom modules,
unavailable repositories, implicit-key fallback, and explicit-key failure before repository
mutation. Existing replacement tests continue to cover rollback.
Actual CLI tests on local EL9, EL10, Debian 12, and Ubuntu 24 ARM64 guests use isolated mount namespaces. They
verify installation as an ordinary user through sudo with missing key directories, exact embedded
bytes and permissions, repeated execution, composite selections, parseable structured output,
implicit-key failure continuing with a warning, and explicit-key failure stopping before
repository replacement. Real DNF/APT refreshes use isolated caches, and the resulting indexes
are queried for available packages. APT tests also verify that the installed key is referenced
without NO_PUBKEY warnings and that explicit signed-by with trusted=no authenticates the
metadata. These checks do not install packages or verify RPM package signatures. The guests'
original repository definitions, keys, and caches remain unchanged.
Follow-up regression checks cover retained APT sources and previously authenticated caches, custom-key absence before both add and replacement, a valid custom key with an unusable default path, and mixed explicit/implicit definitions. Permission tests deny ordinary-user traversal of the key directory and verify both privileged installation and rejection of a hidden symlink. RPM import tests use an isolated copy of the guest’s RPM database.
Current status
Released in v1.9.0. See the current reference linked above for usage and platform requirements.
16 - One Catalog Entry Count for the Website and CLI
Decision date: 2026-10-16
Status: Implemented in the v1.10.0 candidate, not yet released.
Current reference:pig extcatalog count
Scope: Catalog totals and default listing membership; package installation and SQL activation rules remain defined by each entry.
Decision
Use the number of catalog records as the shared extension count. Include packaged SQL utilities and retained lifecycle records. Calculate CLI totals from the catalog actually loaded or downloaded.
Context
The catalog serves package discovery as well as PostgreSQL extension discovery. A packaged SQL
utility can be useful without a control file or a CREATE EXTENSION entry point. Excluding it
from the default list and total makes those outputs disagree with the website and catalog file,
even though an explicit name search still finds it.
Alternatives considered
- Count only records classified as PostgreSQL extensions. Rejected because it gives the CLI and website different totals for the same catalog.
- Add a separate public count for every entry type. Unnecessary for the current listing contract.
- Hard-code the announced total in the CLI. Rejected because a downloaded catalog can differ from the bundled one.
Contract
- The unfiltered default list includes every catalog record, including SQL utilities and removed records.
- Text row counts and JSON/YAML
data.countequal the number of returned records. - Searches count their matches;
--pkgcontinues to count leading package records. - The global availability text summary uses the complete catalog count.
- Catalog refresh results use the downloaded record count in
data.extension_countand their message. - Website totals follow the bundled catalog. A newer downloaded catalog may have a different total.
- Counting a record does not change its DDL requirements, lifecycle state, or platform availability.
Impact
SQL utilities appear in the default list as well as explicit searches. Output field names and types remain stable. Consumers should interpret the extension total as catalog coverage; it is not a count of extensions enabled in a database or installable on a particular host.
Verification
Fixed source commits and regression tests for the committed v1.10.0 refinements cover: catalog entry count and availability. These commits do not establish a release boundary.
The preceding classification rule is preserved in the v1.9.0 catalog source. Acceptance checks compare parsed record counts with default lists, availability summaries, and catalog refresh results. They cover packaged SQL utilities and removed records, while keeping search and package counts specific to their returned rows.
Status
Accepted on 2026-10-16 and implemented with regression tests. The v1.10.0 candidate bundles 603 records and is not yet released. Use the current reference for the counting contract and release notes for version boundaries.