@mriekki/project-forge 4.8.1 → 4.10.0
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.
- package/assets/components.json +3 -3
- package/assets/deployment/distribution.json +5 -0
- package/assets/generated/ASSEMBLY.json +33 -8
- package/assets/generated/core/core/skills/tear-down/SKILL.md +76 -18
- package/assets/generated/deployment/docs/CLOUDFLARE_INGRESS.md +226 -0
- package/assets/generated/deployment/src/project_forge_deployment/cloudflare_api.py +260 -0
- package/assets/generated/deployment/src/project_forge_deployment/cloudflare_ingress.py +834 -0
- package/assets/generated/deployment/src/project_forge_deployment/cloudflare_ingress_remote.py +328 -0
- package/assets/generated/deployment/src/project_forge_deployment/entrypoint.py +10 -0
- package/assets/generated/deployment/src/project_forge_deployment/ingress_cli.py +192 -0
- package/assets/generated/deployment/src/project_forge_deployment/teardown.py +253 -5
- package/assets/generated/deployment/src/project_forge_deployment/teardown_cli.py +79 -3
- package/assets/generated/deployment/src/project_forge_deployment/teardown_remote.py +58 -1
- package/package.json +1 -1
package/assets/components.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@mriekki/project-forge",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.10.0",
|
|
5
5
|
"defaultProfile": "greenfield-project",
|
|
6
6
|
"profiles": {
|
|
7
7
|
"greenfield-project": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"components": {
|
|
15
15
|
"core": {
|
|
16
|
-
"revision": "
|
|
16
|
+
"revision": "8796fd8dc22df8b64078e5b460e1008a8c7a5f7d",
|
|
17
17
|
"version": "4.0.0",
|
|
18
18
|
"distribution": "greenfield-workspace",
|
|
19
19
|
"descriptor": "assets/core/distribution.json",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"canonicalDescriptor": "distributions/greenfield-workspace.json"
|
|
22
22
|
},
|
|
23
23
|
"deployment": {
|
|
24
|
-
"revision": "
|
|
24
|
+
"revision": "23799aec3e7c9a99873aaf912b040d43658fa4fb",
|
|
25
25
|
"distribution": "greenfield-project",
|
|
26
26
|
"descriptor": "assets/deployment/distribution.json",
|
|
27
27
|
"sourceRoot": "assets/generated/deployment",
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
{"source": "src/project_forge_deployment/box_platform.py", "destination": "tools/project-forge-deployment/project_forge_deployment/box_platform.py"},
|
|
17
17
|
{"source": "src/project_forge_deployment/box_platform_assets.py", "destination": "tools/project-forge-deployment/project_forge_deployment/box_platform_assets.py"},
|
|
18
18
|
{"source": "src/project_forge_deployment/box_platform_remote.py", "destination": "tools/project-forge-deployment/project_forge_deployment/box_platform_remote.py"},
|
|
19
|
+
{"source": "src/project_forge_deployment/cloudflare_api.py", "destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_api.py"},
|
|
20
|
+
{"source": "src/project_forge_deployment/cloudflare_ingress.py", "destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_ingress.py"},
|
|
21
|
+
{"source": "src/project_forge_deployment/cloudflare_ingress_remote.py", "destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_ingress_remote.py"},
|
|
19
22
|
{"source": "src/project_forge_deployment/cli.py", "destination": "tools/project-forge-deployment/project_forge_deployment/cli.py"},
|
|
20
23
|
{"source": "src/project_forge_deployment/doctor.py", "destination": "tools/project-forge-deployment/project_forge_deployment/doctor.py"},
|
|
21
24
|
{"source": "src/project_forge_deployment/entrypoint.py", "destination": "tools/project-forge-deployment/project_forge_deployment/entrypoint.py"},
|
|
@@ -26,6 +29,7 @@
|
|
|
26
29
|
{"source": "src/project_forge_deployment/errors.py", "destination": "tools/project-forge-deployment/project_forge_deployment/errors.py"},
|
|
27
30
|
{"source": "src/project_forge_deployment/home_lab.py", "destination": "tools/project-forge-deployment/project_forge_deployment/home_lab.py"},
|
|
28
31
|
{"source": "src/project_forge_deployment/image_transfer.py", "destination": "tools/project-forge-deployment/project_forge_deployment/image_transfer.py"},
|
|
32
|
+
{"source": "src/project_forge_deployment/ingress_cli.py", "destination": "tools/project-forge-deployment/project_forge_deployment/ingress_cli.py"},
|
|
29
33
|
{"source": "src/project_forge_deployment/keys.py", "destination": "tools/project-forge-deployment/project_forge_deployment/keys.py"},
|
|
30
34
|
{"source": "src/project_forge_deployment/local_cli.py", "destination": "tools/project-forge-deployment/project_forge_deployment/local_cli.py"},
|
|
31
35
|
{"source": "src/project_forge_deployment/local_runtime.py", "destination": "tools/project-forge-deployment/project_forge_deployment/local_runtime.py"},
|
|
@@ -52,6 +56,7 @@
|
|
|
52
56
|
{"source": "src/project_forge_deployment/teardown_remote.py", "destination": "tools/project-forge-deployment/project_forge_deployment/teardown_remote.py"},
|
|
53
57
|
{"source": "src/project_forge_deployment/topology_cli.py", "destination": "tools/project-forge-deployment/project_forge_deployment/topology_cli.py"},
|
|
54
58
|
{"source": "src/project_forge_deployment/trust.py", "destination": "tools/project-forge-deployment/project_forge_deployment/trust.py"},
|
|
59
|
+
{"source": "docs/CLOUDFLARE_INGRESS.md", "destination": "docs/project-forge/CLOUDFLARE_INGRESS.md"},
|
|
55
60
|
{"source": "docs/DEVELOPER_ENVIRONMENTS.md", "destination": "docs/project-forge/DEVELOPER_ENVIRONMENTS.md"},
|
|
56
61
|
{"source": "docs/WORKSPACE_INSTANCES.md", "destination": "docs/project-forge/WORKSPACE_INSTANCES.md"},
|
|
57
62
|
{"source": "docs/ENVIRONMENT_PROOF.md", "destination": "docs/project-forge/ENVIRONMENT_PROOF.md"},
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@mriekki/project-forge",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.10.0",
|
|
5
5
|
"components": {
|
|
6
6
|
"core": {
|
|
7
|
-
"revision": "
|
|
7
|
+
"revision": "8796fd8dc22df8b64078e5b460e1008a8c7a5f7d",
|
|
8
8
|
"version": "4.0.0",
|
|
9
9
|
"distribution": "greenfield-workspace",
|
|
10
10
|
"files": [
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
{
|
|
127
127
|
"source": "core/skills/tear-down/SKILL.md",
|
|
128
128
|
"destination": ".github/skills/tear-down/SKILL.md",
|
|
129
|
-
"sha256": "
|
|
129
|
+
"sha256": "468314030c6033885c46aba50f54410928cbe44c74d73b998b88338fe495f3d9"
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
"source": "core/skills/coach/SKILL.md",
|
|
@@ -326,7 +326,7 @@
|
|
|
326
326
|
]
|
|
327
327
|
},
|
|
328
328
|
"deployment": {
|
|
329
|
-
"revision": "
|
|
329
|
+
"revision": "23799aec3e7c9a99873aaf912b040d43658fa4fb",
|
|
330
330
|
"distribution": "greenfield-project",
|
|
331
331
|
"files": [
|
|
332
332
|
{
|
|
@@ -394,6 +394,21 @@
|
|
|
394
394
|
"destination": "tools/project-forge-deployment/project_forge_deployment/box_platform_remote.py",
|
|
395
395
|
"sha256": "65de94ac4b6e980d83ce99ea55b9555aa96959c11808cce8c6ed537b27c15533"
|
|
396
396
|
},
|
|
397
|
+
{
|
|
398
|
+
"source": "src/project_forge_deployment/cloudflare_api.py",
|
|
399
|
+
"destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_api.py",
|
|
400
|
+
"sha256": "de383926e04f6ebb3b8fd95948b475a9dc40d8da01ff2499afbe0f3a7305d4df"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"source": "src/project_forge_deployment/cloudflare_ingress.py",
|
|
404
|
+
"destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_ingress.py",
|
|
405
|
+
"sha256": "98c77950df1d0bba36da1244001bdd157251238778e7068be0c6c20d2683d2d4"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"source": "src/project_forge_deployment/cloudflare_ingress_remote.py",
|
|
409
|
+
"destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_ingress_remote.py",
|
|
410
|
+
"sha256": "35ac39b4f4538d4ff23f68982cc4b176782378cf03539fa51a228becbdfcf4d0"
|
|
411
|
+
},
|
|
397
412
|
{
|
|
398
413
|
"source": "src/project_forge_deployment/cli.py",
|
|
399
414
|
"destination": "tools/project-forge-deployment/project_forge_deployment/cli.py",
|
|
@@ -407,7 +422,7 @@
|
|
|
407
422
|
{
|
|
408
423
|
"source": "src/project_forge_deployment/entrypoint.py",
|
|
409
424
|
"destination": "tools/project-forge-deployment/project_forge_deployment/entrypoint.py",
|
|
410
|
-
"sha256": "
|
|
425
|
+
"sha256": "021434f8f601ebbff0aae354e6c6b25b36d598a521a7948d02d5f1cc797c22fd"
|
|
411
426
|
},
|
|
412
427
|
{
|
|
413
428
|
"source": "src/project_forge_deployment/environment_cli.py",
|
|
@@ -444,6 +459,11 @@
|
|
|
444
459
|
"destination": "tools/project-forge-deployment/project_forge_deployment/image_transfer.py",
|
|
445
460
|
"sha256": "dadd1fe7724372a4ca0e04ab2280769b6c2381f0f9ae75a096f6c5a5f5add755"
|
|
446
461
|
},
|
|
462
|
+
{
|
|
463
|
+
"source": "src/project_forge_deployment/ingress_cli.py",
|
|
464
|
+
"destination": "tools/project-forge-deployment/project_forge_deployment/ingress_cli.py",
|
|
465
|
+
"sha256": "0e4088a17734f560c750528942fbd34d2464bcdd06d60467a9dc684a73fc18e1"
|
|
466
|
+
},
|
|
447
467
|
{
|
|
448
468
|
"source": "src/project_forge_deployment/keys.py",
|
|
449
469
|
"destination": "tools/project-forge-deployment/project_forge_deployment/keys.py",
|
|
@@ -552,17 +572,17 @@
|
|
|
552
572
|
{
|
|
553
573
|
"source": "src/project_forge_deployment/teardown.py",
|
|
554
574
|
"destination": "tools/project-forge-deployment/project_forge_deployment/teardown.py",
|
|
555
|
-
"sha256": "
|
|
575
|
+
"sha256": "0c75bb1ec77218d1b6ee3f54a626556ee46f2de8e91d1e9afa87200bce39254e"
|
|
556
576
|
},
|
|
557
577
|
{
|
|
558
578
|
"source": "src/project_forge_deployment/teardown_cli.py",
|
|
559
579
|
"destination": "tools/project-forge-deployment/project_forge_deployment/teardown_cli.py",
|
|
560
|
-
"sha256": "
|
|
580
|
+
"sha256": "5dbe92b1d5683824bbf3b4892f9099db2d68ad95c0a514bf73f7b596a62775be"
|
|
561
581
|
},
|
|
562
582
|
{
|
|
563
583
|
"source": "src/project_forge_deployment/teardown_remote.py",
|
|
564
584
|
"destination": "tools/project-forge-deployment/project_forge_deployment/teardown_remote.py",
|
|
565
|
-
"sha256": "
|
|
585
|
+
"sha256": "5a898ee8ce981c60839d12c63a776bf742e2c32bc409fbb29cdfd798cf9baa19"
|
|
566
586
|
},
|
|
567
587
|
{
|
|
568
588
|
"source": "src/project_forge_deployment/topology_cli.py",
|
|
@@ -574,6 +594,11 @@
|
|
|
574
594
|
"destination": "tools/project-forge-deployment/project_forge_deployment/trust.py",
|
|
575
595
|
"sha256": "b143fa828c3a23df3909dacfd78ae56b9ec1159628eb45261d69c0aa6ddd1a7b"
|
|
576
596
|
},
|
|
597
|
+
{
|
|
598
|
+
"source": "docs/CLOUDFLARE_INGRESS.md",
|
|
599
|
+
"destination": "docs/project-forge/CLOUDFLARE_INGRESS.md",
|
|
600
|
+
"sha256": "e0d95aa8e1c204390a51a7900ef60542537c1a9d2eeb017f34920e8dcb5e8329"
|
|
601
|
+
},
|
|
577
602
|
{
|
|
578
603
|
"source": "docs/DEVELOPER_ENVIRONMENTS.md",
|
|
579
604
|
"destination": "docs/project-forge/DEVELOPER_ENVIRONMENTS.md",
|
|
@@ -1,27 +1,52 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tear-down
|
|
3
|
-
description: "
|
|
4
|
-
argument-hint: "[local|<environment>|all]"
|
|
3
|
+
description: "List target-owned Project environments and guide destructive cleanup for local state, one exact named environment, or all explicitly configured Project environments. Preserves ownership boundaries, shared Project Forge infrastructure, and separate Production/public-ingress authorization."
|
|
4
|
+
argument-hint: "[list [<target>]|local|<environment>|all]"
|
|
5
5
|
user-invocable: true
|
|
6
6
|
disable-model-invocation: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Tear Down
|
|
10
10
|
|
|
11
|
-
`/tear-down` is the human-facing destructive
|
|
11
|
+
`/tear-down` is the human-facing inventory and destructive-cleanup workflow for Project-owned deployment state.
|
|
12
12
|
|
|
13
13
|
Logical environment names are data-driven and are not limited to Staging/Production:
|
|
14
14
|
|
|
15
15
|
```text
|
|
16
|
+
/tear-down list production
|
|
16
17
|
/tear-down demo
|
|
17
18
|
/tear-down qa
|
|
18
19
|
/tear-down staging
|
|
19
20
|
/tear-down prod-east
|
|
20
21
|
```
|
|
21
22
|
|
|
22
|
-
The skill owns exact scope selection, read-only preflight, destructive preview, and Human-in-the-Loop authorization.
|
|
23
|
+
The skill owns target selection, exact scope selection, read-only inventory/preflight, destructive preview, and Human-in-the-Loop authorization. Deployment Kit deterministic tooling owns inventory and deletion mechanics.
|
|
23
24
|
|
|
24
|
-
##
|
|
25
|
+
## 0. List what is installed before destructive selection
|
|
26
|
+
|
|
27
|
+
When the Builder asks what is deployed, what can be removed, or invokes `list`, use the Deployment Kit read-only target inventory:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
project-forge-deploy target inventory <target> --json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This is the canonical application-cleanup inventory. It correlates the target's managed runtime, trusted-LAN route, and managed public-ingress ownership into application/environment tear-down candidates while reporting the shared host platform separately.
|
|
34
|
+
|
|
35
|
+
The command is intentionally independent of the consumer repository. If a consumer checkout or even its GitHub repository has already been deleted, the same installed/standalone Deployment Kit command may still inventory the target from host-owned metadata and user-scoped target access configuration.
|
|
36
|
+
|
|
37
|
+
Inside a consumer repository, use the repository-provided Deployment Kit launcher when that is the installed contract. Detect the current platform/runtime and invoke the portable Python entrypoint or installed `project-forge-deploy`; do not assume PowerShell syntax or Windows-only paths. macOS/Linux shells and Windows/PowerShell must reach the same deterministic CLI semantics.
|
|
38
|
+
|
|
39
|
+
Do not reconstruct inventory by ad-hoc `ssh`, `docker ps`, filesystem listing, or Cloudflare commands when Deployment Kit target inventory is available.
|
|
40
|
+
|
|
41
|
+
If a public-ingress scope cannot be inferred from the application/environment name, pass only the exact additional scope established by repository topology or trusted operator evidence:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
--ingress-scope <scope>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Listing is read-only. It does not authorize later deletion.
|
|
48
|
+
|
|
49
|
+
## 1. Resolve exact project topology when the repository exists
|
|
25
50
|
|
|
26
51
|
Prefer:
|
|
27
52
|
|
|
@@ -37,8 +62,12 @@ If `demo` is configured, `/tear-down demo` means exactly `demo`. If `prod-east`
|
|
|
37
62
|
|
|
38
63
|
Never guess a similar environment and never widen scope after failure.
|
|
39
64
|
|
|
65
|
+
When the repository no longer exists, use target inventory only for target-owned application identity and resource ownership. Do not invent repository-only topology facts. In particular, if a non-literal custom environment's Production classification cannot be established from trusted evidence, stop before destructive authorization rather than weakening the Production-data gate.
|
|
66
|
+
|
|
40
67
|
## 2. Select exact destructive scope
|
|
41
68
|
|
|
69
|
+
The destructive scope contract remains exactly `[local|<environment>|all]`; `list` is a separate read-only discovery mode and never becomes a destructive scope.
|
|
70
|
+
|
|
42
71
|
```text
|
|
43
72
|
local
|
|
44
73
|
<one exact repository-defined named environment>
|
|
@@ -47,13 +76,15 @@ all
|
|
|
47
76
|
|
|
48
77
|
If no scope is named, present only scopes the repository actually supports; never silently default to a Production environment or `all`.
|
|
49
78
|
|
|
79
|
+
For orphaned deployments where the consumer repository is unavailable, present only exact application/environment candidates returned by target inventory.
|
|
80
|
+
|
|
50
81
|
`all` means only all explicitly configured scopes for this Project; it never means machine-wide cleanup.
|
|
51
82
|
|
|
52
83
|
Selecting scope does not authorize deletion. Physical colocation never merges ownership: deleting `demo` must not remove `staging` or `prod-east` merely because they share hardware.
|
|
53
84
|
|
|
54
|
-
If the requested name is absent from project topology, stop. Do not substitute another environment.
|
|
85
|
+
If the requested name is absent from project topology or target inventory, stop. Do not substitute another environment.
|
|
55
86
|
|
|
56
|
-
## 3. Resolve Production classification from the deletion set
|
|
87
|
+
## 3. Resolve Production and public-ingress classification from the deletion set
|
|
57
88
|
|
|
58
89
|
For each selected environment identify:
|
|
59
90
|
|
|
@@ -62,6 +93,8 @@ environment name
|
|
|
62
93
|
purpose / Production classification
|
|
63
94
|
placement / target
|
|
64
95
|
repository/application identity
|
|
96
|
+
trusted-LAN route
|
|
97
|
+
managed public-ingress scope/hostname, when present
|
|
65
98
|
ownership markers required by installed tooling
|
|
66
99
|
```
|
|
67
100
|
|
|
@@ -71,7 +104,13 @@ For current Deployment Kit consumers, custom Production names may be passed expl
|
|
|
71
104
|
--production-environment prod-east
|
|
72
105
|
```
|
|
73
106
|
|
|
74
|
-
|
|
107
|
+
When target inventory reports an owned public route, preserve its exact environment-to-ingress-scope binding for cleanup, conceptually:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
--public-ingress-scope production=production
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use actual installed help/version evidence. If the installed tool cannot preserve a custom Production classification or exact public-ingress ownership, stop rather than downgrading the destructive gate.
|
|
75
114
|
|
|
76
115
|
## 4. Present an exact destructive preview
|
|
77
116
|
|
|
@@ -86,10 +125,17 @@ Selected environments:
|
|
|
86
125
|
<name> → <placement/target> — <purpose/classification>
|
|
87
126
|
|
|
88
127
|
Will remove:
|
|
89
|
-
|
|
128
|
+
owned public hostname/DNS + tunnel rule, when selected
|
|
129
|
+
owned trusted-LAN route
|
|
130
|
+
owned application container/runtime
|
|
131
|
+
owned releases, evidence, persistent data, and backups
|
|
132
|
+
remote immutable image only when no other managed release/container still references it
|
|
90
133
|
|
|
91
134
|
Will preserve:
|
|
92
|
-
|
|
135
|
+
shared Project Forge Caddy/network/platform
|
|
136
|
+
shared Cloudflare tunnel/runtime
|
|
137
|
+
unrelated Projects/environments
|
|
138
|
+
remote images still referenced by another managed release/container
|
|
93
139
|
```
|
|
94
140
|
|
|
95
141
|
Do not promise deletion of named/shared Docker volumes unless the installed deterministic contract proves exact ownership and explicitly includes them. Do not promise deletion of any shared/unrelated resource whose ownership cannot be proven.
|
|
@@ -108,33 +154,45 @@ whenever **any selected environment is Production-class**, require the stronger
|
|
|
108
154
|
[PRODUCTION DATA DELETE]
|
|
109
155
|
```
|
|
110
156
|
|
|
157
|
+
Whenever the reviewed deletion set includes an owned public-ingress route, also require the separate public-ingress removal authorization, conceptually:
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
[PUBLIC INGRESS ROUTE REMOVE]
|
|
161
|
+
```
|
|
162
|
+
|
|
111
163
|
Resolve exact phrases from the installed tool/version. Do not tie the Production-data gate only to literal scope strings such as `production` or `all`; it depends on the actual resolved deletion set.
|
|
112
164
|
|
|
113
|
-
A prior `/setup`, environment proof, `/deploy`, merge, CI, or other approval never authorizes destructive cleanup.
|
|
165
|
+
A prior `/setup`, environment proof, `/deploy`, merge, CI, public-route registration, or other approval never authorizes destructive cleanup.
|
|
114
166
|
|
|
115
|
-
If identity, selected environment set, classification, binding, ownership evidence, or deletion set changes after preview, require renewed review/authorization.
|
|
167
|
+
If identity, selected environment set, classification, binding, ownership evidence, public-ingress scope, or deletion set changes after preview, require renewed review/authorization.
|
|
116
168
|
|
|
117
|
-
## 6. Execute only
|
|
169
|
+
## 6. Execute only Deployment Kit deterministic cleanup
|
|
118
170
|
|
|
119
171
|
After authorization, invoke only the approved tear-down command with exact named scopes/bindings. Do not improvise shell commands.
|
|
120
172
|
|
|
121
173
|
For a generalized current Deployment Kit invocation, exact bindings may look conceptually like:
|
|
122
174
|
|
|
123
175
|
```text
|
|
124
|
-
--environment-target demo=<target>
|
|
125
176
|
--environment-target staging=<target>
|
|
126
|
-
--environment-target
|
|
177
|
+
--environment-target production=<target>
|
|
178
|
+
--public-ingress-scope production=production
|
|
127
179
|
--production-environment prod-east
|
|
128
180
|
```
|
|
129
181
|
|
|
130
|
-
Use the installed contract rather than inventing flags for older versions.
|
|
182
|
+
Use target inventory output and the installed contract rather than inventing flags for older versions.
|
|
131
183
|
|
|
132
|
-
|
|
184
|
+
Public ingress must be removed before the trusted-LAN route/runtime so a public hostname is never deliberately left pointing at a removed origin. The shared Cloudflare tunnel/runtime is host infrastructure and must remain.
|
|
185
|
+
|
|
186
|
+
Remote image cleanup is bounded: remove only the selected environment's immutable image when no other managed release metadata or container still references it. Never use Docker system/image/volume prune.
|
|
187
|
+
|
|
188
|
+
Never add machine-wide Docker prune, shared platform/network removal, unrelated environment cleanup, host reprovisioning, target rebinding, credential/trust deletion, registry-package deletion, or shared Cloudflare tunnel deletion unless a separate lifecycle operation explicitly owns and authorizes it.
|
|
133
189
|
|
|
134
190
|
If aggregate `all` cleanup stops partway, report completed and remaining scopes. Do not manually compensate by deleting the rest.
|
|
135
191
|
|
|
136
192
|
## 7. Verify and report outcome
|
|
137
193
|
|
|
138
|
-
|
|
194
|
+
Re-run target inventory after cleanup when available. Report only observed evidence: requested scope, exact environment names/targets processed, Production-class environments included, public routes removed/already absent, trusted-LAN routes removed/already absent, runtime/data removal, remote image removed/retained, local state processed, and any remaining scopes after a partial stop.
|
|
195
|
+
|
|
196
|
+
Shared Project Forge platform state and unrelated application candidates should remain present and unchanged.
|
|
139
197
|
|
|
140
198
|
When the installed deterministic contract is idempotent, already-absent owned resources should remain safe to re-check/re-run without broadening scope.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Cloudflare Tunnel public ingress
|
|
2
|
+
|
|
3
|
+
Status: **PARTIAL — IMPLEMENTED MECHANICS; LIVE DOMAIN/GENERATED-CONSUMER PROOF PENDING**
|
|
4
|
+
|
|
5
|
+
Project Forge models Cloudflare Tunnel as optional **public ingress**, not as a deployment target or remote-management transport.
|
|
6
|
+
|
|
7
|
+
The supported shape is:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
placement: remote-target
|
|
11
|
+
adapter: remote-ssh
|
|
12
|
+
exposure: public
|
|
13
|
+
ingress provider: cloudflare-tunnel
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`remote-ssh` continues to own verified workstation-to-target management. Cloudflare Tunnel owns only internet-facing application ingress.
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
Internet
|
|
22
|
+
-> Cloudflare edge / public DNS / HTTPS
|
|
23
|
+
-> remotely managed Cloudflare Tunnel
|
|
24
|
+
-> cloudflared container on the selected Ubuntu target
|
|
25
|
+
-> shared Project Forge caddy-net
|
|
26
|
+
-> project-forge-caddy:80
|
|
27
|
+
-> existing Project Forge application container
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The cloudflared runtime publishes no host ports. It initiates outbound connections to Cloudflare and joins the existing shared Docker network.
|
|
31
|
+
|
|
32
|
+
Public routes do **not** replace the trusted-LAN Route Registry. Each public Cloudflare hostname forwards to the shared Caddy container while setting `originRequest.httpHostHeader` to an already-owned `.local` Project Forge hostname. Caddy therefore reuses the existing Project/environment route and upstream ownership contract.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
inventory.example.com
|
|
38
|
+
-> Cloudflare Tunnel
|
|
39
|
+
-> http://project-forge-caddy:80
|
|
40
|
+
Host: inventory-production.local
|
|
41
|
+
-> existing Caddy route
|
|
42
|
+
-> inventory Production container
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Shared versus Project-owned state
|
|
46
|
+
|
|
47
|
+
The shared ingress scope owns:
|
|
48
|
+
|
|
49
|
+
- one remotely managed Cloudflare Tunnel identity;
|
|
50
|
+
- one cloudflared container;
|
|
51
|
+
- the host-side tunnel token;
|
|
52
|
+
- the Cloudflare account/zone/tunnel non-secret identifiers;
|
|
53
|
+
- the connector Compose definition;
|
|
54
|
+
- a host-side public-route ownership registry.
|
|
55
|
+
|
|
56
|
+
A Project/environment public route owns:
|
|
57
|
+
|
|
58
|
+
- exact `ownerRepo`, environment, app slug and route ID;
|
|
59
|
+
- one exact public hostname;
|
|
60
|
+
- one exact existing `.local` origin hostname;
|
|
61
|
+
- one exact Cloudflare tunnel ingress rule;
|
|
62
|
+
- one exact proxied Cloudflare CNAME record.
|
|
63
|
+
|
|
64
|
+
Removing a Project route must preserve the shared tunnel, cloudflared runtime, Caddy platform, unrelated DNS records and unrelated Projects.
|
|
65
|
+
|
|
66
|
+
## Secrets boundary
|
|
67
|
+
|
|
68
|
+
The Cloudflare API token is workstation-only. It is read from:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
PROJECT_FORGE_CLOUDFLARE_API_TOKEN
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The token is never accepted as a CLI argument and is not written to:
|
|
75
|
+
|
|
76
|
+
- Git;
|
|
77
|
+
- `PROJECT_BOOTSTRAP.json`;
|
|
78
|
+
- `DEPLOYMENT_TOPOLOGY.json`;
|
|
79
|
+
- the portable Project Forge developer profile;
|
|
80
|
+
- the Production host;
|
|
81
|
+
- command-line process arguments.
|
|
82
|
+
|
|
83
|
+
The remotely managed Tunnel token is retrieved only when applying shared ingress. It is streamed through the existing verified SSH operation and written beneath the selected root-owned ingress scope. It is not printed or returned in plans/status.
|
|
84
|
+
|
|
85
|
+
Re-applying shared ingress force-recreates the cloudflared container after securely replacing the token file, so refreshed Tunnel credentials are actually consumed by the running connector.
|
|
86
|
+
|
|
87
|
+
## Shared runtime commands
|
|
88
|
+
|
|
89
|
+
Read-only:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
project-forge-deploy ingress cloudflare status <target> --scope production
|
|
93
|
+
project-forge-deploy ingress cloudflare doctor <target> --scope production
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Configure/create/adopt the shared scope after the domain exists:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
project-forge-deploy ingress cloudflare configure <target> \
|
|
100
|
+
--scope production \
|
|
101
|
+
--zone example.com
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The write requires:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
[HOST PUBLIC INGRESS WRITE]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The default tunnel name is:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
project-forge-<scope>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
An existing tunnel is adopted only when it is remotely managed and has no application ingress rules beyond the final fail-closed `http_status:404` rule. Project Forge never silently takes over a tunnel with existing application configuration.
|
|
117
|
+
|
|
118
|
+
## Project public routes
|
|
119
|
+
|
|
120
|
+
Inventory:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
project-forge-deploy ingress cloudflare route inventory <target> --scope production
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Register:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
project-forge-deploy ingress cloudflare route register <target> \
|
|
130
|
+
--scope production \
|
|
131
|
+
--owner-repo owner/repository \
|
|
132
|
+
--environment production \
|
|
133
|
+
--app-slug inventory \
|
|
134
|
+
--public-hostname inventory.example.com \
|
|
135
|
+
--origin-hostname inventory-production.local
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Registration requires:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
[PUBLIC INGRESS ROUTE WRITE]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Remove:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
project-forge-deploy ingress cloudflare route remove <target> \
|
|
148
|
+
--scope production \
|
|
149
|
+
--owner-repo owner/repository \
|
|
150
|
+
--environment production \
|
|
151
|
+
--app-slug inventory
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Removal requires:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
[PUBLIC INGRESS ROUTE REMOVE]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Route safety
|
|
161
|
+
|
|
162
|
+
Before publishing a hostname, Deployment Kit verifies:
|
|
163
|
+
|
|
164
|
+
1. the selected shared Cloudflare ingress scope is ready;
|
|
165
|
+
2. the exact Project/environment `.local` route already exists and owns the requested origin hostname;
|
|
166
|
+
3. the public hostname is not owned by another managed Project route;
|
|
167
|
+
4. the Cloudflare tunnel configuration has the expected final fail-closed 404 rule;
|
|
168
|
+
5. no conflicting tunnel ingress rule exists;
|
|
169
|
+
6. no conflicting DNS record exists;
|
|
170
|
+
7. any existing DNS record exactly matches Project Forge's tunnel target and ownership marker.
|
|
171
|
+
|
|
172
|
+
The current partial adapter intentionally supports one first-level subdomain beneath the selected zone, such as `inventory.example.com`. Deeper names such as `api.inventory.example.com` require an explicit certificate/support review rather than being silently accepted.
|
|
173
|
+
|
|
174
|
+
## Domain is deliberately late-bound
|
|
175
|
+
|
|
176
|
+
A Builder does not need a domain to generate or develop a Project.
|
|
177
|
+
|
|
178
|
+
Bootstrap may record:
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
exposure: public
|
|
182
|
+
ingress.provider: cloudflare-tunnel
|
|
183
|
+
ingress.bindingState: deferred
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
without a zone or public hostname. Once a domain is registered and active in Cloudflare, environment setup/readiness can bind the shared ingress scope and each Project can register its exact public hostname. Regeneration is not required merely to choose the domain later.
|
|
187
|
+
|
|
188
|
+
The shared Cloudflare zone is environment infrastructure and should not be copied into every Project as a credential-like default. The exact application hostname remains Project/environment state.
|
|
189
|
+
|
|
190
|
+
## Current cloudflared runtime
|
|
191
|
+
|
|
192
|
+
The first partial implementation selects Cloudflare release `2026.10.4` and pins the actual Docker pull to the verified multi-platform image index digest:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
cloudflare/cloudflared:2026.10.4@sha256:c6c7ae1d5dfdcb094ad344ed68ccffc26635c9c6be80965664c33baf064b0515
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`latest` is never used, and a mutable tag is not the final runtime identity. Promotion from `partial` to `supported` remains blocked until the package/release process and unrelated generated consumers prove this pinned artifact together with the live Cloudflare integration path.
|
|
199
|
+
|
|
200
|
+
## Required live proof before support promotion
|
|
201
|
+
|
|
202
|
+
At minimum:
|
|
203
|
+
|
|
204
|
+
1. create/adopt a real remotely managed Tunnel without manually logging into the Ubuntu host;
|
|
205
|
+
2. install cloudflared through verified Project Forge SSH and prove no inbound host/router port is required;
|
|
206
|
+
3. publish a real first-level hostname and verify external HTTPS reaches the expected Project container;
|
|
207
|
+
4. publish a second unrelated Project through the same shared scope and prove isolation/collision checks;
|
|
208
|
+
5. remove one Project route while preserving the other Project and the shared tunnel;
|
|
209
|
+
6. exercise DNS/provider failure rollback;
|
|
210
|
+
7. exercise changed host/provider state and prove stale plans fail closed;
|
|
211
|
+
8. prove separate logical Production/Staging scopes when both are configured;
|
|
212
|
+
9. prove generated/default-branch CI and human workspace acceptance.
|
|
213
|
+
|
|
214
|
+
Tunnel connector health alone is not sufficient application evidence. Final Production readiness must include an external HTTPS application probe.
|
|
215
|
+
|
|
216
|
+
## Deliberate V1 exclusions
|
|
217
|
+
|
|
218
|
+
- Cloudflare Access / Zero Trust identity policy;
|
|
219
|
+
- wildcard DNS publication for arbitrary applications;
|
|
220
|
+
- direct public router/firewall fallback;
|
|
221
|
+
- automatic domain purchase or registrar mutation;
|
|
222
|
+
- deeper-than-first-level public hostnames;
|
|
223
|
+
- implicit adoption of existing unmanaged Tunnel/DNS rules;
|
|
224
|
+
- deleting a shared Tunnel as part of normal Project tear-down.
|
|
225
|
+
|
|
226
|
+
These remain separate capabilities or future decisions.
|