Skip to content
Open
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
121 changes: 59 additions & 62 deletions .github/workflows/deploy-to-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,90 +17,87 @@ env:
jobs:
deploy-web-container:
runs-on: ubuntu-latest
timeout-minutes: 30
environment:
name: ${{ github.event.inputs.environment }}

steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Configure SSH
env:
SSH_HOST: ${{ secrets.APPSERVER_SSH_HOST }}
SSH_PORT: ${{ secrets.APPSERVER_SSH_PORT }}
SSH_USER: ${{ secrets.APPSERVER_SSH_USER }}
SSH_PRIVATE_KEY: ${{ secrets.APPSERVER_SSH_PRIVATE_KEY }}
SSH_KNOWN_HOSTS: ${{ secrets.APPSERVER_SSH_KNOWN_HOSTS }}
run: |
if [ -z "$SSH_PRIVATE_KEY" ]; then
echo "The APPSERVER_SSH_PRIVATE_KEY secret is empty." >&2
exit 1
fi

if [ -z "$SSH_KNOWN_HOSTS" ]; then
echo "The APPSERVER_SSH_KNOWN_HOSTS secret is empty." >&2
echo "Populate it with the output of: ssh-keyscan -p <port> <host>" >&2
exit 1
fi

mkdir -p ~/.ssh
chmod 700 ~/.ssh

printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/id_deploy
chmod 600 ~/.ssh/id_deploy

printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
chmod 600 ~/.ssh/known_hosts

{
echo "Host appserver"
echo " HostName $SSH_HOST"
echo " Port $SSH_PORT"
echo " User $SSH_USER"
echo " IdentityFile ~/.ssh/id_deploy"
echo " IdentitiesOnly yes"
echo " StrictHostKeyChecking yes"
echo " BatchMode yes"
} > ~/.ssh/config
chmod 600 ~/.ssh/config

- name: Stop service
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
with:
host: ${{ secrets.APPSERVER_SSH_HOST }}
port: ${{ secrets.APPSERVER_SSH_PORT }}
username: ${{ secrets.APPSERVER_SSH_USER }}
key: ${{ secrets.APPSERVER_SSH_PRIVATE_KEY }}
script: |
run: |
ssh appserver '
if [ "$(systemctl --user is-active crypter.service)" = "active" ]; then
echo "Stopping service"
systemctl --user stop crypter.service;
systemctl --user stop crypter.service
fi
'

- name: Push latest systemctl service file
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
with:
host: ${{ secrets.APPSERVER_SSH_HOST }}
port: ${{ secrets.APPSERVER_SSH_PORT }}
username: ${{ secrets.APPSERVER_SSH_USER }}
key: ${{ secrets.APPSERVER_SSH_PRIVATE_KEY }}
source: Environments/${{ github.event.inputs.environment }}/crypter.service
target: .config/systemd/user/
strip_components: 2

env:
environment_name: ${{ github.event.inputs.environment }}
run: |
ssh appserver 'mkdir -p .config/systemd/user'
scp "Environments/$environment_name/crypter.service" appserver:.config/systemd/user/

- name: Reload systemctl daemon
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
with:
host: ${{ secrets.APPSERVER_SSH_HOST }}
port: ${{ secrets.APPSERVER_SSH_PORT }}
username: ${{ secrets.APPSERVER_SSH_USER }}
key: ${{ secrets.APPSERVER_SSH_PRIVATE_KEY }}
script: systemctl --user daemon-reload
run: ssh appserver 'systemctl --user daemon-reload'

- name: Push latest Docker Compose file
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
with:
host: ${{ secrets.APPSERVER_SSH_HOST }}
port: ${{ secrets.APPSERVER_SSH_PORT }}
username: ${{ secrets.APPSERVER_SSH_USER }}
key: ${{ secrets.APPSERVER_SSH_PRIVATE_KEY }}
source: docker-compose.yml
target: crypter-web-container/
run: |
ssh appserver 'mkdir -p crypter-web-container'
scp docker-compose.yml appserver:crypter-web-container/

