l3io-wordpress-backup 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. l3io_wordpress_backup-1.0.0/.gitignore +31 -0
  2. l3io_wordpress_backup-1.0.0/LICENSE +21 -0
  3. l3io_wordpress_backup-1.0.0/PKG-INFO +593 -0
  4. l3io_wordpress_backup-1.0.0/README.md +550 -0
  5. l3io_wordpress_backup-1.0.0/docs/adr/0001-ports-and-adapters-functional-core.md +32 -0
  6. l3io_wordpress_backup-1.0.0/docs/adr/0002-publish-wpconfigr-existing-name.md +35 -0
  7. l3io_wordpress_backup-1.0.0/docs/adr/0003-credential-transport-option-file.md +40 -0
  8. l3io_wordpress_backup-1.0.0/docs/adr/0004-pymysql-driver.md +34 -0
  9. l3io_wordpress_backup-1.0.0/docs/adr/0005-relocation-via-wp-cli.md +32 -0
  10. l3io_wordpress_backup-1.0.0/docs/adr/0006-archive-format-and-encryption.md +49 -0
  11. l3io_wordpress_backup-1.0.0/docs/adr/0007-packaging-uv-trusted-publishing.md +63 -0
  12. l3io_wordpress_backup-1.0.0/docs/adr/0008-boto3-optional-extra.md +32 -0
  13. l3io_wordpress_backup-1.0.0/docs/adr/0009-python-floor-3-11.md +34 -0
  14. l3io_wordpress_backup-1.0.0/docs/adr/0010-wpconfigr-regex-parser-scope.md +97 -0
  15. l3io_wordpress_backup-1.0.0/docs/adr/0011-observability-structlog-plus-api-only-tracing.md +70 -0
  16. l3io_wordpress_backup-1.0.0/docs/adr/0012-package-naming-l3io-wp-namespace.md +104 -0
  17. l3io_wordpress_backup-1.0.0/docs/adr/0013-ci-cd-local-replay-and-secrets.md +43 -0
  18. l3io_wordpress_backup-1.0.0/docs/adr/0014-age-encryption-via-database.md +160 -0
  19. l3io_wordpress_backup-1.0.0/docs/adr/0015-credentials-delegated-to-database.md +88 -0
  20. l3io_wordpress_backup-1.0.0/docs/adr/0016-run-context-binds-adapters-once.md +207 -0
  21. l3io_wordpress_backup-1.0.0/docs/adr/0017-manifest-v1-member-index.md +247 -0
  22. l3io_wordpress_backup-1.0.0/docs/adr/0018-redaction-at-the-boundary.md +199 -0
  23. l3io_wordpress_backup-1.0.0/docs/adr/0019-backup-dry-run-scope.md +132 -0
  24. l3io_wordpress_backup-1.0.0/docs/adr/0020-restore-undo-derived-at-read.md +121 -0
  25. l3io_wordpress_backup-1.0.0/docs/adr/0021-run-journal-location-lifecycle-resolution.md +101 -0
  26. l3io_wordpress_backup-1.0.0/docs/adr/0022-restore-planned-from-probe-executor-runs-every-phase.md +163 -0
  27. l3io_wordpress_backup-1.0.0/docs/adr/0023-target-wp-config-kept-unless-overwrite.md +198 -0
  28. l3io_wordpress_backup-1.0.0/docs/adr/0024-one-encrypted-payload-passphrase-only.md +303 -0
  29. l3io_wordpress_backup-1.0.0/docs/adr/0025-api-composes-through-the-cli-drivers.md +43 -0
  30. l3io_wordpress_backup-1.0.0/docs/adr/0027-restore-into-an-environment-driven-database.md +195 -0
  31. l3io_wordpress_backup-1.0.0/docs/adr/0028-layered-configuration-pydantic-settings.md +101 -0
  32. l3io_wordpress_backup-1.0.0/docs/adr/README.md +41 -0
  33. l3io_wordpress_backup-1.0.0/docs/api.md +2038 -0
  34. l3io_wordpress_backup-1.0.0/docs/closeout-gate.yaml +42 -0
  35. l3io_wordpress_backup-1.0.0/docs/exclusions.md +83 -0
  36. l3io_wordpress_backup-1.0.0/docs/handoff/wordpress-backup.md +715 -0
  37. l3io_wordpress_backup-1.0.0/docs/logging.md +310 -0
  38. l3io_wordpress_backup-1.0.0/docs/restore.md +1120 -0
  39. l3io_wordpress_backup-1.0.0/pyproject.toml +412 -0
  40. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/__init__.py +22 -0
  41. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/_version.py +31 -0
  42. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/__init__.py +9 -0
  43. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/clock.py +22 -0
  44. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/config_writer.py +702 -0
  45. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/credential_providers.py +236 -0
  46. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/crypto.py +237 -0
  47. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/database.py +1555 -0
  48. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/database_load.py +1263 -0
  49. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/exclusions.py +83 -0
  50. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/journal.py +507 -0
  51. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/lock.py +372 -0
  52. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/noop.py +666 -0
  53. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/phase.py +218 -0
  54. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/preflight.py +869 -0
  55. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/redaction.py +237 -0
  56. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/restore_extension.py +341 -0
  57. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/restore_runner.py +1030 -0
  58. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/site_files.py +191 -0
  59. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/store.py +227 -0
  60. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/tar.py +1648 -0
  61. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/tracing.py +235 -0
  62. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/workspace.py +299 -0
  63. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/wpcli.py +584 -0
  64. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/adapters/wpcli_child.py +135 -0
  65. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/api/__init__.py +104 -0
  66. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/api/errors.py +344 -0
  67. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/api/operations.py +427 -0
  68. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/__init__.py +228 -0
  69. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/adapter_sets.py +255 -0
  70. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/backup.py +1118 -0
  71. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/context.py +382 -0
  72. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/credentials.py +310 -0
  73. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/doctor.py +171 -0
  74. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/exit.py +171 -0
  75. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/logging.py +147 -0
  76. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/output.py +111 -0
  77. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/parser.py +853 -0
  78. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/passphrase.py +301 -0
  79. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/preflight.py +227 -0
  80. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/privileges.py +327 -0
  81. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/restore.py +1571 -0
  82. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/restore_report.py +396 -0
  83. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/restore_request.py +560 -0
  84. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/scope.py +31 -0
  85. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/settings.py +524 -0
  86. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/cli/verify.py +286 -0
  87. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/__init__.py +9 -0
  88. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/archive_choice.py +156 -0
  89. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/backup.py +105 -0
  90. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/config_location.py +257 -0
  91. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/exclusions.py +189 -0
  92. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/relocation.py +437 -0
  93. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/restore.py +1246 -0
  94. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/restore_journal.py +541 -0
  95. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/restore_space.py +224 -0
  96. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/core/verify.py +198 -0
  97. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/__init__.py +9 -0
  98. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/archive_format.py +1072 -0
  99. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/backup_scope.py +21 -0
  100. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/config.py +27 -0
  101. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/credentials.py +125 -0
  102. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/database.py +113 -0
  103. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/errors.py +1434 -0
  104. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/exclusion.py +74 -0
  105. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/exit_codes.py +87 -0
  106. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/flag_map.py +246 -0
  107. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/journal.py +142 -0
  108. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/lock.py +61 -0
  109. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/preflight.py +126 -0
  110. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/restore.py +181 -0
  111. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/search_replace.py +59 -0
  112. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/secret.py +61 -0
  113. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/site.py +60 -0
  114. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/domain/storage_target.py +60 -0
  115. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/__init__.py +33 -0
  116. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/adapter_set.py +66 -0
  117. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/archive.py +136 -0
  118. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/clock.py +23 -0
  119. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/config.py +71 -0
  120. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/crypto.py +30 -0
  121. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/database.py +129 -0
  122. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/exclusion.py +33 -0
  123. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/journal.py +53 -0
  124. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/lock.py +48 -0
  125. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/preflight.py +23 -0
  126. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/restore.py +59 -0
  127. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/search_replace.py +40 -0
  128. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/site_files.py +95 -0
  129. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/ports/tracing.py +80 -0
  130. l3io_wordpress_backup-1.0.0/src/l3io/wp/backup/py.typed +0 -0
  131. l3io_wordpress_backup-1.0.0/src/wpbackup2/__init__.py +7 -0
  132. l3io_wordpress_backup-1.0.0/src/wpbackup2/__main__.py +8 -0
  133. l3io_wordpress_backup-1.0.0/tests/.gitignore +1 -0
  134. l3io_wordpress_backup-1.0.0/tests/__init__.py +0 -0
  135. l3io_wordpress_backup-1.0.0/tests/adapters/conftest.py +161 -0
  136. l3io_wordpress_backup-1.0.0/tests/adapters/test_age_interop.py +275 -0
  137. l3io_wordpress_backup-1.0.0/tests/adapters/test_backup_executor.py +736 -0
  138. l3io_wordpress_backup-1.0.0/tests/adapters/test_concurrent_runs_tracing.py +108 -0
  139. l3io_wordpress_backup-1.0.0/tests/adapters/test_config_writer.py +547 -0
  140. l3io_wordpress_backup-1.0.0/tests/adapters/test_crypto.py +480 -0
  141. l3io_wordpress_backup-1.0.0/tests/adapters/test_database_backup.py +257 -0
  142. l3io_wordpress_backup-1.0.0/tests/adapters/test_database_restore.py +606 -0
  143. l3io_wordpress_backup-1.0.0/tests/adapters/test_descriptor.py +256 -0
  144. l3io_wordpress_backup-1.0.0/tests/adapters/test_local_store.py +256 -0
  145. l3io_wordpress_backup-1.0.0/tests/adapters/test_locate_wp_config.py +109 -0
  146. l3io_wordpress_backup-1.0.0/tests/adapters/test_outer_gzip.py +169 -0
  147. l3io_wordpress_backup-1.0.0/tests/adapters/test_site_files.py +374 -0
  148. l3io_wordpress_backup-1.0.0/tests/adapters/test_site_lock.py +380 -0
  149. l3io_wordpress_backup-1.0.0/tests/adapters/test_tar_extended_header_bound.py +98 -0
  150. l3io_wordpress_backup-1.0.0/tests/adapters/test_tar_probe.py +891 -0
  151. l3io_wordpress_backup-1.0.0/tests/adapters/test_tar_traversal.py +227 -0
  152. l3io_wordpress_backup-1.0.0/tests/adapters/test_tar_writer.py +561 -0
  153. l3io_wordpress_backup-1.0.0/tests/adapters/test_trace_ids_processor.py +109 -0
  154. l3io_wordpress_backup-1.0.0/tests/adapters/test_tracing.py +576 -0
  155. l3io_wordpress_backup-1.0.0/tests/adapters/test_tracing_database_nesting.py +181 -0
  156. l3io_wordpress_backup-1.0.0/tests/adapters/test_v0_fixtures.py +316 -0
  157. l3io_wordpress_backup-1.0.0/tests/adapters/test_workspace.py +101 -0
  158. l3io_wordpress_backup-1.0.0/tests/adapters/test_wpcli.py +871 -0
  159. l3io_wordpress_backup-1.0.0/tests/archive_fixtures.py +29 -0
  160. l3io_wordpress_backup-1.0.0/tests/classes/__init__.py +0 -0
  161. l3io_wordpress_backup-1.0.0/tests/classes/test_wp_internal_backup.py +68 -0
  162. l3io_wordpress_backup-1.0.0/tests/classes/test_wp_internal_restore.py +95 -0
  163. l3io_wordpress_backup-1.0.0/tests/classes/test_wpbackup.py +76 -0
  164. l3io_wordpress_backup-1.0.0/tests/classes/test_wpsite.py +117 -0
  165. l3io_wordpress_backup-1.0.0/tests/conftest.py +596 -0
  166. l3io_wordpress_backup-1.0.0/tests/contracts/conftest.py +27 -0
  167. l3io_wordpress_backup-1.0.0/tests/contracts/test_act_dryrun_legs.py +140 -0
  168. l3io_wordpress_backup-1.0.0/tests/contracts/test_adapter_set_owner.py +124 -0
  169. l3io_wordpress_backup-1.0.0/tests/contracts/test_api_base_install.py +133 -0
  170. l3io_wordpress_backup-1.0.0/tests/contracts/test_api_docs.py +249 -0
  171. l3io_wordpress_backup-1.0.0/tests/contracts/test_api_exception_exports.py +177 -0
  172. l3io_wordpress_backup-1.0.0/tests/contracts/test_api_one_sequence.py +164 -0
  173. l3io_wordpress_backup-1.0.0/tests/contracts/test_api_parity.py +257 -0
  174. l3io_wordpress_backup-1.0.0/tests/contracts/test_api_typing.py +48 -0
  175. l3io_wordpress_backup-1.0.0/tests/contracts/test_archive_format_owner.py +191 -0
  176. l3io_wordpress_backup-1.0.0/tests/contracts/test_child_env_explicit.py +343 -0
  177. l3io_wordpress_backup-1.0.0/tests/contracts/test_cipher_built_only_by_factory.py +85 -0
  178. l3io_wordpress_backup-1.0.0/tests/contracts/test_cli_docs.py +147 -0
  179. l3io_wordpress_backup-1.0.0/tests/contracts/test_cli_surface.py +356 -0
  180. l3io_wordpress_backup-1.0.0/tests/contracts/test_closeout_gate.py +160 -0
  181. l3io_wordpress_backup-1.0.0/tests/contracts/test_config_name_defined_once.py +68 -0
  182. l3io_wordpress_backup-1.0.0/tests/contracts/test_credential_hygiene.py +985 -0
  183. l3io_wordpress_backup-1.0.0/tests/contracts/test_credential_sources_built_once.py +338 -0
  184. l3io_wordpress_backup-1.0.0/tests/contracts/test_db_tls_root_only.py +157 -0
  185. l3io_wordpress_backup-1.0.0/tests/contracts/test_default_exclusion_reach.py +108 -0
  186. l3io_wordpress_backup-1.0.0/tests/contracts/test_dependencies_from_pypi.py +179 -0
  187. l3io_wordpress_backup-1.0.0/tests/contracts/test_drivers_no_environ.py +95 -0
  188. l3io_wordpress_backup-1.0.0/tests/contracts/test_dry_run_gate.py +144 -0
  189. l3io_wordpress_backup-1.0.0/tests/contracts/test_exception_fields.py +196 -0
  190. l3io_wordpress_backup-1.0.0/tests/contracts/test_exception_hierarchy.py +148 -0
  191. l3io_wordpress_backup-1.0.0/tests/contracts/test_exception_pickle.py +83 -0
  192. l3io_wordpress_backup-1.0.0/tests/contracts/test_exception_raise_sites.py +115 -0
  193. l3io_wordpress_backup-1.0.0/tests/contracts/test_exclusions_documented.py +66 -0
  194. l3io_wordpress_backup-1.0.0/tests/contracts/test_exclusions_pure.py +109 -0
  195. l3io_wordpress_backup-1.0.0/tests/contracts/test_existence_by_admin.py +223 -0
  196. l3io_wordpress_backup-1.0.0/tests/contracts/test_exit_codes_family.py +37 -0
  197. l3io_wordpress_backup-1.0.0/tests/contracts/test_extraction.py +826 -0
  198. l3io_wordpress_backup-1.0.0/tests/contracts/test_family_required.py +324 -0
  199. l3io_wordpress_backup-1.0.0/tests/contracts/test_flag_map.py +233 -0
  200. l3io_wordpress_backup-1.0.0/tests/contracts/test_inner_manifest_pairing.py +195 -0
  201. l3io_wordpress_backup-1.0.0/tests/contracts/test_integration_marker_reach.py +403 -0
  202. l3io_wordpress_backup-1.0.0/tests/contracts/test_legacy_test_exclusion.py +63 -0
  203. l3io_wordpress_backup-1.0.0/tests/contracts/test_log_events_documented.py +172 -0
  204. l3io_wordpress_backup-1.0.0/tests/contracts/test_logging_configured_once.py +150 -0
  205. l3io_wordpress_backup-1.0.0/tests/contracts/test_namespace_coexistence.py +166 -0
  206. l3io_wordpress_backup-1.0.0/tests/contracts/test_namespace_layout.py +182 -0
  207. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_committed_age_key.py +85 -0
  208. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_database_import_in_cli.py +92 -0
  209. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_db_spelling.py +93 -0
  210. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_dependency_text_in_messages.py +133 -0
  211. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_direct_crypto.py +159 -0
  212. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_dry_run_outside_roots.py +153 -0
  213. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_flag_reads.py +158 -0
  214. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_load_replace.py +230 -0
  215. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_mysql_client.py +92 -0
  216. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_mysql_pwd.py +53 -0
  217. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_pymysql_import.py +78 -0
  218. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_secret_valued_flags.py +100 -0
  219. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_sql_ddl.py +155 -0
  220. l3io_wordpress_backup-1.0.0/tests/contracts/test_no_wpconfigr_name.py +44 -0
  221. l3io_wordpress_backup-1.0.0/tests/contracts/test_not_yet_implemented.py +370 -0
  222. l3io_wordpress_backup-1.0.0/tests/contracts/test_otel_dependencies.py +158 -0
  223. l3io_wordpress_backup-1.0.0/tests/contracts/test_port_split.py +265 -0
  224. l3io_wordpress_backup-1.0.0/tests/contracts/test_port_types.py +194 -0
  225. l3io_wordpress_backup-1.0.0/tests/contracts/test_preflight_construction.py +127 -0
  226. l3io_wordpress_backup-1.0.0/tests/contracts/test_private_directories.py +88 -0
  227. l3io_wordpress_backup-1.0.0/tests/contracts/test_public_surface.py +431 -0
  228. l3io_wordpress_backup-1.0.0/tests/contracts/test_release_gate.py +200 -0
  229. l3io_wordpress_backup-1.0.0/tests/contracts/test_repr_clean.py +207 -0
  230. l3io_wordpress_backup-1.0.0/tests/contracts/test_restore_docs.py +141 -0
  231. l3io_wordpress_backup-1.0.0/tests/contracts/test_restore_harness_typed.py +44 -0
  232. l3io_wordpress_backup-1.0.0/tests/contracts/test_run_terminated_propagates.py +158 -0
  233. l3io_wordpress_backup-1.0.0/tests/contracts/test_secret_reveal.py +132 -0
  234. l3io_wordpress_backup-1.0.0/tests/contracts/test_settings_spellings.py +249 -0
  235. l3io_wordpress_backup-1.0.0/tests/contracts/test_single_config_writer.py +116 -0
  236. l3io_wordpress_backup-1.0.0/tests/contracts/test_single_credential_chain.py +193 -0
  237. l3io_wordpress_backup-1.0.0/tests/contracts/test_single_passphrase_chain.py +57 -0
  238. l3io_wordpress_backup-1.0.0/tests/contracts/test_single_redaction.py +140 -0
  239. l3io_wordpress_backup-1.0.0/tests/contracts/test_streaming.py +789 -0
  240. l3io_wordpress_backup-1.0.0/tests/contracts/test_supported_formats.py +71 -0
  241. l3io_wordpress_backup-1.0.0/tests/contracts/test_tar_header_hook.py +160 -0
  242. l3io_wordpress_backup-1.0.0/tests/contracts/test_tool_docs.py +52 -0
  243. l3io_wordpress_backup-1.0.0/tests/contracts/test_tool_requirements_documented.py +299 -0
  244. l3io_wordpress_backup-1.0.0/tests/contracts/test_tracing_no_sdk.py +247 -0
  245. l3io_wordpress_backup-1.0.0/tests/contracts/test_tracing_redaction.py +139 -0
  246. l3io_wordpress_backup-1.0.0/tests/contracts/test_usage_arguments_vocabulary.py +95 -0
  247. l3io_wordpress_backup-1.0.0/tests/contracts/test_v0_fixtures_reproducible.py +132 -0
  248. l3io_wordpress_backup-1.0.0/tests/contracts/test_version_branching.py +170 -0
  249. l3io_wordpress_backup-1.0.0/tests/contracts/test_version_single_source.py +101 -0
  250. l3io_wordpress_backup-1.0.0/tests/contracts/test_workflow_invariants.py +719 -0
  251. l3io_wordpress_backup-1.0.0/tests/contracts/test_wp_cli_pin.py +58 -0
  252. l3io_wordpress_backup-1.0.0/tests/contracts/test_wp_config_never_excluded.py +125 -0
  253. l3io_wordpress_backup-1.0.0/tests/data/wp-config.php +31 -0
  254. l3io_wordpress_backup-1.0.0/tests/db_reach.py +285 -0
  255. l3io_wordpress_backup-1.0.0/tests/exception_scope.py +126 -0
  256. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/make_v0_fixtures.py +265 -0
  257. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/malformed/escape-absolute.tar.gz +0 -0
  258. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/malformed/escape-dotdot.tar.gz +0 -0
  259. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/malformed/hardlink.tar.gz +0 -0
  260. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/malformed/missing-wp-root.tar.gz +0 -0
  261. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/malformed/stray-manifest-late.tar.gz +0 -0
  262. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/malformed/truncated.tar.gz +0 -0
  263. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/released-0.2.21-all.provenance.json +38 -0
  264. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/released-0.2.21-all.tar.gz +0 -0
  265. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/symlink-escape.tar.gz +0 -0
  266. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/synthetic-all-gnu.tar.gz +0 -0
  267. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/synthetic-all-longname.tar.gz +0 -0
  268. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/synthetic-all-pax.tar.gz +0 -0
  269. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/synthetic-all-symlink.tar.gz +0 -0
  270. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/synthetic-files-only.tar.gz +0 -0
  271. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v0/synthetic-renamed.bak +0 -0
  272. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v1/encrypted-passphrase.passphrase.txt +4 -0
  273. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v1/encrypted-passphrase.tar.gz +0 -0
  274. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v1/generate.py +120 -0
  275. l3io_wordpress_backup-1.0.0/tests/fixtures/archives/v1/minimal.tar.gz +0 -0
  276. l3io_wordpress_backup-1.0.0/tests/integration/__init__.py +0 -0
  277. l3io_wordpress_backup-1.0.0/tests/integration/live_cli.py +246 -0
  278. l3io_wordpress_backup-1.0.0/tests/integration/live_restore.py +810 -0
  279. l3io_wordpress_backup-1.0.0/tests/integration/test_admin_tls.py +202 -0
  280. l3io_wordpress_backup-1.0.0/tests/integration/test_api_roundtrip.py +138 -0
  281. l3io_wordpress_backup-1.0.0/tests/integration/test_backup_end_to_end.py +171 -0
  282. l3io_wordpress_backup-1.0.0/tests/integration/test_backup_exclusions.py +121 -0
  283. l3io_wordpress_backup-1.0.0/tests/integration/test_backup_scope.py +120 -0
  284. l3io_wordpress_backup-1.0.0/tests/integration/test_cli_credential_sources.py +145 -0
  285. l3io_wordpress_backup-1.0.0/tests/integration/test_cli_encrypted_roundtrip.py +179 -0
  286. l3io_wordpress_backup-1.0.0/tests/integration/test_config_laid_down_by_role.py +180 -0
  287. l3io_wordpress_backup-1.0.0/tests/integration/test_config_outside_root.py +398 -0
  288. l3io_wordpress_backup-1.0.0/tests/integration/test_config_parent_resolve.py +143 -0
  289. l3io_wordpress_backup-1.0.0/tests/integration/test_credential_sources.py +105 -0
  290. l3io_wordpress_backup-1.0.0/tests/integration/test_cutover_mode.py +113 -0
  291. l3io_wordpress_backup-1.0.0/tests/integration/test_database_restore_report.py +312 -0
  292. l3io_wordpress_backup-1.0.0/tests/integration/test_direct_load.py +336 -0
  293. l3io_wordpress_backup-1.0.0/tests/integration/test_dry_run_backup.py +96 -0
  294. l3io_wordpress_backup-1.0.0/tests/integration/test_dry_run_restore.py +100 -0
  295. l3io_wordpress_backup-1.0.0/tests/integration/test_encrypted_backup.py +260 -0
  296. l3io_wordpress_backup-1.0.0/tests/integration/test_encrypted_restore_loads_dump.py +170 -0
  297. l3io_wordpress_backup-1.0.0/tests/integration/test_exit_codes_end_to_end.py +257 -0
  298. l3io_wordpress_backup-1.0.0/tests/integration/test_kill_mid_restore.py +422 -0
  299. l3io_wordpress_backup-1.0.0/tests/integration/test_preflight_refusal.py +156 -0
  300. l3io_wordpress_backup-1.0.0/tests/integration/test_relocation_config.py +574 -0
  301. l3io_wordpress_backup-1.0.0/tests/integration/test_restore_cli.py +292 -0
  302. l3io_wordpress_backup-1.0.0/tests/integration/test_restore_database_side_hygiene.py +165 -0
  303. l3io_wordpress_backup-1.0.0/tests/integration/test_restore_inroot_config.py +123 -0
  304. l3io_wordpress_backup-1.0.0/tests/integration/test_restore_modes.py +92 -0
  305. l3io_wordpress_backup-1.0.0/tests/integration/test_restore_refusal.py +240 -0
  306. l3io_wordpress_backup-1.0.0/tests/integration/test_restore_skipped_symlinks.py +160 -0
  307. l3io_wordpress_backup-1.0.0/tests/integration/test_root_backup.py +215 -0
  308. l3io_wordpress_backup-1.0.0/tests/integration/test_root_restore.py +190 -0
  309. l3io_wordpress_backup-1.0.0/tests/integration/test_routines_only_target.py +109 -0
  310. l3io_wordpress_backup-1.0.0/tests/integration/test_run_correlation.py +94 -0
  311. l3io_wordpress_backup-1.0.0/tests/integration/test_search_replace.py +385 -0
  312. l3io_wordpress_backup-1.0.0/tests/integration/test_site_lock_signals.py +200 -0
  313. l3io_wordpress_backup-1.0.0/tests/integration/test_snapshot_joins_run.py +253 -0
  314. l3io_wordpress_backup-1.0.0/tests/integration/test_snapshot_reversible.py +133 -0
  315. l3io_wordpress_backup-1.0.0/tests/integration/test_tree_laid_config_mode.py +140 -0
  316. l3io_wordpress_backup-1.0.0/tests/integration/test_v0_restore_empty_target.py +181 -0
  317. l3io_wordpress_backup-1.0.0/tests/integration/test_verify_cli.py +112 -0
  318. l3io_wordpress_backup-1.0.0/tests/integration/test_wp_child_path.py +83 -0
  319. l3io_wordpress_backup-1.0.0/tests/integration/test_wp_cli_missing.py +335 -0
  320. l3io_wordpress_backup-1.0.0/tests/load_fakes.py +654 -0
  321. l3io_wordpress_backup-1.0.0/tests/otel_sdk.py +128 -0
  322. l3io_wordpress_backup-1.0.0/tests/port_roles.py +94 -0
  323. l3io_wordpress_backup-1.0.0/tests/qualified_names.py +83 -0
  324. l3io_wordpress_backup-1.0.0/tests/restore_builders.py +188 -0
  325. l3io_wordpress_backup-1.0.0/tests/restore_harness.py +749 -0
  326. l3io_wordpress_backup-1.0.0/tests/restore_root_rig.py +306 -0
  327. l3io_wordpress_backup-1.0.0/tests/root_mode.py +214 -0
  328. l3io_wordpress_backup-1.0.0/tests/sidecar/Dockerfile +102 -0
  329. l3io_wordpress_backup-1.0.0/tests/sidecar/compose.yaml +112 -0
  330. l3io_wordpress_backup-1.0.0/tests/sidecar/run.sh +463 -0
  331. l3io_wordpress_backup-1.0.0/tests/site_lock_holder.py +85 -0
  332. l3io_wordpress_backup-1.0.0/tests/test_settings.sample.json +6 -0
  333. l3io_wordpress_backup-1.0.0/tests/typing/api_consumer.py +55 -0
  334. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_config_carry.py +366 -0
  335. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_config_environment.py +402 -0
  336. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_credential_providers.py +1051 -0
  337. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_database_load.py +1145 -0
  338. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_database_tls.py +301 -0
  339. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_exclusions_logging.py +104 -0
  340. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_manifest_cap.py +185 -0
  341. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_noop_restore.py +379 -0
  342. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_phase_join.py +114 -0
  343. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_preflight.py +766 -0
  344. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_redaction.py +234 -0
  345. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_restore_runner.py +1648 -0
  346. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_run_journal.py +297 -0
  347. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_state_directory.py +183 -0
  348. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_tar.py +468 -0
  349. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_wpcli_config_placement.py +476 -0
  350. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_wpcli_dumpable.py +82 -0
  351. l3io_wordpress_backup-1.0.0/tests/unit/adapters/test_wpcli_extension.py +482 -0
  352. l3io_wordpress_backup-1.0.0/tests/unit/api/conftest.py +36 -0
  353. l3io_wordpress_backup-1.0.0/tests/unit/api/test_boundary.py +311 -0
  354. l3io_wordpress_backup-1.0.0/tests/unit/api/test_descriptor_construction.py +51 -0
  355. l3io_wordpress_backup-1.0.0/tests/unit/api/test_exit_code_attribute.py +55 -0
  356. l3io_wordpress_backup-1.0.0/tests/unit/api/test_no_credential_in_errors.py +385 -0
  357. l3io_wordpress_backup-1.0.0/tests/unit/api/test_on_warning.py +400 -0
  358. l3io_wordpress_backup-1.0.0/tests/unit/api/test_operations.py +981 -0
  359. l3io_wordpress_backup-1.0.0/tests/unit/api/test_process_state.py +252 -0
  360. l3io_wordpress_backup-1.0.0/tests/unit/cli/conftest.py +124 -0
  361. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_adapter_sets.py +475 -0
  362. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_archive_not_collected.py +162 -0
  363. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_backup_dry_run_effects.py +495 -0
  364. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_backup_entry.py +128 -0
  365. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_backup_flags.py +239 -0
  366. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_binary_preflight_refusal.py +173 -0
  367. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_closeout_notices.py +194 -0
  368. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_closeout_residue.py +142 -0
  369. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_closeout_safety.py +310 -0
  370. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_credentials.py +258 -0
  371. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_db_tls_cli.py +143 -0
  372. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_doctor.py +204 -0
  373. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_drivers.py +258 -0
  374. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_dry_run_exclusions.py +97 -0
  375. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_environment_driven_site.py +275 -0
  376. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_exit_mapping.py +368 -0
  377. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_help_style.py +111 -0
  378. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_lock_joining.py +202 -0
  379. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_lock_order.py +203 -0
  380. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_log_schema.py +157 -0
  381. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_logging.py +183 -0
  382. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_new_db_password.py +344 -0
  383. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_output.py +109 -0
  384. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_passphrase_chain.py +420 -0
  385. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_phase_names_upper.py +93 -0
  386. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_preflight_failed.py +248 -0
  387. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_preflight_order.py +209 -0
  388. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_privileges.py +323 -0
  389. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_publish_gate.py +44 -0
  390. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_render_failure.py +193 -0
  391. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_render_flags.py +81 -0
  392. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_restore_admin_as_root.py +99 -0
  393. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_restore_config_parent.py +253 -0
  394. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_restore_dry_run_links.py +143 -0
  395. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_restore_extension.py +203 -0
  396. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_restore_flags.py +135 -0
  397. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_restore_latest.py +174 -0
  398. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_restore_root.py +1293 -0
  399. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_restore_symlink_notice.py +26 -0
  400. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_root_backup.py +301 -0
  401. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_root_preflight.py +205 -0
  402. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_run_context.py +196 -0
  403. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_scope_mapping.py +93 -0
  404. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_search_replace_report.py +58 -0
  405. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_secret_registry_order.py +130 -0
  406. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_settings.py +312 -0
  407. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_snapshot_join.py +105 -0
  408. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_tracing_hooks.py +288 -0
  409. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_verify_authenticate.py +497 -0
  410. l3io_wordpress_backup-1.0.0/tests/unit/cli/test_verify_platform.py +60 -0
  411. l3io_wordpress_backup-1.0.0/tests/unit/core/test_archive_choice.py +163 -0
  412. l3io_wordpress_backup-1.0.0/tests/unit/core/test_backup_plan.py +169 -0
  413. l3io_wordpress_backup-1.0.0/tests/unit/core/test_config_location.py +268 -0
  414. l3io_wordpress_backup-1.0.0/tests/unit/core/test_database_report.py +219 -0
  415. l3io_wordpress_backup-1.0.0/tests/unit/core/test_exclusions.py +242 -0
  416. l3io_wordpress_backup-1.0.0/tests/unit/core/test_expansion.py +177 -0
  417. l3io_wordpress_backup-1.0.0/tests/unit/core/test_orderly_failure.py +139 -0
  418. l3io_wordpress_backup-1.0.0/tests/unit/core/test_plan_equality.py +82 -0
  419. l3io_wordpress_backup-1.0.0/tests/unit/core/test_relocation.py +492 -0
  420. l3io_wordpress_backup-1.0.0/tests/unit/core/test_restore_journal.py +384 -0
  421. l3io_wordpress_backup-1.0.0/tests/unit/core/test_restore_plan.py +317 -0
  422. l3io_wordpress_backup-1.0.0/tests/unit/core/test_restore_plan_equality.py +105 -0
  423. l3io_wordpress_backup-1.0.0/tests/unit/core/test_snapshot_location.py +56 -0
  424. l3io_wordpress_backup-1.0.0/tests/unit/core/test_snapshot_scope.py +135 -0
  425. l3io_wordpress_backup-1.0.0/tests/unit/core/test_target_guard.py +408 -0
  426. l3io_wordpress_backup-1.0.0/tests/unit/core/test_v0_config_location.py +102 -0
  427. l3io_wordpress_backup-1.0.0/tests/unit/core/test_verify.py +238 -0
  428. l3io_wordpress_backup-1.0.0/tests/unit/core/test_wp_cli_preflight_order.py +344 -0
  429. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_archive_format.py +481 -0
  430. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_backup_scope.py +13 -0
  431. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_classifier.py +219 -0
  432. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_created_at.py +83 -0
  433. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_credential_precedence.py +45 -0
  434. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_exit_codes.py +48 -0
  435. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_flag_table.py +231 -0
  436. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_journal_types.py +133 -0
  437. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_manifest_no_key_material.py +154 -0
  438. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_restore_vocabulary.py +269 -0
  439. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_secret.py +12 -0
  440. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_site.py +68 -0
  441. l3io_wordpress_backup-1.0.0/tests/unit/domain/test_storage_target.py +59 -0
  442. l3io_wordpress_backup-1.0.0/tools/check_base_install.py +156 -0
  443. l3io_wordpress_backup-1.0.0/tools/check_mode_defaults.py +234 -0
  444. l3io_wordpress_backup-1.0.0/tools/check_purity.py +319 -0
  445. l3io_wordpress_backup-1.0.0/tools/gen_cli_docs.py +141 -0
  446. l3io_wordpress_backup-1.0.0/tools/gen_tool_docs.py +161 -0
