Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f402556380 | ||
|
|
b7145e1323 | ||
|
|
87867b9f30 | ||
|
|
8a7833e8f5 | ||
|
|
4426668518 | ||
|
|
eacf71d676 | ||
|
|
b05814980f | ||
|
|
eb67d6361e | ||
|
|
8062b9d208 | ||
|
|
aa01def998 | ||
|
|
f23fe01f3c | ||
|
|
1ad79d40d8 | ||
|
|
7d4f1668a6 | ||
|
|
498231c160 | ||
|
|
898e3003a2 | ||
|
|
0d425c0ed1 | ||
|
|
cd6f85f903 | ||
|
|
0bef6a1907 | ||
|
|
83ca9b61b8 | ||
|
|
e9bcafb64b | ||
|
|
2480790384 | ||
|
|
22d3759756 | ||
|
|
9a6555f1c3 | ||
|
|
d71dc7a86b |
@@ -5,8 +5,8 @@ on:
|
|||||||
branches: [ "main" ]
|
branches: [ "main" ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: tea.ahmza.com
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ gitea.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -22,12 +22,12 @@ jobs:
|
|||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to Gitea Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ gitea.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITEATOKEN }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
@@ -44,11 +44,14 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR=true
|
BUILDKIT_CONTEXT_KEEP_GIT_DIR=true
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/${{ github.repository_owner }}/blog-ahmza-com:latest
|
${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:latest
|
||||||
ghcr.io/${{ github.repository_owner }}/blog-ahmza-com:${{ github.event.head_commit.id }}
|
${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:${{ gitea.event.head_commit.id }}
|
||||||
cache-from: type=gha
|
# Registry-backed build cache. The GitHub-hosted 'gha' cache is not
|
||||||
cache-to: type=gha,mode=max
|
# reachable from self-hosted Gitea runners, so store the cache as a
|
||||||
deploy-to-rpic:
|
# dedicated tag in the same registry instead.
|
||||||
|
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:buildcache
|
||||||
|
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:buildcache,mode=max
|
||||||
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build
|
||||||
|
|
||||||
@@ -57,35 +60,27 @@ jobs:
|
|||||||
uses: Alemiz112/netbird-connect@v1
|
uses: Alemiz112/netbird-connect@v1
|
||||||
with:
|
with:
|
||||||
setup-key: ${{ secrets.NETBIRD_SETUP_KEY }}
|
setup-key: ${{ secrets.NETBIRD_SETUP_KEY }}
|
||||||
hostname: 'blog-gh-actions'
|
hostname: 'gitea-actions-blog'
|
||||||
management-url: ${{ secrets.NETBIRD_URL }}
|
management-url: ${{ secrets.NETBIRD_URL }}
|
||||||
|
|
||||||
- name: Deploy to Cluster
|
- name: Deploy to RPIC-Cluster
|
||||||
|
# RPIC cluster is temporarily out of service. Don't let its failure
|
||||||
|
# block the Gimli deploy or fail the whole job. Remove this line once
|
||||||
|
# the cluster is back.
|
||||||
|
continue-on-error: true
|
||||||
uses: appleboy/ssh-action@v1
|
uses: appleboy/ssh-action@v1
|
||||||
with:
|
with:
|
||||||
host: ${{ secrets.SSH_HOST }}
|
host: ${{ secrets.RPIC_HOST }}
|
||||||
username: ${{ secrets.SSH_USER }}
|
username: ${{ secrets.SSH_USER }}
|
||||||
key: ${{ secrets.SSH_KEY }}
|
key: ${{ secrets.RPIC_KEY }}
|
||||||
port: ${{ secrets.SSH_PORT }}
|
port: ${{ secrets.SSH_PORT }}
|
||||||
script: 'kubectl rollout restart deployment blog -n blog-ahmza-com'
|
script: 'kubectl rollout restart deployment blog -n blog-ahmza-com'
|
||||||
|
|
||||||
deploy-to-gimli-blog:
|
- name: Deploy to Gimli
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Netbird Connect
|
|
||||||
uses: Alemiz112/netbird-connect@v1
|
|
||||||
with:
|
|
||||||
setup-key: ${{ secrets.NETBIRD_SETUP_KEY }}
|
|
||||||
hostname: 'blog-gh-actions'
|
|
||||||
management-url: ${{ secrets.NETBIRD_URL }}
|
|
||||||
|
|
||||||
- name: Deploy to gimli-blog
|
|
||||||
uses: appleboy/ssh-action@v1
|
uses: appleboy/ssh-action@v1
|
||||||
with:
|
with:
|
||||||
host: ${{ secrets.GIMLI_BLOG_HOST }}
|
host: ${{ secrets.GIMLI_HOST }}
|
||||||
username: ${{ secrets.GIMLI_BLOG_USER }}
|
username: ${{ secrets.SSH_USER }}
|
||||||
key: ${{ secrets.GIMLI_BLOG_KEY }}
|
key: ${{ secrets.GIMLI_KEY }}
|
||||||
port: ${{ secrets.GIMLI_BLOG_PORT }}
|
port: ${{ secrets.SSH_PORT }}
|
||||||
script: 'docker compose down && docker compose pull && docker compose up -d'
|
script: 'docker compose down && docker compose pull && docker compose up -d'
|
||||||
|
|||||||
+6
-5
@@ -1,10 +1,11 @@
|
|||||||
FROM ghcr.io/hugomods/hugo:0.128.2 AS build
|
FROM ghcr.io/hugomods/hugo:0.157.0 AS build
|
||||||
WORKDIR /tmp/hugo
|
WORKDIR /tmp/hugo
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN hugo --gc --minify
|
RUN hugo --gc --minify
|
||||||
|
|
||||||
FROM nginx:alpine-slim
|
FROM ghcr.io/static-web-server/static-web-server:2
|
||||||
WORKDIR /usr/share/nginx/html
|
COPY sws.toml /etc/sws.toml
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY --from=build /tmp/hugo/public /public
|
||||||
COPY --from=build /tmp/hugo/public .
|
ENV SERVER_CONFIG_FILE=/etc/sws.toml
|
||||||
EXPOSE 80/tcp
|
EXPOSE 80/tcp
|
||||||
|
CMD ["--config-file", "/etc/sws.toml"]
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
This repository contains the source code for my personal blog hosted at [blog.ahmza.com](https://blog.ahmza.com).
|
This repository contains the source code for my personal blog hosted at [blog.ahmza.com](https://blog.ahmza.com).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
Using GitLab provides several advantages:
|
Using Gitea provides several advantages:
|
||||||
- **Highly Customizable CI/CD Pipelines**: Automated processes for building and deploying the blog.
|
- **Highly Customizable CI/CD Pipelines**: Automated processes for building and deploying the blog via Gitea Actions.
|
||||||
- **GitLab Container Image Repository**: Efficient management and storage of container images.
|
- **Gitea Container Image Repository**: Efficient management and storage of container images.
|
||||||
- **Custom GitLab Runners**: Flexibility in managing the CI/CD environment.
|
- **Custom Gitea Runners**: Flexibility in managing the CI/CD environment.
|
||||||
|
|
||||||
The CI/CD pipeline is configured to build the blog for both amd64 and arm64
|
The CI/CD pipeline is configured to build the blog for both amd64 and arm64
|
||||||
architectures, allowing me to host the site on my [Pi cluster](https://blog.ahmza.com/posts/pi-docker-swarm/)
|
architectures, allowing me to host the site on my [Pi cluster](https://blog.ahmza.com/posts/pi-docker-swarm/)
|
||||||
@@ -33,7 +33,7 @@ sacrificing performance.
|
|||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
- **Hugo**: Converts Markdown files into static HTML/CSS/JS files.
|
- **Hugo**: Converts Markdown files into static HTML/CSS/JS files.
|
||||||
- **GitLab CI/CD**: Automates the build process and creates Docker images.
|
- **Gitea Actions**: Automates the build process and creates Docker images.
|
||||||
- **Kubernetes Cluster**: Serves the static files to users.
|
- **Kubernetes Cluster**: Serves the static files to users.
|
||||||
|
|
||||||
## To-Do List
|
## To-Do List
|
||||||
@@ -47,7 +47,8 @@ sacrificing performance.
|
|||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
### Cloning the Repository
|
### Cloning the Repository
|
||||||
To clone this repository along with its submodules, use the following command:
|
The Blowfish theme is managed as a Hugo module, so no submodules are required.
|
||||||
|
Clone the repository with:
|
||||||
```bash
|
```bash
|
||||||
git clone git@gitlab.com:ahmza/blog.git
|
git clone ssh://gitea@tea.ahmza.com:2222/ahmza/blog-ahmza-com.git
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: {{ replace .TranslationBaseName "-" " " | title }}
|
title: {{ replace .TranslationBaseName "-" " " | title }}
|
||||||
description: ""
|
description: ""
|
||||||
tags: [""]
|
tags: []
|
||||||
author: "ahmza"
|
author: "ahmza"
|
||||||
date: {{ .Date }}
|
date: {{ .Date }}
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
blog:
|
blog:
|
||||||
image: ghcr.io/ahmzaa/blog-ahmza-com
|
image: tea.ahmza.com/ahmza/blog-ahmza-com:latest
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ pagination.pagerSize = 10
|
|||||||
buildDrafts = false
|
buildDrafts = false
|
||||||
buildFuture = false
|
buildFuture = false
|
||||||
|
|
||||||
|
# Keep source-art and editor backup files out of the built site. Without this,
|
||||||
|
# files inside page bundles (e.g. feature.aseprite) are copied into public/ as
|
||||||
|
# page resources and served publicly.
|
||||||
|
ignoreFiles = ['\.aseprite$', '\.bak$']
|
||||||
|
|
||||||
enableEmoji = true
|
enableEmoji = true
|
||||||
|
|
||||||
# googleAnalytics = "G-XXXXXXXXX"
|
# googleAnalytics = "G-XXXXXXXXX"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Arrstack
|
title: Arrstack
|
||||||
description:
|
description:
|
||||||
tags: [""]
|
tags: []
|
||||||
author: "ahmza"
|
author: "ahmza"
|
||||||
date: 2025-05-09T23:44:27+01:00
|
date: 2025-05-09T23:44:27+01:00
|
||||||
draft: true
|
draft: true
|
||||||
@@ -4,7 +4,7 @@ description: In the modern age we are overwhelmed with stimuli. Learn to cut out
|
|||||||
tags:
|
tags:
|
||||||
- lifestyle
|
- lifestyle
|
||||||
- minimalism
|
- minimalism
|
||||||
- prodcutivity
|
- productivity
|
||||||
author: ahmza
|
author: ahmza
|
||||||
date: 2024-01-02
|
date: 2024-01-02
|
||||||
draft: false
|
draft: false
|
||||||
|
|||||||
@@ -185,8 +185,8 @@ cd .server-data
|
|||||||
2. **Download a Minecraft server jar file:**
|
2. **Download a Minecraft server jar file:**
|
||||||
|
|
||||||
- [Vanilla Minecraft Server](https://www.minecraft.net/en-us/download/server)
|
- [Vanilla Minecraft Server](https://www.minecraft.net/en-us/download/server)
|
||||||
- Right-click the green link that says minecraft_server.<version>.jar and copy the link address.
|
- Right-click the green link that says `minecraft_server.<version>.jar` and copy the link address.
|
||||||
- Download with: curl -o server.jar <link you copied here>
|
- Download with: `curl -o server.jar <link you copied here>`
|
||||||
- [Fabric Mod loader server](https://fabricmc.net/use/server/)
|
- [Fabric Mod loader server](https://fabricmc.net/use/server/)
|
||||||
- Select the Minecraft version, Fabric Loader Version, and the Installer Version of your choice. I tend to go with the latest.
|
- Select the Minecraft version, Fabric Loader Version, and the Installer Version of your choice. I tend to go with the latest.
|
||||||
- Download with: Copy the curl command under CLI download
|
- Download with: Copy the curl command under CLI download
|
||||||
|
|||||||
@@ -1,243 +0,0 @@
|
|||||||
---
|
|
||||||
title: Minecraft Server on Docker ⛏ 🐳
|
|
||||||
date: 2022-05-24
|
|
||||||
lastmod: 2024-07-20
|
|
||||||
author: "ahmza"
|
|
||||||
draft: false
|
|
||||||
weight:
|
|
||||||
|
|
||||||
tags: ["games", "docker"]
|
|
||||||
description: "Ever wanted to build a Minecraft server in docker !! Start here"
|
|
||||||
---
|
|
||||||
|
|
||||||
## **Introduction**
|
|
||||||
|
|
||||||
Minecraft is a great game that becomes even more enjoyable when played with
|
|
||||||
friends. One of the best ways to do this is by setting up a dedicated server.
|
|
||||||
This guide will help you do just that!
|
|
||||||
|
|
||||||
Setting up and running a Minecraft server does not require a lot of resources,
|
|
||||||
but based on my experience, allocating more resources significantly enhances
|
|
||||||
performance. Especially when you start to add mods and increase player count.
|
|
||||||
|
|
||||||
## **Prerequisites**
|
|
||||||
|
|
||||||
- Docker
|
|
||||||
- Docker Compose
|
|
||||||
|
|
||||||
See [Pi Cluster](/posts/pi-docker-swarm/)
|
|
||||||
for more information about my docker setup.
|
|
||||||
|
|
||||||
## **Requirements**
|
|
||||||
_Requirements Gathered from [here](https://minecraft.fandom.com/wiki/Server/Requirements/Dedicated#Unix_.28Linux.2C_BSD.2C_macOS.29)_
|
|
||||||
|
|
||||||
### **Minimum (1-5 Players)**
|
|
||||||
|
|
||||||
- Intel Pentium 4 or AMD Athlon based CPU
|
|
||||||
- 512 MB RAM
|
|
||||||
- 2 GB Storage space
|
|
||||||
|
|
||||||
### **Recommended (10-15 Players)**
|
|
||||||
|
|
||||||
- Intel Core or AMD K8 based CPU
|
|
||||||
- 3 GB RAM
|
|
||||||
- 16 GB Storage Space
|
|
||||||
|
|
||||||
### **Optimal (20+ Players)**
|
|
||||||
|
|
||||||
- Intel i5/i7 or AMD Ryzen 5/7 Based CPU
|
|
||||||
- 6 GB RAM
|
|
||||||
- 35 GB Storage Space
|
|
||||||
|
|
||||||
**My opinion**:
|
|
||||||
|
|
||||||
I personally went with the **optimal** settings as I wanted to run mods as well
|
|
||||||
as have the capacity for ~10 players. This gives the server breathing room.
|
|
||||||
|
|
||||||
However if you will be runing no mods or a few light weight mods and only a few
|
|
||||||
players then there really is no need to go higher than 3-4 GB.
|
|
||||||
|
|
||||||
## **Building the Container**
|
|
||||||
|
|
||||||
Lets get started on building the Minecraft server
|
|
||||||
|
|
||||||
1. **Create a directory for the Minecraft server files**:
|
|
||||||
```bash
|
|
||||||
# Make the folder
|
|
||||||
mkdir minecraft-server
|
|
||||||
# Enter the folder
|
|
||||||
cd minecraft-server
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Create an environment file for the Docker Container**:
|
|
||||||
- The _.env_ file allows us to specify variables used in the build process.
|
|
||||||
```bash
|
|
||||||
# Create a .env file
|
|
||||||
touch .env
|
|
||||||
# Open in a text editor
|
|
||||||
vim .env
|
|
||||||
# Enter the following variables
|
|
||||||
CONTAINER_NAME=minecraft-server
|
|
||||||
SERVER_MEM=4096 #change this to your desired value (in MB)
|
|
||||||
SERVER_JAR=<downloaded-server-file>.jar # Minecraft server jar file. Change this later
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Create the _docker-compose.yml_ file**:
|
|
||||||
```bash
|
|
||||||
# Create the file
|
|
||||||
touch docker-compose.yml`
|
|
||||||
# Open in a text editor
|
|
||||||
vim docker-compose.yml
|
|
||||||
```
|
|
||||||
- _docker-compose.yml_ is the spec of our Minecraft server.
|
|
||||||
- Build a docker image using context . and pass some ARGS.
|
|
||||||
- Attach volume .server-data, where Minecraft's files will be stored.
|
|
||||||
- Open network ports to access to the Docker container
|
|
||||||
- Give the docker container a name
|
|
||||||
- The container will always restart unless we tell it to stop.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
minecraft-server:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
args:
|
|
||||||
SERVER_MEM: ${SERVER_MEM}
|
|
||||||
SERVER_JAR: ${SERVER_JAR}
|
|
||||||
volumes:
|
|
||||||
- .server-data:/server
|
|
||||||
ports:
|
|
||||||
- 25565:25565 # Game
|
|
||||||
- 25575:25575 # RCON
|
|
||||||
container_name: ${CONTAINER_NAME}
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
server-data:
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Create the _Dockerfile_ file**:
|
|
||||||
|
|
||||||
- requested in docker-compose.yml build stage as context .
|
|
||||||
- The _Dockerfile_ is the spec for software running inside the container
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create the file
|
|
||||||
touch Dockerfile
|
|
||||||
# Open the file in a text editor
|
|
||||||
vim Dockerfile
|
|
||||||
```
|
|
||||||
- We use the [eclipse-temurin:21](https://hub.docker.com/_/eclipse-temurin) image to provide the java JRE
|
|
||||||
- Use the required java version for the version of Minecraft you are running
|
|
||||||
- Collect the ARGs from the .env file
|
|
||||||
- Set the environment variables
|
|
||||||
- Set the working directory to /server which on our local system is .server-data
|
|
||||||
- Run the minecraft server jar with the ENV variables
|
|
||||||
|
|
||||||
```Dockerfile
|
|
||||||
FROM eclipse-temurin:21
|
|
||||||
|
|
||||||
ARG SERVER_MEM
|
|
||||||
ARG SERVER_JAR
|
|
||||||
|
|
||||||
ENV SERVER_MEM=${SERVER_MEM}
|
|
||||||
ENV SERVER_JAR=${SERVER_JAR}
|
|
||||||
|
|
||||||
WORKDIR /server
|
|
||||||
|
|
||||||
CMD echo "Minecraft Server is starting\n" && java -Xms128M -Xmx${SERVER_MEM}M -jar ${SERVER_JAR} nogui
|
|
||||||
```
|
|
||||||
|
|
||||||
## **Setup the Server files**
|
|
||||||
|
|
||||||
We’re almost there, just a few steps to go.
|
|
||||||
|
|
||||||
When you try to start the container, you might encounter the following error:
|
|
||||||
```bash
|
|
||||||
Minecraft Server is starting
|
|
||||||
|
|
||||||
Error: Unable to access jarfile server.jar
|
|
||||||
```
|
|
||||||
This happens because we haven't downloaded the server jar file yet. Let's do
|
|
||||||
that now
|
|
||||||
|
|
||||||
1. **Create a folder in the minecraft-server folder called .server-data**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir .server-data
|
|
||||||
cd .server-data
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Download a Minecraft server jar file**:
|
|
||||||
- [Vanilla Minecraft Server](https://www.minecraft.net/en-us/download/server)
|
|
||||||
- Right click the green link that says minecraft_server.<version>.jar and copy the link address
|
|
||||||
- Download with: `curl -o server.jar <link you copied here>`
|
|
||||||
- [Fabric Mod loader server](https://fabricmc.net/use/server/)
|
|
||||||
- Select the Minecraft version, Fabric Loader Version and the Installer Version of your choice. I tend to go with the latest.
|
|
||||||
- Download with: `Copy the curl command under CLI download`
|
|
||||||
|
|
||||||
3. **Go back to the root minecraft-server folder**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ..
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Edit SERVER_JAR in .env with the name of the .jar you just downloaded**:
|
|
||||||
```bash
|
|
||||||
# Open in a text editor
|
|
||||||
vim .env
|
|
||||||
# Enter the following variables
|
|
||||||
CONTAINER_NAME=minecraft-server
|
|
||||||
SERVER_MEM=4096
|
|
||||||
SERVER_JAR=<downloaded-server-file>.jar # Change this to the name of the .jar you downloaded
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Run the container to generate necessary files like 'eula.txt'**:
|
|
||||||
```bash
|
|
||||||
docker compose up
|
|
||||||
```
|
|
||||||
you will see something like this:
|
|
||||||
```bash
|
|
||||||
minecraft-server | Starting net.minecraft.server.Main
|
|
||||||
minecraft-server | [18:05:50] [ServerMain/INFO]: You need to agree to the EULA in order to run the server. Go to eula.txt for more info.
|
|
||||||
minecraft-server exited with code 0
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **Accept the EULA**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# edit eula.txt in a text editor
|
|
||||||
vim .server-data/eula.txt
|
|
||||||
# change `eula=false` to `eula=true`
|
|
||||||
eula=true
|
|
||||||
```
|
|
||||||
|
|
||||||
7. **Make any additional changes you want**:
|
|
||||||
- Notable files:
|
|
||||||
- **server.properties** - Configure server name, game mode, world type etc…
|
|
||||||
- **ops.json** - server operators (can be set in-game)
|
|
||||||
- **whitelist.json** - players allowed to join the server (can be set in-game)
|
|
||||||
|
|
||||||
## Run the Minecraft Server
|
|
||||||
|
|
||||||
1. **Run the container in detached mode**:
|
|
||||||
```bash
|
|
||||||
# The -d flag allows the container to run in detached mode, i.e., in the background
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# monitor the logs as the server starts. Use CTRL+C to exit
|
|
||||||
docker logs minecraft-server --follow
|
|
||||||
# If you see the following lines, your server is up and running:
|
|
||||||
[18:17:01] [Server thread/INFO]: Time elapsed: 25529 ms
|
|
||||||
[18:17:01] [Server thread/INFO]: Done (34.571s)! For help, type "help"
|
|
||||||
```
|
|
||||||
|
|
||||||
## **Conclusion**
|
|
||||||
|
|
||||||
Congratulations! You've successfully set up a Minecraft server. Now it's time
|
|
||||||
to play!
|
|
||||||
|
|
||||||
There are many exciting possibilities from here, such as installing mods or
|
|
||||||
creating scripts to manage the server. Enjoy your Minecraft adventures!
|
|
||||||
@@ -39,8 +39,6 @@ a **bare metal server that sucks a lot of power to a low power solution.**
|
|||||||
- 4 x PoE hats
|
- 4 x PoE hats
|
||||||
- 1 x 5 port PoE Switch
|
- 1 x 5 port PoE Switch
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### **Point Of This post**
|
### **Point Of This post**
|
||||||
|
|
||||||
After following this post you will have setup _Docker swarm_ on a cluster of Pis
|
After following this post you will have setup _Docker swarm_ on a cluster of Pis
|
||||||
@@ -329,7 +327,7 @@ vim hosts
|
|||||||
```
|
```
|
||||||
|
|
||||||
- **_[piCluster]_** - specifies a group of hosts
|
- **_[piCluster]_** - specifies a group of hosts
|
||||||
- **_<ip address>_** - change these to the Pis IP addresses
|
- **_`<ip address>`_** - change these to the Pis IP addresses
|
||||||
|
|
||||||
**create the Pi Intercommunication keys**
|
**create the Pi Intercommunication keys**
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
title: Securing SSH 🤖
|
title: Securing SSH 🤖
|
||||||
description: Ever wanted to make your SSH connections more secure. Read this
|
description: Ever wanted to make your SSH connections more secure. Read this
|
||||||
tags:
|
tags:
|
||||||
- HomeLab
|
- homelab
|
||||||
- RemoteAccess
|
- remoteaccess
|
||||||
- SSH
|
- ssh
|
||||||
author: ahmza
|
author: ahmza
|
||||||
date: 2021-12-07
|
date: 2021-12-07
|
||||||
draft: false
|
draft: false
|
||||||
@@ -146,7 +146,7 @@ fine 😊
|
|||||||
- Max Auth Tries 5
|
- Max Auth Tries 5
|
||||||
- PasswordAuthentication no
|
- PasswordAuthentication no
|
||||||
- PermitEmptyPasswords no
|
- PermitEmptyPasswords no
|
||||||
- AllowUsers <user>
|
- AllowUsers `<user>`
|
||||||
- ClientAliveInterval 180
|
- ClientAliveInterval 180
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: Setup a HUGO Site
|
|||||||
description: Always wanted to setup a static website. Look no further !! In this post we go over the basics of setting up a HUGO site
|
description: Always wanted to setup a static website. Look no further !! In this post we go over the basics of setting up a HUGO site
|
||||||
tags:
|
tags:
|
||||||
- blog
|
- blog
|
||||||
- HUGO
|
- hugo
|
||||||
- web
|
- web
|
||||||
- development
|
- development
|
||||||
author: ahmza
|
author: ahmza
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ wsl --install
|
|||||||
Go to the Microsoft store and search for a Linux distribution. I prefer
|
Go to the Microsoft store and search for a Linux distribution. I prefer
|
||||||
to use the "Ubuntu" distribution.
|
to use the "Ubuntu" distribution.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Once installed, go ahead and run the distribution, after it finishes setting
|
Once installed, go ahead and run the distribution, after it finishes setting
|
||||||
itself up it will ask you for a username and password. These credentials are
|
itself up it will ask you for a username and password. These credentials are
|
||||||
@@ -51,7 +51,7 @@ I use the windows terminal app to run the Ubuntu distribution, it makes
|
|||||||
customising the environment a lot easier. To download it head over to the
|
customising the environment a lot easier. To download it head over to the
|
||||||
Microsoft store and search for Windows Terminal and download it.
|
Microsoft store and search for Windows Terminal and download it.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
When you login to WSL it will not default to your home directory. To fix that go
|
When you login to WSL it will not default to your home directory. To fix that go
|
||||||
to the Windows Terminal Settings look for Ubuntu or your distributions name and
|
to the Windows Terminal Settings look for Ubuntu or your distributions name and
|
||||||
@@ -61,11 +61,11 @@ change the Starting directory to
|
|||||||
\\wsl$\<distro>\home\<username>
|
\\wsl$\<distro>\home\<username>
|
||||||
```
|
```
|
||||||
|
|
||||||
> Be sure to change "<distro>" to your distributions name
|
> Be sure to change `<distro>` to your distributions name
|
||||||
|
|
||||||
> Be sure to change "<username>" to your username
|
> Be sure to change `<username>` to your username
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## **Conclusion**
|
## **Conclusion**
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Welcome
|
title: Welcome
|
||||||
|
description: The mandatory hello world post — a quick intro to who I am and what this blog is about.
|
||||||
tags:
|
tags:
|
||||||
- blog
|
- blog
|
||||||
- misc
|
- misc
|
||||||
|
|||||||
@@ -0,0 +1,333 @@
|
|||||||
|
# Caddy as a Reverse Proxy
|
||||||
|
|
||||||
|
This document describes how to run [Caddy](https://caddyserver.com/) as a
|
||||||
|
reverse proxy in front of the blog. It covers both deployment targets used by
|
||||||
|
this project: the Kubernetes (Raspberry Pi) cluster and the Docker Compose host
|
||||||
|
(Gimli).
|
||||||
|
|
||||||
|
It uses the custom [`caddy-cf`](https://github.com/ahmzaa/caddy-cf) image
|
||||||
|
(`ghcr.io/ahmzaa/caddy-cf:latest`) — a Caddy build with the **Cloudflare DNS
|
||||||
|
module** baked in. The stock Caddy image cannot solve the ACME **DNS-01**
|
||||||
|
challenge for Cloudflare; this custom build can, which is what lets us obtain
|
||||||
|
certificates without exposing ports `80`/`443` to the public internet.
|
||||||
|
|
||||||
|
## Why a reverse proxy?
|
||||||
|
|
||||||
|
The blog is served by [Static Web Server (SWS)](https://static-web-server.net/),
|
||||||
|
a tiny Rust binary that ships static files over plain HTTP on port `80`. SWS is
|
||||||
|
intentionally kept lean — it does **not** terminate TLS, and its compression and
|
||||||
|
security headers are disabled (see `sws.toml`). Those concerns are delegated to
|
||||||
|
the reverse proxy so that each layer owns exactly one job:
|
||||||
|
|
||||||
|
| Concern | Owned by | Notes |
|
||||||
|
| -------------------- | -------- | ------------------------------------------------- |
|
||||||
|
| TLS / HTTPS certs | Caddy | Automatic certs via Cloudflare **DNS-01** challenge |
|
||||||
|
| Compression (gzip/zstd) | Caddy | `encode` directive; SWS compression is off |
|
||||||
|
| Security headers | Caddy | Single source of truth, avoids duplicate headers |
|
||||||
|
| Serving static files | SWS | Reads from `/public`, sets `Cache-Control`/`ETag` |
|
||||||
|
| Cache-Control / ETag | SWS | Declared at the origin, passed through by Caddy |
|
||||||
|
|
||||||
|
### Why the Cloudflare DNS-01 challenge?
|
||||||
|
|
||||||
|
Caddy does **automatic HTTPS** out of the box, but the default HTTP-01 /
|
||||||
|
TLS-ALPN-01 challenges require ports `80`/`443` to be publicly reachable so
|
||||||
|
Let's Encrypt can verify domain ownership. This project's infrastructure is
|
||||||
|
reached over [Netbird](https://netbird.io/) (a private overlay network), so
|
||||||
|
those inbound challenges are not an option.
|
||||||
|
|
||||||
|
The **DNS-01** challenge instead proves domain ownership by creating a TXT
|
||||||
|
record via the Cloudflare API — no inbound connectivity required. The `caddy-cf`
|
||||||
|
image bundles the Cloudflare DNS provider plugin so Caddy can do this. All it
|
||||||
|
needs is a Cloudflare API token with **Zone:DNS:Edit** permission on the
|
||||||
|
`ahmza.com` zone.
|
||||||
|
|
||||||
|
## Origin contract
|
||||||
|
|
||||||
|
Whatever the deployment, the SWS origin exposes:
|
||||||
|
|
||||||
|
- **Protocol:** plain HTTP
|
||||||
|
- **Port:** `80`
|
||||||
|
- **Health endpoint:** `GET /health` → `200 OK`
|
||||||
|
- **Cache headers:** already set by SWS (Caddy should pass them through, not override)
|
||||||
|
|
||||||
|
Caddy only needs to terminate TLS (via Cloudflare DNS-01), add compression +
|
||||||
|
security headers, and forward requests to that origin.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker Compose (Gimli)
|
||||||
|
|
||||||
|
Run Caddy and the blog container on the same Docker network. Caddy reaches the
|
||||||
|
blog by its service name (`blog`) over the internal network, so the blog's port
|
||||||
|
does **not** need to be published to the host.
|
||||||
|
|
||||||
|
The Cloudflare API token is passed to Caddy as the `CF_API_TOKEN` environment
|
||||||
|
variable and referenced from the Caddyfile — keep it out of the committed
|
||||||
|
config. Store it in a `.env` file (git-ignored) next to `compose.yml`:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
# .env
|
||||||
|
CF_API_TOKEN=your-cloudflare-api-token-with-zone-dns-edit
|
||||||
|
```
|
||||||
|
|
||||||
|
### `compose.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
blog:
|
||||||
|
image: ghcr.io/ahmzaa/blog-ahmza-com
|
||||||
|
restart: unless-stopped
|
||||||
|
# No host port mapping needed — only Caddy talks to it, over the
|
||||||
|
# internal Docker network. Remove the previous "80:80" publish.
|
||||||
|
expose:
|
||||||
|
- "80"
|
||||||
|
|
||||||
|
caddy:
|
||||||
|
image: ghcr.io/ahmzaa/caddy-cf:latest # Caddy + Cloudflare DNS module
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: caddy-cf
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
- "443:443/udp" # HTTP/3 (QUIC)
|
||||||
|
environment:
|
||||||
|
# Consumed by the {env.CF_API_TOKEN} placeholder in the Caddyfile.
|
||||||
|
- CF_API_TOKEN=${CF_API_TOKEN}
|
||||||
|
volumes:
|
||||||
|
- ./conf:/etc/caddy # contains the Caddyfile
|
||||||
|
- ./data:/data # persists certificates — DO NOT lose this
|
||||||
|
- ./config:/config
|
||||||
|
```
|
||||||
|
|
||||||
|
Place the Caddyfile at `./conf/Caddyfile`.
|
||||||
|
|
||||||
|
### `./conf/Caddyfile`
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
# Reusable Cloudflare DNS-01 TLS snippet. The token is read from the
|
||||||
|
# CF_API_TOKEN environment variable so it never lives in the config file.
|
||||||
|
(cloudflare) {
|
||||||
|
tls {
|
||||||
|
dns cloudflare {env.CF_API_TOKEN}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blog.ahmza.com {
|
||||||
|
# Obtain/renew the cert via the Cloudflare DNS-01 challenge.
|
||||||
|
import cloudflare
|
||||||
|
|
||||||
|
# Reverse proxy to the blog container by its compose service name.
|
||||||
|
reverse_proxy blog:80 {
|
||||||
|
# Use the SWS health endpoint for active upstream checks.
|
||||||
|
health_uri /health
|
||||||
|
health_interval 10s
|
||||||
|
health_timeout 2s
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compression is done here, not in SWS.
|
||||||
|
encode zstd gzip
|
||||||
|
|
||||||
|
# Security headers live here so there is a single source of truth.
|
||||||
|
header {
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
# Remove Caddy's default Server header
|
||||||
|
-Server
|
||||||
|
}
|
||||||
|
|
||||||
|
log {
|
||||||
|
output stdout
|
||||||
|
format console
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Certificate persistence:** the `./data` volume stores the issued
|
||||||
|
> certificates and ACME account key. Keep it — deleting it forces Caddy to
|
||||||
|
> re-request certificates and can hit Let's Encrypt rate limits.
|
||||||
|
|
||||||
|
Bring it up with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kubernetes (Raspberry Pi cluster)
|
||||||
|
|
||||||
|
The blog runs as a `Deployment` behind a `Service`. Caddy runs as its own
|
||||||
|
`Deployment` and proxies to that Service via in-cluster DNS
|
||||||
|
(`blog.blog-ahmza-com.svc.cluster.local`, or just `blog` from within the same
|
||||||
|
namespace).
|
||||||
|
|
||||||
|
### Cloudflare API token Secret
|
||||||
|
|
||||||
|
Store the Cloudflare API token in a `Secret` and inject it into the Caddy
|
||||||
|
container as the `CF_API_TOKEN` environment variable (never bake it into the
|
||||||
|
ConfigMap):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl create secret generic caddy-cloudflare \
|
||||||
|
--namespace blog-ahmza-com \
|
||||||
|
--from-literal=CF_API_TOKEN='your-cloudflare-api-token-with-zone-dns-edit'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caddyfile via ConfigMap
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: caddy-config
|
||||||
|
namespace: blog-ahmza-com
|
||||||
|
data:
|
||||||
|
Caddyfile: |
|
||||||
|
(cloudflare) {
|
||||||
|
tls {
|
||||||
|
dns cloudflare {env.CF_API_TOKEN}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blog.ahmza.com {
|
||||||
|
import cloudflare
|
||||||
|
|
||||||
|
reverse_proxy blog:80 {
|
||||||
|
health_uri /health
|
||||||
|
health_interval 10s
|
||||||
|
health_timeout 2s
|
||||||
|
}
|
||||||
|
|
||||||
|
encode zstd gzip
|
||||||
|
|
||||||
|
header {
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
-Server
|
||||||
|
}
|
||||||
|
|
||||||
|
log {
|
||||||
|
output stdout
|
||||||
|
format console
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caddy Deployment + Service
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: caddy
|
||||||
|
namespace: blog-ahmza-com
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: caddy
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: caddy
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: caddy
|
||||||
|
image: ghcr.io/ahmzaa/caddy-cf:latest # Caddy + Cloudflare DNS module
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
- containerPort: 443
|
||||||
|
- containerPort: 443
|
||||||
|
protocol: UDP
|
||||||
|
env:
|
||||||
|
- name: CF_API_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: caddy-cloudflare
|
||||||
|
key: CF_API_TOKEN
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/caddy
|
||||||
|
- name: data
|
||||||
|
mountPath: /data # certificate storage — must persist
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: caddy-config
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: caddy-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: caddy-data
|
||||||
|
namespace: blog-ahmza-com
|
||||||
|
spec:
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 256Mi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: caddy
|
||||||
|
namespace: blog-ahmza-com
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer # or NodePort, depending on the cluster
|
||||||
|
selector:
|
||||||
|
app: caddy
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
- name: https
|
||||||
|
port: 443
|
||||||
|
targetPort: 443
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Blog Service:** the existing `blog` Service should expose port `80` and
|
||||||
|
> target the SWS container port `80`. Caddy reaches it as `blog:80` from inside
|
||||||
|
> the same namespace.
|
||||||
|
|
||||||
|
> **Certificate persistence:** the `caddy-data` PVC is the K8s equivalent of the
|
||||||
|
> `./data` Docker volume. Without a persistent volume, a pod restart wipes
|
||||||
|
> the certificates and re-triggers ACME issuance.
|
||||||
|
|
||||||
|
> **DNS-01 and Netbird:** because certificates are issued via the Cloudflare
|
||||||
|
> DNS-01 challenge, the cluster does **not** need inbound `80`/`443` from the
|
||||||
|
> public internet for ACME. The `LoadBalancer`/`NodePort` above only needs to be
|
||||||
|
> reachable by your clients (e.g. over Netbird or your LAN).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes and gotchas
|
||||||
|
|
||||||
|
- **DNS-01 needs no inbound reachability.** Because the `caddy-cf` image solves
|
||||||
|
the ACME challenge via the Cloudflare API (DNS-01), Caddy does **not** need
|
||||||
|
ports `80`/`443` exposed to the public internet to issue certificates. This is
|
||||||
|
what makes it work behind Netbird. It does need outbound HTTPS to the Let's
|
||||||
|
Encrypt and Cloudflare APIs.
|
||||||
|
- **Cloudflare API token scope.** The token needs **Zone → DNS → Edit** on the
|
||||||
|
`ahmza.com` zone. Keep it in a `.env` file (Docker) or a `Secret` (K8s), never
|
||||||
|
in the committed Caddyfile — the config references it as `{env.CF_API_TOKEN}`.
|
||||||
|
- **DNS must be managed by Cloudflare.** The `ahmza.com` zone has to be hosted on
|
||||||
|
Cloudflare for the DNS-01 challenge to create the required `_acme-challenge`
|
||||||
|
TXT record.
|
||||||
|
- **Do not double-compress.** SWS compression is off by design; Caddy's `encode`
|
||||||
|
directive is the only place compression happens. Enabling both wastes CPU.
|
||||||
|
- **Do not duplicate headers.** Security headers are set in the `Caddyfile`. SWS
|
||||||
|
has `security-headers = false` so there is no conflict.
|
||||||
|
- **Cache-Control comes from the origin.** SWS already sets long-lived caching on
|
||||||
|
static assets and short/no-cache on HTML. Caddy passes these through; avoid
|
||||||
|
adding conflicting `Cache-Control` header directives in the `Caddyfile`.
|
||||||
|
- **Health checks.** Both examples use SWS's `/health` endpoint for active
|
||||||
|
upstream health checking, and it also works as a K8s liveness/readiness probe
|
||||||
|
on the blog pod itself.
|
||||||
|
- **HTTP/3.** The Docker example publishes `443/udp` to enable QUIC. Remove it if
|
||||||
|
your network path does not support UDP.
|
||||||
@@ -1 +1 @@
|
|||||||
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank"{{ end }}>{{ .Text | safeHTML }}</a>
|
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener noreferrer"{{ end }}>{{ .Text | safeHTML }}</a>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<br>
|
<br>
|
||||||
<div class="flex items-center gap-x-5">
|
<div class="flex items-center gap-x-5">
|
||||||
<a class="text-sm text-neutral-500 dark:text-neutral-400 rel='me'" href="https://mastodon.social/@ahmza">Mastodon</a>
|
<a class="text-sm text-neutral-500 dark:text-neutral-400" rel="me noopener noreferrer" target="_blank" href="https://mastodon.social/@ahmza">Mastodon</a>
|
||||||
<a class="text-sm text-neutral-500 dark:text-neutral-400 rel='me'" href="https://github.com/ahmzaa">Github</a>
|
<a class="text-sm text-neutral-500 dark:text-neutral-400" rel="me noopener noreferrer" target="_blank" href="https://github.com/ahmzaa">Github</a>
|
||||||
<a class="text-sm text-neutral-500 dark:text-neutral-400 rel='me'" href="https://www.linkedin.com/in/ahmza/">Linkedin</a>
|
<a class="text-sm text-neutral-500 dark:text-neutral-400" rel="me noopener noreferrer" target="_blank" href="https://www.linkedin.com/in/ahmza/">Linkedin</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<img src="{{ .Get "url"}}" alt="{{ .Get "alt"}}" style="display:block;margin-left:auto;margin-right:auto;width:50%;height:auto;padding:10px;">
|
<img src="{{ .Get "url"}}" alt="{{ .Get "alt"}}" loading="lazy" decoding="async" style="display:block;margin-left:auto;margin-right:auto;width:50%;height:auto;padding:10px;">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<video autoplay loop muted playsinline aria-label='{{ .Get "label"}}' style="display:block;margin-left:auto;margin-right:auto;width:50%;height:auto;padding:10px;">
|
<video autoplay loop muted playsinline preload="metadata" aria-label='{{ .Get "label"}}' style="display:block;margin-left:auto;margin-right:auto;width:50%;height:auto;padding:10px;">
|
||||||
{{ with .Get "mp4" }}<source src="{{ . }}" type="video/mp4">{{ end }}
|
{{ with .Get "mp4" }}<source src="{{ . }}" type="video/mp4">{{ end }}
|
||||||
{{ with .Get "webm" }}<source src="{{ . }}" type="video/webm">{{ end }}
|
{{ with .Get "webm" }}<source src="{{ . }}" type="video/webm">{{ end }}
|
||||||
</video>
|
</video>
|
||||||
|
|||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
# auto detects a good number of processes to run
|
|
||||||
worker_processes auto;
|
|
||||||
|
|
||||||
#Provides the configuration file context in which the directives that affect connection processing are specified.
|
|
||||||
events {
|
|
||||||
# Sets the maximum number of simultaneous connections that can be opened by a worker process.
|
|
||||||
worker_connections 1024;
|
|
||||||
# Tells the worker to accept multiple connections at a time
|
|
||||||
multi_accept on;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
http {
|
|
||||||
# what times to include
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
# what is the default one
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
# Sets the path, format, and configuration for a buffered log write
|
|
||||||
log_format compression '$remote_addr - $remote_user [$time_local] '
|
|
||||||
'"$request" $status $upstream_addr '
|
|
||||||
'"$http_referer" "$http_user_agent"';
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
|
|
||||||
gzip_vary on;
|
|
||||||
gzip_proxied any;
|
|
||||||
gzip_comp_level 9;
|
|
||||||
gzip_buffers 10 8k;
|
|
||||||
gzip_http_version 1.1;
|
|
||||||
gzip_types text/plain text/css text/xml text/javascript image/webp image/svg+xml image/x-icon application/javascript application/x-javascript application/json application/xml application/xml+rss;
|
|
||||||
|
|
||||||
server {
|
|
||||||
# listen on port 80
|
|
||||||
listen 80;
|
|
||||||
# save logs here
|
|
||||||
access_log /var/log/nginx/access.log compression;
|
|
||||||
|
|
||||||
# where's the root dir
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
# what file to serve as index
|
|
||||||
index index.html index.htm;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
# First attempt to serve request as file, then
|
|
||||||
# as directory, then fall back to redirecting to index.html
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Media: images, icons, video, audio, HTC
|
|
||||||
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|webp)$ {
|
|
||||||
expires 1y;
|
|
||||||
access_log off;
|
|
||||||
add_header Cache-Control "public";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Javascript and CSS files
|
|
||||||
location ~* \.(?:css|js)$ {
|
|
||||||
try_files $uri =404;
|
|
||||||
expires 1y;
|
|
||||||
access_log off;
|
|
||||||
add_header Cache-Control "public";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Any route containing a file extension (e.g. /devicesfile.js)
|
|
||||||
location ~ ^.+\..+$ {
|
|
||||||
try_files $uri =404;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,7 +23,7 @@ media_folder: "assets/uploaded/img"
|
|||||||
public_folder: "/uploaded/img"
|
public_folder: "/uploaded/img"
|
||||||
|
|
||||||
## SiteTitle
|
## SiteTitle
|
||||||
site_url: htpps://blog.ahmza.com
|
site_url: https://blog.ahmza.com
|
||||||
display_url: blog.ahmza.com
|
display_url: blog.ahmza.com
|
||||||
logo_url: https://blog.ahmza.com/logo.webp
|
logo_url: https://blog.ahmza.com/logo.webp
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,13 @@
|
|||||||
<!-- Include the script that enables Netlify Identity on this page. -->
|
<!-- Include the script that enables Netlify Identity on this page. -->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Include the script that builds the page and powers Decap CMS -->
|
<!-- Sveltia CMS, pinned to a specific version with Subresource Integrity.
|
||||||
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
|
Bump the version + regenerate the integrity hash when upgrading:
|
||||||
|
curl -sS https://cdn.jsdelivr.net/npm/@sveltia/cms@<ver>/dist/sveltia-cms.js \
|
||||||
|
| openssl dgst -sha384 -binary | openssl base64 -A -->
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/@sveltia/cms@0.172.4/dist/sveltia-cms.js"
|
||||||
|
integrity="sha384-Sj4Mfbg9OjjwG2ZE/YeUYu7xbZRTXGFg/wa/nszj3KzItKuRLEmGqSpV5C9YI2Ge"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
[general]
|
||||||
|
|
||||||
|
#### Server
|
||||||
|
|
||||||
|
# Listen on all interfaces, port 80 (matches previous nginx setup)
|
||||||
|
host = "0.0.0.0"
|
||||||
|
port = 80
|
||||||
|
|
||||||
|
# Root directory of the static site (Hugo `public/` output)
|
||||||
|
root = "/public"
|
||||||
|
|
||||||
|
# Number of worker threads (0 = auto-detect based on CPU cores)
|
||||||
|
threads-multiplier = 1
|
||||||
|
|
||||||
|
# Log level
|
||||||
|
log-level = "info"
|
||||||
|
|
||||||
|
#### Performance / caching
|
||||||
|
|
||||||
|
# Compression is handled by the upstream reverse proxy, so it is disabled
|
||||||
|
# here to avoid doing the work twice (wasted CPU per request). Re-enable if
|
||||||
|
# SWS is ever exposed directly without a compressing proxy in front.
|
||||||
|
compression = false
|
||||||
|
compression-static = false
|
||||||
|
|
||||||
|
# Emit Cache-Control + ETag headers with sensible per-content-type defaults.
|
||||||
|
# The origin is the correct place to declare cacheability; the proxy/CDN
|
||||||
|
# passes these through. SWS applies long-lived caching to static assets
|
||||||
|
# (images, css, js, fonts) and short/no-cache to HTML.
|
||||||
|
cache-control-headers = true
|
||||||
|
|
||||||
|
#### Routing
|
||||||
|
|
||||||
|
# No SPA-style page fallback: this is a static Hugo site, not a single-page app.
|
||||||
|
# Missing paths should return Hugo's generated 404.html with a real 404 status
|
||||||
|
# (SWS serves /public/404.html automatically) rather than masking broken links
|
||||||
|
# with a 200 homepage response, which harms SEO (soft 404s).
|
||||||
|
|
||||||
|
#### Security
|
||||||
|
|
||||||
|
# Security headers are set by the upstream reverse proxy, so they are
|
||||||
|
# disabled here to avoid duplicate/conflicting headers. Let one layer own
|
||||||
|
# the security posture. Re-enable if SWS is exposed without such a proxy.
|
||||||
|
security-headers = false
|
||||||
|
|
||||||
|
# Health endpoint at /health for container/K8s liveness probes
|
||||||
|
health = true
|
||||||
Reference in New Issue
Block a user