bg-coolify-migrate 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.
- bg_coolify_migrate-1.0.0/.gitignore +83 -0
- bg_coolify_migrate-1.0.0/CHANGELOG.md +3 -0
- bg_coolify_migrate-1.0.0/LICENSE +21 -0
- bg_coolify_migrate-1.0.0/PKG-INFO +157 -0
- bg_coolify_migrate-1.0.0/README.MD +101 -0
- bg_coolify_migrate-1.0.0/docs/README.template.MD +100 -0
- bg_coolify_migrate-1.0.0/docs/SECURITY.template.MD +68 -0
- bg_coolify_migrate-1.0.0/docs/architecture.md +149 -0
- bg_coolify_migrate-1.0.0/docs/cli.md +70 -0
- bg_coolify_migrate-1.0.0/docs/configuration.md +88 -0
- bg_coolify_migrate-1.0.0/docs/index.md +47 -0
- bg_coolify_migrate-1.0.0/docs/installation.md +73 -0
- bg_coolify_migrate-1.0.0/docs/safety.md +147 -0
- bg_coolify_migrate-1.0.0/docs/server-migration.md +170 -0
- bg_coolify_migrate-1.0.0/docs/troubleshooting.md +182 -0
- bg_coolify_migrate-1.0.0/docs/why.md +124 -0
- bg_coolify_migrate-1.0.0/pyproject.toml +173 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/__init__.py +86 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/api/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/api/client.py +491 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/api/fields.py +382 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/api/resources.py +466 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/cli.py +689 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/discovery/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/discovery/docker.py +282 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/discovery/quiesce.py +305 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/dns/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/dns/extract.py +253 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/dns/gate.py +229 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/dns/resolve.py +160 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/domain/__init__.py +10 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/domain/compose.py +281 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/domain/drift.py +479 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/domain/images.py +219 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/domain/kinds.py +260 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/domain/manifest.py +290 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/domain/naming.py +318 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/domain/plan.py +255 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/domain/statemachine.py +256 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/engine/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/engine/compensations.py +178 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/engine/context.py +141 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/engine/executor.py +276 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/engine/keys.py +157 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/engine/planner.py +628 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/engine/runner.py +308 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/engine/steps.py +741 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/errors.py +168 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/journal/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/journal/store.py +302 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/observability/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/observability/logging_setup.py +173 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/py.typed +0 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/server/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/server/appkey.py +234 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/server/fencing.py +141 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/server/inventory.py +205 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/server/runner.py +140 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/server/statemachine.py +94 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/server/steps.py +362 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/settings/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/settings/base.py +188 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/transfer/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/transfer/partition.py +153 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/transfer/rsync.py +279 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/transfer/ssh.py +313 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/transfer/verify.py +269 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/ui/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/ui/console.py +87 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/ui/dashboard.py +189 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/ui/report.py +224 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/ui/run_report.py +150 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/ui/server_report.py +91 -0
- bg_coolify_migrate-1.0.0/src/bg_coolify_migrate/ui/wizard.py +215 -0
- bg_coolify_migrate-1.0.0/tests/__init__.py +8 -0
- bg_coolify_migrate-1.0.0/tests/conftest.py +105 -0
- bg_coolify_migrate-1.0.0/tests/e2e/README.md +125 -0
- bg_coolify_migrate-1.0.0/tests/e2e/__init__.py +1 -0
- bg_coolify_migrate-1.0.0/tests/e2e/bootstrap.py +268 -0
- bg_coolify_migrate-1.0.0/tests/e2e/conftest.py +184 -0
- bg_coolify_migrate-1.0.0/tests/e2e/docker-compose.yml +200 -0
- bg_coolify_migrate-1.0.0/tests/e2e/prepare.py +174 -0
- bg_coolify_migrate-1.0.0/tests/e2e/runner.Dockerfile +53 -0
- bg_coolify_migrate-1.0.0/tests/e2e/test_all_engines.py +314 -0
- bg_coolify_migrate-1.0.0/tests/e2e/test_compose_service.py +198 -0
- bg_coolify_migrate-1.0.0/tests/e2e/test_drift_gate.py +116 -0
- bg_coolify_migrate-1.0.0/tests/e2e/test_label_contract.py +134 -0
- bg_coolify_migrate-1.0.0/tests/e2e/test_multi_resource.py +169 -0
- bg_coolify_migrate-1.0.0/tests/e2e/test_real_migration.py +227 -0
- bg_coolify_migrate-1.0.0/tests/e2e/test_rollback.py +173 -0
- bg_coolify_migrate-1.0.0/tests/integration/__init__.py +6 -0
- bg_coolify_migrate-1.0.0/tests/integration/docker-compose.yml +66 -0
- bg_coolify_migrate-1.0.0/tests/integration/prepare.py +63 -0
- bg_coolify_migrate-1.0.0/tests/integration/test_real_transfer.py +379 -0
- bg_coolify_migrate-1.0.0/tests/test_api_client.py +268 -0
- bg_coolify_migrate-1.0.0/tests/test_api_fields.py +337 -0
- bg_coolify_migrate-1.0.0/tests/test_api_resources.py +477 -0
- bg_coolify_migrate-1.0.0/tests/test_cli.py +219 -0
- bg_coolify_migrate-1.0.0/tests/test_discovery.py +400 -0
- bg_coolify_migrate-1.0.0/tests/test_dns.py +329 -0
- bg_coolify_migrate-1.0.0/tests/test_domain_compose.py +457 -0
- bg_coolify_migrate-1.0.0/tests/test_domain_drift.py +343 -0
- bg_coolify_migrate-1.0.0/tests/test_domain_images.py +182 -0
- bg_coolify_migrate-1.0.0/tests/test_domain_kinds.py +222 -0
- bg_coolify_migrate-1.0.0/tests/test_domain_manifest.py +248 -0
- bg_coolify_migrate-1.0.0/tests/test_domain_naming.py +245 -0
- bg_coolify_migrate-1.0.0/tests/test_domain_plan.py +282 -0
- bg_coolify_migrate-1.0.0/tests/test_domain_statemachine.py +197 -0
- bg_coolify_migrate-1.0.0/tests/test_engine.py +374 -0
- bg_coolify_migrate-1.0.0/tests/test_engine_context.py +85 -0
- bg_coolify_migrate-1.0.0/tests/test_engine_planner.py +141 -0
- bg_coolify_migrate-1.0.0/tests/test_engine_steps.py +638 -0
- bg_coolify_migrate-1.0.0/tests/test_engine_wiring.py +259 -0
- bg_coolify_migrate-1.0.0/tests/test_journal.py +197 -0
- bg_coolify_migrate-1.0.0/tests/test_observability.py +175 -0
- bg_coolify_migrate-1.0.0/tests/test_packaging.py +123 -0
- bg_coolify_migrate-1.0.0/tests/test_server_migration.py +423 -0
- bg_coolify_migrate-1.0.0/tests/test_transfer.py +332 -0
- bg_coolify_migrate-1.0.0/tests/test_ui.py +254 -0
- bg_coolify_migrate-1.0.0/tests/test_ui_run.py +195 -0
- bg_coolify_migrate-1.0.0/tests/test_ui_wizard.py +206 -0
- bg_coolify_migrate-1.0.0/tests/test_verify_collect.py +161 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
|
|
7
|
+
# Distribution / packaging
|
|
8
|
+
build/
|
|
9
|
+
dist/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
.eggs/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# Testing / coverage
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
.coverage.*
|
|
22
|
+
coverage.xml
|
|
23
|
+
htmlcov/
|
|
24
|
+
.hypothesis/
|
|
25
|
+
.benchmarks/
|
|
26
|
+
|
|
27
|
+
# Type checking / linting
|
|
28
|
+
.mypy_cache/
|
|
29
|
+
.ruff_cache/
|
|
30
|
+
|
|
31
|
+
# Docs
|
|
32
|
+
/site
|
|
33
|
+
|
|
34
|
+
# IDE
|
|
35
|
+
.vscode/
|
|
36
|
+
.claude/
|
|
37
|
+
.idea/
|
|
38
|
+
|
|
39
|
+
# Secrets & local config — NEVER commit these.
|
|
40
|
+
# A Coolify root token can read every secret of every project in the team.
|
|
41
|
+
.env
|
|
42
|
+
.env.*
|
|
43
|
+
!.env.example
|
|
44
|
+
!.env.template
|
|
45
|
+
.pypirc
|
|
46
|
+
|
|
47
|
+
# Runtime state. Migration journals contain resource UUIDs and host addresses;
|
|
48
|
+
# they belong in the user state dir (platformdirs), never in the repo. This entry
|
|
49
|
+
# only catches accidental --state-dir=. runs.
|
|
50
|
+
/state/
|
|
51
|
+
*.journal.jsonl
|
|
52
|
+
|
|
53
|
+
# Integration rig: throwaway keys, generated on demand by
|
|
54
|
+
# tests/integration/prepare.py. They authorise nothing but two local containers,
|
|
55
|
+
# but a key in git is a key in git.
|
|
56
|
+
tests/integration/keys/
|
|
57
|
+
|
|
58
|
+
# ── e2e rig artefacts ───────────────────────────────────────────────────────
|
|
59
|
+
# Minted per run and every one of them is a credential. The rig is reproducible
|
|
60
|
+
# from prepare.py + bootstrap.py, so there is nothing here worth keeping.
|
|
61
|
+
tests/e2e/keys/
|
|
62
|
+
tests/e2e/rig.json
|
|
63
|
+
tests/e2e/coolify.env
|
|
64
|
+
tests/e2e/.probe.json
|
|
65
|
+
|
|
66
|
+
# Lockfile — deliberately absent, not merely unwritten
|
|
67
|
+
# ---------------------------------------------------
|
|
68
|
+
# Nothing here would read it. All three CI workflows install with
|
|
69
|
+
# `pip install -e ".[dev]"`, consumers get a PyPI wheel and resolve the ranges in
|
|
70
|
+
# pyproject.toml themselves, and even our own setup instructions use `uv pip
|
|
71
|
+
# install`, which is the pip-compatible interface and ignores a lock (only
|
|
72
|
+
# `uv sync`/`uv run` consume one).
|
|
73
|
+
#
|
|
74
|
+
# A lock nothing consumes is worse than none: it claims a reproducibility that
|
|
75
|
+
# does not exist, and it is a second source of truth beside pyproject.toml that
|
|
76
|
+
# drifts in silence. Dependabot already maintains the pip ecosystem weekly from
|
|
77
|
+
# pyproject; a lock would need its own update path or quietly fossilise.
|
|
78
|
+
#
|
|
79
|
+
# To reverse this, the lock is the small part — the work is consuming it: move
|
|
80
|
+
# the workflows to `uv sync --locked`, add setup-uv, and arrange for the lock to
|
|
81
|
+
# be updated. Committing a lock while CI still runs pip is the worst of both:
|
|
82
|
+
# all of the cost, none of the benefit.
|
|
83
|
+
uv.lock
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BAUER GROUP
|
|
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,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bg-coolify-migrate
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: BAUER GROUP Coolify migration toolkit — failsafe, resumable project and instance migration with byte-exact volume mirroring
|
|
5
|
+
Project-URL: Homepage, https://github.com/bauer-group/IP-CoolifyMigration
|
|
6
|
+
Project-URL: Source, https://github.com/bauer-group/IP-CoolifyMigration
|
|
7
|
+
Project-URL: Issues, https://github.com/bauer-group/IP-CoolifyMigration/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/bauer-group/IP-CoolifyMigration/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: "BAUER GROUP (Karl Bauer)" <karl.bauer@bauer-group.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: coolify,devops,docker,migration,paas,rsync,self-hosted
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
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 :: Mirroring
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Requires-Python: >=3.12
|
|
27
|
+
Requires-Dist: asyncssh<3.0.0,>=2.18.0
|
|
28
|
+
Requires-Dist: dnspython<3.0.0,>=2.7.0
|
|
29
|
+
Requires-Dist: httpx<1.0.0,>=0.28.1
|
|
30
|
+
Requires-Dist: platformdirs<5.0.0,>=4.3.0
|
|
31
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.14.1
|
|
32
|
+
Requires-Dist: pydantic<3.0.0,>=2.10.0
|
|
33
|
+
Requires-Dist: pyyaml<7.0.0,>=6.0.0
|
|
34
|
+
Requires-Dist: questionary<3.0.0,>=2.0.0
|
|
35
|
+
Requires-Dist: rich<16.0.0,>=13.7.0
|
|
36
|
+
Requires-Dist: structlog<26.0.0,>=24.4.0
|
|
37
|
+
Requires-Dist: typer<1.0.0,>=0.15.0
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: build>=1.0.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: hypothesis<7.0.0,>=6.100.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: mypy>=1.13.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest-asyncio<2.0.0,>=0.24.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest-cov<8.0.0,>=6.0.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest<10.0.0,>=8.3.0; extra == 'dev'
|
|
46
|
+
Requires-Dist: python-semantic-release>=9.0.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: respx<1.0.0,>=0.22.0; extra == 'dev'
|
|
48
|
+
Requires-Dist: ruff>=0.7.0; extra == 'dev'
|
|
49
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
|
|
51
|
+
Provides-Extra: docs
|
|
52
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == 'docs'
|
|
53
|
+
Requires-Dist: mkdocs>=1.6.0; extra == 'docs'
|
|
54
|
+
Requires-Dist: mkdocstrings[python]>=0.26.0; extra == 'docs'
|
|
55
|
+
Description-Content-Type: text/markdown
|
|
56
|
+
|
|
57
|
+
<!-- AUTO-GENERATED FILE. DO NOT EDIT. Edit docs/README.template.MD instead. Generated 2026-07-16. -->
|
|
58
|
+
# BAUER GROUP - Coolify Migration Toolkit
|
|
59
|
+
|
|
60
|
+
[](https://pypi.org/project/bg-coolify-migrate/)
|
|
61
|
+
[](https://pypi.org/project/bg-coolify-migrate/)
|
|
62
|
+
[](LICENSE)
|
|
63
|
+
[](https://github.com/bauer-group/IP-CoolifyMigration/actions/workflows/tests.yml)
|
|
64
|
+
|
|
65
|
+
> **Today, Tomorrow, Together** | Building Better Software Together
|
|
66
|
+
|
|
67
|
+
Moves a Coolify **project — with its data —** between servers, and relocates a
|
|
68
|
+
whole Coolify instance to a new host. Failsafe, resumable, and rollback-capable.
|
|
69
|
+
|
|
70
|
+
Coolify can clone a resource to another server but deliberately will not move the
|
|
71
|
+
data. `VolumeCloneJob` and `CloneMe`'s `cloneVolumeData` flag exist upstream, but
|
|
72
|
+
PR #4777 shipped them **disabled**. The maintainer's stated blockers — permission
|
|
73
|
+
damage, job-queue spam at 50+ resources, no progress tracking, large-volume
|
|
74
|
+
failures — are all consequences of running inside Coolify's Laravel queue. An
|
|
75
|
+
external orchestrator has none of those constraints. That is what this is.
|
|
76
|
+
|
|
77
|
+
## Repository Information
|
|
78
|
+
|
|
79
|
+
- **Version:** 0.1.0
|
|
80
|
+
- **Repository:** bauer-group/IP-CoolifyMigration
|
|
81
|
+
- **Branch:** main
|
|
82
|
+
- **Architecture:** Pure-logic cores with thin IO shells; saga engine with a
|
|
83
|
+
crash-safe journal
|
|
84
|
+
|
|
85
|
+
## Features
|
|
86
|
+
|
|
87
|
+
- **Application-unaware mirroring** — a cleanly stopped stack makes a volume just
|
|
88
|
+
bytes. No per-engine logic, no supported-database allowlist. Postgres, MySQL,
|
|
89
|
+
MariaDB, MongoDB, Redis, KeyDB, Dragonfly, ClickHouse and anything else you run
|
|
90
|
+
are all handled the same way, because none of them are special once stopped.
|
|
91
|
+
- **Byte-exact** — `rsync -aHAXS --numeric-ids`, parallelised, with SHA-256 and
|
|
92
|
+
metadata verification on both sides. Never `chown`.
|
|
93
|
+
- **Failsafe** — every step has a compensating action, journalled to disk. The
|
|
94
|
+
source is never destroyed until you explicitly say so, so rollback is always
|
|
95
|
+
available. `resume` reconciles against reality, not against the journal.
|
|
96
|
+
- **DNS gate** — extracts every Traefik/Caddy hostname and refuses to start the
|
|
97
|
+
target while DNS still resolves to the old server, with an actionable cutover
|
|
98
|
+
checklist.
|
|
99
|
+
- **Honest about drift** — the target is built exactly as the source is
|
|
100
|
+
configured, but a tag is a pointer and a branch moves. We detect a floating
|
|
101
|
+
`latest` that could cross a database major, or a HEAD that has moved, and put
|
|
102
|
+
the concrete question to you rather than deciding for you.
|
|
103
|
+
- **Server migration** — relocate the whole Coolify instance, with the `APP_KEY`
|
|
104
|
+
treated as a first-class, asserted artifact rather than a lucky side effect.
|
|
105
|
+
- **Proven, not assumed** — an integration rig of two real sshd containers
|
|
106
|
+
asserts that uid 999, hardlinks, xattrs, sparse files and symlinks survive an
|
|
107
|
+
actual rsync, and that a wrong `chown` is caught by verification.
|
|
108
|
+
|
|
109
|
+
## Architecture
|
|
110
|
+
|
|
111
|
+
| Layer | Role |
|
|
112
|
+
| ----- | ---- |
|
|
113
|
+
| `domain/` | Pure logic: classification, compose analysis, volume pairing, drift, state machine |
|
|
114
|
+
| `api/` | Coolify REST client with per-endpoint request whitelists |
|
|
115
|
+
| `discovery/` | Docker + API reconciliation; the label-based quiesce gate |
|
|
116
|
+
| `transfer/` | asyncssh, rsync planning, checksum verification |
|
|
117
|
+
| `journal/` | Append-only crash-safe state |
|
|
118
|
+
| `dns/` | FQDN extraction, authoritative resolution, cutover gate |
|
|
119
|
+
| `ui/` | Rich dashboard, wizard, and a plain non-TTY fallback |
|
|
120
|
+
|
|
121
|
+
> **Requires the instance API switched on (Settings > API — off by default) and a
|
|
122
|
+
> token with `root` or `read:sensitive`.** Without the switch every call is
|
|
123
|
+
> `403 "API is disabled."`, which looks like a token problem but is not. Without
|
|
124
|
+
> the scope, Coolify's `ApiSensitiveData` middleware silently omits `value`,
|
|
125
|
+
> `real_value` and `docker_compose_raw` from responses — no error, no redaction
|
|
126
|
+
> marker. The tool checks both at startup and fails closed.
|
|
127
|
+
|
|
128
|
+
## Quick start
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pip install bg-coolify-migrate
|
|
132
|
+
|
|
133
|
+
export COOLIFY_URL="https://coolify.example.com"
|
|
134
|
+
export COOLIFY_TOKEN="..." # root or read:sensitive
|
|
135
|
+
|
|
136
|
+
coolify-migrate doctor # verify token scope + server reachability
|
|
137
|
+
coolify-migrate plan my-project --to target-server # dry run: no changes
|
|
138
|
+
coolify-migrate run my-project --to target-server
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Interrupted? `coolify-migrate resume <id>`. Regret it? `coolify-migrate rollback <id>`.
|
|
142
|
+
|
|
143
|
+
## Server migration
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
coolify-migrate server plan --to new-host.example.com
|
|
147
|
+
coolify-migrate server run --to new-host.example.com
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Documentation
|
|
151
|
+
|
|
152
|
+
See [`docs/`](docs/): installation, configuration, cli, architecture, safety,
|
|
153
|
+
server-migration, troubleshooting.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT © BAUER GROUP
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<!-- AUTO-GENERATED FILE. DO NOT EDIT. Edit docs/README.template.MD instead. Generated 2026-07-16. -->
|
|
2
|
+
# BAUER GROUP - Coolify Migration Toolkit
|
|
3
|
+
|
|
4
|
+
[](https://pypi.org/project/bg-coolify-migrate/)
|
|
5
|
+
[](https://pypi.org/project/bg-coolify-migrate/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/bauer-group/IP-CoolifyMigration/actions/workflows/tests.yml)
|
|
8
|
+
|
|
9
|
+
> **Today, Tomorrow, Together** | Building Better Software Together
|
|
10
|
+
|
|
11
|
+
Moves a Coolify **project — with its data —** between servers, and relocates a
|
|
12
|
+
whole Coolify instance to a new host. Failsafe, resumable, and rollback-capable.
|
|
13
|
+
|
|
14
|
+
Coolify can clone a resource to another server but deliberately will not move the
|
|
15
|
+
data. `VolumeCloneJob` and `CloneMe`'s `cloneVolumeData` flag exist upstream, but
|
|
16
|
+
PR #4777 shipped them **disabled**. The maintainer's stated blockers — permission
|
|
17
|
+
damage, job-queue spam at 50+ resources, no progress tracking, large-volume
|
|
18
|
+
failures — are all consequences of running inside Coolify's Laravel queue. An
|
|
19
|
+
external orchestrator has none of those constraints. That is what this is.
|
|
20
|
+
|
|
21
|
+
## Repository Information
|
|
22
|
+
|
|
23
|
+
- **Version:** 0.1.0
|
|
24
|
+
- **Repository:** bauer-group/IP-CoolifyMigration
|
|
25
|
+
- **Branch:** main
|
|
26
|
+
- **Architecture:** Pure-logic cores with thin IO shells; saga engine with a
|
|
27
|
+
crash-safe journal
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Application-unaware mirroring** — a cleanly stopped stack makes a volume just
|
|
32
|
+
bytes. No per-engine logic, no supported-database allowlist. Postgres, MySQL,
|
|
33
|
+
MariaDB, MongoDB, Redis, KeyDB, Dragonfly, ClickHouse and anything else you run
|
|
34
|
+
are all handled the same way, because none of them are special once stopped.
|
|
35
|
+
- **Byte-exact** — `rsync -aHAXS --numeric-ids`, parallelised, with SHA-256 and
|
|
36
|
+
metadata verification on both sides. Never `chown`.
|
|
37
|
+
- **Failsafe** — every step has a compensating action, journalled to disk. The
|
|
38
|
+
source is never destroyed until you explicitly say so, so rollback is always
|
|
39
|
+
available. `resume` reconciles against reality, not against the journal.
|
|
40
|
+
- **DNS gate** — extracts every Traefik/Caddy hostname and refuses to start the
|
|
41
|
+
target while DNS still resolves to the old server, with an actionable cutover
|
|
42
|
+
checklist.
|
|
43
|
+
- **Honest about drift** — the target is built exactly as the source is
|
|
44
|
+
configured, but a tag is a pointer and a branch moves. We detect a floating
|
|
45
|
+
`latest` that could cross a database major, or a HEAD that has moved, and put
|
|
46
|
+
the concrete question to you rather than deciding for you.
|
|
47
|
+
- **Server migration** — relocate the whole Coolify instance, with the `APP_KEY`
|
|
48
|
+
treated as a first-class, asserted artifact rather than a lucky side effect.
|
|
49
|
+
- **Proven, not assumed** — an integration rig of two real sshd containers
|
|
50
|
+
asserts that uid 999, hardlinks, xattrs, sparse files and symlinks survive an
|
|
51
|
+
actual rsync, and that a wrong `chown` is caught by verification.
|
|
52
|
+
|
|
53
|
+
## Architecture
|
|
54
|
+
|
|
55
|
+
| Layer | Role |
|
|
56
|
+
| ----- | ---- |
|
|
57
|
+
| `domain/` | Pure logic: classification, compose analysis, volume pairing, drift, state machine |
|
|
58
|
+
| `api/` | Coolify REST client with per-endpoint request whitelists |
|
|
59
|
+
| `discovery/` | Docker + API reconciliation; the label-based quiesce gate |
|
|
60
|
+
| `transfer/` | asyncssh, rsync planning, checksum verification |
|
|
61
|
+
| `journal/` | Append-only crash-safe state |
|
|
62
|
+
| `dns/` | FQDN extraction, authoritative resolution, cutover gate |
|
|
63
|
+
| `ui/` | Rich dashboard, wizard, and a plain non-TTY fallback |
|
|
64
|
+
|
|
65
|
+
> **Requires the instance API switched on (Settings > API — off by default) and a
|
|
66
|
+
> token with `root` or `read:sensitive`.** Without the switch every call is
|
|
67
|
+
> `403 "API is disabled."`, which looks like a token problem but is not. Without
|
|
68
|
+
> the scope, Coolify's `ApiSensitiveData` middleware silently omits `value`,
|
|
69
|
+
> `real_value` and `docker_compose_raw` from responses — no error, no redaction
|
|
70
|
+
> marker. The tool checks both at startup and fails closed.
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install bg-coolify-migrate
|
|
76
|
+
|
|
77
|
+
export COOLIFY_URL="https://coolify.example.com"
|
|
78
|
+
export COOLIFY_TOKEN="..." # root or read:sensitive
|
|
79
|
+
|
|
80
|
+
coolify-migrate doctor # verify token scope + server reachability
|
|
81
|
+
coolify-migrate plan my-project --to target-server # dry run: no changes
|
|
82
|
+
coolify-migrate run my-project --to target-server
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Interrupted? `coolify-migrate resume <id>`. Regret it? `coolify-migrate rollback <id>`.
|
|
86
|
+
|
|
87
|
+
## Server migration
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
coolify-migrate server plan --to new-host.example.com
|
|
91
|
+
coolify-migrate server run --to new-host.example.com
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Documentation
|
|
95
|
+
|
|
96
|
+
See [`docs/`](docs/): installation, configuration, cli, architecture, safety,
|
|
97
|
+
server-migration, troubleshooting.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT © BAUER GROUP
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# BAUER GROUP - Coolify Migration Toolkit
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/bg-coolify-migrate/)
|
|
4
|
+
[](https://pypi.org/project/bg-coolify-migrate/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[]({{REPO_URL}}/actions/workflows/tests.yml)
|
|
7
|
+
|
|
8
|
+
> **Today, Tomorrow, Together** | Building Better Software Together
|
|
9
|
+
|
|
10
|
+
Moves a Coolify **project — with its data —** between servers, and relocates a
|
|
11
|
+
whole Coolify instance to a new host. Failsafe, resumable, and rollback-capable.
|
|
12
|
+
|
|
13
|
+
Coolify can clone a resource to another server but deliberately will not move the
|
|
14
|
+
data. `VolumeCloneJob` and `CloneMe`'s `cloneVolumeData` flag exist upstream, but
|
|
15
|
+
PR #4777 shipped them **disabled**. The maintainer's stated blockers — permission
|
|
16
|
+
damage, job-queue spam at 50+ resources, no progress tracking, large-volume
|
|
17
|
+
failures — are all consequences of running inside Coolify's Laravel queue. An
|
|
18
|
+
external orchestrator has none of those constraints. That is what this is.
|
|
19
|
+
|
|
20
|
+
## Repository Information
|
|
21
|
+
|
|
22
|
+
- **Version:** {{VERSION}}
|
|
23
|
+
- **Repository:** {{REPO_FULL_NAME}}
|
|
24
|
+
- **Branch:** {{CURRENT_BRANCH}}
|
|
25
|
+
- **Architecture:** Pure-logic cores with thin IO shells; saga engine with a
|
|
26
|
+
crash-safe journal
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Application-unaware mirroring** — a cleanly stopped stack makes a volume just
|
|
31
|
+
bytes. No per-engine logic, no supported-database allowlist. Postgres, MySQL,
|
|
32
|
+
MariaDB, MongoDB, Redis, KeyDB, Dragonfly, ClickHouse and anything else you run
|
|
33
|
+
are all handled the same way, because none of them are special once stopped.
|
|
34
|
+
- **Byte-exact** — `rsync -aHAXS --numeric-ids`, parallelised, with SHA-256 and
|
|
35
|
+
metadata verification on both sides. Never `chown`.
|
|
36
|
+
- **Failsafe** — every step has a compensating action, journalled to disk. The
|
|
37
|
+
source is never destroyed until you explicitly say so, so rollback is always
|
|
38
|
+
available. `resume` reconciles against reality, not against the journal.
|
|
39
|
+
- **DNS gate** — extracts every Traefik/Caddy hostname and refuses to start the
|
|
40
|
+
target while DNS still resolves to the old server, with an actionable cutover
|
|
41
|
+
checklist.
|
|
42
|
+
- **Honest about drift** — the target is built exactly as the source is
|
|
43
|
+
configured, but a tag is a pointer and a branch moves. We detect a floating
|
|
44
|
+
`latest` that could cross a database major, or a HEAD that has moved, and put
|
|
45
|
+
the concrete question to you rather than deciding for you.
|
|
46
|
+
- **Server migration** — relocate the whole Coolify instance, with the `APP_KEY`
|
|
47
|
+
treated as a first-class, asserted artifact rather than a lucky side effect.
|
|
48
|
+
- **Proven, not assumed** — an integration rig of two real sshd containers
|
|
49
|
+
asserts that uid 999, hardlinks, xattrs, sparse files and symlinks survive an
|
|
50
|
+
actual rsync, and that a wrong `chown` is caught by verification.
|
|
51
|
+
|
|
52
|
+
## Architecture
|
|
53
|
+
|
|
54
|
+
| Layer | Role |
|
|
55
|
+
| ----- | ---- |
|
|
56
|
+
| `domain/` | Pure logic: classification, compose analysis, volume pairing, drift, state machine |
|
|
57
|
+
| `api/` | Coolify REST client with per-endpoint request whitelists |
|
|
58
|
+
| `discovery/` | Docker + API reconciliation; the label-based quiesce gate |
|
|
59
|
+
| `transfer/` | asyncssh, rsync planning, checksum verification |
|
|
60
|
+
| `journal/` | Append-only crash-safe state |
|
|
61
|
+
| `dns/` | FQDN extraction, authoritative resolution, cutover gate |
|
|
62
|
+
| `ui/` | Rich dashboard, wizard, and a plain non-TTY fallback |
|
|
63
|
+
|
|
64
|
+
> **Requires the instance API switched on (Settings > API — off by default) and a
|
|
65
|
+
> token with `root` or `read:sensitive`.** Without the switch every call is
|
|
66
|
+
> `403 "API is disabled."`, which looks like a token problem but is not. Without
|
|
67
|
+
> the scope, Coolify's `ApiSensitiveData` middleware silently omits `value`,
|
|
68
|
+
> `real_value` and `docker_compose_raw` from responses — no error, no redaction
|
|
69
|
+
> marker. The tool checks both at startup and fails closed.
|
|
70
|
+
|
|
71
|
+
## Quick start
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install bg-coolify-migrate
|
|
75
|
+
|
|
76
|
+
export COOLIFY_URL="https://coolify.example.com"
|
|
77
|
+
export COOLIFY_TOKEN="..." # root or read:sensitive
|
|
78
|
+
|
|
79
|
+
coolify-migrate doctor # verify token scope + server reachability
|
|
80
|
+
coolify-migrate plan my-project --to target-server # dry run: no changes
|
|
81
|
+
coolify-migrate run my-project --to target-server
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Interrupted? `coolify-migrate resume <id>`. Regret it? `coolify-migrate rollback <id>`.
|
|
85
|
+
|
|
86
|
+
## Server migration
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
coolify-migrate server plan --to new-host.example.com
|
|
90
|
+
coolify-migrate server run --to new-host.example.com
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Documentation
|
|
94
|
+
|
|
95
|
+
See [`docs/`](docs/): installation, configuration, cli, architecture, safety,
|
|
96
|
+
server-migration, troubleshooting.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT © BAUER GROUP
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
> **{{COMPANY_NAME}}** | {{REPO_FULL_NAME}}
|
|
4
|
+
|
|
5
|
+
## Supported Versions
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
| ------- | ------------------ |
|
|
9
|
+
{{SUPPORTED_VERSIONS_TABLE}}
|
|
10
|
+
|
|
11
|
+
## Reporting a Vulnerability
|
|
12
|
+
|
|
13
|
+
Report security issues privately to **security@bauer-group.com** or via
|
|
14
|
+
[GitHub Security Advisories]({{REPO_URL}}/security/advisories/new).
|
|
15
|
+
|
|
16
|
+
Please do not open a public issue for a vulnerability. We aim to acknowledge
|
|
17
|
+
within 2 business days and to ship a fix or mitigation within 30 days.
|
|
18
|
+
|
|
19
|
+
## Threat model
|
|
20
|
+
|
|
21
|
+
This tool is operated by an administrator against infrastructure they already
|
|
22
|
+
control. It is not a multi-tenant service and has no privilege boundary of its
|
|
23
|
+
own. That said, it handles material worth protecting:
|
|
24
|
+
|
|
25
|
+
- **A Coolify API token with `root` / `read:sensitive`** can read every secret of
|
|
26
|
+
every project in the team. The tool requires such a token by design (Coolify
|
|
27
|
+
silently omits secret values otherwise), which makes the token the single most
|
|
28
|
+
sensitive input.
|
|
29
|
+
- **SSH access to both servers**, effectively as root.
|
|
30
|
+
- **`APP_KEY`** during instance migration, which decrypts Coolify's entire
|
|
31
|
+
credential store.
|
|
32
|
+
|
|
33
|
+
## Security invariants
|
|
34
|
+
|
|
35
|
+
These are enforced in code and must never be weakened. See `AGENTS.md`.
|
|
36
|
+
|
|
37
|
+
1. **Never `chown`.** Ownership is preserved numerically (`--numeric-ids`).
|
|
38
|
+
Coolify's own clone hardcodes `chown -R 1000:1000` and that is precisely what
|
|
39
|
+
corrupts postgres/mysql/redis (uid 999) and clickhouse (uid 101) volumes.
|
|
40
|
+
2. **Never route volume data through the operator's workstation filesystem.**
|
|
41
|
+
3. **Never disable host-key checking.** No `StrictHostKeyChecking=no`.
|
|
42
|
+
4. **Never write SQL to `coolify-db`.** The REST API is the only write path.
|
|
43
|
+
5. **Never log secrets** — environment variable values, private keys, `APP_KEY`.
|
|
44
|
+
Redaction is a structlog processor and is applied additively.
|
|
45
|
+
6. **Never start a target whose FQDN still resolves to the source.**
|
|
46
|
+
7. **Never delete the source before verification passes.**
|
|
47
|
+
8. **Never swallow a stop failure**, and never trust a stop endpoint — poll the
|
|
48
|
+
Docker daemon.
|
|
49
|
+
9. **Never string-replace a UUID to derive a volume name.** Pair by `mount_path`.
|
|
50
|
+
|
|
51
|
+
## Credential handling
|
|
52
|
+
|
|
53
|
+
- Secrets are read from the environment or a `.env` file; never hardcoded, never
|
|
54
|
+
committed, never written to the journal.
|
|
55
|
+
- Ephemeral SSH keys minted for a direct transfer are revoked in a guaranteed
|
|
56
|
+
cleanup step, and the revocation is journalled so a crashed run still cleans up
|
|
57
|
+
on the next invocation.
|
|
58
|
+
- The journal stores resource UUIDs and host addresses. It never stores secret
|
|
59
|
+
values.
|
|
60
|
+
|
|
61
|
+
## Scanning
|
|
62
|
+
|
|
63
|
+
Secret scanning (gitleaks + GitGuardian) runs on every push via the
|
|
64
|
+
`security-management` reusable workflow. Dependencies are monitored by Dependabot.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
_Last updated: {{LAST_UPDATED}}_
|