initial hugo site with terminal theme
All checks were successful
CI / build-check (push) Has been skipped
CI / build-and-push (push) Successful in 1m37s

This commit is contained in:
Nik Afiq 2026-03-17 23:30:49 +09:00
parent 2c46b2ad6d
commit ddf013b6b3
16 changed files with 694 additions and 1 deletions

64
.gitea/workflows/ci.yaml Normal file
View File

@ -0,0 +1,64 @@
# Required secrets:
# REGISTRY_USER — Gitea container registry username
# REGISTRY_PASSWORD — Gitea container registry password
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build-check:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Verify Hugo build
uses: docker://hugomods/hugo:exts
with:
args: hugo --minify
build-and-push:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
driver-opts: network=host
- name: Log in to Gitea registry
uses: docker/login-action@v3
with:
registry: gitea.nik4nao.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Resolve short SHA
id: sha
run: echo "short=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
- name: Build and push multiarch image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
gitea.nik4nao.com/nik/portfolio:latest
gitea.nik4nao.com/nik/portfolio:${{ steps.sha.outputs.short }}

3
.gitignore vendored
View File

@ -1 +1,4 @@
.env
.hugo_build.lock
public/
resources/_gen/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "themes/terminal"]
path = themes/terminal
url = https://github.com/panr/hugo-theme-terminal.git

10
Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM hugomods/hugo:exts AS builder
WORKDIR /site
COPY . .
RUN hugo --minify
FROM nginx:alpine
COPY --from=builder /site/public /usr/share/nginx/html
EXPOSE 80

149
content/cv.md Normal file
View File

