unoverse 0.1.125 → 0.1.127
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/operator/infra/aws/main.tf +40 -3
- package/operator/lib/destroy.sh +89 -5
- package/package.json +1 -1
|
@@ -271,6 +271,28 @@ resource "aws_instance" "app" {
|
|
|
271
271
|
volume_type = "gp3"
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
# IMDSv2 REQUIRED, and this is not optional in a governed account.
|
|
275
|
+
#
|
|
276
|
+
# AWS's own recommended guardrail denies ec2:RunInstances unless the launch enforces
|
|
277
|
+
# IMDSv2, and organisations apply it as a Service Control Policy. Without this block the
|
|
278
|
+
# AWS default is `optional`, the condition is not met, and the deny fires:
|
|
279
|
+
#
|
|
280
|
+
# UnauthorizedOperation ... ec2:RunInstances ... with an explicit deny in a service
|
|
281
|
+
# control policy
|
|
282
|
+
#
|
|
283
|
+
# That error names no condition, so it reads as "EC2 is banned here" and sends you to your
|
|
284
|
+
# cloud team for a policy change you do not need. The condition is visible only by
|
|
285
|
+
# decoding the failure message (`aws sts decode-authorization-message`), which showed
|
|
286
|
+
# `ec2:MetadataHttpTokens = required`. Proved by dry run 2026-08-06: identical launch
|
|
287
|
+
# refused without this block, accepted with it.
|
|
288
|
+
#
|
|
289
|
+
# It is the right default everywhere regardless. IMDSv1 lets any process that can forge a
|
|
290
|
+
# simple GET read the instance's credentials; IMDSv2 requires a signed token first.
|
|
291
|
+
metadata_options {
|
|
292
|
+
http_endpoint = "enabled"
|
|
293
|
+
http_tokens = "required"
|
|
294
|
+
}
|
|
295
|
+
|
|
274
296
|
tags = { Name = "${var.name}-app" }
|
|
275
297
|
}
|
|
276
298
|
|
|
@@ -589,10 +611,25 @@ resource "aws_iam_user_policy" "bedrock" {
|
|
|
589
611
|
Version = "2012-10-17"
|
|
590
612
|
Statement = [{
|
|
591
613
|
Effect = "Allow"
|
|
614
|
+
# WIDE ON RUNTIME, silent on spending.
|
|
615
|
+
#
|
|
616
|
+
# Three named Invoke actions dated fast. `Converse` is the current unified API and the
|
|
617
|
+
# Bedrock nodes already call it; `ApplyGuardrail` is a separate action the guardrail
|
|
618
|
+
# node needs; knowledge bases add `Retrieve`. Each arrival meant editing this list,
|
|
619
|
+
# re-applying, and in a managed account asking an administrator again.
|
|
620
|
+
#
|
|
621
|
+
# NOT `bedrock:*`. That includes CreateProvisionedModelThroughput, which commits money.
|
|
622
|
+
# Everything below is invoke-and-read: it cannot buy capacity, and it cannot create or
|
|
623
|
+
# delete a model or a guardrail.
|
|
624
|
+
#
|
|
625
|
+
# Model access is still granted per model in the Bedrock console. No policy grants it.
|
|
592
626
|
Action = [
|
|
593
|
-
"bedrock:InvokeModel",
|
|
594
|
-
"bedrock:
|
|
595
|
-
"bedrock:
|
|
627
|
+
"bedrock:InvokeModel*",
|
|
628
|
+
"bedrock:Converse*",
|
|
629
|
+
"bedrock:ApplyGuardrail",
|
|
630
|
+
"bedrock:Retrieve*",
|
|
631
|
+
"bedrock:List*",
|
|
632
|
+
"bedrock:Get*",
|
|
596
633
|
]
|
|
597
634
|
Resource = "*"
|
|
598
635
|
}]
|
package/operator/lib/destroy.sh
CHANGED
|
@@ -18,6 +18,84 @@
|
|
|
18
18
|
# It never touches the developer's terraform.tfvars, their .env, or their code. Only the
|
|
19
19
|
# cloud resources this ground owns.
|
|
20
20
|
|
|
21
|
+
# ── Resources a previous run created and did not write down ───────────────────────────────
|
|
22
|
+
#
|
|
23
|
+
# `destroy` removes what STATE records. A deploy killed mid-apply leaves AWS holding
|
|
24
|
+
# resources terraform never recorded, and they do not merely linger: a database or a load
|
|
25
|
+
# balancer keeps a security group alive, so the part terraform CAN do fails on
|
|
26
|
+
#
|
|
27
|
+
# DependencyViolation: resource sg-... has a dependent object
|
|
28
|
+
#
|
|
29
|
+
# after fifteen minutes of retrying, and the teardown reports only that it did not finish.
|
|
30
|
+
# The operator is then left deleting a database, a Redis cluster and a load balancer by hand
|
|
31
|
+
# before a "one command" teardown will complete. That happened on 2026-08-05.
|
|
32
|
+
#
|
|
33
|
+
# IT IMPORTS. IT NEVER DELETES.
|
|
34
|
+
#
|
|
35
|
+
# Adopting an orphan into state is a state write and touches no cloud API. The deletion is
|
|
36
|
+
# still terraform's, from the plan the operator sees and confirms by name. That matters twice
|
|
37
|
+
# over: terraform already knows the order to take this stack apart (load balancer before its
|
|
38
|
+
# security group, database before the group holding its interface), and a second deletion
|
|
39
|
+
# engine written in bash would not, and would drift from main.tf the day a resource was
|
|
40
|
+
# added.
|
|
41
|
+
#
|
|
42
|
+
# NOTHING WE DID NOT CREATE, enforced four ways rather than trusted:
|
|
43
|
+
#
|
|
44
|
+
# 1. TAGGED AS OURS. `_cloud_orphans` lists only what carries `Universe=<name>`, and
|
|
45
|
+
# `default_tags` stamps that on resources THIS configuration creates. A borrowed
|
|
46
|
+
# database keeps its own tags and can never appear here (main.tf, "Borrowed resources
|
|
47
|
+
# are never owned").
|
|
48
|
+
# 2. A KNOWN ADDRESS. The ARN must map to one of the handful of resources main.tf declares.
|
|
49
|
+
# Anything else is left alone, whatever it is tagged.
|
|
50
|
+
# 3. NAMED AFTER THIS UNIVERSE. The ARN must contain the universe's own name, which is why
|
|
51
|
+
# `aws_instance.app` is deliberately absent below: an EC2 ARN carries only an opaque
|
|
52
|
+
# instance id, so it cannot be verified this way, and an unverifiable match is not one
|
|
53
|
+
# worth making. An untracked server is reported instead.
|
|
54
|
+
# 4. NOT ALREADY TRACKED. An address present in state is skipped, so this can only ever add
|
|
55
|
+
# what is missing, never re-point something the operator already owns.
|
|
56
|
+
#
|
|
57
|
+
# Failure is not fatal anywhere in here: the worst case is the teardown behaving exactly as
|
|
58
|
+
# it did before.
|
|
59
|
+
_adopt_orphans() {
|
|
60
|
+
local dir="$1" uname_="$2" orphans arn addr id adopted=0 unclaimed=""
|
|
61
|
+
|
|
62
|
+
command -v aws >/dev/null 2>&1 || return 0
|
|
63
|
+
orphans=$(_cloud_orphans aws "$uname_" 2>/dev/null)
|
|
64
|
+
[ -n "$orphans" ] && [ "$orphans" != "unknown" ] || return 0
|
|
65
|
+
|
|
66
|
+
while IFS= read -r arn; do
|
|
67
|
+
[ -n "$arn" ] || continue
|
|
68
|
+
addr=""; id="$arn"
|
|
69
|
+
case "$arn" in
|
|
70
|
+
*:rds:*:db:"$uname_"-pg) addr="aws_db_instance.postgres"; id="$uname_-pg" ;;
|
|
71
|
+
*:elasticache:*:replicationgroup:"$uname_"-redis) addr="aws_elasticache_replication_group.redis"; id="$uname_-redis" ;;
|
|
72
|
+
*:loadbalancer/app/"$uname_"-alb/*) addr="aws_lb.public" ;;
|
|
73
|
+
*:targetgroup/"$uname_"-app/*) addr="aws_lb_target_group.app" ;;
|
|
74
|
+
*:targetgroup/"$uname_"-canvas/*) addr="aws_lb_target_group.canvas[0]" ;;
|
|
75
|
+
*) unclaimed="$unclaimed $arn"$'\n' ; continue ;;
|
|
76
|
+
esac
|
|
77
|
+
terraform -chdir="$dir" state list 2>/dev/null | grep -qxF "$addr" && continue
|
|
78
|
+
if terraform -chdir="$dir" import -input=false "$addr" "$id" >/dev/null 2>&1; then
|
|
79
|
+
adopted=$((adopted + 1))
|
|
80
|
+
else
|
|
81
|
+
unclaimed="$unclaimed $arn"$'\n'
|
|
82
|
+
fi
|
|
83
|
+
done <<EOF
|
|
84
|
+
$orphans
|
|
85
|
+
EOF
|
|
86
|
+
|
|
87
|
+
[ "$adopted" -gt 0 ] && \
|
|
88
|
+
info "Adopted $adopted resource(s) an interrupted run left untracked. They are in the plan below"
|
|
89
|
+
# SAID OUT LOUD, never silently swept. Anything here is billing and this teardown will not
|
|
90
|
+
# remove it, which the operator has to know BEFORE they are told the universe is gone.
|
|
91
|
+
if [ -n "$unclaimed" ]; then
|
|
92
|
+
warn "these carry this universe's tag but are not resources this teardown can remove:"
|
|
93
|
+
printf '%s' "$unclaimed"
|
|
94
|
+
echo " Check them yourself before assuming the bill has stopped."
|
|
95
|
+
fi
|
|
96
|
+
return 0
|
|
97
|
+
}
|
|
98
|
+
|
|
21
99
|
cmd_destroy() {
|
|
22
100
|
# NAME THE GROUND. This took the first configured one, and digitalocean is first in the
|
|
23
101
|
# list — so an operator with both grounds who typed `unoverse destroy` meaning AWS would
|
|
@@ -46,9 +124,19 @@ cmd_destroy() {
|
|
|
46
124
|
echo -e " ${RED}${BOLD}⬡ Taking down your $pretty universe${NC}"
|
|
47
125
|
echo ""
|
|
48
126
|
|
|
49
|
-
local tmp planfile rc
|
|
127
|
+
local tmp planfile rc name
|
|
50
128
|
tmp=$(mktemp -d); planfile="$tmp/plan"
|
|
51
129
|
|
|
130
|
+
# The universe's name, read once. It is the tag every resource carries, so it is what
|
|
131
|
+
# decides which untracked resources are this universe's (_adopt_orphans) as well as the
|
|
132
|
+
# word the operator types to confirm.
|
|
133
|
+
name=$(grep -E '^name[[:space:]]*=' "$dir/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
|
|
134
|
+
name="${name:-universe}"
|
|
135
|
+
|
|
136
|
+
# BEFORE THE PLAN, so what an interrupted run left behind is in the list the operator
|
|
137
|
+
# confirms, rather than something they discover by hand an hour later.
|
|
138
|
+
[ "$cloud" = "aws" ] && _adopt_orphans "$dir" "$name"
|
|
139
|
+
|
|
52
140
|
# APPLY THE CONFIGURATION TO THE DATABASE FIRST, because destroy does not.
|
|
53
141
|
#
|
|
54
142
|
# `terraform destroy` deletes a resource as the last apply left it, so an attribute that
|
|
@@ -98,10 +186,6 @@ cmd_destroy() {
|
|
|
98
186
|
info "Staying: ${BOLD}your own database${NC} ${DIM}(byo_postgres_url — this stack never owned it)${NC}"
|
|
99
187
|
fi
|
|
100
188
|
|
|
101
|
-
local name
|
|
102
|
-
name=$(grep -E '^name[[:space:]]*=' "$dir/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
|
|
103
|
-
name="${name:-universe}"
|
|
104
|
-
|
|
105
189
|
echo ""
|
|
106
190
|
# Say what is actually lost. With a reused cluster the DATABASE survives, and claiming
|
|
107
191
|
# otherwise makes the warning untrue in the commonest case — which teaches people to
|
package/package.json
CHANGED