- name: Push latest Docker Compose override file if deploying to Staging server
if: github.event.inputs.environment == 'staging'
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
with:
host: ${{ secrets.APPSERVER_SSH_HOST }}
port: ${{ secrets.APPSERVER_SSH_PORT }}
username: ${{ secrets.APPSERVER_SSH_USER }}
key: ${{ secrets.APPSERVER_SSH_PRIVATE_KEY }}
source: docker-compose.override.yml
target: crypter-web-container/
run: scp docker-compose.override.yml appserver:crypter-web-container/

- name: Pull latest images
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
with:
host: ${{ secrets.APPSERVER_SSH_HOST }}
port: ${{ secrets.APPSERVER_SSH_PORT }}
username: ${{ secrets.APPSERVER_SSH_USER }}
key: ${{ secrets.APPSERVER_SSH_PRIVATE_KEY }}
script: docker compose --project-directory crypter-web-container --profile ${{ env.docker_compose_profile }} pull
run: ssh appserver 'docker compose --project-directory crypter-web-container --profile ${{ env.docker_compose_profile }} pull'

- name: Migrate database
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
with:
host: ${{ secrets.APPSERVER_SSH_HOST }}
port: ${{ secrets.APPSERVER_SSH_PORT }}
username: ${{ secrets.APPSERVER_SSH_USER }}
key: ${{ secrets.APPSERVER_SSH_PRIVATE_KEY }}
script: docker compose --project-directory crypter-web-container --profile ${{ env.docker_compose_profile }} run api /app/efbundle
run: ssh appserver 'docker compose --project-directory crypter-web-container --profile ${{ env.docker_compose_profile }} run api /app/efbundle'

- name: Start service
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
with:
host: ${{ secrets.APPSERVER_SSH_HOST }}
port: ${{ secrets.APPSERVER_SSH_PORT }}
username: ${{ secrets.APPSERVER_SSH_USER }}
key: ${{ secrets.APPSERVER_SSH_PRIVATE_KEY }}
script: systemctl --user start crypter.service
run: ssh appserver 'systemctl --user start crypter.service'
28 changes: 28 additions & 0 deletions Documentation/Production/Server Setup/Web Server Setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,34 @@ Create an SSH user and add corresponding details to the environment secrets with

The user will need permissions to Docker, so add the user to the `docker` group.

## Record the host key

The deploy workflow verifies the host key of the server it connects to, so record that key while the server is being set up.

The secret holds `known_hosts` lines exactly as ssh writes them. The host field has to match `APPSERVER_SSH_HOST` and `APPSERVER_SSH_PORT`: a bare hostname on port 22, and `[host]:port` on any other port. A line recorded under a different name or port is never consulted, so the deploy fails as though no key had been recorded at all.

Take the key from a workstation that already connects to the server, which by this point is whichever one was used to set it up. Print the entry it trusts:

```bash
ssh-keygen -F '[<host>]:<port>' -f ~/.ssh/known_hosts
```

Drop the brackets and the port if that workstation connects over port 22. An entry that already carries the port the deploy uses can go straight into the secret, ignoring the leading comment line.

An entry recorded under any other port has to be recaptured under the right one, then checked against the entry already trusted:

```bash
ssh-keyscan -t <type> -p <port> <host> > known_hosts
ssh-keygen -lf known_hosts
ssh-keygen -F '<host>' -f ~/.ssh/known_hosts | ssh-keygen -lf -
```

The fingerprints must match. Comparing them is what makes the scan trustworthy, because `ssh-keyscan` on its own only reports whatever answers on the network. Pass `-t` for the key type that was checked, so nothing unverified lands in the secret.

Add the contents of `known_hosts` to the environment secrets as `APPSERVER_SSH_KNOWN_HOSTS`. Every environment has its own server and its own host key, so record one for each.

Rebuilding a server generates a new host key. Deploys fail with `REMOTE HOST IDENTIFICATION HAS CHANGED` until the secret is updated to match.

## Copy the .env file

Locate the `.env` file at the root of this repository, [here](../../../.env).
Expand Down
Loading