@ -0,0 +1,149 @@
---
title: "CV"
type: "cv"
date: 2026-03-17
draft: false
---
# NIK AFIQ
Tokyo, Japan
nik@nik4nao.com | github.com/nikafiq | nik4nao.com
---
## PROFESSIONAL SUMMARY
Backend engineer with 3 years of professional experience designing and
operating distributed, high-throughput systems on GCP and AWS. Core
expertise in Go and Python, with hands-on production experience in
event-driven microservices, Kafka-based pipelines, Kubernetes, and
cloud-native data infrastructure. Comfortable operating systems at
hundreds of TPS with reliability and zero-downtime migration
constraints. Trilingual (English, Japanese N1, Malay) — routinely
bridges Japanese and overseas engineering teams. Actively integrates
AI tooling (GitHub Copilot, Gemini, Claude) into daily coding,
review, and documentation workflows.
---
## WORK EXPERIENCE
### 株式会社ホープス (Hopes Co., Ltd.) — Tokyo
**Backend Engineer** | Aug 2025 Present
Designing and operating a distributed RCS consent management pipeline
(SO→FoRCE) on GCP/GKE connecting a high-traffic notice delivery
system to a downstream fulfillment API.
- Architected an event-driven pipeline using GKE + Managed Kafka
(8 partitions, keyed by account_id) + Cloud Spanner, handling a
global cap of 200 TPS with a 10-second downstream timeout budget
- Built the Go consumer service (so-notice-receiver) with
singleflight coalescing to prevent duplicate in-flight requests,
and circuit breaker logic to shed load under downstream failure
- Designed reliable offset commit ordering: offsets committed only
after durable Spanner write, ensuring at-least-once delivery with
no data loss on crash
- Implemented a retry cronjob requeuing up to 5 failed Spanner rows
back to Kafka every 5 minutes, with configurable backoff
- Designed a zero-downtime interleaved index migration on a Cloud
Spanner accounts table under 400 TPS sustained read traffic
- Right-sized GKE resource configs (CPU/memory requests and limits)
from Locust load test data at 40 TPS steady / 120 TPS burst
- Propagated distributed traces across service boundaries for
end-to-end production observability
### 株式会社ニッポンダイナミックシステムズ — Tokyo
**Full Stack Engineer, IT Solutions — Pharma Market Team**
| Apr 2023 Jul 2025
- Built a scalable analytical DWH on Amazon Aurora (RDS) for a
pharmaceutical client, integrating Salesforce and multiple
external data sources via daily/weekly ETL batch pipelines using
ECS/Fargate and Lambda; designed for HA with Multi-AZ failover
- Constructed a SaaS data lake using AWS CDK + Glue +
TypeScript/Python, fully automating ETL ingestion across
heterogeneous data sources
- Developed an internal AI application using AWS Bedrock (Claude
Sonnet) + React, implementing RAG-based document retrieval and
SES-based user matching in a small cross-functional team
- Built a license authentication service (Node.js + Docker + Azure
Web Apps + ADB2C), owning requirements definition, auth logic
design, and client-facing communication
- Designed and automated monthly maintenance operations: AMI image
updates, security patching, automated regression testing, and
blue/green deployments via AWS CodePipeline and Azure Pipelines
- Conducted Docker image vulnerability scanning as part of CI/CD
pipeline; managed VPC, WAF, and Security Group configurations
- Mentored junior engineers on cloud architecture patterns;
functioned as bilingual (EN/JA) liaison between domestic and
overseas engineering teams
---
## SKILLS
**Languages:** Go, Python, TypeScript/JavaScript
**Frameworks:** Gin, Flask, Next.js, Node.js
**Cloud — AWS:** ECS/Fargate, Lambda, Aurora/RDS, DynamoDB, Glue,
CDK, CodePipeline, Bedrock, Secrets Manager
**Cloud — GCP:** GKE, Cloud Spanner, Managed Kafka (Pub/Sub),
BigQuery, Cloud Trace
**Cloud — Azure:** Web Apps, ADB2C, Azure Pipelines
**Data:** MySQL, Aurora, PostgreSQL, DynamoDB, Cloud Spanner,
Kafka, Redis
**DevOps:** Docker, Kubernetes, ArgoCD, CI/CD, IaC (AWS CDK)
**Observability:** Distributed tracing, ELK stack, Kibana
**AI Tooling:** GitHub Copilot (daily coding + code review),
Gemini (documentation + research), Claude (architecture
reasoning + coding), AWS Bedrock RAG (production)
**Security:** VPC, WAF, Security Groups, Secrets Manager,
Docker vulnerability scanning
**Other:** Homelab (k3s, self-hosted services, Ansible/IaC),
personal dev blog at nik4nao.com
---
## CERTIFICATIONS
| Certification | Issued |
|---|---|
| AWS Certified Solutions Architect Associate (SAA) | Oct 2024 |
| AWS Certified Developer Associate (DVA) | Dec 2024 |
| AWS Certified Cloud Practitioner (CLF) | Apr 2024 |
| 基本情報技術者試験 (FE) — IPA Fundamental IT Engineer | Aug 2024 |
| JLPT N1 — Japanese Language Proficiency | Dec 2022 |
*In progress: AWS Solutions Architect Professional (SAP),
Applied Information Technology Engineer (AP)*
---
## EDUCATION
**Tokai University** — Bachelor of Engineering
Major: Electrical and Electronic Engineering
Minor: Information Technology
Graduated: March 2023
*During a COVID-related leave of absence (20202021), independently
studied programming and cloud architecture; resumed with an
added IT minor upon return.*
---
## ADDITIONAL
- **Languages:** English (business), Japanese (JLPT N1), Malay (native)
- **Homelab:** Self-hosted k3s cluster, Gitea, Jellyfin, Cloudflare
Tunnel, Ansible-based IaC on Minisforum UM790 Pro
- **Dev blog / personal site:** nik4nao.com
- **Self-hosted Git:** git.nik4nao.com (mirrored to github.com/nikafiq)

View File

@ -0,0 +1,14 @@
---
title: "Hello World"
date: 2026-03-17
draft: false
tags: ["meta"]
---
Welcome to my corner of the internet.
This site is where I write about things I'm building, breaking, and learning — mostly homelabs,
Kubernetes, and the occasional software project. Expect posts about infrastructure, self-hosting,
and whatever rabbit hole I've fallen down most recently.
If you want to see what I'm currently working on, check out the [projects](/projects) page.

