diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..584d298 --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -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 }} diff --git a/.gitignore b/.gitignore index 2eea525..a460014 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -.env \ No newline at end of file +.env +.hugo_build.lock +public/ +resources/_gen/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a6851ba --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "themes/terminal"] + path = themes/terminal + url = https://github.com/panr/hugo-theme-terminal.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8bb4f65 --- /dev/null +++ b/Dockerfile @@ -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 diff --git a/content/cv.md b/content/cv.md new file mode 100644 index 0000000..5379963 --- /dev/null +++ b/content/cv.md @@ -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 (2020–2021), 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) \ No newline at end of file diff --git a/content/posts/hello-world.md b/content/posts/hello-world.md new file mode 100644 index 0000000..b578b96 --- /dev/null +++ b/content/posts/hello-world.md @@ -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. diff --git a/content/projects/_index.md b/content/projects/_index.md new file mode 100644 index 0000000..e99a978 --- /dev/null +++ b/content/projects/_index.md @@ -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. diff --git a/content/projects/homelab.md b/content/projects/homelab.md new file mode 100644 index 0000000..86b4ccc --- /dev/null +++ b/content/projects/homelab.md @@ -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. \ No newline at end of file diff --git a/hugo.toml b/hugo.toml new file mode 100644 index 0000000..735c3b4 --- /dev/null +++ b/hugo.toml @@ -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 diff --git a/layouts/cv/single.html b/layouts/cv/single.html new file mode 100644 index 0000000..6336b61 --- /dev/null +++ b/layouts/cv/single.html @@ -0,0 +1,10 @@ +{{ define "main" }} +
+
+ ↓ Download PDF +
+
+ {{ .Content }} +
+
+{{ end }} diff --git a/layouts/index.html b/layouts/index.html new file mode 100644 index 0000000..6445f2c --- /dev/null +++ b/layouts/index.html @@ -0,0 +1,38 @@ +{{ define "main" }} +
+ + {{/* ── Hero / intro ───────────────────────────────────────────────────── */}} +
+

+ Backend engineer based in Tokyo. I build distributed systems, + operate Kubernetes infrastructure, and occasionally write about it. +

+ +
+ + {{/* ── Recent posts ────────────────────────────────────────────────────── */}} + {{ $posts := where .Site.RegularPages "Type" .Site.Params.contentTypeName }} + {{ if $posts }} +
+

Recent Posts

+ {{ range first 5 $posts }} + + {{ end }} + {{ if gt (len $posts) 5 }} +

All posts →

+ {{ end }} +
+ {{ end }} + +
+{{ end }} diff --git a/layouts/partials/extended_head.html b/layouts/partials/extended_head.html new file mode 100644 index 0000000..7c12a5a --- /dev/null +++ b/layouts/partials/extended_head.html @@ -0,0 +1 @@ + diff --git a/layouts/projects/list.html b/layouts/projects/list.html new file mode 100644 index 0000000..6de02ee --- /dev/null +++ b/layouts/projects/list.html @@ -0,0 +1,36 @@ +{{ define "main" }} +
+
+

{{ .Title }}

+ {{ with .Content }} +
{{ . }}
+ {{ end }} +
+ +
+ {{ range .Pages }} +
+

+ {{ .Title }} +

+ + {{ with .Params.description }} +

{{ . }}

+ {{ end }} + + {{ with .Params.tags }} +
    + {{ range . }}
  • {{ . }}
  • {{ end }} +
+ {{ end }} + + +
+ {{ end }} +
+
+{{ end }} diff --git a/layouts/projects/single.html b/layouts/projects/single.html new file mode 100644 index 0000000..c5ae774 --- /dev/null +++ b/layouts/projects/single.html @@ -0,0 +1,32 @@ +{{ define "main" }} +
+
+

{{ .Title }}

+ + {{ with .Params.description }} +

{{ . }}

+ {{ end }} + + {{ with .Params.tags }} + + {{ end }} + + {{ if or .Params.github .Params.url }} + + {{ end }} +
+ +
+ {{ .Content }} +
+ + +
+{{ end }} diff --git a/static/css/custom.css b/static/css/custom.css new file mode 100644 index 0000000..848b28a --- /dev/null +++ b/static/css/custom.css @@ -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; +} diff --git a/themes/terminal b/themes/terminal new file mode 160000 index 0000000..5a2b4c0 --- /dev/null +++ b/themes/terminal @@ -0,0 +1 @@ +Subproject commit 5a2b4c0f1fdb9180d525930b2c8f68a90221d245