From db73822b5931a94763e1d386cf82dc0b1c7ca6ed Mon Sep 17 00:00:00 2001 From: Darren Reid Date: Wed, 6 Nov 2024 15:19:10 +1100 Subject: [PATCH] Initial update to migrate to kamal deployments. --- .github/workflows/README.md | 99 ------------ .github/workflows/release.yml | 215 +++++++++----------------- .kamal/hooks/docker-setup.sample | 3 + .kamal/hooks/post-deploy.sample | 14 ++ .kamal/hooks/post-proxy-reboot.sample | 3 + .kamal/hooks/pre-build.sample | 51 ++++++ .kamal/hooks/pre-connect.sample | 47 ++++++ .kamal/hooks/pre-deploy.sample | 109 +++++++++++++ .kamal/hooks/pre-proxy-reboot.sample | 3 + .kamal/secrets | 22 +++ config/deploy.yml | 65 ++++++++ config/litestream.yml | 18 +++ 12 files changed, 408 insertions(+), 241 deletions(-) delete mode 100644 .github/workflows/README.md create mode 100755 .kamal/hooks/docker-setup.sample create mode 100755 .kamal/hooks/post-deploy.sample create mode 100755 .kamal/hooks/post-proxy-reboot.sample create mode 100755 .kamal/hooks/pre-build.sample create mode 100755 .kamal/hooks/pre-connect.sample create mode 100755 .kamal/hooks/pre-deploy.sample create mode 100755 .kamal/hooks/pre-proxy-reboot.sample create mode 100644 .kamal/secrets create mode 100644 config/deploy.yml create mode 100644 config/litestream.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md deleted file mode 100644 index f6656a2..0000000 --- a/.github/workflows/README.md +++ /dev/null @@ -1,99 +0,0 @@ -## Overview - -This template uses the deployment configurations for a ServiceStack .NET 8 application. The application is containerized using Docker and is set up to be automatically built and deployed via GitHub Actions. The recommended deployment target is a stand-alone Linux server running Ubuntu, with an NGINX reverse proxy also containerized using Docker, which a Docker Compose file is included in the template under the `.deploy` directory. - -### Highlights -- 🌐 **NGINX Reverse Proxy**: Utilizes an NGINX reverse proxy to handle web traffic and SSL termination. -- 🚀 **GitHub Actions**: Leverages GitHub Actions for CI/CD, pushing Docker images to GitHub Container Registry and deploying them on a remote server. -- 🐳 **Dockerized ServiceStack App**: The application is containerized, with the image built using `.NET 8`. -- 🔄 **Automated Migrations**: Includes a separate service for running database migrations. - -### Technology Stack -- **Web Framework**: ServiceStack -- **Language**: C# (.NET 8) -- **Containerization**: Docker -- **Reverse Proxy**: NGINX -- **CI/CD**: GitHub Actions -- **OS**: Ubuntu 22.04 (Deployment Server) - - - -## Deployment Server Setup - -To successfully host your ServiceStack applications, there are several components you need to set up on your deployment server. This guide assumes you're working on a standalone Linux server (Ubuntu is recommended) with SSH access enabled. - -### Prerequisites - -1. **SSH Access**: Required for GitHub Actions to communicate with your server. -2. **Docker**: To containerize your application. -3. **Docker-Compose**: For orchestrating multiple containers. -4. **Ports**: 80 and 443 should be open for web access. -5. **nginx-reverse-proxy**: For routing traffic to multiple ServiceStack applications and managing TLS certificates. - -You can use any cloud-hosted or on-premises server like Digital Ocean, AWS, Azure, etc., for this setup. - -### Step-by-Step Guide - -#### 1. Install Docker and Docker-Compose - -It is best to follow the [latest installation instructions on the Docker website](https://docs.docker.com/engine/install/ubuntu/) to ensure to have the correct setup with the latest patches. - -#### 2. Configure SSH for GitHub Actions - -Generate a dedicated SSH key pair to be used by GitHub Actions: - -```bash -ssh-keygen -t rsa -b 4096 -f ~/.ssh/github_actions -``` - -Add the public key to the `authorized_keys` file on your server: - -```bash -cat ~/.ssh/github_actions.pub >> ~/.ssh/authorized_keys -``` - -Then, add the *private* key to your GitHub Secrets as `DEPLOY_KEY` to enable GitHub Actions to SSH into the server securely. - -#### 3. Set Up nginx-reverse-proxy - -You should have a `docker-compose` file similar to the `nginx-proxy-compose.yml` in your repository. Upload this file to your server: - -```bash -scp nginx-proxy-compose.yml user@your_server:~/ -``` - -To bring up the nginx reverse proxy and its companion container for handling TLS certificates, run: - -```bash -docker compose -f ~/nginx-proxy-compose.yml up -d -``` - -This will start an nginx reverse proxy along with a companion container. They will automatically watch for additional Docker containers on the same network and initialize them with valid TLS certificates. - - - -## GitHub Repository Setup - -Configuring your GitHub repository is an essential step for automating deployments via GitHub Actions. This guide assumes you have a `release.yml` workflow file in your repository's `.github/workflows/` directory, and your deployment server has been set up according to the [Deployment Server Setup](#Deployment-Server-Setup) guidelines. - -### Secrets Configuration - -Your GitHub Actions workflow requires the following secrets to be set in your GitHub repository: - -1. **`DEPLOY_HOST`**: The hostname for SSH access. This can be either an IP address or a domain with an A-record pointing to your server. -2. **`DEPLOY_USERNAME`**: The username for SSH login. Common examples include `ubuntu`, `ec2-user`, or `root`. -3. **`DEPLOY_KEY`**: The SSH private key to securely access the deployment server. This should be the same key you've set up on your server for GitHub Actions. -4. **`LETSENCRYPT_EMAIL`**: Your email address, required for Let's Encrypt automated TLS certificates. - -#### Using GitHub CLI for Secret Management - -You can conveniently set these secrets using the [GitHub CLI](https://cli.github.com/manual/gh_secret_set) like this: - -```bash -gh secret set DEPLOY_HOST --body="your-host-or-ip" -gh secret set DEPLOY_USERNAME --body="your-username" -gh secret set DEPLOY_KEY --bodyFile="path/to/your/ssh-private-key" -gh secret set LETSENCRYPT_EMAIL --body="your-email@example.com" -``` - -These secrets will populate environment variables within your GitHub Actions workflow and other configuration files, enabling secure and automated deployment of your ServiceStack applications. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e5eb4e7..355e688 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,176 +1,107 @@ -name: Release +name: Release and Deploy permissions: packages: write contents: write on: - # Triggered on new GitHub Release - release: - types: [published] - # Triggered on every successful Build action workflow_run: workflows: ["Build"] - branches: [main,master] types: - completed - # Manual trigger for rollback to specific release or redeploy latest workflow_dispatch: - inputs: - version: - default: latest - description: Tag you want to release. - required: true +# Only update envs here if you need to change them for this workflow +env: + DOCKER_BUILDKIT: 1 + KAMAL_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + KAMAL_REGISTRY_USERNAME: ${{ github.actor }} + REDDIT_CLIENT: ${{ secrets.REDDIT_CLIENT }} + REDDIT_SECRET: ${{ secrets.REDDIT_SECRET }} + R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} + R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + + +# Standard steps for building and deploying a .NET app via Kamal jobs: - push_to_registry: - runs-on: ubuntu-22.04 - if: ${{ github.event.workflow_run.conclusion != 'failure' }} + build-and-deploy: + runs-on: ubuntu-latest steps: - # Checkout latest or specific tag - - name: checkout - if: ${{ github.event.inputs.version == '' || github.event.inputs.version == 'latest' }} - uses: actions/checkout@v3 - - name: checkout tag - if: ${{ github.event.inputs.version != '' && github.event.inputs.version != 'latest' }} + - name: Checkout code uses: actions/checkout@v3 - with: - ref: refs/tags/${{ github.event.inputs.version }} - - # Assign environment variables used in subsequent steps - - name: Env variable assignment - run: echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - # TAG_NAME defaults to 'latest' if not a release or manual deployment - - name: Assign version + + - name: Set up environment variables run: | - echo "TAG_NAME=latest" >> $GITHUB_ENV - if [ "${{ github.event.release.tag_name }}" != "" ]; then - echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV - fi; - if [ "${{ github.event.inputs.version }}" != "" ]; then - echo "TAG_NAME=${{ github.event.inputs.version }}" >> $GITHUB_ENV - fi; - + echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + echo "repository_name=$(echo ${{ github.repository }} | cut -d '/' -f 2)" >> $GITHUB_ENV + - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - - name: Setup dotnet + username: ${{ env.KAMAL_REGISTRY_USERNAME }} + password: ${{ env.KAMAL_REGISTRY_PASSWORD }} + + - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.*' - + dotnet-version: '8.0' + - name: Install x tool run: dotnet tool install -g x - + + # Temp test - name: Apply Production AppSettings - working-directory: ./MyApp - run: | + working-directory: ./BlazorDiffusion + run: | cat <> appsettings.json.patch ${{ secrets.APPSETTINGS_PATCH }} EOF x patch appsettings.json.patch - ls -l appsettings.json.patch - - # Build and push new docker image, skip for manual redeploy other than 'latest' + - name: Build and push Docker image run: | - dotnet publish --os linux --arch x64 -c Release -p:ContainerRepository=${{ env.image_repository_name }} -p:ContainerRegistry=ghcr.io -p:ContainerImageTags=${{ env.TAG_NAME }} -p:ContainerPort=80 + dotnet publish --os linux --arch x64 -c Release -p:ContainerRepository=${{ env.image_repository_name }} -p:ContainerRegistry=ghcr.io -p:ContainerImageTags=latest -p:ContainerPort=80 - # Deploy UI to GitHub Pages - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + - name: Set up SSH key + uses: webfactory/ssh-agent@v0.9.0 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./MyApp/wwwroot - user_name: 'GitHub Action' - user_email: 'action@github.com' - - deploy_via_ssh: - needs: push_to_registry - runs-on: ubuntu-22.04 - if: ${{ github.event.workflow_run.conclusion != 'failure' }} - steps: - # Checkout latest or specific tag - - name: checkout - if: ${{ github.event.inputs.version == '' || github.event.inputs.version == 'latest' }} - uses: actions/checkout@v3 - - name: checkout tag - if: ${{ github.event.inputs.version != '' && github.event.inputs.version != 'latest' }} - uses: actions/checkout@v3 + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 with: - ref: refs/tags/${{ github.event.inputs.version }} + ruby-version: 3.3.0 + bundler-cache: true - - name: repository name fix and env + - name: Install Kamal + run: gem install kamal -v 2.2.2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: image=moby/buildkit:master + + - name: Kamal bootstrap + run: kamal server bootstrap + + # This ensures the web.env has been setup ready for deployments + - name: Check if first run and execute kamal app boot if necessary run: | - echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - echo "TAG_NAME=latest" >> $GITHUB_ENV - if [ "${{ github.event.release.tag_name }}" != "" ]; then - echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV - fi; - if [ "${{ github.event.inputs.version }}" != "" ]; then - echo "TAG_NAME=${{ github.event.inputs.version }}" >> $GITHUB_ENV - fi; - - - name: Create .env file + FIRST_RUN_FILE=".${{ env.repository_name }}" + if ! kamal server exec --no-interactive -q "test -f $FIRST_RUN_FILE"; then + kamal server exec --no-interactive -q "touch $FIRST_RUN_FILE" || true + kamal deploy -q -P --version latest > /dev/null 2>&1 || true + else + echo "Not first run, skipping kamal app boot" + fi + + - name: Ensure file permissions + run: kamal server exec --no-interactive "mkdir -p /opt/docker/${{ env.repository_name }}/App_Data && chown -R 1654:1654 /opt/docker/${{ env.repository_name }}/App_Data" + + - name: Migration + run: kamal app exec --no-reuse --no-interactive --version=latest "--AppTasks=migrate" + + - name: Deploy with Kamal run: | - echo "Generating .env file" - - echo "# Autogenerated .env file" > .deploy/.env - echo "HOST_DOMAIN=${{ secrets.DEPLOY_HOST }}" >> .deploy/.env - echo "LETSENCRYPT_EMAIL=${{ secrets.LETSENCRYPT_EMAIL }}" >> .deploy/.env - echo "APP_NAME=${{ github.event.repository.name }}" >> .deploy/.env - echo "IMAGE_REPO=${{ env.image_repository_name }}" >> .deploy/.env - echo "RELEASE_VERSION=${{ env.TAG_NAME }}" >> .deploy/.env - - # Copy only the docker-compose.yml to remote server home folder - - name: copy files to target server via scp - uses: appleboy/scp-action@v0.1.3 - with: - host: ${{ secrets.DEPLOY_HOST }} - username: ${{ secrets.DEPLOY_USERNAME }} - port: 22 - key: ${{ secrets.DEPLOY_KEY }} - strip_components: 2 - source: "./.deploy/docker-compose.yml,./.deploy/.env" - target: "~/.deploy/${{ github.event.repository.name }}/" - - - name: Run remote db migrations - uses: appleboy/ssh-action@v0.1.5 - env: - APPTOKEN: ${{ secrets.GITHUB_TOKEN }} - USERNAME: ${{ secrets.DEPLOY_USERNAME }} - with: - host: ${{ secrets.DEPLOY_HOST }} - username: ${{ secrets.DEPLOY_USERNAME }} - key: ${{ secrets.DEPLOY_KEY }} - port: 22 - envs: APPTOKEN,USERNAME - script: | - set -e - echo $APPTOKEN | docker login ghcr.io -u $USERNAME --password-stdin - cd ~/.deploy/${{ github.event.repository.name }} - docker compose pull - export APP_ID=$(docker compose run --entrypoint "id -u" --rm app) - docker compose run --entrypoint "chown $APP_ID:$APP_ID /app/App_Data" --user root --rm app - docker compose up app-migration --exit-code-from app-migration - - # Deploy Docker image with your application using `docker compose up` remotely - - name: remote docker-compose up via ssh - uses: appleboy/ssh-action@v0.1.5 - env: - APPTOKEN: ${{ secrets.GITHUB_TOKEN }} - USERNAME: ${{ secrets.DEPLOY_USERNAME }} - with: - host: ${{ secrets.DEPLOY_HOST }} - username: ${{ secrets.DEPLOY_USERNAME }} - key: ${{ secrets.DEPLOY_KEY }} - port: 22 - envs: APPTOKEN,USERNAME - script: | - echo $APPTOKEN | docker login ghcr.io -u $USERNAME --password-stdin - cd ~/.deploy/${{ github.event.repository.name }} - docker compose pull - docker compose up app -d + kamal lock release -v + kamal deploy -P --version latest \ No newline at end of file diff --git a/.kamal/hooks/docker-setup.sample b/.kamal/hooks/docker-setup.sample new file mode 100755 index 0000000..2fb07d7 --- /dev/null +++ b/.kamal/hooks/docker-setup.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Docker set up on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/post-deploy.sample b/.kamal/hooks/post-deploy.sample new file mode 100755 index 0000000..75efafc --- /dev/null +++ b/.kamal/hooks/post-deploy.sample @@ -0,0 +1,14 @@ +#!/bin/sh + +# A sample post-deploy hook +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLE (if set) +# KAMAL_DESTINATION (if set) +# KAMAL_RUNTIME + +echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" diff --git a/.kamal/hooks/post-proxy-reboot.sample b/.kamal/hooks/post-proxy-reboot.sample new file mode 100755 index 0000000..1435a67 --- /dev/null +++ b/.kamal/hooks/post-proxy-reboot.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Rebooted kamal-proxy on $KAMAL_HOSTS" diff --git a/.kamal/hooks/pre-build.sample b/.kamal/hooks/pre-build.sample new file mode 100755 index 0000000..f87d811 --- /dev/null +++ b/.kamal/hooks/pre-build.sample @@ -0,0 +1,51 @@ +#!/bin/sh + +# A sample pre-build hook +# +# Checks: +# 1. We have a clean checkout +# 2. A remote is configured +# 3. The branch has been pushed to the remote +# 4. The version we are deploying matches the remote +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLE (if set) +# KAMAL_DESTINATION (if set) + +if [ -n "$(git status --porcelain)" ]; then + echo "Git checkout is not clean, aborting..." >&2 + git status --porcelain >&2 + exit 1 +fi + +first_remote=$(git remote) + +if [ -z "$first_remote" ]; then + echo "No git remote set, aborting..." >&2 + exit 1 +fi + +current_branch=$(git branch --show-current) + +if [ -z "$current_branch" ]; then + echo "Not on a git branch, aborting..." >&2 + exit 1 +fi + +remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) + +if [ -z "$remote_head" ]; then + echo "Branch not pushed to remote, aborting..." >&2 + exit 1 +fi + +if [ "$KAMAL_VERSION" != "$remote_head" ]; then + echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 + exit 1 +fi + +exit 0 diff --git a/.kamal/hooks/pre-connect.sample b/.kamal/hooks/pre-connect.sample new file mode 100755 index 0000000..18e61d7 --- /dev/null +++ b/.kamal/hooks/pre-connect.sample @@ -0,0 +1,47 @@ +#!/usr/bin/env ruby + +# A sample pre-connect check +# +# Warms DNS before connecting to hosts in parallel +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLE (if set) +# KAMAL_DESTINATION (if set) +# KAMAL_RUNTIME + +hosts = ENV["KAMAL_HOSTS"].split(",") +results = nil +max = 3 + +elapsed = Benchmark.realtime do + results = hosts.map do |host| + Thread.new do + tries = 1 + + begin + Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) + rescue SocketError + if tries < max + puts "Retrying DNS warmup: #{host}" + tries += 1 + sleep rand + retry + else + puts "DNS warmup failed: #{host}" + host + end + end + + tries + end + end.map(&:value) +end + +retries = results.sum - hosts.size +nopes = results.count { |r| r == max } + +puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] diff --git a/.kamal/hooks/pre-deploy.sample b/.kamal/hooks/pre-deploy.sample new file mode 100755 index 0000000..1b280c7 --- /dev/null +++ b/.kamal/hooks/pre-deploy.sample @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby + +# A sample pre-deploy hook +# +# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. +# +# Fails unless the combined status is "success" +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_COMMAND +# KAMAL_SUBCOMMAND +# KAMAL_ROLE (if set) +# KAMAL_DESTINATION (if set) + +# Only check the build status for production deployments +if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" + exit 0 +end + +require "bundler/inline" + +# true = install gems so this is fast on repeat invocations +gemfile(true, quiet: true) do + source "https://rubygems.org" + + gem "octokit" + gem "faraday-retry" +end + +MAX_ATTEMPTS = 72 +ATTEMPTS_GAP = 10 + +def exit_with_error(message) + $stderr.puts message + exit 1 +end + +class GithubStatusChecks + attr_reader :remote_url, :git_sha, :github_client, :combined_status + + def initialize + @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/") + @git_sha = `git rev-parse HEAD`.strip + @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) + refresh! + end + + def refresh! + @combined_status = github_client.combined_status(remote_url, git_sha) + end + + def state + combined_status[:state] + end + + def first_status_url + first_status = combined_status[:statuses].find { |status| status[:state] == state } + first_status && first_status[:target_url] + end + + def complete_count + combined_status[:statuses].count { |status| status[:state] != "pending"} + end + + def total_count + combined_status[:statuses].count + end + + def current_status + if total_count > 0 + "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." + else + "Build not started..." + end + end +end + + +$stdout.sync = true + +puts "Checking build status..." +attempts = 0 +checks = GithubStatusChecks.new + +begin + loop do + case checks.state + when "success" + puts "Checks passed, see #{checks.first_status_url}" + exit 0 + when "failure" + exit_with_error "Checks failed, see #{checks.first_status_url}" + when "pending" + attempts += 1 + end + + exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS + + puts checks.current_status + sleep(ATTEMPTS_GAP) + checks.refresh! + end +rescue Octokit::NotFound + exit_with_error "Build status could not be found" +end diff --git a/.kamal/hooks/pre-proxy-reboot.sample b/.kamal/hooks/pre-proxy-reboot.sample new file mode 100755 index 0000000..061f805 --- /dev/null +++ b/.kamal/hooks/pre-proxy-reboot.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Rebooting kamal-proxy on $KAMAL_HOSTS..." diff --git a/.kamal/secrets b/.kamal/secrets new file mode 100644 index 0000000..200ef82 --- /dev/null +++ b/.kamal/secrets @@ -0,0 +1,22 @@ +# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, +# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either +# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. + +# Option 1: Read secrets from the environment +KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD +REDDIT_CLIENT=$REDDIT_CLIENT +REDDIT_SECRET=$REDDIT_SECRET +R2_ACCOUNT_ID=$R2_ACCOUNT_ID +R2_ACCESS_KEY_ID=$R2_ACCESS_KEY_ID +R2_SECRET_ACCESS_KEY=$R2_SECRET_ACCESS_KEY + +# Option 2: Read secrets via a command +# RAILS_MASTER_KEY=$(cat config/master.key) + +# Option 3: Read secrets via kamal secrets helpers +# These will handle logging in and fetching the secrets in as few calls as possible +# There are adapters for 1Password, LastPass + Bitwarden +# +# SECRETS=$(kamal secrets fetch --adapter 1password --account my-account --from MyVault/MyItem KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) +# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD $SECRETS) +# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS) diff --git a/config/deploy.yml b/config/deploy.yml new file mode 100644 index 0000000..f471726 --- /dev/null +++ b/config/deploy.yml @@ -0,0 +1,65 @@ +# Name of your application. Used to uniquely configure containers. +service: pvq.app + +# Name of the container image. +image: servicestack/pvq.app + +env: + clear: + VIRTUAL_HOST: pvq.app + secret: + - REDDIT_CLIENT + - REDDIT_SECRET + - R2_ACCOUNT_ID + - R2_ACCESS_KEY_ID + - R2_SECRET_ACCESS_KEY + + +# Deploy to these servers. +servers: + web: + - 5.78.128.205 + # job: + # hosts: + # - 192.168.0.1 + # cmd: bin/jobs + +# Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server). +proxy: + ssl: true + host: pvq.app + # kamal-proxy connects to your container over port 80, use `app_port` to specify a different port. + app_port: 8080 + response_timeout: 300 + # kamal-proxy connects to your container over port 80, use `app_port` to specify a different port. + # app_port: 3000 + +# Credentials for your image host. +registry: + # Specify the registry server, if you're not using Docker Hub + server: ghcr.io + username: + - KAMAL_REGISTRY_USERNAME + + # Always use an access token rather than real password (pulled from .kamal/secrets). + password: + - KAMAL_REGISTRY_PASSWORD + +# Configure builder setup. +builder: + arch: amd64 + +volumes: + - "/opt/docker/pvq.app/App_Data:/app/App_Data" + +accessories: + litestream: + roles: ["web"] + image: litestream/litestream + files: ["config/litestream.yml:/etc/litestream.yml"] + volumes: ["/opt/docker/pvq.app/App_Data:/data"] + cmd: replicate + env: + secret: + - R2_ACCESS_KEY_ID + - R2_SECRET_ACCESS_KEY diff --git a/config/litestream.yml b/config/litestream.yml new file mode 100644 index 0000000..53b7727 --- /dev/null +++ b/config/litestream.yml @@ -0,0 +1,18 @@ +access-key-id: $R2_ACCESS_KEY_ID +secret-access-key: $R2_SECRET_ACCESS_KEY + +dbs: + - path: /data/db.sqlite + replicas: + - type: s3 + bucket: pvq-app-dbs + path: db.sqlite + region: auto + endpoint: https://b95f38ca3a6ac31ea582cd624e6eb385.r2.cloudflarestorage.com + - path: /data/analytics.sqlite + replicas: + - type: s3 + bucket: pvq-app-dbs + path: analytics.sqlite + region: auto + endpoint: https://b95f38ca3a6ac31ea582cd624e6eb385.r2.cloudflarestorage.com