View File

@ -0,0 +1,8 @@
---
title: "Projects"
date: 2026-03-17
draft: false
---
A collection of things I've built or am currently working on — mostly self-hosted infrastructure,
homelab experiments, and open-source tooling.

View File

@ -0,0 +1,54 @@
---
title: "Homelab Kubernetes Cluster"
date: 2026-03-17
draft: false
description: "Self-hosted k3s cluster on bare-metal with Gitea CI/CD, multi-arch builds, Authentik SSO, and ~15 running workloads."
tags: ["kubernetes", "k3s", "homelab", "infrastructure", "traefik", "authentik"]
github: ""
url: ""
---
## Overview
A self-hosted Kubernetes cluster running on bare-metal hardware at home. The cluster serves as a
platform for running personal services, experimenting with cloud-native tooling, and learning
operational patterns without a cloud bill.
## Hardware
| Host | Role | Specs |
|---|---|---|
| Minisforum UM780 XTX | K3s control-plane | AMD Ryzen 7 8745H |
| HP ProDesk (nik-debian) | K3s storage agent | NFS server, mergerfs media pool |
| Mac Mini M2 | Standalone Docker host | ARM, outside the cluster |
## Stack
- **Distribution:** k3s
- **Ingress:** Traefik v3
- **TLS:** cert-manager — Let's Encrypt (public) + internal CA (LAN)
- **Auth:** Authentik SSO — OIDC + forwardAuth proxy, TOTP MFA enforced
- **DNS:** Pihole (primary + secondary, externalIPs)
- **Storage:** NFS (Debian) + local-path dynamic provisioner
- **CI/CD:** Gitea Actions + act_runner, Docker buildx multiarch (amd64 + arm64)
- **Registry:** Gitea built-in container registry
- **Observability:** Prometheus + Grafana + Loki + Promtail
- **IaC:** Ansible (host-level), Helm + raw manifests (cluster-level), all tracked in Gitea
## Highlights
- All cluster state is managed as code in a Gitea monorepo — single-file manifests per service, organised by concern
- Authentik SSO protects all web-facing services via Traefik forwardAuth; OIDC integrated with Gitea and Grafana
- Multi-arch image builds (amd64 + arm64) via buildx on every push to `main`, pushed to the self-hosted registry
- Dual-cert TLS strategy: internal CA for `*.home.arpa` services, Let's Encrypt for `*.nik4nao.com` public services
- Pihole running as primary + secondary with externalIPs for LAN-wide DNS and ad-blocking
- DDNS CronJob keeps the public A record in sync via the Porkbun API
## Running Workloads
Traefik, cert-manager, Pihole, Authentik, Gitea, Prometheus, Grafana, Loki, Promtail,
Jellyfin, qBittorrent, JDownloader, Photoview, Dashy, Glances, DDNS CronJob, this portfolio site.
## Status
Active and in daily use.

54
hugo.toml Normal file
View File

@ -0,0 +1,54 @@
baseURL = "https://nik4nao.com/"
languageCode = "en-us"
title = "nik4nao"
theme = "terminal"
[params]
# dir name of your main content (default is `content/posts`).
# the list of set content will show up on your index page (baseurl).
contentTypeName = "posts"
# ["orange", "blue", "red", "green", "pink"]
themeColor = "orange"
# if you set this to 0, only metadata of posts will appear
fullWidthTheme = false
# centered theme with any value (e.g. "true")
centerTheme = false
# if your resource directory contains an image called `cover.png`,
# it will be used as the cover of your blog
showCoverImage = true
# set terminal prompt
terminalPrompt = "nik@nik4nao ~ $"
# show/hide posts list on start page
showMenuItems = 3
# show a footer instead of the HUGO signature
# customFooter = ""
# set theme to full screen width
# fullWidthTheme = false
[[params.menu]]
identifier = "posts"
name = "posts"
url = "/posts"
[[params.menu]]
identifier = "projects"
name = "projects"
url = "/projects"
[[params.menu]]
identifier = "cv"
name = "cv"
url = "/cv"
[taxonomies]
tag = "tags"
paginate = 5

