direxio-deployer 0.1.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.
Files changed (77) hide show
  1. package/AGENTS.md +92 -0
  2. package/LICENSE +21 -0
  3. package/README.md +221 -0
  4. package/README_zh.md +218 -0
  5. package/SKILL.md +722 -0
  6. package/agents/README.md +25 -0
  7. package/agents/openai.yaml +12 -0
  8. package/bin/direxio-deployer.mjs +375 -0
  9. package/package.json +28 -0
  10. package/references/agent-targets.md +128 -0
  11. package/references/architecture.md +44 -0
  12. package/references/bug-history.md +78 -0
  13. package/references/deployment-lessons.md +218 -0
  14. package/references/deployment-optimization-audit.md +317 -0
  15. package/references/deployment-workflow.md +341 -0
  16. package/references/iam-policy.json +52 -0
  17. package/references/runtime-wiring.md +209 -0
  18. package/references/state-machine.md +46 -0
  19. package/references/token-refresh.md +81 -0
  20. package/references/tooling.md +106 -0
  21. package/references/troubleshooting.md +26 -0
  22. package/references/user-journey.md +75 -0
  23. package/references/verification-recovery.md +84 -0
  24. package/references/voip-turn-runbook.md +154 -0
  25. package/references/windows-deployment-notes.md +119 -0
  26. package/scripts/aws-credentials.sh +195 -0
  27. package/scripts/cloud-init/Caddyfile +48 -0
  28. package/scripts/cloud-init/docker-compose.yml +125 -0
  29. package/scripts/cloud-init/init-tokens.sh +238 -0
  30. package/scripts/cloud-init/user-data.yaml +40 -0
  31. package/scripts/destroy.ps1 +77 -0
  32. package/scripts/destroy.sh +589 -0
  33. package/scripts/lib/aws.sh +73 -0
  34. package/scripts/lib/domain.sh +175 -0
  35. package/scripts/lib/operation_report.sh +240 -0
  36. package/scripts/lib/ops.sh +230 -0
  37. package/scripts/lib/paths.sh +35 -0
  38. package/scripts/lib/state.sh +137 -0
  39. package/scripts/mcp-tools-list.mjs +95 -0
  40. package/scripts/orchestrate.ps1 +112 -0
  41. package/scripts/orchestrate.sh +1126 -0
  42. package/scripts/phases/s0_prereq_aws.sh +39 -0
  43. package/scripts/phases/s1_preflight.sh +72 -0
  44. package/scripts/phases/s2_domain.sh +103 -0
  45. package/scripts/phases/s3_provision.sh +421 -0
  46. package/scripts/phases/s4_bootstrap_stack.sh +38 -0
  47. package/scripts/phases/s5_init_tokens.sh +118 -0
  48. package/scripts/phases/s6_wire_local.sh +1435 -0
  49. package/scripts/phases/s7_verify_e2e.sh +136 -0
  50. package/scripts/pricing-estimate.sh +256 -0
  51. package/scripts/render/render-userdata.sh +86 -0
  52. package/scripts/reset-app-data.sh +40 -0
  53. package/scripts/update.sh +30 -0
  54. package/tests/aws_credentials_test.sh +139 -0
  55. package/tests/connect_daemon_runtime_check_test.sh +120 -0
  56. package/tests/default_paths_test.sh +58 -0
  57. package/tests/destroy_local_bridge_test.sh +154 -0
  58. package/tests/destroy_root_identity_test.sh +91 -0
  59. package/tests/destroy_route53_zone_test.sh +80 -0
  60. package/tests/domain_authoritative_dns_test.sh +49 -0
  61. package/tests/mcp_doctor_runtime_check_test.sh +86 -0
  62. package/tests/mcp_smoke_runtime_check_test.sh +121 -0
  63. package/tests/mcp_tools_runtime_check_test.sh +123 -0
  64. package/tests/npm_skill_distribution_test.sh +95 -0
  65. package/tests/operation_report_test.sh +258 -0
  66. package/tests/orchestrate_status_recovery_test.sh +91 -0
  67. package/tests/phase_timeout_test.sh +88 -0
  68. package/tests/pricing_estimate_test.sh +159 -0
  69. package/tests/render_userdata_remote_nodes_test.sh +40 -0
  70. package/tests/root_volume_tracking_test.sh +41 -0
  71. package/tests/route53_overwrite_guard_test.sh +86 -0
  72. package/tests/route53_zone_auto_create_test.sh +66 -0
  73. package/tests/runtime_summary_check_test.sh +203 -0
  74. package/tests/s6_wire_local_test.sh +405 -0
  75. package/tests/skill_structure_test.sh +298 -0
  76. package/tests/update_reset_ops_test.sh +230 -0
  77. package/tests/user_confirmation_gates_test.sh +152 -0
