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
139 changes: 139 additions & 0 deletions .github/workflows/dev-instance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: Dev instance
run-name: Dev instance (${{ inputs.ref || github.ref_name }})

# Builds the images that differ from upstream and deploys them to the dev
# server (deploy/dev-instance). A merge to the `dev` branch ships it.
#
# From the Actions tab, "Run workflow" can also deploy another branch, tag or
# commit (`ref`), for example a pull request that is not merged yet. There is
# only one server: the last deploy wins, and the next merge to `dev` replaces
# it. The ref must contain deploy/dev-instance.
on:
push:
branches:
- dev
workflow_dispatch:
inputs:
ref:
description: "Branch, tag or commit to deploy. The last deploy wins: the next merge to dev replaces it."
type: string
default: dev

permissions:
contents: read

# Never cancel a deploy halfway; queue the next one instead.
concurrency:
group: dev-instance
cancel-in-progress: false

jobs:
resolve:
runs-on: ubuntu-latest
outputs:
sha: ${{ steps.sha.outputs.sha }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.sha }}
- name: Commit to deploy
id: sha
run: |
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "Commit: $(git log -1 --format='%h %s')" >> "$GITHUB_STEP_SUMMARY"

build:
needs: resolve
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
fail-fast: true
matrix:
include:
- name: frontend
file: src/frontend/Dockerfile
target: frontend-production
- name: yhub
file: src/yhub-server/Dockerfile
target: yhub
- name: y-provider
file: src/frontend/servers/y-provider/Dockerfile
target: y-provider
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.resolve.outputs.sha }}
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Image name
id: image
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}/${{ matrix.name }}" >> "$GITHUB_OUTPUT"
- uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
with:
context: .
file: ${{ matrix.file }}
target: ${{ matrix.target }}
platforms: linux/amd64
build-args: |
DOCKER_USER=1001:127
PUBLISH_AS_MIT=false
push: true
provenance: false
tags: ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.sha }}
cache-from: type=gha,scope=dev-${{ matrix.name }}
cache-to: type=gha,scope=dev-${{ matrix.name }},mode=max

deploy:
needs: [resolve, build]
runs-on: ubuntu-latest
timeout-minutes: 150
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.resolve.outputs.sha }}
- name: Set up SSH
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
run: |
install -d -m 700 ~/.ssh
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts
chmod 600 ~/.ssh/id_ed25519 ~/.ssh/known_hosts
cat > ~/.ssh/config <<EOF
Host dev-instance
HostName $DEPLOY_HOST
User $DEPLOY_USER
StrictHostKeyChecking yes
ServerAliveInterval 30
EOF
- name: Copy the deploy files
run: |
cp docker/files/production/etc/nginx/conf.d/default.conf.template deploy/dev-instance/
tar -C deploy/dev-instance --exclude=node_modules -czf - . \
| ssh dev-instance 'mkdir -p /opt/docs && tar -C /opt/docs -xzf -'
- name: Deploy
env:
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
printf '%s' "$GHCR_TOKEN" | ssh dev-instance \
"docker login ghcr.io -u '${{ github.actor }}' --password-stdin >/dev/null \
&& trap 'docker logout ghcr.io >/dev/null' EXIT \
&& /opt/docs/scripts/deploy.sh '${{ needs.resolve.outputs.sha }}'"
- name: Check the instance answers
run: |
host=$(ssh dev-instance "grep '^DOCS_HOST=' /opt/docs/.env | cut -d= -f2")
curl -fsS --retry 10 --retry-delay 6 --retry-all-errors -o /dev/null "https://$host/"
curl -fsS --retry 10 --retry-delay 6 --retry-all-errors "https://$host/api/v1.0/config/" -o /dev/null
echo "Deployed to https://$host/" >> "$GITHUB_STEP_SUMMARY"
17 changes: 17 additions & 0 deletions deploy/dev-instance/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TLS for the three public host names, with certificates from Let's Encrypt.
# The host names come from the .env file on the server.

{$DOCS_HOST} {
encode zstd gzip
reverse_proxy frontend:8083
}

{$KEYCLOAK_HOST} {
reverse_proxy keycloak:8080
}

# The media bucket. Access is signed by the backend (see /media-auth in the
# nginx template), the bucket itself is private.
{$S3_HOST} {
reverse_proxy minio:9000
}
56 changes: 56 additions & 0 deletions deploy/dev-instance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Dev instance

A dev instance of Docs on one server, with the BlockNote version history work.
`.github/workflows/dev-instance.yml` deploys it.

- Docs: https://167-233-225-101.sslip.io
- Keycloak: https://id.167-233-225-101.sslip.io

## Demo users

The password of each demo user is the user name.

| User | Password |
| --- | --- |
| alice | alice |
| bob | bob |
| carol | carol |
| dave | dave |

Every deploy makes sure that the sample document "07/10 meeting agenda (sample)"
exists. alice owns it, and bob, carol and dave can edit it. Its history has 15
named versions by the four users (`scripts/seed.sh`).

## Admin accounts

The admin accounts have random passwords. They are only on the server, in
`/opt/docs/.env`:

```bash
ssh deploy@167.233.225.101 'grep -E "^(SEED_PASSWORD_ADMIN|KEYCLOAK_ADMIN_PASSWORD)=" /opt/docs/.env'
```

| Account | User | Password variable |
| --- | --- | --- |
| Django admin (`/admin`) | admin@example.com | `SEED_PASSWORD_ADMIN` |
| "admin" user of the "docs" realm | admin | `SEED_PASSWORD_ADMIN` |
| Keycloak administration console | admin | `KEYCLOAK_ADMIN_PASSWORD` |

## Deploy a change

1. Open a pull request against `dev`.
2. Merge it. The workflow builds the frontend, yhub and y-provider images and
deploys them.

To deploy a branch that is not merged, run the "Dev instance" workflow from the
Actions tab and set `ref`. There is only one server: the last deploy wins.

## Server

- The stack is in `/opt/docs` (`compose.yaml`). The `deploy` user owns it.
- The first deploy writes `/opt/docs/.env` with random secrets. It never
changes them after that.
- The data of the sample document is `/opt/docs/seed-data/meetingVersions.json`.
It is not in the repository. Without it, the deploy skips the sample
document.
- There are no backups.
Loading
Loading