10
layouts/cv/single.html Normal file
View File

@ -0,0 +1,10 @@
{{ define "main" }}
<article class="post">
<div class="cv-download">
<a href="/cv/nik-afiq-cv.pdf" download class="cv-download-btn">↓ Download PDF</a>
</div>
<div class="post-content">
{{ .Content }}
</div>
</article>
{{ end }}

38
layouts/index.html Normal file
View File

@ -0,0 +1,38 @@
{{ define "main" }}
<div class="index-content">
{{/* ── Hero / intro ───────────────────────────────────────────────────── */}}
<div class="index-hero">
<p class="index-intro">
Backend engineer based in Tokyo. I build distributed systems,
operate Kubernetes infrastructure, and occasionally write about it.
</p>
<ul class="index-nav">
{{ range .Site.Params.menu }}
<li>
<span class="prompt">{{ $.Site.Params.terminalPrompt }}</span>
<a href="{{ .url }}">cd {{ .name }}</a>
</li>
{{ end }}
</ul>
</div>
{{/* ── Recent posts ────────────────────────────────────────────────────── */}}
{{ $posts := where .Site.RegularPages "Type" .Site.Params.contentTypeName }}
{{ if $posts }}
<section class="index-posts">
<h2>Recent Posts</h2>
{{ range first 5 $posts }}
<article class="post-preview">
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2006-01-02" }}</time>
<a href="{{ .Permalink }}">{{ .Title }}</a>
</article>
{{ end }}
{{ if gt (len $posts) 5 }}
<p class="index-all-posts"><a href="/posts">All posts →</a></p>
{{ end }}
</section>
{{ end }}
</div>
{{ end }}

View File

@ -0,0 +1 @@
<link rel="stylesheet" href="/css/custom.css">

View File

@ -0,0 +1,36 @@
{{ define "main" }}
<div class="projects-page">
<header>
<h1 class="post-title">{{ .Title }}</h1>
{{ with .Content }}
<div class="page-intro">{{ . }}</div>
{{ end }}
</header>
<div class="project-grid">
{{ range .Pages }}
<div class="project-card">
<h2 class="project-card__title">
<a href="{{ .Permalink }}">{{ .Title }}</a>
</h2>
{{ with .Params.description }}
<p class="project-card__desc">{{ . }}</p>
{{ end }}
{{ with .Params.tags }}
<ul class="project-tags">
{{ range . }}<li>{{ . }}</li>{{ end }}
</ul>
{{ end }}
<div class="project-card__links">
{{ with .Params.github }}<a href="{{ . }}" target="_blank" rel="noopener">GitHub →</a>{{ end }}
{{ with .Params.url }}<a href="{{ . }}" target="_blank" rel="noopener">Live →</a>{{ end }}
<a href="{{ .Permalink }}">Details →</a>
</div>
</div>
{{ end }}
</div>
</div>
{{ end }}

View File

@ -0,0 +1,32 @@
{{ define "main" }}
<article class="post">
<header class="project-header">
<h1 class="post-title">{{ .Title }}</h1>
{{ with .Params.description }}
<p class="project-description">{{ . }}</p>
{{ end }}
{{ with .Params.tags }}
<ul class="project-tags">
{{ range . }}<li>{{ . }}</li>{{ end }}
</ul>
{{ end }}
{{ if or .Params.github .Params.url }}
<div class="project-links">
{{ with .Params.github }}<a href="{{ . }}" target="_blank" rel="noopener">GitHub →</a>{{ end }}
{{ with .Params.url }}<a href="{{ . }}" target="_blank" rel="noopener">Live Site →</a>{{ end }}
</div>
{{ end }}
</header>
<div class="post-content">
{{ .Content }}
</div>
<footer class="post-footer">
<a href="/projects">← Back to Projects</a>
</footer>
</article>
{{ end }}