@@ -0,0 +1,31 @@
1
+ __pycache__
2
+ .coverage
3
+ .DS_Store
4
+ .vscode
5
+ build
6
+ dist
7
+ htmlcov
8
+ *.egg-info
9
+
10
+ # BMad/l3io tool output, regenerated by the installer. Both sibling repos ignore
11
+ # and untrack these; this repo had six committed, so a tool refresh showed up as
12
+ # repo changes. Tool output does not belong in the release or in review.
13
+ .github/agents/
14
+
15
+ # act's real configuration, generated from the .example files by `make act-init`.
16
+ # The .example versions are tracked; these are not. .act.secrets is the one that
17
+ # holds credentials once populated.
18
+ #
19
+ # These entries were present as an UNCOMMITTED .gitignore change and were lost to
20
+ # a `git reset --hard` during this session -- so for a while the file meant to
21
+ # hold act's secrets was not ignored at all. Nothing leaked (all three have zero
22
+ # commits in any branch, and .act.secrets was empty), but the protection was
23
+ # absent and a later commit touched .gitignore without noticing the gap.
24
+ .act.env
25
+ .act.secrets
26
+ .act.vars
27
+
28
+ # Local agent tool state: Codex's directory and Claude Code's per-agent git
29
+ # worktrees. Neither belongs in the repository (BL-E007-012).
30
+ .codex
31
+ .claude/worktrees/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Cariad Eccleston
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,593 @@
1
+ Metadata-Version: 2.5
2
+ Name: l3io-wordpress-backup
3
+ Version: 1.0.0
4
+ Summary: WordPress backup, restore and relocation for automation
5
+ Project-URL: Homepage, https://github.com/LiquidLogicLabs/l3io-wordpress-backup
6
+ Project-URL: Source, https://github.com/LiquidLogicLabs/l3io-wordpress-backup
7
+ Project-URL: Issues, https://github.com/LiquidLogicLabs/l3io-wordpress-backup/issues
8
+ Author-email: Shawn Anderson <ravensorb@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: backup,migration,mysqldump,restore,wordpress,wp-cli
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: System :: Archiving :: Backup
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.11
27
+ Requires-Dist: l3io-age-passphrase<0.2,>=0.1.1
28
+ Requires-Dist: l3io-wordpress-config<0.2,>=0.1.7
29
+ Requires-Dist: l3io-wordpress-database[encryption]<0.4,>=0.3.5
30
+ Requires-Dist: opentelemetry-api<2,>=1.40
31
+ Requires-Dist: packaging<27,>=25
32
+ Requires-Dist: pathspec<2,>=1.1
33
+ Requires-Dist: platformdirs<5,>=4.3
34
+ Requires-Dist: pydantic-settings<3,>=2.16
35
+ Requires-Dist: structlog<27,>=26.1
36
+ Provides-Extra: aws
37
+ Requires-Dist: l3io-wordpress-database[aws]<0.4,>=0.3.5; extra == 'aws'
38
+ Provides-Extra: database
39
+ Provides-Extra: otel
40
+ Requires-Dist: opentelemetry-exporter-otlp-json-file>=0.65b0; extra == 'otel'
41
+ Requires-Dist: opentelemetry-sdk<2,>=1.40; extra == 'otel'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # py-wordpress-backup
45
+
46
+ A Python package for backing up and restoring a self-hosted WordPress installation, including the database and uploaded content.
47
+
48
+ The backup file will contain:
49
+
50
+ - Your database as a MySQL dump.
51
+ - An exact copy of your entire WordPress directory.
52
+
53
+ The restore process will:
54
+ - Extract the files into the correct location
55
+ - Update ``wp-config.php`` with new settings (if needed)
56
+ - Create the database if it doesn't exist
57
+ - Add the user and grant it access if it doesn't exist
58
+ - Restore the database
59
+
60
+ ## Credit
61
+ Credit for the original version of this goes to [Cariad Eccleston](https://github.com/cariad/py-wordpress-database).
62
+
63
+ ## Installation
64
+
65
+ ```shell
66
+ pip install l3io-wordpress-backup
67
+ ```
68
+
69
+ That is everything a backup and a restore need: `l3io-wordpress-database`, `l3io-wordpress-config` and the `l3io-age-passphrase` binder are required dependencies, and every dependency installs from PyPI; no extra index is needed. The optional extras add:
70
+
71
+ - `aws` — AWS Secrets Manager credentials (boto3): `pip install 'l3io-wordpress-backup[aws]'`;
72
+ - `otel` — the OpenTelemetry SDK and a JSON-file exporter, for tracing.
73
+
74
+ `pip install 'l3io-wordpress-backup[database]'`, the install earlier releases documented, still works: `database` is now an empty extra that adds nothing. A feature used without its extra (`aws`) stops with exit 7 (`NOT_READY`) naming the extra to install. An installation missing a required dependency (installed with `--no-deps`, or pruned afterwards) stops with exit 7 too, saying the installation is incomplete; reinstall `l3io-wordpress-backup`.
75
+
76
+ Requirements: Python 3.11 or newer. Extracting an archive also needs a Python with the June 2025 `tarfile` security fixes (3.11.13+, 3.12.11+, 3.13.4+; CVE-2025-4517); on an older one extraction is refused — upgrade Python.
77
+
78
+ Upgrading from 0.2.21: that release can no longer be installed from PyPI, because two of its dependencies (`wpconfigr`, `chesney`) were removed from there. You do not need it: this package reads 0.2.21 Archives itself (`wpbackup2 verify --archive <file>` reports them as `STRUCTURAL_ONLY`, and `restore` restores them). The restore side is tested with Archives in 0.2.21's layout built by the test suite and by extracting the committed 0.2.21-layout fixtures; no test restores an Archive written by a 0.2.21 install.
79
+
80
+ ### External tools
81
+
82
+ A Run checks every external tool its plan needs before its first effect, and stops with exit 7 (`NOT_READY`) naming the tool, the step that needed it and what was found. The table lists the tools this package runs itself and the minimum a plan in this build asks for:
83
+
84
+ <!-- BEGIN GENERATED: tool-requirements (tools/gen_tool_docs.py) -->
85
+ | Tool | Minimum | Purpose | Needed for | Install |
86
+ |---|---|---|---|---|
87
+ | `age` | 1.3 | encrypts the Archive under a passphrase (CLI passphrase backend) | the encrypted payload (ENCRYPT, VERIFY) | https://github.com/FiloSottile/age#installation |
88
+ | `age-plugin-batchpass` | — | supplies the passphrase to age non-interactively (CLI backend) | the encrypted payload (ENCRYPT, VERIFY) | https://github.com/FiloSottile/age/releases |
89
+ | `wp` | 2.12.0 | WP-CLI, which relocation runs to rewrite the Site's URLs | SEARCH_REPLACE: serialization-safe content URL rewrite for --new-site-url/--new-site-home-url (FR-12) | https://wp-cli.org/#installing |
90
+ | MySQL/MariaDB client tools | client 8.4+, MariaDB 11.4+ | the database dump and load | checked per Site by l3io-wordpress-database (`check_dump`/`check_load`), never run by this package | your distribution's MySQL or MariaDB client package |
91
+ <!-- END GENERATED: tool-requirements (tools/gen_tool_docs.py) -->
92
+
93
+ The `l3io-age-passphrase` binder, a required dependency, is published as wheels for Linux (glibc and musl; x86_64, aarch64, armv7l), macOS (x86_64, arm64) and Windows (amd64). On any other platform the installer builds it from its source distribution, which needs a Rust toolchain; if that build fails, the package does not install. When the binder is not importable, the passphrase backend falls back to the `age` command line (`L3IO_AGE_PASSPHRASE_BACKEND=auto`, the default), so `age` 1.3 or newer and `age-plugin-batchpass` must be on `PATH`, or an encrypted Run stops with exit 7 (`NOT_READY`) before its first effect. `L3IO_AGE_PASSPHRASE_BACKEND=cli` selects that backend explicitly.
94
+
95
+ ## Usage
96
+
97
+ ### Quick start: unattended, with no flags
98
+
99
+ Every option is also an `L3IO_WP_BACKUP_<NAME>` variable (`--wp-dir` is
100
+ `L3IO_WP_BACKUP_WP_DIR`) and a key of a TOML config file (`wp_dir = "..."`),
101
+ so a scheduled job needs no flags. A flag wins over a variable, a variable over
102
+ the file, the file over the default. The file is `--config PATH` or
103
+ `L3IO_WP_BACKUP_CONFIG`, else `/etc/l3io-wp-backup/config.toml` and then
104
+ `~/.config/l3io-wp-backup/config.toml` when present; one holding a secret
105
+ (`passphrase`, `db_password`, `admin_password`, `new_db_password`) must be
106
+ readable by its owner only (`chmod 600`), or it is refused. ADR-0028 records
107
+ the design.
108
+
109
+ **As a Docker sidecar** next to the official `wordpress` image, sharing its
110
+ site volume and its `WORDPRESS_DB_*` environment:
111
+
112
+ ```yaml
113
+ services:
114
+ backup:
115
+ image: <your wpbackup2 image>
116
+ env_file: wordpress.env # the site's WORDPRESS_DB_*
117
+ environment:
118
+ L3IO_WP_BACKUP_WP_DIR: /var/www/html
119
+ L3IO_WP_BACKUP_ARCHIVE_DIR: /backups
120
+ volumes:
121
+ - html:/var/www/html
122
+ - backups:/backups
123
+ ```
124
+
125
+ then `docker compose exec backup wpbackup2 backup` on a schedule, and to
126
+ restore the newest backup of this site:
127
+
128
+ ```
129
+ docker compose exec backup wpbackup2 restore --archive latest
130
+ ```
131
+
132
+ Set `L3IO_WP_BACKUP_PASSPHRASE` (and add `--encrypt-with-passphrase`, or
133
+ `encrypt_with_passphrase = true` in the config file) to encrypt; a restore
134
+ then decrypts with the same passphrase. Started as root, a backup or a restore
135
+ runs as the owner of `/var/www/html` (www-data in the official image), after
136
+ reading the passphrase and credentials, so Archives and restored files belong
137
+ to it and both share its state directory; `/backups` must then be writable by
138
+ that user, or the backup is refused before it starts. `tests/sidecar/` is a
139
+ working example.
140
+
141
+ **From cron**, with the tool installed by `pip`:
142
+
143
+ ```toml
144
+ # ~/.config/l3io-wp-backup/config.toml (chmod 600)
145
+ wp_dir = "/var/www/example"
146
+ archive_dir = "/var/backups/example"
147
+ encrypt_with_passphrase = true
148
+ passphrase = "a long passphrase, 12 characters or more"
149
+ log_format = "console"
150
+ ```
151
+
152
+ ```
153
+ 15 3 * * * wpbackup2 backup
154
+ ```
155
+
156
+ Without `--archive` or `archive_dir`, the Archive goes into the current
157
+ directory, or into the per-user data directory
158
+ (`~/.local/share/l3io-wp-backup/archives`) when the current directory is
159
+ inside the site, never inside the Site Root. A target you put inside the Site
160
+ Root is written with a warning: the web server may serve it. A backup started
161
+ as root runs as the Site Root's owner: that user's data directory, and an
162
+ Archive directory that user can write (otherwise the backup is refused before
163
+ it starts, naming the directory).
164
+
165
+ ### By hand
166
+
167
+ To backup:
168
+
169
+ ```
170
+ wpbackup2 backup --wp-dir /www/wordpress --archive ~/backup.tar.gz
171
+ ```
172
+
173
+ To rehearse a backup first, add `--whatIf` (before or after `backup`). The Dry Run resolves the credentials, runs the preflight and asks the database whether it can be dumped, all for real; then it prints the ordered plan and writes nothing. Its last line says so. It does not detect what only writing finds: a full disk, a file that cannot be read, or an Archive larger than the Manifest cap. A Site Lock is taken as for a real Run, so a Dry Run waits for nobody and is refused (exit 9) while another Run of the same Site by the same OS user holds the lock.
174
+
175
+ **Output for cron mail.** stderr is the log stream. On a terminal it is human-readable; anywhere else (cron, a pipe, journald) it is strict JSON lines, one object per line, so a log collector can parse every line. Where a person reads the output instead — a cron job that mails it — pass `--log-format console` before the subcommand to get human-readable lines there too, failures and warnings included:
176
+
177
+ ```
178
+ wpbackup2 --log-format console backup --wp-dir /www/wordpress --archive ~/backup.tar.gz
179
+ ```
180
+
181
+ To check an archive without touching a site:
182
+
183
+ ```
184
+ wpbackup2 verify --archive ~/backup.tar.gz
185
+ ```
186
+
187
+ Without a key, `verify` proves the archive's checksums are **consistent** with its manifest, not that the archive is **authentic**: the manifest is unprotected, so anyone who can rewrite the archive can rewrite its checksums too. It says so (`authenticity not checked (no key)`). For an encrypted archive, `--authenticate` decrypts the whole payload with its passphrase — taken from `L3IO_WP_BACKUP_PASSPHRASE` or a terminal prompt, never from the command line — and `age` authenticates every chunk. A wrong passphrase or a tampered payload exits 8; a plain archive is refused, since there is nothing to authenticate. **Encryption protects file contents only.** The manifest stays readable in clear in an encrypted archive: the site path, the database name, every file path and size, and the exclusions are visible to anyone who can read the archive, without the passphrase. To refuse a plain archive where an encrypted one is expected (for example one substituted by someone who can write the storage target), restore with `--require-encrypted`; without it a plain archive is restored with a warning, since plain archives are not authenticated.
188
+
189
+ **Memory when reading an archive.** `verify` and `restore` read the whole manifest into memory and parse it before anything is authenticated, taking about 4.3 times its size. A manifest is capped at 256 MiB, so the worst case is about 1.1 GB; an ordinary site's manifest is a few MB. A streaming manifest reader is planned after v1.
190
+
191
+ To restore (see [docs/restore.md](docs/restore.md) for every mode, the refusals and their exit codes):
192
+
193
+ ```
194
+ wpbackup2 restore --wp-dir /www/wordpress --archive ~/backup.tar.gz --mode all
195
+ ```
196
+
197
+ A restore takes a Safety Snapshot of whatever it would change first. A database restore then loads the Dump straight into the live database, after dropping every table that carries the site's table prefix (tables without it are never touched), and reloads the Safety Snapshot automatically if the load fails; see [docs/restore.md](docs/restore.md#the-database-loaded-straight-into-the-live-database). Over a Site Root that is not empty and does not look like WordPress (none of `wp-config.php`, `wp-includes/`, `wp-content/`), or a database with tables none of which carries the site's prefix, it refuses unless `--force` is given (exit 3, naming the flag). `--archive latest` restores the newest Archive of this site in `--archive-dir` (see [docs/restore.md](docs/restore.md#--archive-latest)). A target with no `wp-config.php` takes its database from an encrypted archive's config as it says, and from an unencrypted one only when you confirm it with `--archive-database USER@HOST[:PORT]/NAME`. Database connections require no TLS unless you ask with `--db-tls {required,verify-ca,verify-identity}` (and `--db-tls-ca FILE`). Started as root, a restore (like a backup) runs as the Site Root's owner. The library (`api.backup`, `api.restore`) refuses to run as root: it never switches its host's user.
198
+
199
+ `wp-config.php` is read for your database credentials. It is looked for in the WordPress directory, then its parent; pass `--wp-config PATH` to use another.
200
+
201
+ ### Credentials
202
+
203
+ No credential has to be on the command line, so a cron line needs none. Each account's credentials come from the first source that has them, in a fixed order:
204
+
205
+ | Account | 1. Explicit | 2. Environment | 3. `wp-config.php` |
206
+ |---|---|---|---|
207
+ | WordPress database (backup) | `--db-user`/`--db-password`, their config-file keys, or library `BackupRequest.credentials` | `L3IO_WP_BACKUP_DB_USER` and `L3IO_WP_BACKUP_DB_PASSWORD` | `DB_USER`/`DB_PASSWORD` |
208
+ | WordPress database (restore) | none | none | `DB_USER`/`DB_PASSWORD`, the account the restored site will use |
209
+ | Administrator (restore) | `--admin-user` (or `--admin-username`)/`--admin-password`, their config-file keys, or library `RestoreRequest.admin` | `L3IO_WP_BACKUP_ADMIN_USER` and `L3IO_WP_BACKUP_ADMIN_PASSWORD` | never |
210
+
211
+ - **Both variables of a pair, or neither.** Setting one without its partner is an error (exit 4), not a fall-through to `wp-config.php`. A variable set to an empty value counts as set. The one exception is `--admin-username` without `--admin-password`: the password then comes from `L3IO_WP_BACKUP_ADMIN_PASSWORD`, and an empty value there counts as unset. The administrator pair is read only by a restore that needs an administrator, so `--mode files` never asks for one, and neither does a database restore whose WordPress account can load its own database (owner decision 55): an administrator is needed only when the database does not exist yet or the account cannot log in or lacks a right on it.
212
+ - **A source that fails stops the search.** An unreadable AWS secret, for example, never becomes the next source's account. The administrator is never taken from `wp-config.php` or from the WordPress account. A restore that needs one and has none is refused before anything changes (exit 4), naming the database, the account and what is missing.
213
+ - **AWS Secrets Manager.** A secret holding the JSON `{"username": ..., "password": ...}` can supply either account as its explicit source. Install the `aws` extra: `pip install 'l3io-wordpress-backup[aws]'`. Without it, exit 7 names the extra. Without a region from the request or the AWS session, exit 4 names the region flag. In this release only the library selects it: `AwsSecretCredentialSpec(secret_id, region)` as `BackupRequest.credentials` or `RestoreRequest.admin`. The `--db-credentials-aws-*` and `--admin-credentials-aws-*` flags are accepted but not implemented yet.
214
+ - **`getenv_docker()` and `getenv()` in `wp-config.php`.** The official `wordpress` Docker image defines `DB_HOST`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` and `$table_prefix` as `getenv_docker('WORDPRESS_DB_HOST', 'mysql')` and so on. Backup, `doctor` and restore resolve them as PHP would, from their own environment: the variable, a Docker secret named by `<NAME>_FILE`, else the default. Run the tool with the site's `WORDPRESS_DB_*` (a sidecar container can share the WordPress container's environment file); the resolved host is logged (`database_host`) and named in the Dry Run. Any other PHP expression in those settings is refused (exit 3) without being evaluated. A database restore loads straight into the database the environment names and leaves `wp-config.php` as it is (owner decision 49); only a relocation flag (`--new-db-*`) that would rewrite such a constant is refused (exit 3). `--overwrite-wp-config` keeps those definitions as written, and is refused (exit 3) when the Archive's `wp-config.php` does not define `getenv_docker()`: see [docs/restore.md](docs/restore.md#wp-configphp).
215
+ - **The log names the source, never the value.** Each resolution logs `credential_source_selected` with the source's label, for example `environment:L3IO_WP_BACKUP_DB_USER,L3IO_WP_BACKUP_DB_PASSWORD`.
216
+
217
+ The environment of a process can be read by the same user and by root (`/proc/<pid>/environ`), and a secret flag (`--passphrase`, `--db-password`, `--admin-password`, `--new-db-password`) is visible to every user in `ps` and logs WARNING `password_on_command_line`. On a shared host, prefer `wp-config.php`, a `chmod 600` config file or AWS Secrets Manager. A database password, or a passphrase that decrypts (`verify --authenticate`, `restore`), shorter than 4 characters is accepted with WARNING `credential_below_redaction_floor`: it is too short to be masked in logs. A passphrase that encrypts needs 12 characters or more.
218
+
219
+ ### Command-line reference
220
+
221
+ Generated from the parser by `tools/gen_cli_docs.py`; CI fails if it drifts.
222
+
223
+ <!-- BEGIN GENERATED: cli-usage (tools/gen_cli_docs.py) -->
224
+ ```text
225
+ $ wpbackup2 --help
226
+ usage: wpbackup2 [-h] [--version] [--config PATH]
227
+ [--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [--log-format {console,json}]
228
+ [--whatIf]
229
+ {backup,restore,verify,doctor} ...
230
+
231
+ Backup/restore your WordPress content and database.
232
+
233
+ Every option can also be set as an L3IO_WP_BACKUP_<NAME> variable
234
+ (--wp-dir is L3IO_WP_BACKUP_WP_DIR) or as a key in a TOML config file
235
+ (wp_dir = "..."); a flag wins over a variable, a variable over the file.
236
+
237
+ positional arguments:
238
+ {backup,restore,verify,doctor}
239
+ Action being requested
240
+ backup Perform a WordPress backup
241
+ restore Perform a WordPress restore
242
+ verify Check an archive without touching a site
243
+ doctor Report whether this host is ready to back up and restore
244
+
245
+ options:
246
+ -h, --help Show this help message and exit
247
+ --version Show the program's version number and exit
248
+ --config PATH Read settings from this TOML file (also L3IO_WP_BACKUP_CONFIG; default:
249
+ /etc/l3io-wp-backup/config.toml, then the per-user ~/.config/l3io-wp-
250
+ backup/config.toml). A file holding a secret must be readable by its owner
251
+ only
252
+ --log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}
253
+ Logging level (stdlib level names; default INFO)
254
+ --log-format {console,json}
255
+ Log format on stderr (default: console on a terminal, else json)
256
+ --whatIf, --what-if Dry Run: resolve credentials, run preflight and every query for real, and
257
+ print the ordered plan; changes no Site, database or archive, but takes
258
+ the Site Lock (exit 9 LOCKED while another Run holds it)
259
+
260
+ exit codes:
261
+ 0 OK the Run succeeded
262
+ 1 FAILED the Run failed for a reason no other code names
263
+ 2 USAGE the command line was invalid
264
+ 3 REFUSED a check found a reason not to act; nothing was done
265
+ 4 CREDENTIALS credentials were missing, unusable or rejected
266
+ 5 UNREACHABLE the database server could not be reached
267
+ 7 NOT_READY a required tool or optional extra is not available
268
+ 8 VERIFICATION_FAILED the archive failed verification
269
+ 9 LOCKED another Run holds the site lock
270
+ ```
271
+
272
+ ```text
273
+ $ wpbackup2 backup --help
274
+ usage: wpbackup2 backup [-h] [--config PATH] [--wp-dir WP_DIR] [--archive-dir DIR] [--whatIf]
275
+ [--archive ARCHIVE] [--wp-config PATH] [--mode {files,db,all}]
276
+ [--encrypt-with-passphrase] [--passphrase SECRET] [--exclude PATTERN]
277
+ [--no-default-excludes] [--list-default-excludes]
278
+ [--db-credentials-aws-secret-id ID] [--db-credentials-aws-region REGION]
279
+ [--db-user USER] [--db-password SECRET]
280
+ [--db-tls {required,verify-ca,verify-identity}] [--db-tls-ca FILE]
281
+
282
+ Perform a WordPress backup
283
+
284
+ Every option can also be set as an L3IO_WP_BACKUP_<NAME> variable
285
+ (--wp-dir is L3IO_WP_BACKUP_WP_DIR) or as a key in a TOML config file
286
+ (wp_dir = "..."); a flag wins over a variable, a variable over the file.
287
+
288
+ options:
289
+ -h, --help Show this help message and exit
290
+ --config PATH Read settings from this TOML file (also L3IO_WP_BACKUP_CONFIG; default:
291
+ /etc/l3io-wp-backup/config.toml, then the per-user ~/.config/l3io-wp-
292
+ backup/config.toml). A file holding a secret must be readable by its owner
293
+ only
294
+ --wp-dir WP_DIR Path to the root of the WordPress directory (required, except with --list-
295
+ default-excludes)
296
+ --archive-dir DIR Directory the archive is written to when --archive is not given (default:
297
+ the current directory, or the per-user data directory when the current
298
+ directory is inside the Site Root)
299
+ --whatIf, --what-if Dry Run: run every check and query for real and print the ordered plan;
300
+ writes no archive but takes the Site Lock (also accepted before `backup`)
301
+ --archive ARCHIVE Archive path or file: URL (default: wpbackup2-<db>-<UTC timestamp>.tar.gz
302
+ in --archive-dir). Inside the Site Root it is written with a warning: the
303
+ web server may serve it
304
+ --wp-config PATH Use this wp-config.php instead of searching for it
305
+ --mode {files,db,all}
306
+ What to back up: files, db, or all (default: all)
307
+ --encrypt-with-passphrase
308
+ Encrypt the archive with a passphrase (--passphrase,
309
+ L3IO_WP_BACKUP_PASSPHRASE, the config file or a prompt)
310
+ --passphrase SECRET Passphrase that encrypts the archive (with --encrypt-with-passphrase). The
311
+ value is visible in the process list; prefer L3IO_WP_BACKUP_PASSPHRASE or
312
+ the config file (chmod 600)
313
+ --exclude PATTERN Leave matching paths out of the archive (repeatable)
314
+ --no-default-excludes
315
+ Do not apply the default exclusion set
316
+ --list-default-excludes
317
+ Print the default exclusion set and exit without backing up (needs no
318
+ --wp-dir; takes no other backup flag)
319
+
320
+ database credentials:
321
+ --db-credentials-aws-secret-id ID
322
+ Read the WordPress database account from this AWS Secrets Manager secret
323
+ --db-credentials-aws-region REGION
324
+ AWS region of --db-credentials-aws-secret-id (needs the secret id)
325
+ --db-user USER WordPress database account to dump as, instead of wp-config.php's (give
326
+ --db-password with it)
327
+ --db-password SECRET Password of --db-user. The value is visible in the process list; prefer
328
+ L3IO_WP_BACKUP_DB_PASSWORD or the config file (chmod 600)
329
+ --db-tls {required,verify-ca,verify-identity}
330
+ Require TLS on every database connection, at this level (default: no
331
+ requirement, as WordPress connects)
332
+ --db-tls-ca FILE CA bundle that verifies the database server, with --db-tls verify-ca or
333
+ verify-identity (default: the system store)
334
+
335
+ exit codes:
336
+ 0 OK the Run succeeded
337
+ 1 FAILED the Run failed for a reason no other code names
338
+ 2 USAGE the command line was invalid
339
+ 3 REFUSED a check found a reason not to act; nothing was done
340
+ 4 CREDENTIALS credentials were missing, unusable or rejected
341
+ 5 UNREACHABLE the database server could not be reached
342
+ 7 NOT_READY a required tool or optional extra is not available
343
+ 8 VERIFICATION_FAILED the archive failed verification
344
+ 9 LOCKED another Run holds the site lock
345
+ ```
346
+
347
+ ```text
348
+ $ wpbackup2 restore --help
349
+ usage: wpbackup2 restore [-h] [--config PATH] --wp-dir WP_DIR --archive ARCHIVE
350
+ [--archive-dir DIR] [--mode {files,db,all}] [--cleanfirst]
351
+ [--drop-database] [--force] [--whatIf] [--wp-config PATH]
352
+ [--overwrite-wp-config] [--reset-existing-db-user] [--no-safety-snapshot]
353
+ [--require-encrypted] [--passphrase SECRET] [--snapshot-dir PATH]
354
+ [--archive-database USER@HOST[:PORT]/NAME]
355
+ [--db-tls {required,verify-ca,verify-identity}] [--db-tls-ca FILE]
356
+ [--admin-tls {required,verify-ca,verify-identity}] [--admin-tls-ca FILE]
357
+ [--resolve-interrupted-run RUN_ID] [--admin-user USER]
358
+ [--admin-password SECRET] [--admin-credentials-aws-secret-id ID]
359
+ [--admin-credentials-aws-region REGION] [--only-appointed-in-asg]
360
+ [--new-site-url URL] [--new-site-home-url URL] [--new-db-host HOST]
361
+ [--new-db-name NAME] [--new-db-user USER] [--new-db-password SECRET]
362
+ [--new-db-password-file PATH] [--allow-unencrypted]
363
+ [--db-credentials-aws-secret-id ID] [--db-credentials-aws-region REGION]
364
+
365
+ Perform a WordPress restore
366
+
367
+ Every option can also be set as an L3IO_WP_BACKUP_<NAME> variable
368
+ (--wp-dir is L3IO_WP_BACKUP_WP_DIR) or as a key in a TOML config file
369
+ (wp_dir = "..."); a flag wins over a variable, a variable over the file.
370
+
371
+ options:
372
+ -h, --help Show this help message and exit
373
+ --config PATH Read settings from this TOML file (also L3IO_WP_BACKUP_CONFIG; default:
374
+ /etc/l3io-wp-backup/config.toml, then the per-user ~/.config/l3io-wp-
375
+ backup/config.toml). A file holding a secret must be readable by its owner
376
+ only
377
+ --wp-dir WP_DIR Path to the root of the WordPress directory
378
+ --archive ARCHIVE Path or file: URL of the archive to restore from, or `latest`: the newest
379
+ archive of this site in --archive-dir (required, --resolve-interrupted-run
380
+ included)
381
+ --archive-dir DIR Directory --archive latest searches (default: the current directory, or
382
+ the per-user data directory when the current directory is inside the Site
383
+ Root)
384
+
385
+ restore mode:
386
+ Together these select one Restore Mode; any other combination is refused.
387
+
388
+ --mode {files,db,all}
389
+ What to restore: files, db, or all (default: all)
390
+ --cleanfirst Clear the Site Root before restoring the files. The database is replaced
391
+ by every restore that loads it
392
+ --drop-database Deprecated; has no effect. A database restore loads into the live database
393
+ and drops the tables with the site's prefix first, so no previous database
394
+ is left to remove
395
+ --force Restore over a non-empty Site Root that does not look like WordPress (none
396
+ of wp-config.php, wp-includes/, wp-content/), or a non-empty database none
397
+ of whose tables has the site's table prefix. It selects no mode and
398
+ removes nothing
399
+
400
+ restore options:
401
+ The flags that do not select the Restore Mode: Dry Run, wp-config.php, the Safety Snapshot, the database administrator and relocation
402
+
403
+ --whatIf, --what-if Dry Run: run every check and query for real and print the ordered plan;
404
+ changes no Site or database but takes the Site Lock (also accepted before
405
+ `restore`)
406
+ --wp-config PATH Use this wp-config.php instead of searching for it
407
+ --overwrite-wp-config
408
+ Replace the target's existing wp-config.php with the archive's (only with
409
+ a mode that restores files), keeping the target's DB_* definitions as
410
+ written, getenv_docker(...) included. Refused (exit 3) when the target
411
+ uses getenv_docker(...) and the archive's wp-config.php does not define it
412
+ --reset-existing-db-user
413
+ When the target has no wp-config.php and the archive's database account
414
+ already exists: reset its password to the archive's (refused otherwise)
415
+ --no-safety-snapshot Do not take the Safety Snapshot before changing anything
416
+ --require-encrypted Refuse an archive that is not encrypted, before anything changes (guards
417
+ against a plain archive substituted for an encrypted one; without it a
418
+ plain archive is restored with a warning)
419
+ --passphrase SECRET Passphrase that decrypts the archive and encrypts the Safety Snapshot. The
420
+ value is visible in the process list; prefer L3IO_WP_BACKUP_PASSPHRASE or
421
+ the config file (chmod 600)
422
+ --snapshot-dir PATH Directory to write the Safety Snapshot to (default: the per-user state
423
+ directory). Refused inside the Site Root or the wp-config.php directory; a
424
+ directory a web server serves from elsewhere is not detected
425
+ --archive-database USER@HOST[:PORT]/NAME
426
+ When the target has no wp-config.php and the archive is not encrypted:
427
+ confirm the database the archive's config names (user, host, port and name
428
+ must all match), or the restore is refused
429
+ --db-tls {required,verify-ca,verify-identity}
430
+ Require TLS on every database connection, at this level (default: no
431
+ requirement, as WordPress connects)
432
+ --db-tls-ca FILE CA bundle that verifies the database server, with --db-tls verify-ca or
433
+ verify-identity (default: the system store)
434
+ --admin-tls {required,verify-ca,verify-identity}
435
+ Deprecated: use --db-tls
436
+ --admin-tls-ca FILE Deprecated: use --db-tls-ca
437
+ --resolve-interrupted-run RUN_ID
438
+ Resolve the journal left by the interrupted Run with this ID, then stop:
439
+ nothing is restored (run the restore again afterwards)
440
+ --admin-user, --admin-username USER
441
+ Database administrator username (give --admin-password with it, or set
442
+ L3IO_WP_BACKUP_ADMIN_PASSWORD)
443
+ --admin-password SECRET
444
+ Database administrator password. The value is visible in the process list;
445
+ prefer L3IO_WP_BACKUP_ADMIN_PASSWORD or the config file (chmod 600)
446
+ --admin-credentials-aws-secret-id ID
447
+ Read the database administrator from this AWS Secrets Manager secret
448
+ --admin-credentials-aws-region REGION
449
+ AWS region of --admin-credentials-aws-secret-id (needs the secret id)
450
+ --only-appointed-in-asg
451
+ Removed; accepted only to say so (not available in this build)
452
+ --new-site-url URL Relocate the Site to this URL
453
+ --new-site-home-url URL
454
+ Relocate the Site's home URL
455
+ --new-db-host HOST Relocate to this database host
456
+ --new-db-name NAME Relocate to this database name; it must be missing (created, and dropped
457
+ again if the restore fails) or empty, --force or not
458
+ --new-db-user USER Relocate to this database user
459
+ --new-db-password SECRET
460
+ Password of --new-db-user. The value is visible in the process list;
461
+ prefer --new-db-password-file, L3IO_WP_BACKUP_NEW_DB_PASSWORD or the
462
+ config file (chmod 600)
463
+ --new-db-password-file PATH
464
+ Read the password of --new-db-user from this file (its first line; make it
465
+ readable only by you)
466
+ --allow-unencrypted Deprecated, no effect: a plain archive restored with a passphrase proceeds
467
+ with a warning; --require-encrypted refuses it
468
+
469
+ database credentials:
470
+ --db-credentials-aws-secret-id ID
471
+ Read the WordPress database account from this AWS Secrets Manager secret
472
+ --db-credentials-aws-region REGION
473
+ AWS region of --db-credentials-aws-secret-id (needs the secret id)
474
+
475
+ exit codes:
476
+ 0 OK the Run succeeded
477
+ 1 FAILED the Run failed for a reason no other code names
478
+ 2 USAGE the command line was invalid
479
+ 3 REFUSED a check found a reason not to act; nothing was done
480
+ 4 CREDENTIALS credentials were missing, unusable or rejected
481
+ 5 UNREACHABLE the database server could not be reached
482
+ 7 NOT_READY a required tool or optional extra is not available
483
+ 8 VERIFICATION_FAILED the archive failed verification
484
+ 9 LOCKED another Run holds the site lock
485
+ ```
486
+
487
+ ```text
488
+ $ wpbackup2 verify --help
489
+ usage: wpbackup2 verify [-h] [--config PATH] [--whatIf] --archive ARCHIVE [--authenticate]
490
+ [--passphrase SECRET]
491
+
492
+ Check an archive without touching a site
493
+
494
+ Every option can also be set as an L3IO_WP_BACKUP_<NAME> variable
495
+ (--wp-dir is L3IO_WP_BACKUP_WP_DIR) or as a key in a TOML config file
496
+ (wp_dir = "..."); a flag wins over a variable, a variable over the file.
497
+
498
+ options:
499
+ -h, --help Show this help message and exit
500
+ --config PATH Read settings from this TOML file (also L3IO_WP_BACKUP_CONFIG; default:
501
+ /etc/l3io-wp-backup/config.toml, then the per-user ~/.config/l3io-wp-
502
+ backup/config.toml). A file holding a secret must be readable by its owner
503
+ only
504
+ --whatIf, --what-if Accepted for symmetry: verify is read-only, so it changes nothing
505
+ --archive ARCHIVE Path or file: URL of the archive to check
506
+ --authenticate Decrypt the encrypted payload to prove the archive authentic; without it,
507
+ verify proves only that its checksums are consistent. The passphrase comes
508
+ from --passphrase, L3IO_WP_BACKUP_PASSPHRASE, the config file or a prompt.
509
+ It does not show the archive is the latest one: an older archive under the
510
+ same passphrase also passes
511
+ --passphrase SECRET Passphrase that decrypts the archive (with --authenticate). The value is
512
+ visible in the process list; prefer L3IO_WP_BACKUP_PASSPHRASE or the config
513
+ file (chmod 600)
514
+
515
+ exit codes:
516
+ 0 OK the Run succeeded
517
+ 1 FAILED the Run failed for a reason no other code names
518
+ 2 USAGE the command line was invalid
519
+ 3 REFUSED a check found a reason not to act; nothing was done
520
+ 4 CREDENTIALS credentials were missing, unusable or rejected
521
+ 5 UNREACHABLE the database server could not be reached
522
+ 7 NOT_READY a required tool or optional extra is not available
523
+ 8 VERIFICATION_FAILED the archive failed verification
524
+ 9 LOCKED another Run holds the site lock
525
+ ```
526
+
527
+ ```text
528
+ $ wpbackup2 doctor --help
529
+ usage: wpbackup2 doctor [-h] [--config PATH] [--wp-dir DIR] [--archive TARGET] [--json]
530
+
531
+ Report whether this host is ready to back up and restore
532
+
533
+ Every option can also be set as an L3IO_WP_BACKUP_<NAME> variable
534
+ (--wp-dir is L3IO_WP_BACKUP_WP_DIR) or as a key in a TOML config file
535
+ (wp_dir = "..."); a flag wins over a variable, a variable over the file.
536
+
537
+ options:
538
+ -h, --help Show this help message and exit
539
+ --config PATH Read settings from this TOML file (also L3IO_WP_BACKUP_CONFIG; default:
540
+ /etc/l3io-wp-backup/config.toml, then the per-user ~/.config/l3io-wp-
541
+ backup/config.toml). A file holding a secret must be readable by its owner
542
+ only
543
+ --wp-dir DIR Also check this Site Root
544
+ --archive TARGET Also check this archive target
545
+ --json Print the diagnosis as JSON
546
+
547
+ exit codes:
548
+ 0 OK the Run succeeded
549
+ 1 FAILED the Run failed for a reason no other code names
550
+ 2 USAGE the command line was invalid
551
+ 3 REFUSED a check found a reason not to act; nothing was done
552
+ 4 CREDENTIALS credentials were missing, unusable or rejected
553
+ 5 UNREACHABLE the database server could not be reached
554
+ 7 NOT_READY a required tool or optional extra is not available
555
+ 8 VERIFICATION_FAILED the archive failed verification
556
+ 9 LOCKED another Run holds the site lock
557
+ ```
558
+ <!-- END GENERATED: cli-usage -->
559
+
560
+ ### Exit codes
561
+
562
+ A scheduler can branch on the exit code alone: `0` means the Run succeeded and
563
+ every failure exits non-zero with a code naming its kind. The table is
564
+ append-only — a code is never renumbered or reused. Ctrl-C exits with the
565
+ shell's `130` (`INTERRUPTED` in the log), which is not part of the table.
566
+
567
+ <!-- BEGIN GENERATED: exit-codes (tools/gen_cli_docs.py) -->
568
+ | Code | Name | Meaning |
569
+ |---:|---|---|
570
+ | 0 | `OK` | the Run succeeded |
571
+ | 1 | `FAILED` | the Run failed for a reason no other code names |
572
+ | 2 | `USAGE` | the command line was invalid |
573
+ | 3 | `REFUSED` | a check found a reason not to act; nothing was done |
574
+ | 4 | `CREDENTIALS` | credentials were missing, unusable or rejected |
575
+ | 5 | `UNREACHABLE` | the database server could not be reached |
576
+ | 7 | `NOT_READY` | a required tool or optional extra is not available |
577
+ | 8 | `VERIFICATION_FAILED` | the archive failed verification |
578
+ | 9 | `LOCKED` | another Run holds the site lock |
579
+
580
+ Reserved for the `l3io-wp-*` family and never used: 6.
581
+ <!-- END GENERATED: exit-codes -->
582
+
583
+ ## Development
584
+
585
+ ### Prerequisites
586
+
587
+ Python 3.11 or newer, and [uv](https://docs.astral.sh/uv/).
588
+
589
+ ### Installing dependencies
590
+
591
+ ```shell
592
+ uv sync
593
+ ```