Last active
May 11, 2026 09:22
-
-
Save koliadych/870a61eb5fa33e80597854723e19cb15 to your computer and use it in GitHub Desktop.
fintechner deploy app to tokyo EC2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # fintechner — build connector + dashboard in CloudShell and ship to EC2. | |
| # Idempotent: re-running re-builds + re-installs. | |
| set -euo pipefail | |
| REGION=ap-northeast-1 | |
| INSTANCE_ID=i-089ad4a6b76132ba2 | |
| WORKDIR=$HOME/work/fintechner | |
| TMPDIR=$(mktemp -d) | |
| S3_BUCKET="fintechner-deploy-$(date +%s)-$(openssl rand -hex 4 2>/dev/null || echo $$)" | |
| cd "$HOME" | |
| # Redirect Go and npm caches to /tmp — CloudShell's $HOME is only 1 GB | |
| # but /tmp has several GB of ephemeral space. | |
| export GOPATH=/tmp/go | |
| export GOMODCACHE=/tmp/go/pkg/mod | |
| export GOCACHE=/tmp/go/build-cache | |
| export NPM_CONFIG_CACHE=/tmp/npm-cache | |
| mkdir -p "$GOPATH" "$GOMODCACHE" "$GOCACHE" "$NPM_CONFIG_CACHE" | |
| # Free any prior $HOME caches that might still take up space. | |
| rm -rf "$HOME/go" "$HOME/.npm" 2>/dev/null || true | |
| # Trap only removes the build temp + S3 bucket. /tmp/go and /tmp/npm-cache | |
| # stay — they're free space we want to reuse on re-run, and Go module | |
| # files are chmod 0444 which makes blanket rm noisy. | |
| trap 'rm -rf "$TMPDIR" 2>/dev/null; aws --region "$REGION" s3 rb "s3://$S3_BUCKET" --force >/dev/null 2>&1 || true' EXIT | |
| if [ ! -d "$WORKDIR/.git" ]; then | |
| echo "ERROR: $WORKDIR not a git checkout — run 02-tf-plan.sh first." | |
| exit 1 | |
| fi | |
| cd "$WORKDIR" | |
| # CloudShell wipes /usr/bin/gh on session restart but ~/.gitconfig keeps a | |
| # credential.helper pointing at it — that prompts for a password. Reinstall | |
| # gh first, then re-bind the helper. | |
| if ! command -v gh >/dev/null 2>&1; then | |
| echo " reinstalling gh..." | |
| sudo curl -fsSL https://cli.github.com/packages/rpm/gh-cli.repo -o /etc/yum.repos.d/gh-cli.repo | |
| sudo dnf install -y gh | |
| fi | |
| # Strip stale credential helpers (multi-line array) and let gh re-add the right one. | |
| git config --global --unset-all credential.helper 2>/dev/null || true | |
| gh auth setup-git --hostname github.com >/dev/null 2>&1 || true | |
| echo " pulling latest feature/tokyo-deploy..." | |
| git fetch origin feature/tokyo-deploy | |
| git checkout feature/tokyo-deploy | |
| git pull --ff-only origin feature/tokyo-deploy | |
| echo " HEAD: $(git rev-parse --short HEAD) — $(git log -1 --pretty=%s)" | |
| echo "=== Installing build tools ===" | |
| # CloudShell's bundled Go is 1.25 and GOSUMDB=off blocks toolchain auto-download. | |
| # Install Go 1.26 directly from go.dev into /tmp (ephemeral, no $HOME pressure). | |
| GO_VERSION=1.26.0 | |
| if [ ! -x /tmp/go-1.26/bin/go ]; then | |
| ARCH=amd64; [ "$(uname -m)" = "aarch64" ] && ARCH=arm64 | |
| echo "Installing Go ${GO_VERSION} (${ARCH}) into /tmp/go-1.26..." | |
| curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" -o /tmp/go.tgz | |
| rm -rf /tmp/go-1.26 && mkdir -p /tmp/go-1.26 | |
| tar -xzf /tmp/go.tgz -C /tmp/go-1.26 --strip-components=1 | |
| rm /tmp/go.tgz | |
| fi | |
| export PATH=/tmp/go-1.26/bin:$PATH | |
| unset GOTOOLCHAIN GOSUMDB GOPROXY # let Go pick safe defaults | |
| export GOTOOLCHAIN=local # use the binary we just installed, don't fetch more | |
| if ! command -v node >/dev/null; then | |
| sudo dnf install -y nodejs20 >/dev/null 2>&1 || sudo dnf install -y nodejs >/dev/null | |
| fi | |
| echo " Go: $(go version)" | |
| echo " Node: $(node --version)" | |
| echo " npm: $(npm --version)" | |
| echo | |
| echo "=== Building connector for linux/arm64 (auto-fetches Go 1.26 if needed) ===" | |
| cd "$WORKDIR/code/connector" | |
| GOOS=linux GOARCH=arm64 CGO_ENABLED=0 \ | |
| go build -ldflags="-w -s" -o "$TMPDIR/connector" ./cmd/connector | |
| echo " connector binary: $(du -h "$TMPDIR/connector" | cut -f1)" | |
| echo | |
| echo "=== Building dashboard (npm ci + next build, 2-4 min) ===" | |
| # Copy source into /tmp so node_modules + .next don't fight $HOME's 1 GB cap | |
| # and we sidestep npm's quirks with symlinked node_modules. | |
| DBUILD=/tmp/dashboard-build | |
| rm -rf "$DBUILD" | |
| cp -r "$WORKDIR/code/dashboard" "$DBUILD" | |
| cd "$DBUILD" | |
| rm -rf node_modules .next | |
| NEXT_TELEMETRY_DISABLED=1 npm ci 2>&1 | tail -3 | |
| # NEXT_PUBLIC_* vars are baked into the client bundle at build time. The WS | |
| # URL must point at Caddy's HTTPS (443), not the connector's raw port 8080 | |
| # which is blocked by the security group from the public internet. | |
| NEXT_TELEMETRY_DISABLED=1 \ | |
| NEXT_PUBLIC_WS_URL="wss://13-193-136-150.sslip.io/ws" \ | |
| NODE_OPTIONS=--max-old-space-size=2048 \ | |
| npm run build 2>&1 | tail -8 | |
| mkdir -p "$TMPDIR/dashboard" | |
| cp -r .next/standalone/. "$TMPDIR/dashboard/" | |
| mkdir -p "$TMPDIR/dashboard/.next" | |
| cp -r .next/static "$TMPDIR/dashboard/.next/" | |
| [ -d public ] && cp -r public "$TMPDIR/dashboard/" || true | |
| echo " dashboard bundle: $(du -sh "$TMPDIR/dashboard" | cut -f1)" | |
| echo | |
| echo "=== Writing install.sh into bundle ===" | |
| cat > "$TMPDIR/install.sh" <<'INSTALL' | |
| #!/bin/bash | |
| set -euxo pipefail | |
| cd "$(dirname "$0")" | |
| sudo install -m 0755 connector /opt/fintechner/bin/connector | |
| sudo rm -rf /opt/fintechner/dashboard | |
| sudo cp -r dashboard /opt/fintechner/dashboard | |
| sudo chown -R ec2-user:ec2-user /opt/fintechner | |
| sudo install -m 0644 /dev/stdin /etc/fintechner/dashboard.env <<DENV | |
| PORT=3000 | |
| HOSTNAME=127.0.0.1 | |
| NEXT_PUBLIC_WS_URL=wss://13-193-136-150.sslip.io/ws | |
| DENV | |
| # DO NOT touch /etc/fintechner/connector.env if it already has the required | |
| # bootstrap (DATABASE_URL, MASTER_ENCRYPTION_KEY, ADMIN_EMAIL). Earlier | |
| # revisions of this script clobbered those, killing the connector on every | |
| # deploy. The bootstrap script (14-bootstrap-and-start.sh) is the only thing | |
| # that writes connector.env; deploys only touch the binary. | |
| if [ ! -s /etc/fintechner/connector.env ] || ! grep -q "^DATABASE_URL=" /etc/fintechner/connector.env; then | |
| echo "WARNING: connector.env missing or incomplete. Connector will fail to start." | |
| echo "Run 14-bootstrap-and-start.sh OR 36-restore-and-fix-deploy.sh first." | |
| fi | |
| # Patch the Caddyfile: previous version used handle_path /ws which strips | |
| # the path and breaks the WS upgrade. handle /ws preserves it. | |
| if grep -q "handle_path /ws" /etc/caddy/Caddyfile; then | |
| sudo sed -i 's|handle_path /ws|handle /ws|' /etc/caddy/Caddyfile | |
| sudo systemctl reload caddy | |
| fi | |
| sudo systemctl daemon-reload | |
| sudo systemctl enable --now fintechner-dashboard | |
| sudo systemctl restart fintechner-dashboard | |
| # Connector binary changed too — restart so the new build (with the | |
| # 60s HTTP timeouts, etc.) actually runs. | |
| if sudo systemctl is-enabled fintechner-connector >/dev/null 2>&1; then | |
| sudo systemctl restart fintechner-connector | |
| fi | |
| INSTALL | |
| chmod +x "$TMPDIR/install.sh" | |
| echo "=== Tarballing (connector + dashboard + install.sh) ===" | |
| cd "$TMPDIR" | |
| tar -czf bundle.tar.gz connector dashboard install.sh | |
| echo " bundle.tar.gz: $(du -h bundle.tar.gz | cut -f1)" | |
| echo | |
| echo "=== Uploading via temp S3 bucket ===" | |
| aws --region "$REGION" s3 mb "s3://$S3_BUCKET" >/dev/null | |
| aws --region "$REGION" s3api put-public-access-block --bucket "$S3_BUCKET" \ | |
| --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" >/dev/null | |
| aws --region "$REGION" s3 cp "$TMPDIR/bundle.tar.gz" "s3://$S3_BUCKET/" >/dev/null | |
| URL=$(aws --region "$REGION" s3 presign "s3://$S3_BUCKET/bundle.tar.gz" --expires-in 1200) | |
| echo " bucket: $S3_BUCKET (auto-deleted on script exit)" | |
| echo | |
| echo "=== Installing on EC2 via SSM ===" | |
| # Each array element is a separate command (no embedded newlines to mangle). | |
| cat > /tmp/ssm-params.json <<JSON | |
| { | |
| "commands": [ | |
| "set -e", | |
| "cd /tmp", | |
| "curl -fsSL '$URL' -o bundle.tar.gz", | |
| "rm -rf /tmp/fintechner-stage", | |
| "mkdir /tmp/fintechner-stage", | |
| "tar -xzf bundle.tar.gz -C /tmp/fintechner-stage", | |
| "bash /tmp/fintechner-stage/install.sh" | |
| ] | |
| } | |
| JSON | |
| CMD_ID=$(aws --region "$REGION" ssm send-command \ | |
| --instance-ids "$INSTANCE_ID" \ | |
| --document-name "AWS-RunShellScript" \ | |
| --parameters file:///tmp/ssm-params.json \ | |
| --query 'Command.CommandId' --output text) | |
| for i in $(seq 1 40); do | |
| sleep 5 | |
| STATUS=$(aws --region "$REGION" ssm get-command-invocation \ | |
| --command-id "$CMD_ID" --instance-id "$INSTANCE_ID" \ | |
| --query 'Status' --output text 2>/dev/null || echo "Pending") | |
| case "$STATUS" in | |
| Success) echo " install: Success"; break ;; | |
| Failed|Cancelled|TimedOut) | |
| echo " install: $STATUS" | |
| aws --region "$REGION" ssm get-command-invocation \ | |
| --command-id "$CMD_ID" --instance-id "$INSTANCE_ID" \ | |
| --query 'StandardErrorContent' --output text | |
| exit 1 ;; | |
| *) echo " install: $STATUS" ;; | |
| esac | |
| done | |
| echo | |
| echo "============================================================" | |
| echo " DEPLOY COMPLETE" | |
| echo "============================================================" | |
| echo | |
| echo " Dashboard: https://13-193-136-150.sslip.io" | |
| echo " Connector: NOT started — needs HL agent key set in Secrets Manager." | |
| echo | |
| echo "Next: set the admin login password." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment