diff --git a/posts/img/post-index-build-script.webm b/posts/img/post-index-build-script.webm new file mode 100644 index 0000000..30f3708 Binary files /dev/null and b/posts/img/post-index-build-script.webm differ diff --git a/posts/img/systemsobscure-forgejo-runners.png b/posts/img/systemsobscure-forgejo-runners.png new file mode 100644 index 0000000..4361b91 Binary files /dev/null and b/posts/img/systemsobscure-forgejo-runners.png differ diff --git a/posts/self-hosting-this-site.md b/posts/self-hosting-this-site.md new file mode 100644 index 0000000..7613dcb --- /dev/null +++ b/posts/self-hosting-this-site.md @@ -0,0 +1,136 @@ +--- +title: "Self-hosting this site" +slug: /self-hosting-this-site/ +date: 2025-07-21 +tags: ["self-hosting"] +--- + +[Previously](https://systemsobscure.blog/posts/how-I-deploy-this-site) this site +was deployed as follows. I used [Gatsby.js](https://gatsbyjs.com) to generate a +static website from React JavaScript. When changes to the `main` branch were +pushed to the remote GitHub repository, a GitHub action would execute, building +the source files and transferring them to an S3 bucket. The HTML generated was +then served using AWS Cloud Front. + +As I am now trying to self-host as many services as I can, it was time remove +the dependency on AWS and GitHub and instead serve the site from my VPS and +manage deployment via my +[self-hosted Forgejo instance](https://forgejo.systemsobscure.net/thomasabishop). + +As part of this process I went on a bit of a side-quest to rebuild the site +without Gatsby. + +When I tried to update to the lastest version of Gatsby I faced several problems +mostly due to Gatsby trying to foist server-side rendering on every project. For +such a small blog site with next to zero traffic this is unnecessary. Moreover, +I realised that I could also do without all the bloat that Gatsby adds via its +plugin ecosystem. (Perhaps if frontend bundles weren't so large, SSR wouldn't be +needed lol.) + +So I decided to simplify the blog and build it as a [Vite](https://vite.dev) +React application that gets its content from a JSON index that is generated via +a pre-build script. + +I +[wrote a script](https://forgejo.systemsobscure.net/thomasabishop/systems-obscure/src/branch/main/scripts/generate-post-index.js) +in JavaScript that loops through all the blog posts in Markdown format from a +`/posts` directory at the project root. It creates a JSON array with an entry +for each post. For example, this post would be represented as follows: + +```json +[ + { + "slug": "self-hosting-this-site", + "title": "Self-hosting this site", + "date": "2025-07-22T00:00:00.00Z", + "tags": ["self-hosting"], + "html": "

Previously this site was..." + } +] +``` + +To convert the raw markdown to HTML I used +[marked](https://github.com/markedjs/marked). I also added the following steps: + +- convert `` tags into syntax highlighted blocks using + [shiki](https://shiki.style/) +- compress and resize images and then transfer them to the `public/` directory + where Vite sources static assets when deployed +- find and replace all image `src` attributes in the resulting HTML with the + `public/` file path + +When I run `npm run build:posts` this generates the `post-index.json` file and +saves it to `/public` so that the React application can read from it when +rendering the site content. + +

+ +
Running the pre-build script to generate the site content.
+
+ +I created a custom hook called `usePosts` that fetches the index and saves it to +the session storage to avoid unnecessary network requests. You can see this +being invoked in the +[template component for blog posts](https://forgejo.systemsobscure.net/thomasabishop/systems-obscure/src/commit/43eec03edd2e6330362cc5605a48a91387eb49d3/src/templates/BlogTemplate.tsx). + +Having rebuilt the site without the dependency on Gatsby, the next step was to +deploy it. + +I wanted to retain the "push and deploy" model that I previously achieved via +the GitHub Action. This was easy because the syntax for Forgejo Actions is +practically identical. You place the YAML declaration in the `.forejo/` +directory of the project and Forejo picks it up on each push to the remote. + +Here's the file: + +```yaml +name: Deploy Blog +on: + push: + branches: [main] +jobs: + deploy: + runs-on: ubuntu-latest + container: node:18 + steps: + - uses: actions/checkout@v3 + - run: npm install + - run: npm run build:posts + - run: npm run build + - run: | + echo "${{ secrets.SSH_FORGEJO_KEY }}" > /tmp/ssh_key + chmod 600 /tmp/ssh_key + ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ${{ vars.VPS_USER }} "bash -c 'rm -rf /var/www/systemsobscure.blog/*'" + scp -i /tmp/ssh_key -o StrictHostKeyChecking=no -r dist/* ${{ vars.VPS_USER }}:/var/www/systemsobscure.blog/ + rm /tmp/ssh_key +``` + +Before compiling the build I first run the pre-build script to generate the post +index. Then I remove the existing source files for the site in `var/www/` over +`ssh` and then transfer the new source files using `scp`. (While I run most +third-party services on my VPS via Docker, I prefer to keep it simple with my +own applications and not use containers.) + +![Forgejo Actions runner executions.](./img/systemsobscure-forgejo-runners.png) + +In order for the site to be served from `var/www/systemsobscure.blog`, I had to +give my Docker instance of nginx running on the VPS access to this directory by +[adding it to the volume mappings](https://forgejo.systemsobscure.net/thomasabishop/self-host/commit/8c380b71735f278f4309bbd14ad96cb9a29104b7) +in the `docker-compose.` + +Then I added an +[nginx `.conf` file for the blog](https://forgejo.systemsobscure.net/thomasabishop/self-host/src/branch/main/proxy/nginx/conf.d/systemsobscure.conf). +This file specifies the SSL certificate to use and sets `index.html` as the +document root. It also adds some default caching and compression, along with +security headers. + +Finally I +[updated my SSL certificate generation script](https://forgejo.systemsobscure.net/thomasabishop/self-host/commit/61cdbe43c2041d5961ef74416270794eb6fb91c6) +to include `systemsobscure.blog`. + +And that's it. My personal website is now self-hosted on my VPS and is +automatically deployed via pushes to my self-hosted Git forge. No more GitHub, +no more AWS. diff --git a/scripts/process-blog-imgs.js b/scripts/process-blog-imgs.js index 97bc91c..4e62593 100644 --- a/scripts/process-blog-imgs.js +++ b/scripts/process-blog-imgs.js @@ -15,23 +15,27 @@ const processBlogImages = async () => { fs.mkdirSync(destDir, { recursive: true }) } - // Copy all images const files = fs.readdirSync(srcDir) - const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"] + const imageExtensions = [ + ".jpg", + ".jpeg", + ".png", + ".gif", + ".webp", + ".svg", + ".webm", + ] - // Use for...of loop instead of forEach to work with async/await for (const file of files) { const ext = path.extname(file).toLowerCase() if (imageExtensions.includes(ext)) { const inputPath = path.join(srcDir, file) // Define inputPath and outputPath const outputPath = path.join(destDir, file) - if (ext === ".svg") { - // SVGs just get copied + if (ext === ".svg" || ext === ".webm") { fs.copyFileSync(inputPath, outputPath) console.info(`📸 Copied ${file}`) } else { - // Process other images await sharp(inputPath) .resize(1200, 800, { fit: "inside",