Skip to main content

Uploading to the webserver from Forgejo

·705 words·4 mins

To get Hugo up to the webserver, I had to scp the files up there using my keyfile. For that, I started with the image that I gave the docker tag, which is one of the default Forgejo images.

Setting up variables
#

First I set up a bunch of variables:

  • WEBSERVER_SSH_USERNAME: my username on the webserver
  • WEBSERVER_SSH_HOST: the hostname of the webserver I upload to
  • WEBSERVER_HTML_DIR: the directory where the webserver files get sent

Setting up secrets
#

I ended up with two secrets:

  • SSH_PRIV_KEY: my private key
  • SSH_KNOWN_HOSTS: I also needed to set up my ~/.ssh/known_hosts so that I wouldn’t get errors when connecting via scp. I temporarily threw away my existing deploy.yaml and built a new one that did a little (very little) ssh using sftp:
.forgejo/workflows/deploy.yaml
on: [push]
  deployscp:
    runs-on: docker
    steps:
      - run: |
          echo dir > ftpbatch
          echo bye >> ftpbatch
      - name: setup ssh
        run: |
          mkdir -p ~/.ssh
          chmod 0700 ~/.ssh
          echo "${{ secrets.SSH_PRIV_KEY }}" > ~/.ssh/id_rsa_webserver
          chmod 0600 ~/.ssh/id_rsa_webserver
          ssh-keyscan ${{ vars.WEBSERVER_SSH_HOST }} >> ~/.ssh/known_hosts
          cat ~/.ssh/known_hosts
      - name: push public
        run: |
          echo about to sftp
          sftp -i ~/.ssh/id_rsa_webserver -b ./ftpbatch ${{ vars.WEBSERVER_SSH_USERNAME }}@${{ vars.WEBSERVER_SSH_HOST }}
          echo did sftp

The first time I ran that, I copied the output of the cat ~/.ssh/known_hosts line into the secret SSH_KNOWN_HOSTS. Then I updated deploy.yaml to use the secret instead:

.forgejo/workflows/deploy.yaml
on: [push]
  deployscp:
    runs-on: docker
    steps:
      - run: |
          echo dir > ftpbatch
          echo bye >> ftpbatch
      - name: setup ssh
        run: |
          mkdir -p ~/.ssh
          chmod 0700 ~/.ssh
          echo "${{ secrets.SSH_PRIV_KEY }}" > ~/.ssh/id_rsa_webserver
          chmod 0600 ~/.ssh/id_rsa_webserver
          echo "${{ secrets.SSH_KNOWN_HOST }}" > ~/.ssh/known_hosts
          chmod 0600 ~/.ssh/known_hosts
      - name: push public
        run: |
          echo about to sftp
          sftp -i ~/.ssh/id_rsa_webserver -b ./ftpbatch ${{ vars.WEBSERVER_SSH_USERNAME }}@${{ vars.WEBSERVER_SSH_HOST }}
          echo did sftp

I was hoping that Forgejo would preserve the line separators that I pasted into the secret dialog. It did! Nice!

Add Hugo back to the build and really upload
#

With a working sftp (and presumably a working ssh) and a good private key, I could add the hugo step back. It packages up the build, then the docker image retrieves the build and uploads the important bit. Here’s the whole enchilada:

.forgejo/workflows/deploy.yaml
on: [push]
jobs:
  buildhugo:
    runs-on: hugo
    steps:
      - run: hugo version
      - uses: actions/checkout@v4
        with:
          submodules: 'recursive'
      - name: run hugo
        run: hugo --gc --minify
      - name: fix up RSS
        run: cp ./public/index.xml ./public/rss.xml
      - name: stash public files
        uses: https://code.forgejo.org/forgejo/upload-artifact@v4
        with:
          name: public
          path: ./public/ 

  deployscp:
    runs-on: docker
    needs: buildhugo
    steps:
      - name: grab public files
        uses: https://code.forgejo.org/forgejo/download-artifact@v4
      - name: setup ssh
        run: |
          mkdir -p ~/.ssh
          chmod 0700 ~/.ssh
          echo "${{ secrets.SSH_PRIV_KEY }}" > ~/.ssh/id_rsa_webserver
          chmod 0600 ~/.ssh/id_rsa_webserver
          echo "${{ secrets.SSH_KNOWN_HOST }}" > ~/.ssh/known_hosts
          chmod 0600 ~/.ssh/known_hosts
      - name: push public
        run: |
          ssh -i ~/.ssh/id_rsa_webserver ${{ vars.WEBSERVER_SSH_USERNAME }}@${{ vars.WEBSERVER_SSH_HOST }} 'rm -rf /home/${{ vars.WEBSERVER_SSH_USERNAME }}/${{ vars.WEBSERVER_HTML_DIR }}/*'
          scp -r -i ~/.ssh/id_rsa_webserver ./public/* ${{ vars.WEBSERVER_SSH_USERNAME }}@${{ vars.WEBSERVER_SSH_HOST }}:/home/${{ vars.WEBSERVER_SSH_USERNAME }}/${{ vars.WEBSERVER_HTML_DIR }}/

This makes it look easy. It wasn’t. I was nervous about the process, and started with echo ssh -i ~/... and echo scp -r -i ~/... instead of actually running the ssh and scp commands. I made sure things looked right in the Actions window on the Forgejo server before I did the scp for real, and it took me a couple more deep breaths before I did the ssh (which does a rm -rf).

Ultimately, I had to run things 70 times — I counted — before I got a working deploy. Lots of echo and ls -al to make sure I was oriented. And probably a third of the commits were fixes for this error:

Workflow was not executed due to an error that blocked the execution attempt.
Unable to parse supported events in workflow: yaml: line 25: found a tab character where an indentation space is expected

I really need to set my Emacs up so it uses only spaces for yaml files. Esc-X untabify was my best friend.

Now, I have a website that updates automatically when I push the code. Not when I commit — I’m still trying to remember that git push origin after publishing. I think I’ll commit this and do that now.