216
static/css/custom.css Normal file
View File

@ -0,0 +1,216 @@
/* ─── CV page ─────────────────────────────────────────────────────────────── */
.cv-download {
margin-bottom: 2rem;
}
.cv-download-btn {
display: inline-block;
padding: 0.4rem 1rem;
border: 1px solid var(--accent);
color: var(--accent);
text-decoration: none;
font-size: 0.9rem;
letter-spacing: 0.05em;
transition: background 0.15s, color 0.15s;
}
.cv-download-btn:hover {
background: var(--accent);
color: var(--background);
}
/* ─── Projects list ────────────────────────────────────────────────────────── */
.projects-page .page-intro {
margin-bottom: 2.5rem;
color: var(--color);
opacity: 0.8;
}
.project-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.project-card {
border: 1px solid var(--border-color);
padding: 1.25rem 1.5rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.project-card__title {
margin: 0;
font-size: 1.1rem;
}
.project-card__title a {
color: var(--accent);
text-decoration: none;
}
.project-card__title a:hover {
text-decoration: underline;
}
.project-card__desc {
margin: 0;
font-size: 0.9rem;
opacity: 0.85;
flex: 1;
}
/* ─── Tags (shared by list and detail) ────────────────────────────────────── */
.project-tags {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.project-tags li {
font-size: 0.75rem;
padding: 0.15rem 0.5rem;
border: 1px solid var(--border-color);
opacity: 0.75;
}
/* ─── Project links (shared) ───────────────────────────────────────────────── */
.project-card__links,
.project-links {
display: flex;
flex-wrap: wrap;
gap: 1rem;
font-size: 0.85rem;
}
.project-card__links a,
.project-links a {
color: var(--accent);
text-decoration: none;
}
.project-card__links a:hover,
.project-links a:hover {
text-decoration: underline;
}
/* ─── Project detail page ──────────────────────────────────────────────────── */
.project-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
.project-description {
font-size: 1rem;
opacity: 0.85;
margin: 0.5rem 0 1rem;
}
.post-footer {
margin-top: 3rem;
padding-top: 1rem;
border-top: 1px solid var(--border-color);
font-size: 0.9rem;
}
.post-footer a {
color: var(--accent);
text-decoration: none;
}
.post-footer a:hover {
text-decoration: underline;
}
/* ─── Homepage ─────────────────────────────────────────────────────────────── */
.index-hero {
margin-bottom: 2.5rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--border-color);
}
.index-intro {
margin: 0 0 1.5rem;
font-size: 1rem;
line-height: 1.6;
opacity: 0.85;
}
.index-nav {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5rem 2rem;
}
.index-nav li {
font-size: 0.9rem;
}
.index-nav .prompt {
opacity: 0.5;
margin-right: 0.4rem;
}
.index-nav a {
color: var(--accent);
text-decoration: none;
}
.index-nav a:hover {
text-decoration: underline;
}
.index-posts h2 {
margin-bottom: 1rem;
}
.post-preview {
display: flex;
align-items: baseline;
gap: 1rem;
margin-bottom: 0.6rem;
}
.post-preview time {
font-size: 0.8rem;
opacity: 0.5;
flex-shrink: 0;
}
.post-preview a {
color: var(--color);
text-decoration: none;
}
.post-preview a:hover {
color: var(--accent);
}
.index-all-posts {
margin-top: 1.25rem;
font-size: 0.9rem;
}
.index-all-posts a {
color: var(--accent);
text-decoration: none;
}
.index-all-posts a:hover {
text-decoration: underline;
}

1
themes/terminal Submodule

@ -0,0 +1 @@
Subproject commit 5a2b4c0f1fdb9180d525930b2c8f68a90221d245