Automating blog.jaf.sh: The OpenBao & Garage S3 Pipeline
I finally got tired of manually building my blog and syncing it to S3. I wanted a "push-to-deploy" workflow that didn't involve hardcoding secrets or managing local environment variables. Here is exactly how I built the pipeline.
The Architecture
The goal was simple: push Markdown to Forgejo, and have it appear on blog.jaf.sh. The challenge was doing this securely and natively on my own hardware.
Step 1: Secret Management with OpenBao
I didn't want my S3 keys sitting in plain Forgejo secrets. I used OpenBao with the approle auth method. I created a custom mount and a policy that only allows reading the blog deployment keys.
On the OpenBao side, I configured the role like this:
# Define the policy for restricted access
bao policy write blog-jaf-sh-policy - <<EOF
path "secret/data/blog-jaf-sh" {
capabilities = ["read"]
}
EOF
# Create the AppRole at a custom mount point
bao write auth/blog-jaf-sh/role/forgejo-runner \
token_policies="blog-jaf-sh-policy" \
token_ttl=20mStep 2: The Forgejo Action
My .forgejo/workflows/deploy.yaml handles the heavy lifting. It uses the vault-action to authenticate against OpenBao.
One specific fix I had to implement was specifying the path: blog-jaf-sh in the action. By default, the action looks for the approle mount, but since I used a custom one, I had to point it to the right place:
- name: Retrieve Credentials from OpenBao
uses: https://github.com/hashicorp/vault-action@v3
with:
url: https://openbao.jaf.sh
method: approle
path: blog-jaf-sh
roleId: ${{ secrets.OPENBAO_ROLE_ID }}
secretId: ${{ secrets.OPENBAO_SECRET_ID }}
secrets: |
secret/data/blog-jaf-sh access_key | S3_ACCESS_KEY ;
secret/data/blog-jaf-sh secret_key | S3_SECRET_KEYStep 3: Syncing to Garage S3
Garage is great for self-hosted S3, but I had to make sure rclone was talking to it correctly. Specifically, I had to ensure the REGION was set to garage to match the cluster's signing requirements.
env:
RCLONE_CONFIG_GARAGE_TYPE: s3
RCLONE_CONFIG_GARAGE_ENDPOINT: https://s3.jaf.sh
RCLONE_CONFIG_GARAGE_REGION: garage
run: rclone sync ./public garage:blog.jaf.sh
Now, every time I push to main, the site builds in a clean container and syncs only the changed files. No more manual builds!