@@ -0,0 +1,139 @@
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"
@@ -0,0 +1,120 @@
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"
@@ -0,0 +1,58 @@
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
+ unset P2P_WORKDIR
11
+ unset DIREXIO_WORKDIR
12
+ export DOMAIN="IM.Example.test"
13
+
14
+ # shellcheck disable=SC1090
15
+ source "$ROOT/scripts/lib/state.sh"
16
+
17
+ [ "$P2P_WORKDIR" = "$HOME/.direxio/nodes/im.example.test" ]
18
+ [ "$STATE_JSON" = "$HOME/.direxio/nodes/im.example.test/state.json" ]
19
+
20
+ (
21
+ unset P2P_WORKDIR
22
+ export DIREXIO_WORKDIR="$HOME/.direxio/custom-workdir"
23
+ # shellcheck disable=SC1090
24
+ source "$ROOT/scripts/lib/state.sh"
25
+ [ "$P2P_WORKDIR" = "$HOME/.direxio/custom-workdir" ]
26
+ [ "$STATE_JSON" = "$HOME/.direxio/custom-workdir/state.json" ]
27
+ )
28
+
29
+ rm -rf "$HOME/.direxio"
30
+ (
31
+ unset DOMAIN P2P_WORKDIR DIREXIO_WORKDIR
32
+ HOME="$HOME" bash "$ROOT/scripts/orchestrate.sh" status >/dev/null 2>&1
33
+ )
34
+ [ ! -e "$HOME/.direxio/deploy" ]
35
+ [ ! -e "$HOME/.direxio/nodes/state.json" ]
36
+
37
+ mkdir -p "$HOME/.direxio/nodes/solo.example.test"
38
+ jq -n '{domain:"solo.example.test", phase:"S3_PROVISION", resources:{instance_id:"i-solo"}}' > "$HOME/.direxio/nodes/solo.example.test/state.json"
39
+ (
40
+ unset DOMAIN P2P_WORKDIR DIREXIO_WORKDIR
41
+ # shellcheck disable=SC1090
42
+ source "$ROOT/scripts/lib/state.sh"
43
+ [ "$P2P_WORKDIR" = "$HOME/.direxio/nodes" ]
44
+ [ "$STATE_JSON" = "$HOME/.direxio/nodes/state.json" ]
45
+ )
46
+
47
+ mkdir -p "$HOME/.direxio/nodes/second.example.test"
48
+ jq -n '{domain:"second.example.test", phase:"S6_WIRE_LOCAL", resources:{instance_id:"i-second"}}' > "$HOME/.direxio/nodes/second.example.test/state.json"
49
+ status_output=$(
50
+ unset DOMAIN P2P_WORKDIR DIREXIO_WORKDIR
51
+ HOME="$HOME" bash "$ROOT/scripts/orchestrate.sh" status
52
+ )
53
+ [[ "$status_output" == *"solo.example.test"* ]]
54
+ [[ "$status_output" == *"second.example.test"* ]]
55
+ [[ "$status_output" == *"i-solo"* ]]
56
+ [[ "$status_output" == *"i-second"* ]]
57
+
58
+ echo "default paths ok"
@@ -0,0 +1,154 @@
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
+ "sts get-caller-identity")
23
+ case "$*" in
24
+ *"--query Arn"*) printf 'arn:aws:iam::123456789012:user/DirexioDeployer-Test\n' ;;
25
+ *"--query Account"*) printf '123456789012\n' ;;
26
+ *) printf '{"Account":"123456789012","Arn":"arn:aws:iam::123456789012:user/DirexioDeployer-Test"}\n' ;;
27
+ esac
28
+ ;;
29
+ "ec2 terminate-instances") exit 0 ;;
30
+ "ec2 wait") exit 0 ;;
31
+ "ec2 release-address") exit 0 ;;
32
+ "ec2 delete-security-group") exit 0 ;;
33
+ "ec2 delete-key-pair") exit 0 ;;
34
+ *) exit 0 ;;
35
+ esac
36
+ EOF
37
+ chmod 700 "$fakebin/aws"
38
+
39
+ cat > "$fakebin/direxio-connect" <<'EOF'
40
+ #!/usr/bin/env bash
41
+ set -euo pipefail
42
+ printf 'direxio-connect' >> "$CALLS"
43
+ printf ' %q' "$@" >> "$CALLS"
44
+ printf '\n' >> "$CALLS"
45
+ if [ "${1:-}" = "daemon" ] && [ "${2:-}" = "status" ]; then
46
+ [ "${3:-}" = "--service-name" ]
47
+ [ -n "${4:-}" ]
48
+ cat <<STATUS
49
+ cc-connect daemon status
50
+
51
+ Status: Running
52
+ Platform: test
53
+ WorkDir: ${STATUS_WORK_DIR:-}
54
+ STATUS
55
+ fi
56
+ EOF
57
+ chmod 700 "$fakebin/direxio-connect"
58
+
59
+ write_state() {
60
+ local state=$1 domain=$2 service_dir=$3
61
+ mkdir -p "$(dirname "$state")" "$service_dir/cc-connect"
62
+ : > "$service_dir/cc-connect/config.toml"
63
+ jq -n \
64
+ --arg region "us-east-1" \
65
+ --arg domain "$domain" \
66
+ --arg service_dir "$service_dir" \
67
+ '{
68
+ region: $region,
69
+ domain_mode: "user",
70
+ domain: $domain,
71
+ as_url: ("https://" + $domain),
72
+ agent_service_dir: $service_dir,
73
+ agent_service_id: $domain,
74
+ resources: {
75
+ instance_id: "i-test",
76
+ eip_id: "eipalloc-test",
77
+ sg_id: "sg-test",
78
+ key_name: "direxio-test"
79
+ }
80
+ }' > "$state"
81
+ }
82
+
83
+ run_destroy() {
84
+ local state=$1 calls=$2 status_work_dir=$3
85
+ : > "$calls"
86
+ CALLS="$calls" STATUS_WORK_DIR="$status_work_dir" PATH="$fakebin:$PATH" bash "$ROOT/scripts/destroy.sh" "$state" >/dev/null
87
+ }
88
+
89
+ current_service="$HOME/.direxio/nodes/a5.direxio.ai"
90
+ current_state="$current_service/state.json"
91
+ current_calls="$tmp/current.calls"
92
+ write_state "$current_state" "a5.direxio.ai" "$current_service"
93
+ run_destroy "$current_state" "$current_calls" "$current_service/cc-connect"
94
+
95
+ grep -q '^direxio-connect daemon status --service-name a5.direxio.ai$' "$current_calls" || {
96
+ echo "destroy should query the current named daemon status" >&2
97
+ cat "$current_calls" >&2
98
+ exit 1
99
+ }
100
+
101
+ grep -q '^direxio-connect daemon stop --service-name a5.direxio.ai$' "$current_calls" || {
102
+ echo "destroy should stop the daemon when daemon status WorkDir matches the current service cc-connect dir" >&2
103
+ cat "$current_calls" >&2
104
+ exit 1
105
+ }
106
+
107
+ grep -q '^direxio-connect daemon uninstall --service-name a5.direxio.ai$' "$current_calls" || {
108
+ echo "destroy should uninstall the daemon after stopping the matching current service daemon" >&2
109
+ cat "$current_calls" >&2
110
+ exit 1
111
+ }
112
+
113
+ if [ -d "$current_service" ]; then
114
+ echo "destroy should remove the current service directory after stopping its daemon" >&2
115
+ exit 1
116
+ fi
117
+
118
+ other_service="$HOME/.direxio/nodes/b5.direxio.ai"
119
+ other_state="$other_service/state.json"
120
+ active_other_service="$HOME/.direxio/nodes/active-other"
121
+ other_calls="$tmp/other.calls"
122
+ mkdir -p "$active_other_service/cc-connect"
123
+ write_state "$other_state" "b5.direxio.ai" "$other_service"
124
+ run_destroy "$other_state" "$other_calls" "$active_other_service/cc-connect"
125
+
126
+ grep -q '^direxio-connect daemon status --service-name b5.direxio.ai$' "$other_calls" || {
127
+ echo "destroy should query the named daemon for the service being destroyed" >&2
128
+ cat "$other_calls" >&2
129
+ exit 1
130
+ }
131
+
132
+ if grep -q '^direxio-connect daemon stop' "$other_calls"; then
133
+ echo "destroy must not stop a daemon whose status WorkDir belongs to a different service" >&2
134
+ cat "$other_calls" >&2
135
+ exit 1
136
+ fi
137
+
138
+ if grep -q '^direxio-connect daemon uninstall' "$other_calls"; then
139
+ echo "destroy must not uninstall a daemon whose status WorkDir belongs to a different service" >&2
140
+ cat "$other_calls" >&2
141
+ exit 1
142
+ fi
143
+
144
+ if [ -d "$other_service" ]; then
145
+ echo "destroy should remove the current service directory even when another service daemon is active" >&2
146
+ exit 1
147
+ fi
148
+
149
+ if [ ! -d "$active_other_service" ]; then
150
+ echo "destroy must not remove another service directory" >&2
151
+ exit 1
152
+ fi
153
+
154
+ echo "destroy local bridge ok"
@@ -0,0 +1,91 @@
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
+ "sts get-caller-identity")
23
+ case "$*" in
24
+ *"--query Arn"*) printf 'arn:aws:iam::123456789012:root\n' ;;
25
+ *"--query Account"*) printf '123456789012\n' ;;
26
+ *) printf '{"Account":"123456789012","Arn":"arn:aws:iam::123456789012:root"}\n' ;;
27
+ esac
28
+ ;;
29
+ "ec2 terminate-instances"|"ec2 release-address"|"ec2 delete-security-group"|"ec2 delete-key-pair"|"route53 change-resource-record-sets")
30
+ exit 0
31
+ ;;
32
+ *)
33
+ exit 0
34
+ ;;
35
+ esac
36
+ EOF
37
+ chmod 700 "$fakebin/aws"
38
+
39
+ service_dir="$HOME/.direxio/nodes/root-destroy.example.test"
40
+ mkdir -p "$service_dir"
41
+ state="$service_dir/state.json"
42
+ jq -n \
43
+ --arg service_dir "$service_dir" \
44
+ '{
45
+ region: "us-east-1",
46
+ domain_mode: "user",
47
+ domain: "root-destroy.example.test",
48
+ agent_service_dir: $service_dir,
49
+ agent_service_id: "root-destroy.example.test",
50
+ resources: {
51
+ instance_id: "i-root-destroy",
52
+ eip_id: "eipalloc-root-destroy",
53
+ sg_id: "sg-root-destroy",
54
+ key_name: "direxio-root-destroy"
55
+ }
56
+ }' > "$state"
57
+
58
+ calls="$tmp/aws.calls"
59
+ : > "$calls"
60
+ set +e
61
+ CALLS="$calls" PATH="$fakebin:$PATH" bash "$ROOT/scripts/destroy.sh" "$state" > "$tmp/destroy.out" 2>&1
62
+ destroy_rc=$?
63
+ set -e
64
+
65
+ [ "$destroy_rc" -eq 0 ] || {
66
+ echo "destroy must allow root identity when the operator chose root credentials" >&2
67
+ cat "$tmp/destroy.out" >&2
68
+ exit 1
69
+ }
70
+ grep -q 'source = ' "$tmp/destroy.out"
71
+
72
+ for expected in 'ec2 terminate-instances' 'ec2 release-address' 'ec2 delete-security-group' 'ec2 delete-key-pair'; do
73
+ if ! grep -F "$expected" "$calls" >/dev/null; then
74
+ echo "destroy should process recorded AWS resource with root identity: $expected" >&2
75
+ cat "$calls" >&2
76
+ exit 1
77
+ fi
78
+ done
79
+
80
+ if grep -F 'route53 change-resource-record-sets' "$calls" >/dev/null; then
81
+ echo "destroy should not touch Route53 for DOMAIN_MODE=user" >&2
82
+ cat "$calls" >&2
83
+ exit 1
84
+ fi
85
+
86
+ if [ -d "$service_dir" ]; then
87
+ echo "destroy should remove local service state after processing resources" >&2
88
+ exit 1
89
+ fi
90
+
91
+ echo "destroy root identity allowed ok"
@@ -0,0 +1,80 @@
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/aws" <<'EOF'
14
+ #!/usr/bin/env bash
15
+ set -euo pipefail
16
+ printf 'aws' >> "$CALLS"
17
+ printf ' %q' "$@" >> "$CALLS"
18
+ printf '\n' >> "$CALLS"
19
+
20
+ case "${1:-} ${2:-}" in
21
+ "sts get-caller-identity")
22
+ case "$*" in
23
+ *"--query Arn"*) printf 'arn:aws:iam::123456789012:user/DirexioDeployer-Test\n' ;;
24
+ *"--query Account"*) printf '123456789012\n' ;;
25
+ *) printf '{"Account":"123456789012","Arn":"arn:aws:iam::123456789012:user/DirexioDeployer-Test"}\n' ;;
26
+ esac
27
+ ;;
28
+ "route53 list-hosted-zones")
29
+ printf '{"HostedZones":[{"Id":"/hostedzone/ZCREATE","Name":"route53-destroy.example.test."}]}\n'
30
+ ;;
31
+ "route53 change-resource-record-sets")
32
+ exit 0
33
+ ;;
34
+ "route53 delete-hosted-zone")
35
+ exit 0
36
+ ;;
37
+ "ec2 terminate-instances"|"ec2 wait"|"ec2 release-address"|"ec2 delete-security-group"|"ec2 delete-key-pair")
38
+ exit 0
39
+ ;;
40
+ *)
41
+ exit 0
42
+ ;;
43
+ esac
44
+ EOF
45
+ chmod 700 "$fakebin/aws"
46
+
47
+ service_dir="$HOME/.direxio/nodes/route53-destroy.example.test"
48
+ mkdir -p "$service_dir"
49
+ state="$service_dir/state.json"
50
+ jq -n \
51
+ --arg service_dir "$service_dir" \
52
+ '{
53
+ region: "us-east-1",
54
+ domain_mode: "route53",
55
+ domain: "route53-destroy.example.test",
56
+ agent_service_dir: $service_dir,
57
+ resources: {
58
+ public_ip: "203.0.113.99",
59
+ route53_zone_id: "ZCREATE",
60
+ route53_zone_name: "route53-destroy.example.test",
61
+ route53_zone_created_by_deployer: "true"
62
+ }
63
+ }' > "$state"
64
+
65
+ calls="$tmp/aws.calls"
66
+ CALLS="$calls" PATH="$fakebin:$PATH" bash "$ROOT/scripts/destroy.sh" "$state" >/dev/null
67
+
68
+ grep -q '^aws route53 change-resource-record-sets --hosted-zone-id ZCREATE' "$calls" || {
69
+ echo "destroy should delete the Route53 A record from the recorded zone" >&2
70
+ cat "$calls" >&2
71
+ exit 1
72
+ }
73
+
74
+ grep -q '^aws route53 delete-hosted-zone --id ZCREATE$' "$calls" || {
75
+ echo "destroy should delete a deployer-created hosted zone to stop Route53 hosted-zone billing" >&2
76
+ cat "$calls" >&2
77
+ exit 1
78
+ }
79
+
80
+ echo "destroy route53 zone ok"