Release #163
This file contains 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
name: Release | |
on: | |
workflow_dispatch: | |
inputs: | |
imageTag: | |
description: 'Docker image tag (default: latest for main or branch name for other branches)' | |
required: false | |
env: | |
HOST_GATEWAY_IP: "172.17.0.1" | |
REGISTRY: ghcr.io | |
IMAGE_NAME: reifnir | |
COMPOSE_PROJECT_NAME: nellebot | |
COMPOSE_FILE_PATH_SRC: docker/docker-compose.prod.yml | |
COMPOSE_FILE_NAME: docker-compose.prod.yml | |
DB_NAME: nellebot | |
DB_BACKUP_SCRIPT: nellebot-backup-db.sh | |
DB_MIGRATION_SCRIPT: database_migration.sql | |
jobs: | |
setup: | |
runs-on: ubuntu-latest | |
outputs: | |
imageTag: ${{ steps.setup-vars.outputs.IMAGE_TAG }} | |
needsSemver: ${{ steps.setup-vars.outputs.NEEDS_SEMVER }} | |
repositoryOwnerLC: ${{ steps.setup-vars.outputs.REPOSITORY_OWNER_LC }} | |
repositoryName: ${{ steps.setup-vars.outputs.REPOSITORY_NAME }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Set up environment variables | |
id: setup-vars | |
run: | | |
BRANCH_NAME_DASH=$(echo "${GITHUB_REF#refs/heads/}" | tr '/' '-') | |
# Set the image tag based on the branch name. If the branch is main, use "latest". | |
# If the image tag is provided as an input, use that instead. | |
if [ -z "$IMAGE_TAG_OVERRIDE" ]; then | |
if [ "$GITHUB_REF" = "refs/heads/main" ]; then | |
IMAGE_TAG=latest | |
else | |
IMAGE_TAG="$BRANCH_NAME_DASH" | |
fi | |
else | |
IMAGE_TAG="$IMAGE_TAG_OVERRIDE" | |
fi | |
echo "IMAGE_TAG=$IMAGE_TAG" >> "$GITHUB_OUTPUT" | |
# If the image tag is "latest" or the branch name, flag it as needing semver. | |
if [ "$IMAGE_TAG" = "latest" ]; then | |
NEEDS_SEMVER=true | |
elif [ "$IMAGE_TAG" = "$BRANCH_NAME_DASH" ]; then | |
NEEDS_SEMVER=true | |
fi | |
echo "NEEDS_SEMVER: $NEEDS_SEMVER" | |
echo "NEEDS_SEMVER=$NEEDS_SEMVER" >> "$GITHUB_OUTPUT" | |
# Set the repository name to lowercase | |
REPOSITORY_OWNER_LC=$(echo $REPOSITORY_OWNER | tr '[:upper:]' '[:lower:]'); | |
echo "REPOSITORY_OWNER_LC=$REPOSITORY_OWNER_LC" >> "$GITHUB_OUTPUT" | |
# Extract the repository name from the repository env i.e. nelle/reifnir => reifnir | |
REPOSITORY_NAME=${REPOSITORY##*/} | |
echo "REPOSITORY_NAME=$REPOSITORY_NAME" >> "$GITHUB_OUTPUT" | |
env: | |
IMAGE_TAG_OVERRIDE: ${{ inputs.imageTag }} | |
REPOSITORY_OWNER: ${{ github.repository_owner }} | |
REPOSITORY: ${{ github.repository }} | |
release: | |
runs-on: ubuntu-latest | |
if: ${{ !github.event.act }} # skip during local actions testing | |
permissions: | |
contents: read | |
packages: write | |
needs: setup | |
env: | |
IMAGE_TAG: ${{ needs.setup.outputs.imageTag }} | |
REPOSITORY_OWNER_LC: ${{ needs.setup.outputs.repositoryOwnerLC }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
with: | |
sparse-checkout: | | |
${{ env.COMPOSE_FILE_PATH_SRC }} | |
sparse-checkout-cone-mode: false | |
- name: Upload compose.yml | |
uses: appleboy/scp-action@v0.1.7 | |
with: | |
host: ${{secrets.VPS_HOST}} | |
port: ${{secrets.VPS_PORT}} | |
username: ${{secrets.NELLEBOT_USER}} | |
key: ${{secrets.NELLEBOT_KEY}} | |
passphrase: ${{secrets.NELLEBOT_PASSPHRASE}} | |
source: ${{ env.COMPOSE_FILE_PATH_SRC }} | |
target: "$HOME/" | |
strip_components: 1 | |
overwrite: true | |
- name: Release | |
uses: appleboy/ssh-action@v1.0.3 | |
env: | |
GHCR_USERNAME: ${{ github.actor }} | |
GHCR_PASSWORD: ${{ secrets.GITHUB_TOKEN }} | |
DB_CONNECTION_STRING: ${{secrets.DB_CONNECTION_STRING}} | |
BOT_TOKEN: ${{secrets.BOT_TOKEN}} | |
PROTECTOR_CERT_PASSWORD: ${{secrets.PROTECTOR_CERT_PASSWORD}} | |
with: | |
host: ${{secrets.VPS_HOST}} | |
port: ${{secrets.VPS_PORT}} | |
username: ${{secrets.NELLEBOT_USER}} | |
key: ${{secrets.NELLEBOT_KEY}} | |
passphrase: ${{secrets.NELLEBOT_PASSPHRASE}} | |
debug: ${{vars.ACTIONS_RUNNER_DEBUG}} | |
script_stop: true | |
script: | | |
FULL_IMAGE_NAME="$REGISTRY/$REPOSITORY_OWNER_LC/$IMAGE_NAME:$IMAGE_TAG" | |
echo "Full image name: $FULL_IMAGE_NAME" | |
MIGRATIONS_CONTAINER_NAME="$COMPOSE_PROJECT_NAME-migrations" | |
# Pull the image from the registry | |
echo $GHCR_PASSWORD | docker login $REGISTRY -u $GHCR_USERNAME --password-stdin | |
docker pull $FULL_IMAGE_NAME | |
# Create a temporary container to extract the migration files | |
docker create --name $MIGRATIONS_CONTAINER_NAME --add-host=host.docker.internal:$HOST_GATEWAY_IP $FULL_IMAGE_NAME | |
# Extract the migration files into a temporary directory | |
TMP_MIGRATIONS_DIR=$(mktemp -d -t "$MIGRATIONS_CONTAINER_NAME-XXXXXX") | |
docker cp $MIGRATIONS_CONTAINER_NAME:/app/migrations/. $TMP_MIGRATIONS_DIR | |
# Remove the temporary container | |
docker rm $MIGRATIONS_CONTAINER_NAME | |
# Stop the running compose project, if it exists | |
docker compose -p $COMPOSE_PROJECT_NAME stop -t 30 || true | |
# Copy the database backup script, if newer, to home directory and run it | |
cp -u "$TMP_MIGRATIONS_DIR/$DB_BACKUP_SCRIPT" ~ | |
chmod +x "$HOME/$DB_BACKUP_SCRIPT" | |
"$HOME/$DB_BACKUP_SCRIPT" | |
# Run the database migration script | |
psql -d $DB_NAME -q -f "$TMP_MIGRATIONS_DIR/$DB_MIGRATION_SCRIPT" | |
# Remove the temporary directory | |
rm -rf $TMP_MIGRATIONS_DIR | |
# Take down the old compose project, if it exists | |
docker compose -p $COMPOSE_PROJECT_NAME down || true | |
# Start the new compose project | |
docker compose -p $COMPOSE_PROJECT_NAME -f "$HOME/$COMPOSE_FILE_NAME" up -d | |
# Prune untagged images | |
docker image prune -f | |
envs: >- | |
HOST_GATEWAY_IP, | |
REGISTRY, | |
REPOSITORY_OWNER_LC, | |
IMAGE_NAME, | |
IMAGE_TAG, | |
COMPOSE_PROJECT_NAME, | |
COMPOSE_FILE_NAME, | |
DB_NAME, | |
DB_BACKUP_SCRIPT, | |
DB_MIGRATION_SCRIPT, | |
GHCR_USERNAME, | |
GHCR_PASSWORD, | |
BOT_TOKEN, | |
DB_CONNECTION_STRING, | |
PROTECTOR_CERT_PASSWORD | |
create-release: | |
runs-on: ubuntu-latest | |
if: ${{ github.ref == 'refs/heads/main' }} | |
permissions: | |
packages: read | |
contents: read | |
needs: [setup, release] | |
env: | |
IMAGE_TAG: ${{ needs.setup.outputs.imageTag }} | |
NEEDS_SEMVER: ${{ needs.setup.outputs.needsSemver }} | |
REPOSITORY_OWNER_LC: ${{ needs.setup.outputs.repositoryOwnerLC }} | |
REPOSITORY_NAME: ${{ needs.setup.outputs.repositoryName }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- name: GitHub Packages Admin | |
id: package-info | |
uses: selfdocumentingcode/ghaction-package-admin@v1.1 | |
with: | |
ghtoken: ${{ github.token }} | |
operation: listPackageVersions | |
org: ${{ github.repository_owner }} | |
package_type: container | |
package_name: ${{ env.REPOSITORY_NAME }} | |
include: metadata.container.tags[*] ${{ env.IMAGE_TAG }} | |
slice: __NONE__ 1 # get the first item only | |
- name: Get container tags | |
run: | | |
echo "IMAGE_TAG: $IMAGE_TAG" | |
echo "NEEDS_SEMVER: $NEEDS_SEMVER" | |
TAG_LIST=$(echo "$PACKAGE_INFO" | jq --raw-output '.[0].metadata.container.tags') | |
echo "TAG_LIST: $TAG_LIST" | |
SHA_TAG=$(echo "$PACKAGE_INFO" | jq --raw-output '.[0].metadata.container.tags | map(select(startswith("sha"))) | .[0]') | |
echo "SHA_TAG: $SHA_TAG" | |
# Exclude "sha-" prefix from SHA_TAG | |
COMMIT_SHA=${SHA_TAG#"sha-"} | |
echo "COMMIT_SHA: $COMMIT_SHA" | |
echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_ENV | |
if [ "${NEEDS_SEMVER}" == "true" ]; then | |
SEMVER_TAG=$(echo "$PACKAGE_INFO" | jq --raw-output '.[0].metadata.container.tags | map(select((startswith("sha") | not) and . != "${IMAGE_TAG}")) | .[0]') | |
else | |
SEMVER_TAG=${IMAGE_TAG} | |
fi | |
echo "SEMVER_TAG: $SEMVER_TAG" | |
echo "SEMVER_TAG=$SEMVER_TAG" >> $GITHUB_ENV | |
env: | |
PACKAGE_INFO: ${{ steps.package-info.outputs.result_json_output }} | |
- name: Build changelog PRs | |
id: build-pr-changelog | |
uses: selfdocumentingcode/pr-release-changelog-builder-action@1.0.0-beta6 | |
with: | |
configuration: ./.config/changelog-builder.json | |
mode: HYBRID | |
owner: ${{ github.repository_owner }} | |
repo: ${{ env.REPOSITORY_NAME }} | |
toTag: ${{ env.COMMIT_SHA }} | |
failOnError: true | |
- name: Check if commit exists on current branch | |
id: check_commit | |
run: | | |
BRANCH_NAME=${GITHUB_REF#refs/heads/} | |
echo "BRANCH_NAME: $BRANCH_NAME" | |
if git branch --contains "$COMMIT_SHA" | grep -q "$BRANCH_NAME"; then | |
echo "Commit $COMMIT_SHA exists on branch $BRANCH_NAME." | |
else | |
echo "Commit $COMMIT_SHA does not exist on $BRANCH_NAME." | |
exit 1 | |
fi | |
env: | |
COMMIT_SHA: ${{ env.COMMIT_SHA }} | |
- name: Install gh cli | |
if: ${{ github.event.act }} # only for local actions testing | |
run: | | |
(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \ | |
&& sudo mkdir -p -m 755 /etc/apt/keyrings \ | |
&& wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ | |
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ | |
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ | |
&& sudo apt update -q \ | |
&& sudo apt install gh -y | |
- uses: actions/create-github-app-token@v1 | |
id: app-token | |
with: | |
app-id: ${{ secrets.GH_APP_ID }} | |
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
- name: Create Release | |
run: | | |
gh release create $SEMVER_TAG \ | |
--target $COMMIT_SHA \ | |
--title $SEMVER_TAG \ | |
--notes "$NOTES" \ | |
--draft \ | |
--repo $REPOSITORY | |
env: | |
GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
REPOSITORY: ${{ github.repository }} | |
SEMVER_TAG: ${{ env.SEMVER_TAG }} | |
COMMIT_SHA: ${{ env.COMMIT_SHA }} | |
NOTES: ${{steps.build-pr-changelog.outputs.changelog}} |