direxio-deployer 0.1.0 → 0.1.2
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 +10 -2
- package/README_zh.md +10 -2
- package/SKILL.md +32 -8
- package/bin/direxio-deployer.mjs +1 -2
- package/package.json +2 -3
- package/references/agent-targets.md +7 -1
- package/references/deployment-lessons.md +5 -7
- package/references/deployment-workflow.md +8 -4
- package/references/runtime-wiring.md +5 -5
- 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 +66 -5
- package/scripts/orchestrate.sh +166 -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 +22 -42
- 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
|
@@ -253,12 +253,7 @@ _route53_existing_a_value() {
|
|
|
253
253
|
local zone_id=$1 domain=$2 records name
|
|
254
254
|
name="${domain}."
|
|
255
255
|
records=$(aws route53 list-resource-record-sets --hosted-zone-id "$zone_id" --output json 2>/dev/null) || return 0
|
|
256
|
-
printf '%s\n' "$records" |
|
|
257
|
-
.ResourceRecordSets[]?
|
|
258
|
-
| select(.Name == $name and .Type == "A")
|
|
259
|
-
| [.ResourceRecords[]?.Value]
|
|
260
|
-
| join(",")
|
|
261
|
-
' | sed -n '1p'
|
|
256
|
+
printf '%s\n' "$records" | json_stdin_route53_a_values "$name" | sed -n '1p'
|
|
262
257
|
}
|
|
263
258
|
|
|
264
259
|
_guard_route53_a_overwrite() {
|
|
@@ -342,7 +337,7 @@ _find_route53_zone() {
|
|
|
342
337
|
fi
|
|
343
338
|
;;
|
|
344
339
|
esac
|
|
345
|
-
done < <(printf '%s\n' "$zones_json" |
|
|
340
|
+
done < <(printf '%s\n' "$zones_json" | json_stdin_tsv HostedZones Id Name)
|
|
346
341
|
[ -n "$best_id" ] || return 1
|
|
347
342
|
printf '%s\t%s\n' "$best_id" "$best_name"
|
|
348
343
|
}
|
|
@@ -355,9 +350,9 @@ _create_route53_zone() {
|
|
|
355
350
|
--name "$zone_name" \
|
|
356
351
|
--caller-reference "$caller" \
|
|
357
352
|
--output json) || return 1
|
|
358
|
-
zone_id=$(printf '%s\n' "$created" |
|
|
359
|
-
returned_name=$(printf '%s\n' "$created" |
|
|
360
|
-
name_servers=$(printf '%s\n' "$created" |
|
|
353
|
+
zone_id=$(printf '%s\n' "$created" | json_stdin_get HostedZone.Id | sed 's#^/hostedzone/##')
|
|
354
|
+
returned_name=$(printf '%s\n' "$created" | json_stdin_get HostedZone.Name)
|
|
355
|
+
name_servers=$(printf '%s\n' "$created" | json_stdin_join DelegationSet.NameServers ",")
|
|
361
356
|
[ -n "$zone_id" ] && [ -n "$returned_name" ] || return 1
|
|
362
357
|
|
|
363
358
|
_record_route53_zone "$zone_id" "${returned_name%.}" true "$name_servers"
|
|
@@ -38,8 +38,8 @@ run_phase() {
|
|
|
38
38
|
phase_set S5_INIT_TOKENS failed "bootstrap.json missing password/access/agent credentials"
|
|
39
39
|
fail "bootstrap.json must contain password as an eight-digit initialization-code string plus access_token and agent_token."
|
|
40
40
|
fi
|
|
41
|
-
asurl=$(
|
|
42
|
-
agent_room_id=$(
|
|
41
|
+
asurl=$(json_get "$out" as_url "https://$domain")
|
|
42
|
+
agent_room_id=$(json_get "$out" agent_room_id)
|
|
43
43
|
if [ -z "$agent_room_id" ] || [[ "$agent_room_id" == \!agent:* ]]; then
|
|
44
44
|
phase_set S5_INIT_TOKENS failed "bootstrap.json missing real agent_room_id"
|
|
45
45
|
fail "bootstrap.json must contain a real Matrix agent_room_id; legacy !agent:<domain> ids are not supported."
|
|
@@ -59,9 +59,10 @@ run_phase() {
|
|
|
59
59
|
|
|
60
60
|
_extract_output_tokens() {
|
|
61
61
|
local out=$1 password token access_token
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
[ "$(json_type "$out" password)" = "string" ] || return 1
|
|
63
|
+
password=$(json_get "$out" password)
|
|
64
|
+
token=$(json_get "$out" agent_token)
|
|
65
|
+
access_token=$(json_get "$out" access_token)
|
|
65
66
|
[ -n "$password" ] && [ -n "$token" ] && [ -n "$access_token" ] || return 1
|
|
66
67
|
printf '%s' "$password" | grep -Eq '^[0-9]{8}$' || return 1
|
|
67
68
|
printf '%s\t%s\t%s\n' "$password" "$token" "$access_token"
|
|
@@ -91,18 +92,7 @@ _normalize_bootstrap_output() {
|
|
|
91
92
|
local domain=$1 src=$2 out=$3
|
|
92
93
|
local tmp
|
|
93
94
|
tmp=$(mktemp)
|
|
94
|
-
if !
|
|
95
|
-
. + {
|
|
96
|
-
domain: (.domain // $domain),
|
|
97
|
-
as_url: (.as_url // $asurl),
|
|
98
|
-
p2p_url: (.p2p_url // $asurl),
|
|
99
|
-
user_id: (.user_id // .owner_user_id // ""),
|
|
100
|
-
bot_mxid: (.bot_mxid // .owner_user_id // .user_id // ("@owner:" + $domain)),
|
|
101
|
-
access_token: (.access_token // ""),
|
|
102
|
-
agent_token: (.agent_token // ""),
|
|
103
|
-
agent_room_id: (.agent_room_id // "")
|
|
104
|
-
}
|
|
105
|
-
' "$src" > "$tmp"; then
|
|
95
|
+
if ! json_build bootstrap-normalized "$src" "$domain" > "$tmp"; then
|
|
106
96
|
rm -f "$tmp"
|
|
107
97
|
return 1
|
|
108
98
|
fi
|
|
@@ -572,15 +572,21 @@ _openclaw_acp_args_toml() {
|
|
|
572
572
|
url=${DIREXIO_OPENCLAW_ACP_URL:-}
|
|
573
573
|
token_file=${DIREXIO_OPENCLAW_ACP_TOKEN_FILE:-}
|
|
574
574
|
session=${DIREXIO_OPENCLAW_ACP_SESSION:-}
|
|
575
|
-
[ -n "$url" ]
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
575
|
+
if [ -n "$url" ] && [ -n "$token_file" ] && [ -n "$session" ]; then
|
|
576
|
+
token_file=$(_local_connect_path "$token_file")
|
|
577
|
+
_toml_array acp --url "$url" --token-file "$token_file" --session "$session"
|
|
578
|
+
return 0
|
|
579
|
+
fi
|
|
580
|
+
if [ -n "$url" ] || [ -n "$token_file" ]; then
|
|
581
|
+
[ -n "$url" ] || missing="${missing} DIREXIO_OPENCLAW_ACP_URL"
|
|
582
|
+
[ -n "$token_file" ] || missing="${missing} DIREXIO_OPENCLAW_ACP_TOKEN_FILE"
|
|
583
|
+
[ -n "$session" ] || missing="${missing} DIREXIO_OPENCLAW_ACP_SESSION"
|
|
584
|
+
fail "OpenClaw ACP explicit Gateway settings are incomplete:${missing}. Set all of DIREXIO_OPENCLAW_ACP_URL, DIREXIO_OPENCLAW_ACP_TOKEN_FILE, and DIREXIO_OPENCLAW_ACP_SESSION; otherwise leave URL/token-file unset so openclaw acp can auto-detect from its config."
|
|
580
585
|
return 1
|
|
581
586
|
fi
|
|
582
|
-
|
|
583
|
-
|
|
587
|
+
# Fallback: OpenClaw acp auto-discovers gateway from ~/.openclaw/openclaw.json.
|
|
588
|
+
warn "OpenClaw ACP: Gateway URL/token-file not set; using session '${session:-agent:main:main}' and letting openclaw acp auto-detect the Gateway from its config."
|
|
589
|
+
_toml_array acp --session "${session:-agent:main:main}"
|
|
584
590
|
}
|
|
585
591
|
|
|
586
592
|
_hermes_acp_args_toml() {
|
|
@@ -768,22 +774,7 @@ _write_mcp_json_config() {
|
|
|
768
774
|
local path=$1 server_name=$2 command=$3 credentials_file=$4 node_id=${5:-}
|
|
769
775
|
mkdir -p "$(dirname "$path")"
|
|
770
776
|
umask 077
|
|
771
|
-
|
|
772
|
-
--arg server_name "$server_name" \
|
|
773
|
-
--arg command "$command" \
|
|
774
|
-
--arg credentials_file "$credentials_file" \
|
|
775
|
-
--arg node_id "$node_id" \
|
|
776
|
-
'{
|
|
777
|
-
mcpServers: {
|
|
778
|
-
($server_name): {
|
|
779
|
-
command: $command,
|
|
780
|
-
env: {
|
|
781
|
-
DIREXIO_CREDENTIALS_FILE: $credentials_file,
|
|
782
|
-
DIREXIO_AGENT_NODE_ID: $node_id
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}' > "$path"
|
|
777
|
+
json_build mcp-json-config "$server_name" "$command" "$credentials_file" "$node_id" > "$path"
|
|
787
778
|
chmod 600 "$path" 2>/dev/null || true
|
|
788
779
|
}
|
|
789
780
|
|
|
@@ -791,17 +782,7 @@ _write_mcp_openclaw_server_config() {
|
|
|
791
782
|
local path=$1 command=$2 credentials_file=$3 node_id=${4:-}
|
|
792
783
|
mkdir -p "$(dirname "$path")"
|
|
793
784
|
umask 077
|
|
794
|
-
|
|
795
|
-
--arg command "$command" \
|
|
796
|
-
--arg credentials_file "$credentials_file" \
|
|
797
|
-
--arg node_id "$node_id" \
|
|
798
|
-
'{
|
|
799
|
-
command: $command,
|
|
800
|
-
env: {
|
|
801
|
-
DIREXIO_CREDENTIALS_FILE: $credentials_file,
|
|
802
|
-
DIREXIO_AGENT_NODE_ID: $node_id
|
|
803
|
-
}
|
|
804
|
-
}' > "$path"
|
|
785
|
+
json_build mcp-openclaw-server-config "$command" "$credentials_file" "$node_id" > "$path"
|
|
805
786
|
chmod 600 "$path" 2>/dev/null || true
|
|
806
787
|
}
|
|
807
788
|
|
|
@@ -905,7 +886,7 @@ EOF
|
|
|
905
886
|
|
|
906
887
|
_create_cc_connect_matrix_session() {
|
|
907
888
|
local asurl=$1 access_token=$2 device_id=$3 out=$4 body code http_body
|
|
908
|
-
body=$(
|
|
889
|
+
body=$(json_build matrix-session-create "$device_id")
|
|
909
890
|
http_body=$(mktemp)
|
|
910
891
|
code=$(curl -sk -o "$http_body" -w '%{http_code}' -X POST "$asurl/_p2p/command" \
|
|
911
892
|
-H 'Content-Type: application/json' \
|
|
@@ -916,7 +897,7 @@ _create_cc_connect_matrix_session() {
|
|
|
916
897
|
rm -f "$http_body"
|
|
917
898
|
return 1
|
|
918
899
|
fi
|
|
919
|
-
if !
|
|
900
|
+
if ! json_assert "$http_body" matrix-session >/dev/null; then
|
|
920
901
|
warn "agent.matrix_session.create response is missing Matrix session fields: $(head -c 200 "$http_body" 2>/dev/null)"
|
|
921
902
|
rm -f "$http_body"
|
|
922
903
|
return 1
|
|
@@ -1221,8 +1202,7 @@ _agent_node_id_matches_host() {
|
|
|
1221
1202
|
_write_credentials_file() {
|
|
1222
1203
|
local cred=$1 domain=$2 asurl=$3 token=$4 password=$5 access_token=$6 agent_room_id=$7 node_id=$8
|
|
1223
1204
|
mkdir -p "$(dirname "$cred")"
|
|
1224
|
-
|
|
1225
|
-
'{profiles:{default:{domain:$domain,password:$password,access_token:$access,agent_room_id:$room,direxio_domain:$url,direxio_agent_token:$tok,direxio_agent_room_id:$room,direxio_agent_node_id:$node_id}}}' > "$cred"
|
|
1205
|
+
json_build credentials-profile "$domain" "$asurl" "$token" "$password" "$access_token" "$agent_room_id" "$node_id" > "$cred"
|
|
1226
1206
|
chmod 600 "$cred"
|
|
1227
1207
|
}
|
|
1228
1208
|
|
|
@@ -1358,10 +1338,10 @@ run_phase() {
|
|
|
1358
1338
|
phase_set S6_WIRE_LOCAL failed "agent Matrix session creation failed"
|
|
1359
1339
|
fail "failed to create cc-connect Matrix session via agent.matrix_session.create."
|
|
1360
1340
|
fi
|
|
1361
|
-
matrix_token=$(
|
|
1362
|
-
matrix_user=$(
|
|
1363
|
-
matrix_device=$(
|
|
1364
|
-
matrix_homeserver=$(
|
|
1341
|
+
matrix_token=$(json_get "$cc_session" access_token)
|
|
1342
|
+
matrix_user=$(json_get "$cc_session" user_id)
|
|
1343
|
+
matrix_device=$(json_get "$cc_session" device_id)
|
|
1344
|
+
matrix_homeserver=$(json_get "$cc_session" homeserver)
|
|
1365
1345
|
if [ "$matrix_user" = "@owner:$domain" ]; then
|
|
1366
1346
|
phase_set S6_WIRE_LOCAL failed "agent Matrix session returned owner user"
|
|
1367
1347
|
fail "agent.matrix_session.create returned owner Matrix user; deploy a message-server build with agent Matrix session support."
|
|
@@ -45,8 +45,8 @@ _check_p2p_agent_auth() {
|
|
|
45
45
|
-X POST "https://$domain/_p2p/query" \
|
|
46
46
|
-H 'Content-Type: application/json' \
|
|
47
47
|
-H "Authorization: Bearer $token" \
|
|
48
|
-
-d "
|
|
49
|
-
if [ "$code" = "200" ] &&
|
|
48
|
+
-d "$(json_build mcp-messages-list "$room_id")" 2>/dev/null)
|
|
49
|
+
if [ "$code" = "200" ] && json_assert "$body" messages-response >/dev/null 2>&1; then
|
|
50
50
|
rm -f "$body"
|
|
51
51
|
ok " ✓ _p2p/query mcp.messages.list (agent token)"
|
|
52
52
|
return 0
|
|
@@ -61,7 +61,7 @@ _p2p_access_token() {
|
|
|
61
61
|
local args=()
|
|
62
62
|
while IFS= read -r arg; do args+=("$arg"); done < <(curl_resolve_args "$domain")
|
|
63
63
|
at=$(curl -sk "${args[@]}" -X POST "https://$domain/_p2p/command" -H 'Content-Type: application/json' \
|
|
64
|
-
-d "{\"action\":\"portal.auth\",\"params\":{\"password\":\"$password\"}}" 2>/dev/null |
|
|
64
|
+
-d "{\"action\":\"portal.auth\",\"params\":{\"password\":\"$password\"}}" 2>/dev/null | json_stdin_get access_token)
|
|
65
65
|
printf '%s' "$at"
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -97,7 +97,7 @@ _check_matrix_server_wellknown() {
|
|
|
97
97
|
local args=()
|
|
98
98
|
while IFS= read -r arg; do args+=("$arg"); done < <(curl_resolve_args "$domain")
|
|
99
99
|
body=$(curl -sk "${args[@]}" "https://$domain/.well-known/matrix/server" 2>/dev/null)
|
|
100
|
-
if printf '%s' "$body" |
|
|
100
|
+
if printf '%s' "$body" | json_stdin_assert well-known-server "$domain:443" >/dev/null 2>&1; then
|
|
101
101
|
ok " ✓ matrix federation well-known ($domain:443)"; return 0
|
|
102
102
|
fi
|
|
103
103
|
warn " x matrix federation well-known invalid:$(printf '%s' "$body" | head -c 120)"; return 1
|
|
@@ -128,7 +128,7 @@ _check_turn() {
|
|
|
128
128
|
if [ -z "$at" ]; then warn " x TURN (failed to exchange access_token; cannot verify turnServer)"; return 1; fi
|
|
129
129
|
turn=$(curl -sk "${args[@]}" "https://$domain/_matrix/client/v3/voip/turnServer" \
|
|
130
130
|
-H "Authorization: Bearer $at" 2>/dev/null)
|
|
131
|
-
if printf '%s' "$turn" |
|
|
131
|
+
if printf '%s' "$turn" | json_stdin_assert turn-credentials >/dev/null 2>&1; then
|
|
132
132
|
ok " ✓ TURN turnServer non-empty and valid"; return 0
|
|
133
133
|
else
|
|
134
134
|
warn " x TURN turnServer invalid/empty:$(printf '%s' "$turn" | head -c 120)"; return 1
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
# pricing-estimate.sh - estimate monthly AWS costs for a Direxio EC2 node.
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
|
+
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
6
|
+
# shellcheck disable=SC1090
|
|
7
|
+
source "$HERE/lib/json.sh"
|
|
8
|
+
|
|
5
9
|
usage() {
|
|
6
10
|
cat >&2 <<'EOF'
|
|
7
11
|
Usage:
|
|
@@ -38,13 +42,7 @@ price_from_get_products() {
|
|
|
38
42
|
--filters "Type=TERM_MATCH,Field=location,Value=$location" "$@" \
|
|
39
43
|
--max-results 1 \
|
|
40
44
|
--output json 2>/dev/null) || return 1
|
|
41
|
-
printf '%s\n' "$json" |
|
|
42
|
-
.PriceList[0]
|
|
43
|
-
| fromjson
|
|
44
|
-
| .terms.OnDemand
|
|
45
|
-
| to_entries[0].value.priceDimensions
|
|
46
|
-
| to_entries[0].value.pricePerUnit.USD
|
|
47
|
-
' 2>/dev/null
|
|
45
|
+
printf '%s\n' "$json" | json_stdin_price_usd 2>/dev/null
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
numeric_or_empty() {
|
|
@@ -98,7 +96,7 @@ build_estimate() {
|
|
|
98
96
|
|
|
99
97
|
if [ -z "$location" ]; then
|
|
100
98
|
status=fallback
|
|
101
|
-
warnings_json
|
|
99
|
+
warnings_json='["Region is not mapped to an AWS Pricing location; using conservative fallback estimates"]'
|
|
102
100
|
fi
|
|
103
101
|
|
|
104
102
|
if [ "$status" = "queried" ] && ec2_hourly=$(lookup_ec2_hourly "$location" "$instance_type"); then
|
|
@@ -126,7 +124,11 @@ build_estimate() {
|
|
|
126
124
|
fi
|
|
127
125
|
|
|
128
126
|
if [ "$status" = "fallback" ]; then
|
|
129
|
-
|
|
127
|
+
case "$warnings_json" in
|
|
128
|
+
*"AWS Pricing API unavailable; using conservative fallback estimates"*) ;;
|
|
129
|
+
"[]") warnings_json='["AWS Pricing API unavailable; using conservative fallback estimates"]' ;;
|
|
130
|
+
*) warnings_json=${warnings_json%]}; warnings_json="$warnings_json,\"AWS Pricing API unavailable; using conservative fallback estimates\"]" ;;
|
|
131
|
+
esac
|
|
130
132
|
fi
|
|
131
133
|
|
|
132
134
|
if [ "$domain_mode" = "route53" ]; then
|
|
@@ -135,71 +137,25 @@ build_estimate() {
|
|
|
135
137
|
route53_monthly=0
|
|
136
138
|
fi
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def total:
|
|
158
|
-
(.components.ec2_instance.monthly_usd
|
|
159
|
-
+ .components.ebs_gp3.monthly_usd
|
|
160
|
-
+ .components.public_ipv4.monthly_usd
|
|
161
|
-
+ .components.route53_hosted_zone.monthly_usd);
|
|
162
|
-
{
|
|
163
|
-
pricing_status: $pricing_status,
|
|
164
|
-
region: $region,
|
|
165
|
-
location: $location,
|
|
166
|
-
hours_per_month: $hours,
|
|
167
|
-
warnings: $warnings,
|
|
168
|
-
components: {
|
|
169
|
-
ec2_instance: {
|
|
170
|
-
instance_type: $instance_type,
|
|
171
|
-
hourly_usd: $ec2_hourly,
|
|
172
|
-
monthly_usd: $ec2_monthly,
|
|
173
|
-
source: $ec2_source
|
|
174
|
-
},
|
|
175
|
-
ebs_gp3: {
|
|
176
|
-
storage_gb: $disk_gb,
|
|
177
|
-
gb_month_usd: $gp3_rate,
|
|
178
|
-
monthly_usd: $gp3_monthly,
|
|
179
|
-
source: $gp3_source
|
|
180
|
-
},
|
|
181
|
-
public_ipv4: {
|
|
182
|
-
hourly_usd: $ipv4_hourly,
|
|
183
|
-
monthly_usd: $ipv4_monthly,
|
|
184
|
-
billed_even_when_attached: true,
|
|
185
|
-
source: $ipv4_source
|
|
186
|
-
},
|
|
187
|
-
route53_hosted_zone: {
|
|
188
|
-
monthly_usd: $route53_monthly,
|
|
189
|
-
included: ($domain_mode == "route53")
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
notes: [
|
|
193
|
-
"Estimate excludes data transfer, TURN relay traffic, domain registration, taxes, and AWS credit eligibility.",
|
|
194
|
-
"Public IPv4 is billed hourly by AWS even when attached to a running instance.",
|
|
195
|
-
"AWS credits may reduce charges only when the account, plan, region, and service usage are eligible; verify in AWS Billing Console."
|
|
196
|
-
],
|
|
197
|
-
recommendations: [
|
|
198
|
-
"Set an AWS Budget or billing alert before leaving the node running.",
|
|
199
|
-
"Review AWS Billing Console after deployment and after destroy to confirm actual charges and remaining credits."
|
|
200
|
-
]
|
|
201
|
-
} | .total_monthly_usd = ((total * 100 | round) / 100)
|
|
202
|
-
'
|
|
140
|
+
json_build pricing-estimate \
|
|
141
|
+
"$status" \
|
|
142
|
+
"$region" \
|
|
143
|
+
"$location" \
|
|
144
|
+
"$instance_type" \
|
|
145
|
+
"$domain_mode" \
|
|
146
|
+
"$ec2_source" \
|
|
147
|
+
"$gp3_source" \
|
|
148
|
+
"$ipv4_source" \
|
|
149
|
+
"$warnings_json" \
|
|
150
|
+
"$hours" \
|
|
151
|
+
"$disk_gb" \
|
|
152
|
+
"$ec2_hourly" \
|
|
153
|
+
"$(round2 "$(awk -v h="$ec2_hourly" -v m="$hours" 'BEGIN { print h*m }')")" \
|
|
154
|
+
"$gp3_rate" \
|
|
155
|
+
"$(round2 "$(awk -v r="$gp3_rate" -v gb="$disk_gb" 'BEGIN { print r*gb }')")" \
|
|
156
|
+
"$public_ipv4_hourly" \
|
|
157
|
+
"$(round2 "$(awk -v h="$public_ipv4_hourly" -v m="$hours" 'BEGIN { print h*m }')")" \
|
|
158
|
+
"$route53_monthly"
|
|
203
159
|
}
|
|
204
160
|
|
|
205
161
|
state=""
|
|
@@ -227,10 +183,11 @@ if [ -n "$state" ]; then
|
|
|
227
183
|
echo "state.json not found: $state" >&2
|
|
228
184
|
exit 1
|
|
229
185
|
}
|
|
230
|
-
region=${region:-$(
|
|
231
|
-
instance_type=${instance_type:-$(
|
|
232
|
-
domain_mode=${domain_mode:-$(
|
|
233
|
-
disk_gb=${disk_gb:-$(
|
|
186
|
+
region=${region:-$(json_get "$state" region)}
|
|
187
|
+
instance_type=${instance_type:-$(json_get "$state" instance_type)}
|
|
188
|
+
domain_mode=${domain_mode:-$(json_get "$state" domain_mode user)}
|
|
189
|
+
disk_gb=${disk_gb:-$(json_get "$state" resources.root_volume_gb)}
|
|
190
|
+
disk_gb=${disk_gb:-$(json_get "$state" root_volume_gb 8)}
|
|
234
191
|
fi
|
|
235
192
|
|
|
236
193
|
region=${region:-${AWS_DEFAULT_REGION:-${AWS_REGION:-}}}
|
|
@@ -249,8 +206,7 @@ if [ "$write_state" = "1" ]; then
|
|
|
249
206
|
echo "--write-state requires --state" >&2
|
|
250
207
|
exit 1
|
|
251
208
|
}
|
|
252
|
-
|
|
253
|
-
jq --argjson estimate "$estimate" '.cost_estimate = $estimate' "$state" > "$tmp" && mv "$tmp" "$state"
|
|
209
|
+
json_mutate "$state" set-json cost_estimate "$estimate"
|
|
254
210
|
fi
|
|
255
211
|
|
|
256
212
|
printf '%s\n' "$estimate"
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
5
|
-
tmp=$(mktemp -d)
|
|
6
|
-
trap 'rm -rf "$tmp"' EXIT
|
|
7
|
-
|
|
8
|
-
export HOME="$tmp/home"
|
|
9
|
-
mkdir -p "$HOME"
|
|
10
|
-
|
|
11
|
-
fakebin="$tmp/bin"
|
|
12
|
-
mkdir -p "$fakebin"
|
|
13
|
-
|
|
14
|
-
cat > "$fakebin/aws" <<'EOF'
|
|
15
|
-
#!/usr/bin/env bash
|
|
16
|
-
set -euo pipefail
|
|
17
|
-
printf 'aws' >> "$CALLS"
|
|
18
|
-
printf ' %q' "$@" >> "$CALLS"
|
|
19
|
-
printf '\n' >> "$CALLS"
|
|
20
|
-
|
|
21
|
-
case "${1:-} ${2:-}" in
|
|
22
|
-
"configure get")
|
|
23
|
-
exit 1
|
|
24
|
-
;;
|
|
25
|
-
"sts get-caller-identity")
|
|
26
|
-
profile=${AWS_PROFILE:-}
|
|
27
|
-
key=${AWS_ACCESS_KEY_ID:-}
|
|
28
|
-
if [ "$profile" = "root-profile" ] || [ "$key" = "AKIAROOTTEST" ]; then
|
|
29
|
-
arn="arn:aws:iam::123456789012:root"
|
|
30
|
-
account="123456789012"
|
|
31
|
-
else
|
|
32
|
-
arn="arn:aws:iam::123456789012:user/DirexioDeployer-20260628"
|
|
33
|
-
account="123456789012"
|
|
34
|
-
fi
|
|
35
|
-
case "$*" in
|
|
36
|
-
*"--query Arn"*) printf '%s\n' "$arn" ;;
|
|
37
|
-
*"--query Account"*) printf '%s\n' "$account" ;;
|
|
38
|
-
*) printf '{"Account":"%s","Arn":"%s"}\n' "$account" "$arn" ;;
|
|
39
|
-
esac
|
|
40
|
-
;;
|
|
41
|
-
*)
|
|
42
|
-
echo "unexpected aws call: $*" >&2
|
|
43
|
-
exit 2
|
|
44
|
-
;;
|
|
45
|
-
esac
|
|
46
|
-
EOF
|
|
47
|
-
chmod 700 "$fakebin/aws"
|
|
48
|
-
|
|
49
|
-
CALLS="$tmp/aws.calls"
|
|
50
|
-
export CALLS
|
|
51
|
-
export PATH="$fakebin:$PATH"
|
|
52
|
-
export AWS_SHARED_CREDENTIALS_FILE="$tmp/aws/credentials"
|
|
53
|
-
export AWS_CONFIG_FILE="$tmp/aws/config"
|
|
54
|
-
|
|
55
|
-
file_mode() {
|
|
56
|
-
if stat -c '%a' "$1" >/dev/null 2>&1; then
|
|
57
|
-
stat -c '%a' "$1"
|
|
58
|
-
else
|
|
59
|
-
stat -f '%Lp' "$1"
|
|
60
|
-
fi
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
cat > "$tmp/direxio.csv" <<'CSV'
|
|
64
|
-
User name,Access key ID,Secret access key
|
|
65
|
-
DirexioDeployer-20260628,AKIADIREXIOTEST,SECRET_DIREXIO_VALUE
|
|
66
|
-
CSV
|
|
67
|
-
|
|
68
|
-
out=$(bash "$ROOT/scripts/aws-credentials.sh" import-csv "$tmp/direxio.csv" direxio-deployer ap-southeast-1)
|
|
69
|
-
|
|
70
|
-
[[ "$out" == *"profile=direxio-deployer"* ]]
|
|
71
|
-
[[ "$out" == *"arn:aws:iam::<account>:user/DirexioDeployer-20260628"* ]]
|
|
72
|
-
if [[ "$out" == *"AKIADIREXIOTEST"* || "$out" == *"SECRET_DIREXIO_VALUE"* ]]; then
|
|
73
|
-
echo "aws-credentials output leaked credential values" >&2
|
|
74
|
-
printf '%s\n' "$out" >&2
|
|
75
|
-
exit 1
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
grep -q '^\[direxio-deployer\]$' "$AWS_SHARED_CREDENTIALS_FILE"
|
|
79
|
-
grep -q '^aws_access_key_id = AKIADIREXIOTEST$' "$AWS_SHARED_CREDENTIALS_FILE"
|
|
80
|
-
grep -q '^aws_secret_access_key = SECRET_DIREXIO_VALUE$' "$AWS_SHARED_CREDENTIALS_FILE"
|
|
81
|
-
grep -q '^\[profile direxio-deployer\]$' "$AWS_CONFIG_FILE"
|
|
82
|
-
grep -q '^region = ap-southeast-1$' "$AWS_CONFIG_FILE"
|
|
83
|
-
|
|
84
|
-
credential_perm=$(file_mode "$AWS_SHARED_CREDENTIALS_FILE")
|
|
85
|
-
config_perm=$(file_mode "$AWS_CONFIG_FILE")
|
|
86
|
-
case "$(uname -s)" in
|
|
87
|
-
MINGW*|MSYS*|CYGWIN*)
|
|
88
|
-
[[ "$credential_perm" == "600" || "$credential_perm" == "644" ]]
|
|
89
|
-
[[ "$config_perm" == "600" || "$config_perm" == "644" ]]
|
|
90
|
-
;;
|
|
91
|
-
*)
|
|
92
|
-
[ "$credential_perm" = "600" ]
|
|
93
|
-
[ "$config_perm" = "600" ]
|
|
94
|
-
;;
|
|
95
|
-
esac
|
|
96
|
-
|
|
97
|
-
verify_out=$(AWS_PROFILE=direxio-deployer bash "$ROOT/scripts/aws-credentials.sh" verify direxio-deployer)
|
|
98
|
-
[[ "$verify_out" == *"profile=direxio-deployer"* ]]
|
|
99
|
-
[[ "$verify_out" == *"root=false"* ]]
|
|
100
|
-
|
|
101
|
-
cat > "$tmp/root.csv" <<'CSV'
|
|
102
|
-
Access key ID,Secret access key
|
|
103
|
-
AKIAROOTTEST,SECRET_ROOT_VALUE
|
|
104
|
-
CSV
|
|
105
|
-
|
|
106
|
-
root_out=$(bash "$ROOT/scripts/aws-credentials.sh" import-csv "$tmp/root.csv" root-profile us-east-1)
|
|
107
|
-
[[ "$root_out" == *"profile=root-profile"* ]]
|
|
108
|
-
[[ "$root_out" == *"root=true"* ]]
|
|
109
|
-
if [[ "$root_out" == *"AKIAROOTTEST"* || "$root_out" == *"SECRET_ROOT_VALUE"* ]]; then
|
|
110
|
-
echo "aws-credentials root output leaked credential values" >&2
|
|
111
|
-
printf '%s\n' "$root_out" >&2
|
|
112
|
-
exit 1
|
|
113
|
-
fi
|
|
114
|
-
grep -q '^\[root-profile\]$' "$AWS_SHARED_CREDENTIALS_FILE"
|
|
115
|
-
grep -q '^aws_access_key_id = AKIAROOTTEST$' "$AWS_SHARED_CREDENTIALS_FILE"
|
|
116
|
-
grep -q '^aws_secret_access_key = SECRET_ROOT_VALUE$' "$AWS_SHARED_CREDENTIALS_FILE"
|
|
117
|
-
|
|
118
|
-
root_verify_out=$(AWS_PROFILE=root-profile bash "$ROOT/scripts/aws-credentials.sh" verify root-profile)
|
|
119
|
-
[[ "$root_verify_out" == *"profile=root-profile"* ]]
|
|
120
|
-
[[ "$root_verify_out" == *"root=true"* ]]
|
|
121
|
-
|
|
122
|
-
set +e
|
|
123
|
-
s0_output=$(
|
|
124
|
-
P2P_WORKDIR="$tmp/state-root" AWS_PROFILE=root-profile bash -c '
|
|
125
|
-
set -uo pipefail
|
|
126
|
-
cd "$1"
|
|
127
|
-
source scripts/lib/state.sh
|
|
128
|
-
state_init >/dev/null 2>&1
|
|
129
|
-
source scripts/lib/aws.sh
|
|
130
|
-
source scripts/phases/s0_prereq_aws.sh
|
|
131
|
-
run_phase
|
|
132
|
-
' _ "$ROOT" 2>&1
|
|
133
|
-
)
|
|
134
|
-
s0_rc=$?
|
|
135
|
-
set -e
|
|
136
|
-
[ "$s0_rc" -eq 0 ]
|
|
137
|
-
[[ "$s0_output" == *"AWS credentials are valid"* ]]
|
|
138
|
-
|
|
139
|
-
echo "aws credentials ok"
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
5
|
-
tmp=$(mktemp -d)
|
|
6
|
-
trap 'rm -rf "$tmp"' EXIT
|
|
7
|
-
|
|
8
|
-
export HOME="$tmp/home"
|
|
9
|
-
mkdir -p "$HOME"
|
|
10
|
-
|
|
11
|
-
fakebin="$tmp/bin"
|
|
12
|
-
mkdir -p "$fakebin"
|
|
13
|
-
cat > "$fakebin/direxio-connect" <<'EOF'
|
|
14
|
-
#!/usr/bin/env bash
|
|
15
|
-
set -euo pipefail
|
|
16
|
-
|
|
17
|
-
if [ "${1:-}" = "daemon" ] && [ "${2:-}" = "logs" ]; then
|
|
18
|
-
[ "${3:-}" = "--service-name" ]
|
|
19
|
-
[ "${4:-}" = "connect-check.example.test" ]
|
|
20
|
-
printf '%s\n' "${CONNECT_LOG_OUTPUT:-}"
|
|
21
|
-
exit 0
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
[ "${1:-}" = "daemon" ]
|
|
25
|
-
[ "${2:-}" = "status" ]
|
|
26
|
-
[ "${3:-}" = "--service-name" ]
|
|
27
|
-
[ "${4:-}" = "connect-check.example.test" ]
|
|
28
|
-
|
|
29
|
-
cat <<STATUS
|
|
30
|
-
cc-connect daemon status
|
|
31
|
-
|
|
32
|
-
Status: ${CONNECT_STATUS:-Running}
|
|
33
|
-
Platform: test
|
|
34
|
-
WorkDir: ${CONNECT_WORK_DIR:-}
|
|
35
|
-
STATUS
|
|
36
|
-
EOF
|
|
37
|
-
chmod 700 "$fakebin/direxio-connect"
|
|
38
|
-
|
|
39
|
-
service_dir="$HOME/.direxio/nodes/connect-check.example.test"
|
|
40
|
-
mkdir -p "$service_dir/cc-connect"
|
|
41
|
-
config="$service_dir/cc-connect/config.toml"
|
|
42
|
-
: > "$config"
|
|
43
|
-
state="$service_dir/state.json"
|
|
44
|
-
jq -n \
|
|
45
|
-
--arg service_dir "$service_dir" \
|
|
46
|
-
--arg config "$config" \
|
|
47
|
-
'{
|
|
48
|
-
run_id: "connect-daemon-test",
|
|
49
|
-
region: "ap-northeast-1",
|
|
50
|
-
domain_mode: "user",
|
|
51
|
-
domain: "connect-check.example.test",
|
|
52
|
-
agent_service_id: "connect-check.example.test",
|
|
53
|
-
agent_service_dir: $service_dir,
|
|
54
|
-
cc_connect_config: $config,
|
|
55
|
-
cc_connect_binary: "direxio-connect",
|
|
56
|
-
phase: "S7_VERIFY_E2E",
|
|
57
|
-
phases: {
|
|
58
|
-
S0_PREREQ_AWS: {status: "done"},
|
|
59
|
-
S1_PREFLIGHT: {status: "done"},
|
|
60
|
-
S2_DOMAIN: {status: "done"},
|
|
61
|
-
S3_PROVISION: {status: "done"},
|
|
62
|
-
S4_BOOTSTRAP_STACK: {status: "done"},
|
|
63
|
-
S5_INIT_TOKENS: {status: "done"},
|
|
64
|
-
S6_WIRE_LOCAL: {status: "done"},
|
|
65
|
-
S7_VERIFY_E2E: {status: "done"}
|
|
66
|
-
},
|
|
67
|
-
resources: {}
|
|
68
|
-
}' > "$state"
|
|
69
|
-
|
|
70
|
-
verify_output=$(P2P_WORKDIR="$service_dir" PATH="$fakebin:$PATH" CONNECT_WORK_DIR="$service_dir/cc-connect" bash "$ROOT/scripts/orchestrate.sh" verify connect_daemon)
|
|
71
|
-
printf '%s\n' "$verify_output" | grep -q 'verified runtime check: connect_daemon'
|
|
72
|
-
|
|
73
|
-
expected_work_dir="$service_dir/cc-connect"
|
|
74
|
-
if command -v cygpath >/dev/null 2>&1; then
|
|
75
|
-
expected_work_dir=$(cygpath -m "$expected_work_dir")
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
jq -e '
|
|
79
|
-
.runtime_checks.connect_daemon.status == "passed"
|
|
80
|
-
and .runtime_checks.connect_daemon.service_name == "connect-check.example.test"
|
|
81
|
-
and .runtime_checks.connect_daemon.daemon_status == "Running"
|
|
82
|
-
and .runtime_checks.connect_daemon.work_dir == "'"$expected_work_dir"'"
|
|
83
|
-
and (.user_confirmations.agent_mcp_runtime | not)
|
|
84
|
-
' "$state" >/dev/null
|
|
85
|
-
|
|
86
|
-
set +e
|
|
87
|
-
P2P_WORKDIR="$service_dir" PATH="$fakebin:$PATH" CONNECT_WORK_DIR="$service_dir/cc-connect" CONNECT_LOG_OUTPUT='ACP error (ACP_SESSION_INIT_FAILED): ACP metadata is missing for agent:main:acp:a18569b4-1f24-4f8a-aec6-f6a54530d50e. Recreate this ACP session with /acp spawn and rebind the thread.' bash "$ROOT/scripts/orchestrate.sh" verify connect_daemon > "$tmp/acp-error.out" 2>&1
|
|
88
|
-
acp_rc=$?
|
|
89
|
-
set -e
|
|
90
|
-
[ "$acp_rc" -ne 0 ] || {
|
|
91
|
-
echo "connect daemon check must fail when daemon logs show ACP session init failure" >&2
|
|
92
|
-
exit 1
|
|
93
|
-
}
|
|
94
|
-
jq -e '
|
|
95
|
-
.runtime_checks.connect_daemon.status == "failed"
|
|
96
|
-
and (.runtime_checks.connect_daemon.evidence | contains("ACP session initialization failure"))
|
|
97
|
-
and (.runtime_checks.connect_daemon.agent_error | contains("ACP_SESSION_INIT_FAILED"))
|
|
98
|
-
' "$state" >/dev/null
|
|
99
|
-
|
|
100
|
-
report_output=$(P2P_WORKDIR="$service_dir" bash "$ROOT/scripts/orchestrate.sh" report new_deploy)
|
|
101
|
-
report_path=$(printf '%s\n' "$report_output" | sed -nE 's/^operation report: //p' | tail -n 1)
|
|
102
|
-
jq -e '
|
|
103
|
-
.runtime_checks.connect_daemon.status == "failed"
|
|
104
|
-
and .gates.user_confirmation.agent_mcp_runtime == "pending_runtime_confirmation"
|
|
105
|
-
' "$report_path" >/dev/null
|
|
106
|
-
|
|
107
|
-
set +e
|
|
108
|
-
P2P_WORKDIR="$service_dir" PATH="$fakebin:$PATH" CONNECT_WORK_DIR="$HOME/.direxio/nodes/other.example.test/cc-connect" bash "$ROOT/scripts/orchestrate.sh" verify connect_daemon > "$tmp/wrong.out" 2>&1
|
|
109
|
-
wrong_rc=$?
|
|
110
|
-
set -e
|
|
111
|
-
[ "$wrong_rc" -ne 0 ] || {
|
|
112
|
-
echo "connect daemon check must fail when daemon WorkDir belongs to another service" >&2
|
|
113
|
-
exit 1
|
|
114
|
-
}
|
|
115
|
-
jq -e '
|
|
116
|
-
.runtime_checks.connect_daemon.status == "failed"
|
|
117
|
-
and (.runtime_checks.connect_daemon.evidence | contains("different service"))
|
|
118
|
-
' "$state" >/dev/null
|
|
119
|
-
|
|
120
|
-
echo "connect daemon runtime check ok"
|