unoverse 0.1.100 → 0.1.102
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 +13 -1
- package/operator/infra/aws/variables.tf +10 -0
- package/operator/lib/deploy.sh +41 -14
- package/operator/lib/ground.sh +22 -2
- package/package.json +1 -1
|
@@ -242,10 +242,22 @@ data "aws_ami" "ubuntu" {
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
# The key pair, from the operator's own public key. AWS accepts an imported key, so nothing
|
|
246
|
+
# has to pre-exist in the account and no private key is ever downloaded or stored.
|
|
247
|
+
resource "aws_key_pair" "operator" {
|
|
248
|
+
count = var.operator_public_key != "" ? 1 : 0
|
|
249
|
+
key_name = "${var.name}-operator"
|
|
250
|
+
public_key = var.operator_public_key
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
locals {
|
|
254
|
+
key_name = var.operator_public_key != "" ? aws_key_pair.operator[0].key_name : var.ssh_key_name
|
|
255
|
+
}
|
|
256
|
+
|
|
245
257
|
resource "aws_instance" "app" {
|
|
246
258
|
ami = data.aws_ami.ubuntu.id
|
|
247
259
|
instance_type = local.s.instance
|
|
248
|
-
key_name =
|
|
260
|
+
key_name = local.key_name
|
|
249
261
|
vpc_security_group_ids = [aws_security_group.app.id]
|
|
250
262
|
|
|
251
263
|
root_block_device {
|
|
@@ -102,3 +102,13 @@ variable "marketplace_url" {
|
|
|
102
102
|
type = string
|
|
103
103
|
default = ""
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
# YOUR public key, uploaded as this universe's EC2 key pair. Deploying means ssh-ing from
|
|
107
|
+
# the machine that runs `unoverse deploy`, so the key that must work is the one that machine
|
|
108
|
+
# holds — not whichever pair happens to exist in the account. Empty falls back to
|
|
109
|
+
# ssh_key_name, for an operator with no local key.
|
|
110
|
+
variable "operator_public_key" {
|
|
111
|
+
description = "An ssh public key (the contents of ~/.ssh/id_ed25519.pub). Empty = use ssh_key_name instead."
|
|
112
|
+
type = string
|
|
113
|
+
default = ""
|
|
114
|
+
}
|
package/operator/lib/deploy.sh
CHANGED
|
@@ -366,6 +366,35 @@ _ensure_ground_config() {
|
|
|
366
366
|
esac
|
|
367
367
|
fi
|
|
368
368
|
|
|
369
|
+
# CAN THIS MACHINE ACTUALLY REACH WHAT IT IS ABOUT TO BUILD?
|
|
370
|
+
#
|
|
371
|
+
# An EC2 key pair the operator does not hold applies perfectly and then fails at the ship
|
|
372
|
+
# step: eleven minutes of RDS and ElastiCache provisioning, then "Permission denied
|
|
373
|
+
# (publickey)". Generating the ground correctly is not enough — tfvars is the developer's
|
|
374
|
+
# file and `unoverse update` never rewrites it, so a ground made before this check keeps
|
|
375
|
+
# its unusable key forever and fails the same way every time.
|
|
376
|
+
#
|
|
377
|
+
# Checked here, before the plan, because the cost of being wrong is money and eleven
|
|
378
|
+
# minutes, and the fix is one line in a file this command already edits.
|
|
379
|
+
if [ "$cloud" = "aws" ]; then
|
|
380
|
+
local named_key pub_key
|
|
381
|
+
named_key=$(grep -E '^ssh_key_name[[:space:]]*=' "$tfv" 2>/dev/null | sed -E 's/.*"([^"]*)".*/\1/')
|
|
382
|
+
if [ -n "$named_key" ] && ! grep -qE '^operator_public_key[[:space:]]*=[[:space:]]*"ssh-' "$tfv" 2>/dev/null; then
|
|
383
|
+
for pub_key in "$HOME/.ssh/id_ed25519.pub" "$HOME/.ssh/id_rsa.pub" ""; do
|
|
384
|
+
[ -n "$pub_key" ] && [ -f "$pub_key" ] && break
|
|
385
|
+
done
|
|
386
|
+
if [ -n "$pub_key" ]; then
|
|
387
|
+
node -e 'const fs=require("fs");const[f,v]=process.argv.slice(1);let s=fs.readFileSync(f,"utf8");
|
|
388
|
+
s=s.replace(/^ssh_key_name\s*=.*$/m,"ssh_key_name = \"\" # empty = terraform uploads operator_public_key below\noperator_public_key = "+JSON.stringify(v)+" # your key: the deploy ssh-es from this machine");
|
|
389
|
+
fs.writeFileSync(f,s)' "$tfv" "$(cat "$pub_key")"
|
|
390
|
+
ok "SSH key: your own ${DIM}($(basename "$pub_key") — replacing '"'"'$named_key'"'"', whose private half is not on this machine)${NC}"
|
|
391
|
+
else
|
|
392
|
+
warn "No ssh key on this machine. The deploy will build, then fail to reach the server"
|
|
393
|
+
info "Make one with ${BOLD}ssh-keygen -t ed25519${NC} and run this again"
|
|
394
|
+
fi
|
|
395
|
+
fi
|
|
396
|
+
fi
|
|
397
|
+
|
|
369
398
|
_tf_fill docr_token DOCR_TOKEN "Registry access token (from your Unoverse admin)"
|
|
370
399
|
_tf_fill openai_api_key OPENAI_API_KEY "OPENAI_API_KEY (powers the platform's AI)"
|
|
371
400
|
if grep -q '^auth_issuer[[:space:]]*=[[:space:]]*"FILL_ME"' "$tfv" 2>/dev/null; then
|
|
@@ -540,9 +569,8 @@ _ground_apply() {
|
|
|
540
569
|
}
|
|
541
570
|
|
|
542
571
|
cmd_deploy() {
|
|
543
|
-
# A LEADING GROUND NAME IS NOT A SUBCOMMAND. `unoverse deploy aws` names the cloud
|
|
544
|
-
#
|
|
545
|
-
# ground off the front when it is there, and leave everything else exactly as it was.
|
|
572
|
+
# A LEADING GROUND NAME IS NOT A SUBCOMMAND. `unoverse deploy aws` names the cloud. Take
|
|
573
|
+
# it off the front when it is there, and leave everything else exactly as it was.
|
|
546
574
|
local GROUND_ARG=""
|
|
547
575
|
case "${1:-}" in
|
|
548
576
|
do|digitalocean|aws|amazon) GROUND_ARG="$1"; shift ;;
|
|
@@ -700,9 +728,8 @@ EOF
|
|
|
700
728
|
# A FIRST DEPLOY TO A NEW SERVER IS A PROVISION, NOT AN IMAGE PULL. Bare `deploy` meant
|
|
701
729
|
# only "pull the latest images and restart", which on a freshly built droplet fails on
|
|
702
730
|
# "Destination directory /opt/gravity does not exist" — /opt/gravity being the directory
|
|
703
|
-
# install.yml creates.
|
|
704
|
-
#
|
|
705
|
-
# journey, so it asks the server what it is and picks the right playbook itself.
|
|
731
|
+
# install.yml creates. Deploy owns the whole journey, so it asks the server what it is and
|
|
732
|
+
# picks the right playbook itself rather than expecting anyone to know a second command.
|
|
706
733
|
# THE MARKER IS "SETUP FINISHED", NOT "A DIRECTORY EXISTS". Testing for /opt/gravity
|
|
707
734
|
# looked right and was not: install.yml creates that directory as its seventh task, so a
|
|
708
735
|
# first-time setup that then FAILED at the database step still left it behind. The next
|
|
@@ -714,7 +741,7 @@ EOF
|
|
|
714
741
|
"$deploy_user@$deploy_host" 'test -f /opt/gravity/.setup-complete' >/dev/null 2>&1; then
|
|
715
742
|
echo ""
|
|
716
743
|
info "This server's setup has not finished. Running it ${DIM}(install, database, verify)${NC}"
|
|
717
|
-
subcommand="
|
|
744
|
+
subcommand="first-time"
|
|
718
745
|
fi
|
|
719
746
|
fi
|
|
720
747
|
|
|
@@ -732,10 +759,10 @@ EOF
|
|
|
732
759
|
-e "universe_root=$ROOT" \
|
|
733
760
|
-e "env_file=$env_prod"
|
|
734
761
|
;;
|
|
735
|
-
|
|
736
|
-
#
|
|
737
|
-
#
|
|
738
|
-
#
|
|
762
|
+
first-time)
|
|
763
|
+
# INTERNAL, never typed. Deploy reaches this by finding no .setup-complete stamp on
|
|
764
|
+
# the server: install → database → verify, end to end. Hardening stays a deliberate
|
|
765
|
+
# follow-up (POCs get verified first; harden when you decide to keep the box).
|
|
739
766
|
info "First-time setup: install → database → verify"
|
|
740
767
|
echo ""
|
|
741
768
|
info "[1/3] Provisioning (Docker, services, mounts)..."
|
|
@@ -743,7 +770,7 @@ EOF
|
|
|
743
770
|
-i "$tmp_inventory" \
|
|
744
771
|
"$ansible_dir/playbooks/install.yml" \
|
|
745
772
|
-e "universe_root=$ROOT" \
|
|
746
|
-
-e "env_file=$env_prod" || { rm -f "$tmp_inventory"; fail "install failed
|
|
773
|
+
-e "env_file=$env_prod" || { rm -f "$tmp_inventory"; fail "install failed. Fix the error above, then: unoverse deploy $cloud"; exit 1; }
|
|
747
774
|
echo ""
|
|
748
775
|
info "[2/3] Database setup..."
|
|
749
776
|
ansible-playbook \
|
|
@@ -751,14 +778,14 @@ EOF
|
|
|
751
778
|
"$ansible_dir/playbooks/db-setup.yml" \
|
|
752
779
|
-e "universe_root=$ROOT" \
|
|
753
780
|
-e "pg_admin_url=$pg_admin_url" \
|
|
754
|
-
-e "env_file=$env_prod" || { rm -f "$tmp_inventory"; fail "
|
|
781
|
+
-e "env_file=$env_prod" || { rm -f "$tmp_inventory"; fail "database setup failed. Fix the error above, then: unoverse deploy $cloud"; exit 1; }
|
|
755
782
|
echo ""
|
|
756
783
|
info "[3/3] Verifying..."
|
|
757
784
|
ansible-playbook \
|
|
758
785
|
-i "$tmp_inventory" \
|
|
759
786
|
"$ansible_dir/playbooks/test-connectivity.yml" \
|
|
760
787
|
-e "universe_root=$ROOT" \
|
|
761
|
-
-e "env_file=$env_prod" || { rm -f "$tmp_inventory"; fail "verification failed
|
|
788
|
+
-e "env_file=$env_prod" || { rm -f "$tmp_inventory"; fail "verification failed. Look at the output above, then: unoverse deploy $cloud"; exit 1; }
|
|
762
789
|
# Only now. Every step passed, so the next deploy can safely be an image push.
|
|
763
790
|
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15 \
|
|
764
791
|
"$deploy_user@$deploy_host" 'touch /opt/gravity/.setup-complete' >/dev/null 2>&1
|
package/operator/lib/ground.sh
CHANGED
|
@@ -239,9 +239,28 @@ _ground_aws() {
|
|
|
239
239
|
region=$(aws configure get region 2>/dev/null)
|
|
240
240
|
[ -n "$region" ] && ok "region: $region" || { region="us-east-1"; info "no default region configured. Using us-east-1"; }
|
|
241
241
|
|
|
242
|
+
# A KEY PAIR YOU CANNOT USE IS NOT A KEY PAIR. This took the FIRST key pair in the region,
|
|
243
|
+
# which on a real account is whatever was created years ago in the console — its private
|
|
244
|
+
# half is not on this laptop, so terraform applied happily and Ansible then died on
|
|
245
|
+
# "Permission denied (publickey)" after eleven minutes of RDS provisioning.
|
|
246
|
+
#
|
|
247
|
+
# The operator's own key is the one that works, because deploying IS ssh-ing from here.
|
|
248
|
+
# Upload it under the universe's name and use that: AWS lets terraform import a public
|
|
249
|
+
# key, so nothing has to pre-exist and nothing has to be downloaded. Fall back to the
|
|
250
|
+
# account's existing pairs only when this machine has no key at all.
|
|
251
|
+
local pubkey=""
|
|
252
|
+
for pubkey in "$HOME/.ssh/id_ed25519.pub" "$HOME/.ssh/id_rsa.pub" ""; do
|
|
253
|
+
[ -n "$pubkey" ] && [ -f "$pubkey" ] && break
|
|
254
|
+
done
|
|
255
|
+
|
|
242
256
|
keys=$(aws ec2 describe-key-pairs --query 'KeyPairs[].KeyName' --output text 2>/dev/null | tr '\t' '\n')
|
|
243
257
|
first_key=$(echo "$keys" | head -1)
|
|
244
|
-
if [ -n "$
|
|
258
|
+
if [ -n "$pubkey" ]; then
|
|
259
|
+
first_key="" # terraform creates the pair from operator_public_key
|
|
260
|
+
ok "SSH key: your own ${DIM}($(basename "$pubkey") — uploaded as ${GROUND_NAME:-this universe}-operator)${NC}"
|
|
261
|
+
elif [ -n "$first_key" ]; then
|
|
262
|
+
warn "no SSH key on this machine — using the account's ${BOLD}$first_key${NC}"
|
|
263
|
+
warn "the deploy will fail unless you hold its private half"
|
|
245
264
|
ok "EC2 key pair: $first_key"
|
|
246
265
|
else
|
|
247
266
|
warn "no EC2 key pairs in $region. Create one first (aws ec2 create-key-pair)"
|
|
@@ -268,7 +287,8 @@ _ground_aws() {
|
|
|
268
287
|
region = "$region"
|
|
269
288
|
name = "$GROUND_NAME"
|
|
270
289
|
admin_cidr = "${ip:-FILL_ME}${ip:+/32}" # YOUR IP — the only SSH source
|
|
271
|
-
ssh_key_name = "${first_key
|
|
290
|
+
ssh_key_name = "${first_key}" # empty = terraform uploads operator_public_key below
|
|
291
|
+
operator_public_key = "$( [ -n "$pubkey" ] && cat "$pubkey" )" # your key: the deploy ssh-es from this machine
|
|
272
292
|
admin_email = "FILL_ME" # initial admin (Cognito user, all roles; invite emailed)
|
|
273
293
|
size = "small" # small (POC) | medium | large
|
|
274
294
|
# Domain is OPTIONAL — empty brings the universe up on the ALB's DNS name over
|
package/package.json
CHANGED