direxio-deployer 0.1.0 → 0.1.1
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/README.md +3 -1
- package/README_zh.md +3 -1
- package/SKILL.md +2 -2
- package/bin/direxio-deployer.mjs +1 -2
- package/package.json +2 -3
- package/references/deployment-lessons.md +5 -7
- package/references/deployment-workflow.md +1 -1
- package/references/tooling.md +11 -12
- package/references/user-journey.md +2 -2
- package/references/voip-turn-runbook.md +2 -2
- package/references/windows-deployment-notes.md +2 -1
- package/scripts/destroy.sh +24 -43
- package/scripts/json.mjs +841 -0
- package/scripts/lib/aws.sh +5 -1
- package/scripts/lib/json.sh +114 -0
- package/scripts/lib/operation_report.sh +8 -195
- package/scripts/lib/ops.sh +8 -21
- package/scripts/lib/state.sh +18 -44
- package/scripts/mcp-tools-list.mjs +21 -4
- package/scripts/orchestrate.sh +147 -249
- package/scripts/phases/s3_provision.sh +5 -10
- package/scripts/phases/s5_init_tokens.sh +7 -17
- package/scripts/phases/s6_wire_local.sh +9 -35
- package/scripts/phases/s7_verify_e2e.sh +5 -5
- package/scripts/pricing-estimate.sh +36 -80
- package/tests/aws_credentials_test.sh +0 -139
- package/tests/connect_daemon_runtime_check_test.sh +0 -120
- package/tests/default_paths_test.sh +0 -58
- package/tests/destroy_local_bridge_test.sh +0 -154
- package/tests/destroy_root_identity_test.sh +0 -91
- package/tests/destroy_route53_zone_test.sh +0 -80
- package/tests/domain_authoritative_dns_test.sh +0 -49
- package/tests/mcp_doctor_runtime_check_test.sh +0 -86
- package/tests/mcp_smoke_runtime_check_test.sh +0 -121
- package/tests/mcp_tools_runtime_check_test.sh +0 -123
- package/tests/npm_skill_distribution_test.sh +0 -95
- package/tests/operation_report_test.sh +0 -258
- package/tests/orchestrate_status_recovery_test.sh +0 -91
- package/tests/phase_timeout_test.sh +0 -88
- package/tests/pricing_estimate_test.sh +0 -159
- package/tests/render_userdata_remote_nodes_test.sh +0 -40
- package/tests/root_volume_tracking_test.sh +0 -41
- package/tests/route53_overwrite_guard_test.sh +0 -86
- package/tests/route53_zone_auto_create_test.sh +0 -66
- package/tests/runtime_summary_check_test.sh +0 -203
- package/tests/s6_wire_local_test.sh +0 -405
- package/tests/skill_structure_test.sh +0 -298
- package/tests/update_reset_ops_test.sh +0 -230
- package/tests/user_confirmation_gates_test.sh +0 -152
package/scripts/lib/aws.sh
CHANGED
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
# Some local proxy setups truncate AWS API TLS (UNEXPECTED_EOF). Bypass proxies
|
|
5
5
|
# for AWS endpoints in every phase that calls aws.
|
|
6
6
|
|
|
7
|
+
AWS_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
8
|
+
# shellcheck disable=SC1090
|
|
9
|
+
source "$AWS_LIB_DIR/json.sh"
|
|
10
|
+
|
|
7
11
|
aws_env_prep() {
|
|
8
12
|
local region=${AWS_DEFAULT_REGION:-${AWS_REGION:-}}
|
|
9
13
|
if [ -n "${STATE_JSON:-}" ] && [ -f "$STATE_JSON" ]; then
|
|
10
14
|
local state_region
|
|
11
|
-
state_region=$(
|
|
15
|
+
state_region=$(json_get "$STATE_JSON" region 2>/dev/null || true)
|
|
12
16
|
[ -n "$state_region" ] && region="$state_region"
|
|
13
17
|
fi
|
|
14
18
|
if [ -z "$region" ]; then
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Portable JSON helpers backed by Node.js.
|
|
3
|
+
|
|
4
|
+
JSON_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
5
|
+
JSON_HELPER="$JSON_LIB_DIR/../json.mjs"
|
|
6
|
+
|
|
7
|
+
json_node() {
|
|
8
|
+
local uname_s node_path
|
|
9
|
+
if [ -n "${NODE:-}" ]; then
|
|
10
|
+
printf '%s\n' "$NODE"
|
|
11
|
+
return 0
|
|
12
|
+
fi
|
|
13
|
+
uname_s=$(uname -s 2>/dev/null || printf unknown)
|
|
14
|
+
if command -v node >/dev/null 2>&1; then
|
|
15
|
+
node_path=$(command -v node)
|
|
16
|
+
case "$uname_s:$node_path" in
|
|
17
|
+
Linux*:*.exe|Linux*:/mnt/*|Linux*:/c/*) ;;
|
|
18
|
+
*)
|
|
19
|
+
printf '%s\n' "$node_path"
|
|
20
|
+
return 0
|
|
21
|
+
;;
|
|
22
|
+
esac
|
|
23
|
+
fi
|
|
24
|
+
case "$uname_s" in
|
|
25
|
+
Linux*)
|
|
26
|
+
local user_home
|
|
27
|
+
user_home=$(eval "printf '%s' ~${USER:-}" 2>/dev/null || true)
|
|
28
|
+
for node_path in "$HOME/.local/node/bin/node" "$user_home/.local/node/bin/node" /usr/local/bin/node /usr/bin/node; do
|
|
29
|
+
if [ -x "$node_path" ]; then
|
|
30
|
+
printf '%s\n' "$node_path"
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
echo "POSIX node is required for JSON processing in Linux/WSL; Windows node.exe cannot read POSIX paths." >&2
|
|
35
|
+
return 1
|
|
36
|
+
;;
|
|
37
|
+
esac
|
|
38
|
+
if command -v node.exe >/dev/null 2>&1; then
|
|
39
|
+
command -v node.exe
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
echo "node is required for JSON processing." >&2
|
|
43
|
+
return 1
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
json_cli() {
|
|
47
|
+
local node_bin
|
|
48
|
+
node_bin=$(json_node) || return 1
|
|
49
|
+
"$node_bin" "$JSON_HELPER" "$@"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
json_get() {
|
|
53
|
+
json_cli get "$@"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
json_stdin_get() {
|
|
57
|
+
json_cli stdin-get "$@"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
json_assert() {
|
|
61
|
+
json_cli assert "$@"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
json_stdin_assert() {
|
|
65
|
+
json_cli stdin-assert "$@"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
json_check() {
|
|
69
|
+
json_cli check "$@"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
json_entries() {
|
|
73
|
+
json_cli entries "$@"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
json_stdin_tsv() {
|
|
77
|
+
json_cli stdin-tsv "$@"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
json_stdin_join() {
|
|
81
|
+
json_cli stdin-join "$@"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
json_stdin_route53_a_values() {
|
|
85
|
+
json_cli stdin-route53-a-values "$@"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
json_stdin_route53_a_present() {
|
|
89
|
+
json_cli stdin-route53-a-present "$@"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
json_stdin_price_usd() {
|
|
93
|
+
json_cli stdin-price-usd "$@"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
json_length() {
|
|
97
|
+
json_cli length "$@"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
json_type() {
|
|
101
|
+
json_cli type "$@"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
json_build() {
|
|
105
|
+
json_cli build "$@"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
json_mutate() {
|
|
109
|
+
json_cli mutate "$@"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
json_valid() {
|
|
113
|
+
json_cli valid "$@"
|
|
114
|
+
}
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# lib/operation_report.sh - redacted operation reports for deploy/destroy flows.
|
|
3
3
|
|
|
4
|
+
OPERATION_REPORT_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
5
|
+
# shellcheck disable=SC1090
|
|
6
|
+
source "$OPERATION_REPORT_LIB_DIR/json.sh"
|
|
7
|
+
|
|
4
8
|
operation_report_now() {
|
|
5
9
|
date -u +%Y-%m-%dT%H:%M:%SZ
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
operation_report_service_id() {
|
|
9
13
|
local state=$1 service_id
|
|
10
|
-
service_id=$(
|
|
14
|
+
service_id=$(json_get "$state" agent_service_id)
|
|
15
|
+
[ -n "$service_id" ] || service_id=$(json_get "$state" domain)
|
|
11
16
|
printf '%s\n' "${service_id:-unknown-service}"
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
operation_report_default_path() {
|
|
15
20
|
local operation=$1 state=$2 service_id service_dir root
|
|
16
21
|
service_id=$(operation_report_service_id "$state")
|
|
17
|
-
service_dir=$(
|
|
22
|
+
service_dir=$(json_get "$state" agent_service_dir)
|
|
18
23
|
[ -n "$service_dir" ] || service_dir=$(dirname "$state")
|
|
19
24
|
case "$operation" in
|
|
20
25
|
destroy)
|
|
@@ -29,199 +34,7 @@ operation_report_default_path() {
|
|
|
29
34
|
|
|
30
35
|
operation_report_json() {
|
|
31
36
|
local operation=$1 status=$2 state=$3 generated_at=$4
|
|
32
|
-
|
|
33
|
-
--arg operation_type "$operation" \
|
|
34
|
-
--arg status "$status" \
|
|
35
|
-
--arg generated_at "$generated_at" \
|
|
36
|
-
--arg state_json "$state" \
|
|
37
|
-
--slurpfile state "$state" '
|
|
38
|
-
$state[0] as $st |
|
|
39
|
-
def redacted_status:
|
|
40
|
-
if (($st.password // "") | tostring | length) > 0
|
|
41
|
-
then "available_in_state_password_field_redacted"
|
|
42
|
-
else "missing"
|
|
43
|
-
end;
|
|
44
|
-
def phase_statuses:
|
|
45
|
-
($st.phases // {} | with_entries(.value = (.value.status // "unknown")));
|
|
46
|
-
def user_gate($gate; $default):
|
|
47
|
-
($st.user_confirmations[$gate].status // $default);
|
|
48
|
-
def local_refresh_status:
|
|
49
|
-
if ($st.agent_install_status // "") == "refresh_pending"
|
|
50
|
-
then "refresh_pending"
|
|
51
|
-
else "current_or_not_recorded"
|
|
52
|
-
end;
|
|
53
|
-
def redact_text($value):
|
|
54
|
-
def redact_one($secret):
|
|
55
|
-
if (($secret // "") | tostring | length) > 0
|
|
56
|
-
then split($secret | tostring) | join("<redacted>")
|
|
57
|
-
else .
|
|
58
|
-
end;
|
|
59
|
-
($value // "" | tostring)
|
|
60
|
-
| redact_one($st.password)
|
|
61
|
-
| redact_one($st.access_token)
|
|
62
|
-
| redact_one($st.agent_token)
|
|
63
|
-
| redact_one($st.matrix_access_token)
|
|
64
|
-
| redact_one($st.owner_access_token)
|
|
65
|
-
| redact_one($st.aws_secret_access_key)
|
|
66
|
-
| redact_one($st.aws_session_token)
|
|
67
|
-
| gsub("[0-9]{8,}"; "<redacted>");
|
|
68
|
-
def user_gate_detail($gate; $default):
|
|
69
|
-
($st.user_confirmations[$gate] // {}) as $gate_state |
|
|
70
|
-
{
|
|
71
|
-
status: ($gate_state.status // $default),
|
|
72
|
-
ts: ($gate_state.ts // ""),
|
|
73
|
-
evidence: redact_text($gate_state.evidence // ""),
|
|
74
|
-
evidence_redacted: ((redact_text($gate_state.evidence // "")) != (($gate_state.evidence // "") | tostring))
|
|
75
|
-
}
|
|
76
|
-
+ (if $gate == "agent_mcp_runtime" then {
|
|
77
|
-
runtime_summary_status: ($gate_state.runtime_summary_status // ""),
|
|
78
|
-
runtime_probe_confirmed: ($gate_state.runtime_probe_confirmed // false)
|
|
79
|
-
} else {} end);
|
|
80
|
-
def billable:
|
|
81
|
-
[
|
|
82
|
-
(if (($st.resources.instance_id // "") | tostring | length) > 0 then "EC2 \($st.resources.instance_id)" else empty end),
|
|
83
|
-
(if (($st.resources.root_volume_id // "") | tostring | length) > 0 then "EBS root volume \($st.resources.root_volume_id)" else empty end),
|
|
84
|
-
(if (($st.resources.public_ip // "") | tostring | length) > 0 then "public IPv4 \($st.resources.public_ip)" else empty end),
|
|
85
|
-
(if (($st.resources.eip_id // "") | tostring | length) > 0 then "Elastic IP \($st.resources.eip_id)" else empty end),
|
|
86
|
-
(if (($st.resources.route53_zone_id // "") | tostring | length) > 0 then "Route53 hosted zone \($st.resources.route53_zone_id)" else empty end)
|
|
87
|
-
];
|
|
88
|
-
def destroy_status($key):
|
|
89
|
-
($st.destroy_evidence[$key].status // "not_checked");
|
|
90
|
-
def status_not_in($status; $safe):
|
|
91
|
-
(($safe | index($status)) == null);
|
|
92
|
-
def destroy_billable_residue:
|
|
93
|
-
[
|
|
94
|
-
(if (($st.resources.instance_id // "") | tostring | length) > 0
|
|
95
|
-
and status_not_in(destroy_status("ec2_instance"); ["terminated", "not_found", "skipped"])
|
|
96
|
-
then "EC2 \($st.resources.instance_id) status=\(destroy_status("ec2_instance"))"
|
|
97
|
-
else empty end),
|
|
98
|
-
(if (($st.resources.root_volume_id // "") | tostring | length) > 0
|
|
99
|
-
and status_not_in(destroy_status("ebs_root_volume"); ["deleted", "skipped"])
|
|
100
|
-
then "EBS root volume \($st.resources.root_volume_id) status=\(destroy_status("ebs_root_volume"))"
|
|
101
|
-
else empty end),
|
|
102
|
-
(if (($st.resources.eip_id // "") | tostring | length) > 0
|
|
103
|
-
and status_not_in(destroy_status("elastic_ip"); ["released", "skipped"])
|
|
104
|
-
then "Elastic IP \($st.resources.eip_id) status=\(destroy_status("elastic_ip"))"
|
|
105
|
-
else empty end),
|
|
106
|
-
(if (($st.resources.route53_zone_id // "") | tostring | length) > 0
|
|
107
|
-
and status_not_in(destroy_status("route53_hosted_zone"); ["deleted", "skipped"])
|
|
108
|
-
then "Route53 hosted zone \($st.resources.route53_zone_id) status=\(destroy_status("route53_hosted_zone"))"
|
|
109
|
-
else empty end)
|
|
110
|
-
];
|
|
111
|
-
{
|
|
112
|
-
operation_type: $operation_type,
|
|
113
|
-
status: $status,
|
|
114
|
-
generated_at: $generated_at,
|
|
115
|
-
domain: ($st.domain // ""),
|
|
116
|
-
service_id: ($st.agent_service_id // $st.domain // ""),
|
|
117
|
-
service_dir: ($st.agent_service_dir // ""),
|
|
118
|
-
state_json: $state_json,
|
|
119
|
-
delivery: {
|
|
120
|
-
app_domain: ($st.domain // ""),
|
|
121
|
-
product_completion_status: $status,
|
|
122
|
-
init_code_status: redacted_status,
|
|
123
|
-
init_code_secret_redacted: true,
|
|
124
|
-
user_path: "enter app_domain and the eight-digit initialization code in the App"
|
|
125
|
-
},
|
|
126
|
-
agent: {
|
|
127
|
-
node_id: ($st.agent_node_id // ""),
|
|
128
|
-
room_id: ($st.agent_room_id // ""),
|
|
129
|
-
runtime: ($st.agent_runtime // "unknown"),
|
|
130
|
-
service_id: ($st.agent_service_id // $st.domain // ""),
|
|
131
|
-
credentials_file: ($st.agent_credentials_file // "")
|
|
132
|
-
},
|
|
133
|
-
gates: {
|
|
134
|
-
automated: phase_statuses,
|
|
135
|
-
user_confirmation: {
|
|
136
|
-
app_initialization: user_gate("app_initialization"; "pending_user_confirmation"),
|
|
137
|
-
real_chat: user_gate("real_chat"; "pending_user_confirmation"),
|
|
138
|
-
agent_mcp_runtime: user_gate("agent_mcp_runtime"; "pending_runtime_confirmation")
|
|
139
|
-
},
|
|
140
|
-
user_confirmation_details: {
|
|
141
|
-
app_initialization: user_gate_detail("app_initialization"; "pending_user_confirmation"),
|
|
142
|
-
real_chat: user_gate_detail("real_chat"; "pending_user_confirmation"),
|
|
143
|
-
agent_mcp_runtime: user_gate_detail("agent_mcp_runtime"; "pending_runtime_confirmation")
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
runtime_checks: {
|
|
147
|
-
summary: ($st.runtime_checks.summary // {status: "not_run"}),
|
|
148
|
-
connect_daemon: ($st.runtime_checks.connect_daemon // {status: "not_run"}),
|
|
149
|
-
mcp_doctor: ($st.runtime_checks.mcp_doctor // {status: "not_run"}),
|
|
150
|
-
mcp_smoke: ($st.runtime_checks.mcp_smoke // {status: "not_run"}),
|
|
151
|
-
mcp_tools: ($st.runtime_checks.mcp_tools // {status: "not_run"})
|
|
152
|
-
},
|
|
153
|
-
credentials: {
|
|
154
|
-
status: local_refresh_status,
|
|
155
|
-
credentials_file: ($st.agent_credentials_file // ""),
|
|
156
|
-
contains_secrets: true,
|
|
157
|
-
values_redacted: true
|
|
158
|
-
},
|
|
159
|
-
connect: {
|
|
160
|
-
package: ($st.cc_connect_npm_package // "direxio-connent@latest"),
|
|
161
|
-
agent: ($st.cc_connect_agent // ""),
|
|
162
|
-
config: ($st.cc_connect_config // ""),
|
|
163
|
-
install_status: ($st.agent_install_status // "")
|
|
164
|
-
},
|
|
165
|
-
mcp: {
|
|
166
|
-
status: local_refresh_status,
|
|
167
|
-
package: ($st.mcp_npm_package // "direxio-mcp@latest"),
|
|
168
|
-
server_name: ($st.mcp_server_name // ""),
|
|
169
|
-
config_dir: ($st.mcp_config_dir // ""),
|
|
170
|
-
codex: ($st.mcp_codex_config // ""),
|
|
171
|
-
openclaw: ($st.mcp_openclaw_config // ""),
|
|
172
|
-
hermes: ($st.mcp_hermes_config // ""),
|
|
173
|
-
doctor: ($st.mcp_doctor_command // "")
|
|
174
|
-
},
|
|
175
|
-
resources: {
|
|
176
|
-
region: ($st.region // ""),
|
|
177
|
-
domain_mode: ($st.domain_mode // ""),
|
|
178
|
-
instance_type: ($st.instance_type // ""),
|
|
179
|
-
instance_id: ($st.resources.instance_id // ""),
|
|
180
|
-
root_volume_id: ($st.resources.root_volume_id // ""),
|
|
181
|
-
public_ip: ($st.resources.public_ip // ""),
|
|
182
|
-
eip_id: ($st.resources.eip_id // ""),
|
|
183
|
-
route53_zone_id: ($st.resources.route53_zone_id // ""),
|
|
184
|
-
route53_zone_name: ($st.resources.route53_zone_name // ""),
|
|
185
|
-
route53_zone_created_by_deployer: ($st.resources.route53_zone_created_by_deployer // ""),
|
|
186
|
-
route53_name_servers: ($st.resources.route53_name_servers // ""),
|
|
187
|
-
route53_existing_a_value: ($st.resources.route53_existing_a_value // ""),
|
|
188
|
-
route53_pending_a_value: ($st.resources.route53_pending_a_value // ""),
|
|
189
|
-
route53_overwrite_confirmed: ($st.resources.route53_overwrite_confirmed // ""),
|
|
190
|
-
sg_id: ($st.resources.sg_id // ""),
|
|
191
|
-
key_name: ($st.resources.key_name // "")
|
|
192
|
-
},
|
|
193
|
-
billing: {
|
|
194
|
-
keeps_billing_until_destroy: ($operation_type != "destroy"),
|
|
195
|
-
recorded_billable_resources: billable,
|
|
196
|
-
cost_estimate: ($st.cost_estimate // null),
|
|
197
|
-
destroy_cleanup_status: (
|
|
198
|
-
if $operation_type != "destroy" then "not_destroy"
|
|
199
|
-
elif (destroy_billable_residue | length) == 0 then "no_recorded_billable_resource_residue"
|
|
200
|
-
else "possible_billable_resource_residue"
|
|
201
|
-
end
|
|
202
|
-
),
|
|
203
|
-
possible_remaining_billable_resources: (
|
|
204
|
-
if $operation_type == "destroy" then destroy_billable_residue else [] end
|
|
205
|
-
)
|
|
206
|
-
},
|
|
207
|
-
security: {
|
|
208
|
-
secrets_included: false,
|
|
209
|
-
values_redacted: true,
|
|
210
|
-
root_access_key_allowed: true,
|
|
211
|
-
temporary_iam_cleanup_required: true,
|
|
212
|
-
temporary_iam_cleanup_action: "if a temporary DirexioDeployer access key was used, delete or disable it after deployment, or reduce it to a maintenance-only policy"
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
+ (if $operation_type == "destroy" then {
|
|
216
|
-
destroy: {
|
|
217
|
-
resources_processed_from_state: true,
|
|
218
|
-
user_managed_dns_not_removed: true,
|
|
219
|
-
purchased_domain_not_removed: true,
|
|
220
|
-
local_service_dir: ($st.agent_service_dir // ""),
|
|
221
|
-
evidence: ($st.destroy_evidence // {})
|
|
222
|
-
}
|
|
223
|
-
} else {} end)
|
|
224
|
-
'
|
|
37
|
+
json_cli operation-report "$operation" "$status" "$state" "$generated_at"
|
|
225
38
|
}
|
|
226
39
|
|
|
227
40
|
operation_report_write() {
|
package/scripts/lib/ops.sh
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# lib/ops.sh - existing-node update/reset helpers.
|
|
3
3
|
|
|
4
|
+
OPS_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
5
|
+
# shellcheck disable=SC1090
|
|
6
|
+
source "$OPS_LIB_DIR/json.sh"
|
|
7
|
+
|
|
4
8
|
ops_state_path() {
|
|
5
9
|
local explicit=${1:-}
|
|
6
10
|
if [ -n "$explicit" ]; then
|
|
@@ -20,7 +24,8 @@ ops_require_state() {
|
|
|
20
24
|
|
|
21
25
|
ops_state_get() {
|
|
22
26
|
local state=$1 path=$2
|
|
23
|
-
|
|
27
|
+
path=${path#\.}
|
|
28
|
+
json_get "$state" "$path"
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
ops_sh_quote() {
|
|
@@ -201,26 +206,8 @@ EOF
|
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
ops_mark_refresh_pending() {
|
|
204
|
-
local state=$1 start_phase=${2:-S4_BOOTSTRAP_STACK}
|
|
205
|
-
|
|
206
|
-
jq --arg start "$start_phase" --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '
|
|
207
|
-
del(
|
|
208
|
-
.password,
|
|
209
|
-
.access_token,
|
|
210
|
-
.agent_token,
|
|
211
|
-
.agent_room_id,
|
|
212
|
-
.user_confirmations,
|
|
213
|
-
.runtime_checks
|
|
214
|
-
)
|
|
215
|
-
| .agent_install_status = "refresh_pending"
|
|
216
|
-
| .phase = $start
|
|
217
|
-
| (if ($start == "S4_BOOTSTRAP_STACK") then
|
|
218
|
-
.phases.S4_BOOTSTRAP_STACK = {status:"pending", ts:$ts, evidence:"existing node operation requires fresh health check"}
|
|
219
|
-
else . end)
|
|
220
|
-
| .phases.S5_INIT_TOKENS = {status:"pending", ts:$ts, evidence:"existing node operation requires fresh bootstrap credentials"}
|
|
221
|
-
| .phases.S6_WIRE_LOCAL = {status:"pending", ts:$ts, evidence:"existing node operation requires local credentials and MCP refresh"}
|
|
222
|
-
| .phases.S7_VERIFY_E2E = {status:"pending", ts:$ts, evidence:"existing node operation requires fresh verification"}
|
|
223
|
-
' "$state" > "$tmp" && mv "$tmp" "$state"
|
|
209
|
+
local state=$1 start_phase=${2:-S4_BOOTSTRAP_STACK}
|
|
210
|
+
json_mutate "$state" ops-refresh-pending "$start_phase" "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
224
211
|
}
|
|
225
212
|
|
|
226
213
|
ops_write_report() {
|
package/scripts/lib/state.sh
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# lib/state.sh - state.json helpers for the deployment state machine.
|
|
3
3
|
#
|
|
4
4
|
# Sourced by orchestrate.sh and phases/*.sh. All state.json reads/writes go
|
|
5
|
-
# through this file to keep structure and fields consistent. Requires
|
|
5
|
+
# through this file to keep structure and fields consistent. Requires Node.js.
|
|
6
6
|
#
|
|
7
7
|
# state.json path: $P2P_WORKDIR/state.json.
|
|
8
8
|
# By default, DOMAIN=__DOMAIN__ maps to ~/.direxio/nodes/<service_id>/state.json.
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
STATE_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
13
13
|
# shellcheck disable=SC1090
|
|
14
14
|
source "$STATE_LIB_DIR/paths.sh"
|
|
15
|
+
# shellcheck disable=SC1090
|
|
16
|
+
source "$STATE_LIB_DIR/json.sh"
|
|
15
17
|
|
|
16
18
|
# Phase list; order matters.
|
|
17
19
|
PHASES=(
|
|
@@ -48,30 +50,8 @@ is_yes() {
|
|
|
48
50
|
state_init() {
|
|
49
51
|
mkdir -p "$P2P_WORKDIR"
|
|
50
52
|
local run_id=${RUN_ID:-p2p-$(date -u +%Y%m%d-%H%M%S)}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
for p in "${PHASES[@]}"; do
|
|
54
|
-
phases_json=$(echo "$phases_json" | jq --arg k "$p" '. + {($k): {"status":"pending"}}')
|
|
55
|
-
done
|
|
56
|
-
jq -n \
|
|
57
|
-
--arg run_id "$run_id" \
|
|
58
|
-
--arg region "${AWS_DEFAULT_REGION:-${AWS_REGION:-}}" \
|
|
59
|
-
--argjson phases "$phases_json" \
|
|
60
|
-
--arg ts "$(_now)" \
|
|
61
|
-
'{
|
|
62
|
-
run_id: $run_id,
|
|
63
|
-
region: (if $region == "" then null else $region end),
|
|
64
|
-
domain_mode: null,
|
|
65
|
-
domain: null,
|
|
66
|
-
domain_confirmed_irreversible: false,
|
|
67
|
-
instance_type: null,
|
|
68
|
-
dns_ready: false,
|
|
69
|
-
existing_state_confirmed: false,
|
|
70
|
-
phase: "S0_PREREQ_AWS",
|
|
71
|
-
created_at: $ts,
|
|
72
|
-
phases: $phases,
|
|
73
|
-
resources: {}
|
|
74
|
-
}' > "$STATE_JSON"
|
|
53
|
+
: > "$STATE_JSON"
|
|
54
|
+
json_mutate "$STATE_JSON" state-init "$run_id" "${AWS_DEFAULT_REGION:-${AWS_REGION:-}}" "$(_now)" "${PHASES[@]}"
|
|
75
55
|
log "Initialized state.json -> $STATE_JSON (run_id=$run_id)"
|
|
76
56
|
}
|
|
77
57
|
|
|
@@ -80,35 +60,29 @@ state_ensure() {
|
|
|
80
60
|
[ -f "$STATE_JSON" ] || state_init
|
|
81
61
|
}
|
|
82
62
|
|
|
83
|
-
# Atomic write using a jq filter.
|
|
84
|
-
_state_write() {
|
|
85
|
-
local filter=$1; shift
|
|
86
|
-
local tmp="$STATE_JSON.tmp.$$"
|
|
87
|
-
jq "$@" "$filter" "$STATE_JSON" > "$tmp" && mv "$tmp" "$STATE_JSON"
|
|
88
|
-
}
|
|
89
|
-
|
|
90
63
|
# Top-level field accessors.
|
|
91
|
-
state_get() {
|
|
92
|
-
state_set() {
|
|
93
|
-
state_set_raw() {
|
|
64
|
+
state_get() { json_get "$STATE_JSON" "$1"; }
|
|
65
|
+
state_set() { json_mutate "$STATE_JSON" set-string "$1" "$2"; }
|
|
66
|
+
state_set_raw() { json_mutate "$STATE_JSON" set-json "$1" "$2"; }
|
|
67
|
+
state_set_object() {
|
|
68
|
+
local path=$1 object_json
|
|
69
|
+
shift
|
|
70
|
+
object_json=$(json_build object "$@")
|
|
71
|
+
json_mutate "$STATE_JSON" set-json "$path" "$object_json"
|
|
72
|
+
}
|
|
94
73
|
|
|
95
74
|
# Resource records used by destroy.sh.
|
|
96
|
-
res_set() {
|
|
97
|
-
res_get() {
|
|
75
|
+
res_set() { json_mutate "$STATE_JSON" set-string "resources.$1" "$2"; }
|
|
76
|
+
res_get() { json_get "$STATE_JSON" "resources.$1"; }
|
|
98
77
|
|
|
99
78
|
# Phase status helpers.
|
|
100
79
|
# phase_status <PHASE>
|
|
101
|
-
phase_status() {
|
|
80
|
+
phase_status() { json_get "$STATE_JSON" "phases.$1.status" "pending"; }
|
|
102
81
|
|
|
103
82
|
# phase_set <PHASE> <status> [evidence]
|
|
104
83
|
phase_set() {
|
|
105
84
|
local p=$1 st=$2 ev=${3:-}
|
|
106
|
-
|
|
107
|
-
.phases[$p].status = $st
|
|
108
|
-
| .phases[$p].ts = $ts
|
|
109
|
-
| (if $ev != "" then .phases[$p].evidence = $ev else . end)
|
|
110
|
-
| .phase = $p
|
|
111
|
-
' --arg p "$p" --arg st "$st" --arg ev "$ev" --arg ts "$(_now)"
|
|
85
|
+
json_mutate "$STATE_JSON" phase-set "$p" "$st" "$(_now)" "$ev"
|
|
112
86
|
}
|
|
113
87
|
|
|
114
88
|
# Find the first phase whose status is not done.
|
|
@@ -65,15 +65,32 @@ function send(message) {
|
|
|
65
65
|
|
|
66
66
|
function readFrames() {
|
|
67
67
|
while (true) {
|
|
68
|
+
const headerEnd = stdout.indexOf("\r\n\r\n");
|
|
69
|
+
if (headerEnd >= 0) {
|
|
70
|
+
const header = stdout.subarray(0, headerEnd).toString("utf8");
|
|
71
|
+
const match = /^Content-Length:\s*(\d+)/im.exec(header);
|
|
72
|
+
if (match) {
|
|
73
|
+
const length = Number.parseInt(match[1], 10);
|
|
74
|
+
const bodyStart = headerEnd + 4;
|
|
75
|
+
if (stdout.length < bodyStart + length) return;
|
|
76
|
+
const body = stdout.subarray(bodyStart, bodyStart + length).toString("utf8");
|
|
77
|
+
stdout = stdout.subarray(bodyStart + length);
|
|
78
|
+
recordMessage(JSON.parse(body));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
68
82
|
const lineEnd = stdout.indexOf("\n");
|
|
69
83
|
if (lineEnd < 0) return;
|
|
70
84
|
const line = stdout.subarray(0, lineEnd).toString("utf8").replace(/\r$/, "");
|
|
71
85
|
stdout = stdout.subarray(lineEnd + 1);
|
|
72
86
|
if (line.length === 0) continue;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
recordMessage(JSON.parse(line));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function recordMessage(message) {
|
|
92
|
+
if (typeof message.id !== "undefined") {
|
|
93
|
+
responses.set(message.id, message);
|
|
77
94
|
}
|
|
78
95
|
}
|
|
79
96
|
|