---
title: "Learning Go: Day Eight"
date: 2024-05-08T08:00:00.0Z
tags:
    - learning
    - go
excerpt: "Getting the project deployed via Gitea actions"
---

So that I can do the whole build-in-public thing properly, I always want my code to automatically deploy. I've got [Gitea Actions](https://docs.gitea.com/usage/actions/overview) on my Gitea server, so I can use those to build, deploy, and start a Go binary.

## Building and copying a binary

This is the simplest part. I had a decent template from my Eleventy action that I was able to take and turn into the following workflow:

```yaml
name: Build and copy to prod
on:
  push:
jobs:
  build-and-copy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: 1.22
      - name: Build binary
        run: go build -o dist/oopsie
      - name: Install SSH Key
        uses: shimataro/ssh-key-action@v2
        with:
          key: ${{ secrets.SSH_KEY }}
          known_hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
      - name: Copy to prod
        run: scp -rp dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:oopsie/
```

This installs Go on the action runner, builds the binary, and then uses SCP to copy the compiled binary onto my server. To facilitate this, I created a user on my VPS, created a new SSH key, and added the public key to the `.ssh/authorized_keys` file. Then I added the private key to the Gitea action secrets, along with the `known_hosts` entry for my git server, the name of the user I created, and the hostname for my VPS.

## Running the software

So now I need to run my compiled software. I can create a systemd service to do this, and then run it as the user I've created. First of all I create a new file, `/etc/systemd/user/oopsie.service`:

```systemd
[Unit]
Description=Daemon for the Oopsie service

[Service]
Type=simple
#User=
#Group=
ExecStart=/home/<user>/oopsie/oopsie
Restart=on-failure
StandardOutput=file:%h/log_file

[Install]
WantedBy=default.target
```

Then, as the user I've created I run:

```bash
systemctl --user daemon-reload
systemctl --user start oopsie.service
```

And can confirm my service is running locally:

```bash
curl -X POST http://localhost:8000
> This was a POST request!
```

## Nginx proxy

Next up I need to use Nginx's `proxy_pass` directive to direct any requests to https://oopsie.lewisdale.dev to my running service. Again, this was mostly lifted from an existing template I already had:

```nginx
server {
	listen 80;
	listen [::]:80;
	server_name oopsie.lewisdale.dev;
	rewrite ^ https://$server_name$request_uri? permanent;
}

server {
	# SSL configuration
	#
	listen 443 ssl;
	listen [::]:443 ssl;
	server_name oopsie.lewisdale.dev;

    # Include certificate params
    include snippets/certs/lewisdale.dev;
	ssl_certificate /etc/letsencrypt/live/lewisdale.dev/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/lewisdale.dev/privkey.pem; # managed by Certbot

	include /etc/letsencrypt/options-ssl-nginx.conf;

	location / {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

Woo, now I can actually access my service over the internet!

![Screenshot from Mozilla Firefox, showing oopsie.lewisdale.dev responding with "This was a GET request!"](./src/images/oopsie-working.png)

## Restarting the service

Finally, I can use the `-o` argument with the `RemoteCommand` [SSH config option](https://linux.die.net/man/5/ssh_config) to execute a command. I can use that to run `systemctl restart`:

```yaml
- name: Restart the service
  run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} -o RemoteCommand="systemctl --user restart oopsie.service"
```

## Nope

Ah, that's not quite correct. My first build failed:

```bash
scp: oopsie//oopsie: Text file busy
```

I can't overwrite a file while it's in use. Instead, I have to stop the service, copy the file, and then start the service again:

```yaml
- name: Stop the service
  run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} -o RemoteCommand="systemctl --user stop oopsie.service"
- name: Copy to prod
  run: scp -rp dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:oopsie/
- name: Restart the service
  run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} -o RemoteCommand="systemctl --user start oopsie.service"
```

And that works! It deploys successfully. Ironically, there's a minor bit of downtime while it does, but for now that's really not an issue. You can see the project in progress on [its deployed home](https://oopsie.lewisdale.dev) or [on the Git repo](https://git.lewisdale.dev/lewis/oopsie).