Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
.idea
.gradle
.vscode
build
build/*
!build/libs/
build/libs/*
!build/libs/*.jar
out
bin
*.iml
Expand Down
6 changes: 3 additions & 3 deletions .github/scripts/home-dev-sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ render_env() {
echo "IMAGE_TAG=$1" >>.env
}

deploy() { "${COMPOSE[@]}" pull && "${COMPOSE[@]}" up -d --remove-orphans; }
deploy() { "${COMPOSE[@]}" pull app && "${COMPOSE[@]}" up -d --remove-orphans db redis app; }

healthy() {
for _ in $(seq 1 20); do
for _ in $(seq 1 90); do
if curl -fsS -m 3 http://127.0.0.1:8081/actuator/health >/dev/null 2>&1; then
return 0
fi
sleep 3
sleep 1
done
return 1
}
Expand Down
64 changes: 64 additions & 0 deletions .github/scripts/ssm-command.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail

instance_id="${1:?usage: ssm-command.sh <instance-id> [--quiet] <command> | --stdin}"
shift
quiet=0
if [ "${1:-}" = "--quiet" ]; then
quiet=1
shift
fi

if [ "${1:-}" = "--stdin" ]; then
script=$(cat)
encoded=$(printf '%s' "$script" | base64 | tr -d '\n')
command="echo '$encoded' | base64 -d | bash"
else
command="${1:?command is required}"
fi

region="${AWS_REGION:-ap-northeast-2}"
encoded_command=$(printf '%s' "$command" | base64 | tr -d '\n')
params="commands=echo ${encoded_command} | base64 -d | bash"
command_id=$(aws ssm send-command \
--region "$region" \
--instance-ids "$instance_id" \
--document-name AWS-RunShellScript \
--parameters "$params" \
--query 'Command.CommandId' \
--output text)

deadline=$((SECONDS + 120))
while :; do
status=$(aws ssm get-command-invocation \
--region "$region" \
--command-id "$command_id" \
--instance-id "$instance_id" \
--query Status \
--output text 2>/dev/null || echo Pending)
case "$status" in
Success|Failed|Cancelled|TimedOut) break ;;
esac
[ "$SECONDS" -lt "$deadline" ] || { status=TimedOut; break; }
sleep 2
done

aws ssm get-command-invocation \
--region "$region" \
--command-id "$command_id" \
--instance-id "$instance_id" \
--query StandardOutputContent \
--output text

if [ "$status" != Success ]; then
echo "SSM command status: $status" >&2
aws ssm get-command-invocation \
--region "$region" \
--command-id "$command_id" \
--instance-id "$instance_id" \
--query StandardErrorContent \
--output text >&2
exit 1
fi

[ "$quiet" -eq 1 ] || echo "SSM command status: Success" >&2
33 changes: 20 additions & 13 deletions .github/scripts/ssm-deploy.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Deploy to the app instance via SSM: find it by tag, ship repo's deploy artifacts
# Deploy to the app instance via SSM: read its ID from Parameter Store, ship deploy artifacts
# (docker-compose.yml, Caddyfile, deploy.sh), run deploy.sh <sha> on it,
# and fail if the command does not succeed. TLS certs come from Doppler, not the repo.
# Usage: ssm-deploy.sh <git-sha|latest>
Expand All @@ -8,29 +8,36 @@ set -euo pipefail
sha="${1:?usage: ssm-deploy.sh <git-sha|latest>}"

[[ "$sha" =~ ^([0-9a-f]{7,40}|latest)$ ]] || { echo "invalid ref: $sha"; exit 1; }
: "${INSTANCE_TAG:?INSTANCE_TAG is required}"
: "${AWS_REGION:?AWS_REGION is required}"
INSTANCE_PARAMETER="${INSTANCE_PARAMETER:-/chaeso/prod/instance-id}"

deploy_dir="$(dirname "$0")/../../deploy"
compose_b64=$(base64 < "$deploy_dir/docker-compose.yml" | tr -d '\n')
alloy_b64=$(base64 < "$deploy_dir/config.alloy" | tr -d '\n')
caddyfile_b64=$(base64 < "$deploy_dir/Caddyfile" | tr -d '\n')
deploysh_b64=$(base64 < "$(dirname "$0")/../../infra/modules/aws/app/remote-deploy.sh" | tr -d '\n')

instance_id=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=${INSTANCE_TAG}" "Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[].InstanceId" --output text)
instance_id="${INSTANCE_ID:-}"
if [ -z "$instance_id" ]; then
instance_id=$(aws ssm get-parameter \
--region "$AWS_REGION" \
--name "$INSTANCE_PARAMETER" \
--query 'Parameter.Value' --output text)
fi
[ -n "$instance_id" ] || {
echo "no running instance tagged ${INSTANCE_TAG}"
echo "no target instance found"
exit 1
}
echo "target: $instance_id"

params=$(jq -n --arg compose "$compose_b64" --arg caddy "$caddyfile_b64" --arg deploysh "$deploysh_b64" --arg sha "$sha" '{commands: [
"echo \($compose) | base64 -d > /opt/app/docker-compose.yml",
"echo \($caddy) | base64 -d > /opt/app/Caddyfile",
"echo \($deploysh) | base64 -d > /opt/app/deploy.sh",
"chmod +x /opt/app/deploy.sh",
"/opt/app/deploy.sh \($sha)"
]}')
deploy_command="echo ${compose_b64} | base64 -d > /opt/app/docker-compose.yml
echo ${alloy_b64} | base64 -d > /opt/app/config.alloy
echo ${caddyfile_b64} | base64 -d > /opt/app/Caddyfile
echo ${deploysh_b64} | base64 -d > /opt/app/deploy.sh
chmod +x /opt/app/deploy.sh
/opt/app/deploy.sh ${sha}"
encoded_command=$(printf '%s' "$deploy_command" | base64 | tr -d '\n')
params="commands=echo ${encoded_command} | base64 -d | bash"
command_id=$(aws ssm send-command \
--instance-ids "$instance_id" \
--document-name AWS-RunShellScript \
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ jobs:
test:
name: Build & Test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
IMAGE: ghcr.io/yapp-github/chaeso-zip-server
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -48,3 +53,25 @@ jobs:
path: build/reports/tests/test
if-no-files-found: ignore
retention-days: 7

- name: Login to GHCR
if: ${{ success() && github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
if: ${{ success() && github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') }}
uses: docker/setup-buildx-action@v3

- name: Build and push tested application image
if: ${{ success() && github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') }}
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.IMAGE }}:${{ github.sha }}
${{ env.IMAGE }}:${{ github.ref_name }}
40 changes: 31 additions & 9 deletions .github/workflows/deploy-home-dev.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
name: Deploy to home server (dev)

on:
push:
workflow_run:
workflows: [CI]
types: [completed]
branches: [dev]
workflow_dispatch:

Expand All @@ -14,35 +16,55 @@ env:

jobs:
build-push:
name: Build & push image (GHCR)
if: github.ref == 'refs/heads/dev'
name: Prepare GHCR image
if: ${{ (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/dev') || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') }}
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
- name: Set up JDK 21 (manual run)
if: github.event_name == 'workflow_dispatch'
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Set up Gradle (manual run)
if: github.event_name == 'workflow_dispatch'
uses: gradle/actions/setup-gradle@v4
- name: Build application JAR (manual run)
if: github.event_name == 'workflow_dispatch'
run: ./gradlew bootJar --no-daemon -x test
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- name: Verify CI-built image
if: github.event_name == 'workflow_run'
env:
IMAGE_TAG: ${{ github.event.workflow_run.head_sha }}
run: docker buildx imagetools inspect "$IMAGE:$IMAGE_TAG"
- uses: docker/build-push-action@v6
if: github.event_name == 'workflow_dispatch'
with:
context: .
push: true
tags: |
${{ env.IMAGE }}:dev
${{ env.IMAGE }}:${{ github.sha }}
${{ env.IMAGE }}:${{ github.event.workflow_run.head_sha || github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

deploy:
name: Deploy over Tailscale SSH
needs: build-push
if: github.ref == 'refs/heads/dev'
if: ${{ needs.build-push.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
Expand All @@ -56,7 +78,7 @@ jobs:

- name: Deploy & verify (auto-rollback)
env:
SHA: ${{ github.sha }}
SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
GHCR_USER: ${{ github.actor }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
Expand All @@ -65,13 +87,13 @@ jobs:
trap 'docker logout ghcr.io >/dev/null 2>&1' EXIT HUP INT TERM
cd /opt/chaeso-dev
git fetch -q origin dev
git reset --hard -q origin/dev
git reset --hard -q "$SHA"
bash .github/scripts/home-dev-sync.sh $SHA"

notify:
name: Deploy result → Discord
needs: [build-push, deploy]
if: always() && github.ref == 'refs/heads/dev'
if: ${{ always() && ((github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/dev') || github.event_name == 'workflow_run') }}
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -81,7 +103,7 @@ jobs:
WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEV_URL }}
BUILD_RESULT: ${{ needs.build-push.result }}
DEPLOY_RESULT: ${{ needs.deploy.result }}
SHA: ${{ github.sha }}
SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
ACTOR: ${{ github.actor }}
REPO: ${{ github.repository }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
Expand Down
Loading
Loading