Initial commit — Hugo source, Dockerfile, Woodpecker pipeline

Modernized kms.viktorbarzin.me reference page covering every Windows
+ Office Volume License GVLK Microsoft publishes, plus activation
snippets, ODT config, and bootstrap script links.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-05-07 23:13:25 +00:00
commit 3fc75b636a
12 changed files with 1349 additions and 0 deletions

11
.dockerignore Normal file
View file

@ -0,0 +1,11 @@
.git
.gitignore
.dockerignore
.woodpecker.yml
.woodpecker/
README.md
public/
resources/
.hugo_build.lock
node_modules/
*.log

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
public/
resources/
.hugo_build.lock
node_modules/
*.log

39
.woodpecker.yml Normal file
View file

@ -0,0 +1,39 @@
when:
event: [push, manual]
clone:
git:
image: woodpeckerci/plugin-git
settings:
attempts: 5
backoff: 10s
steps:
- name: build-and-push
image: woodpeckerci/plugin-docker-buildx
settings:
repo:
- forgejo.viktorbarzin.me/viktor/kms-website
logins:
- registry: forgejo.viktorbarzin.me
username:
from_secret: forgejo_user
password:
from_secret: forgejo_push_token
dockerfile: Dockerfile
context: .
auto_tag: true
platforms:
- linux/amd64
tags:
- "latest"
- "${CI_COMMIT_SHA:0:8}"
- name: deploy
image: bitnami/kubectl:latest
when:
branch: master
event: [push, manual]
commands:
- "kubectl set image deployment/kms-web-page kms-web-page=forgejo.viktorbarzin.me/viktor/kms-website:${CI_COMMIT_SHA:0:8} -n kms"
- "kubectl rollout status deployment/kms-web-page -n kms --timeout=300s"

20
Dockerfile Normal file
View file

@ -0,0 +1,20 @@
# syntax=docker/dockerfile:1.6
ARG HUGO_VERSION=0.139.0
ARG NGINX_VERSION=1.27-alpine
FROM klakegg/hugo:${HUGO_VERSION}-ext-alpine AS build
WORKDIR /src
COPY . .
RUN hugo --minify --gc --destination /out
FROM nginx:${NGINX_VERSION}
LABEL org.opencontainers.image.source="https://forgejo.viktorbarzin.me/viktor/kms-website" \
org.opencontainers.image.description="kms.viktorbarzin.me — KMS activation reference"
# Strip default config so the bundled simple one stays
RUN rm -f /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /out /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://127.0.0.1/ >/dev/null 2>&1 || exit 1

29
README.md Normal file
View file

@ -0,0 +1,29 @@
# kms-website
Source for [kms.viktorbarzin.me](https://kms.viktorbarzin.me) — a single-page reference for activating
Microsoft Volume License products against the home-lab KMS host (`kms.viktorbarzin.lan` / `kms.viktorbarzin.me`).
## Stack
- **Hugo** static site (one custom layout, single `_index`)
- **YAML data file** at `data/products.yaml` is the source of truth for all GVLK tables
- **nginx:alpine** Docker image (multi-stage build via Hugo)
- **Woodpecker CI** builds + pushes to `forgejo.viktorbarzin.me/viktor/kms-website`
and rolls the `kms-web-page` Deployment in the `kms` namespace
- **Terraform** in `infra/stacks/kms` consumes the image (`var.image_tag`)
## Local dev
```sh
hugo server -D
# → http://localhost:1313
```
## Update GVLKs
Edit `data/products.yaml`. Push. CI rebuilds and rolls.
Sources of truth for keys:
- Windows: <https://learn.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys>
- Office / Project / Visio: <https://learn.microsoft.com/en-us/deployoffice/vlactivation/gvlks>

3
content/_index.md Normal file
View file

@ -0,0 +1,3 @@
---
title: "KMS Activation"
---

568
data/products.yaml Normal file
View file

@ -0,0 +1,568 @@
windows:
# Windows 11 / Windows 10 — desktop SKUs (modern)
- family: "Windows 11 / 10"
edition: "Pro"
gvlk: "W269N-WFGWX-YVC9B-4J6C9-T83GX"
notes: "Same GVLK for Win11 and Win10 Pro."
current: true
- family: "Windows 11 / 10"
edition: "Pro N"
gvlk: "MH37W-N47XK-V7XM9-C7227-GCQG9"
notes: "EU 'N' edition (no media pack)."
current: true
- family: "Windows 11 / 10"
edition: "Pro for Workstations"
gvlk: "NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84J"
current: true
- family: "Windows 11 / 10"
edition: "Pro for Workstations N"
gvlk: "9FNHH-K3HBT-3W4TD-6383H-6XYWF"
current: true
- family: "Windows 11 / 10"
edition: "Pro Education"
gvlk: "6TP4R-GNPTD-KYYHQ-7B7DP-J447Y"
current: true
- family: "Windows 11 / 10"
edition: "Pro Education N"
gvlk: "YVWGF-BXNMC-HTQYQ-CPQ99-66QFC"
current: true
- family: "Windows 11 / 10"
edition: "Education"
gvlk: "NW6C2-QMPVW-D7KKK-3GKT6-VCFB2"
current: true
- family: "Windows 11 / 10"
edition: "Education N"
gvlk: "2WH4N-8QGBV-H22JP-CT43Q-MDWWJ"
current: true
- family: "Windows 11 / 10"
edition: "Enterprise"
gvlk: "NPPR9-FWDCX-D2C8J-H872K-2YT43"
current: true
- family: "Windows 11 / 10"
edition: "Enterprise N"
gvlk: "DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4"
current: true
- family: "Windows 11 / 10"
edition: "Enterprise G"
gvlk: "YYVX9-NTFWV-6MDM3-9PT4T-4M68B"
notes: "German government SKU."
current: true
- family: "Windows 11 / 10"
edition: "Enterprise G N"
gvlk: "44RPN-FTY23-9VTTB-MP9BX-T84FV"
current: true
# LTSC editions
- family: "Windows LTSC"
edition: "Enterprise LTSC 2024 / 2021 / 2019"
gvlk: "M7XTQ-FN8P6-TTKYV-9D4CC-J462D"
notes: "Single GVLK across Win11 LTSC 2024 and Win10 LTSC 2021/2019."
current: true
- family: "Windows LTSC"
edition: "Enterprise N LTSC 2024 / 2021 / 2019"
gvlk: "92NFX-8DJQP-P6BBQ-THF9C-7CG2H"
current: true
- family: "Windows LTSC"
edition: "IoT Enterprise LTSC 2024 / 2021"
gvlk: "KBN8V-HFGQ4-MGXVD-347P6-PDQGT"
notes: "Same GVLK for x64 and ARM64 IoT."
current: true
- family: "Windows LTSC"
edition: "Enterprise LTSB 2016"
gvlk: "DCPHK-NFMTC-H88MJ-PFHPY-QJ4BJ"
notes: "Out of mainstream support; security updates ended Oct 2026."
- family: "Windows LTSC"
edition: "Enterprise N LTSB 2016"
gvlk: "QFFDN-GRT3P-VKWWX-X7T3R-8B639"
- family: "Windows LTSC"
edition: "Enterprise LTSB 2015"
gvlk: "WNMTR-4C88C-JK8YV-HQ7T2-76DF9"
notes: "End of support reached."
- family: "Windows LTSC"
edition: "Enterprise N LTSB 2015"
gvlk: "2F77B-TNFGY-69QQF-B8YKP-D69TJ"
# Legacy desktop
- family: "Legacy desktop"
edition: "Windows 8.1 Pro"
gvlk: "GCRJD-8NW9H-F2CDX-CCM8D-9D6T9"
notes: "Out of support since Jan 2023."
- family: "Legacy desktop"
edition: "Windows 8.1 Pro N"
gvlk: "HMCNV-VVBFX-7HMBH-CTY9B-B4FXY"
- family: "Legacy desktop"
edition: "Windows 8.1 Enterprise"
gvlk: "MHF9N-XY6XB-WVXMC-BTDCT-MKKG7"
- family: "Legacy desktop"
edition: "Windows 8.1 Enterprise N"
gvlk: "TT4HM-HN7YT-62K67-RGRQJ-JFFXW"
- family: "Legacy desktop"
edition: "Windows 8 Pro"
gvlk: "NG4HW-VH26C-733KW-K6F98-J8CK4"
notes: "EOL Jan 2016."
- family: "Legacy desktop"
edition: "Windows 8 Pro N"
gvlk: "XCVCF-2NXM9-723PB-MHCB7-2RYQQ"
- family: "Legacy desktop"
edition: "Windows 8 Enterprise"
gvlk: "32JNW-9KQ84-P47T8-D8GGY-CWCK7"
- family: "Legacy desktop"
edition: "Windows 8 Enterprise N"
gvlk: "JMNMF-RHW7P-DMY6X-RF3DR-X2BQT"
- family: "Legacy desktop"
edition: "Windows 7 Professional"
gvlk: "FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4"
notes: "EOL Jan 2020 (ESU through 2023)."
- family: "Legacy desktop"
edition: "Windows 7 Professional N"
gvlk: "MRPKT-YTG23-K7D7T-X2JMM-QY7MG"
- family: "Legacy desktop"
edition: "Windows 7 Professional E"
gvlk: "W82YF-2Q76Y-63HXB-FGJG9-GF7QX"
- family: "Legacy desktop"
edition: "Windows 7 Enterprise"
gvlk: "33PXH-7Y6KF-2VJC9-XBBR8-HVTHH"
- family: "Legacy desktop"
edition: "Windows 7 Enterprise N"
gvlk: "YDRBP-3D83W-TY26F-D46B2-XCKRJ"
- family: "Legacy desktop"
edition: "Windows 7 Enterprise E"
gvlk: "C29WB-22CC8-VJ326-GHFJW-H9DH4"
notes: "Win7 Ultimate is NOT KMS-activatable — not a Volume License SKU."
- family: "Legacy desktop"
edition: "Windows Vista Business"
gvlk: "YFKBB-PQJJV-G996G-VWGXY-2V3X8"
notes: "EOL Apr 2017."
- family: "Legacy desktop"
edition: "Windows Vista Business N"
gvlk: "HMBQG-8H2RH-C77VX-27R82-VMQBT"
- family: "Legacy desktop"
edition: "Windows Vista Enterprise"
gvlk: "VKK3X-68KWM-X2YGT-QR4M6-4BWMV"
- family: "Legacy desktop"
edition: "Windows Vista Enterprise N"
gvlk: "VTC42-BM838-43QHV-84HX6-XJXKV"
windows_server:
- family: "Windows Server 2025"
edition: "Standard"
gvlk: "TVRH6-WHNXV-R9WG3-9XRFY-MY832"
current: true
- family: "Windows Server 2025"
edition: "Datacenter"
gvlk: "D764K-2NDRG-47T6Q-P8T8W-YP6DF"
current: true
- family: "Windows Server 2025"
edition: "Datacenter: Azure Edition"
gvlk: "XGN3F-F394H-FD2MY-PP6FD-8MCRC"
notes: "Azure Edition expects Azure Arc attestation; KMS may fail outside Azure."
current: true
- family: "Windows Server 2022"
edition: "Standard"
gvlk: "VDYBN-27WPP-V4HQT-9VMD4-VMK7H"
current: true
- family: "Windows Server 2022"
edition: "Datacenter"
gvlk: "WX4NM-KYWYW-QJJR4-XV3QB-6VM33"
current: true
- family: "Windows Server 2022"
edition: "Datacenter: Azure Edition"
gvlk: "NTBV8-9K7Q8-V27C6-M2BTV-KHMXV"
notes: "Azure Edition — Arc attestation needed for full activation outside Azure."
current: true
- family: "Windows Server 2019"
edition: "Standard"
gvlk: "N69G4-B89J2-4G8F4-WWYCC-J464C"
current: true
- family: "Windows Server 2019"
edition: "Datacenter"
gvlk: "WMDGN-G9PQG-XVVXX-R3X43-63DFG"
current: true
- family: "Windows Server 2019"
edition: "Essentials"
gvlk: "WVDHN-86M7X-466P6-VHXV7-YY726"
current: true
- family: "Windows Server 2016"
edition: "Standard"
gvlk: "WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY"
notes: "EOL Jan 2027."
- family: "Windows Server 2016"
edition: "Datacenter"
gvlk: "CB7KF-BWN84-R7R2Y-793K2-8XDDG"
- family: "Windows Server 2016"
edition: "Essentials"
gvlk: "JCKRF-N37P4-C2D82-9YXRT-4M63B"
- family: "Windows Server 2012 R2"
edition: "Standard"
gvlk: "D2N9P-3P6X9-2R39C-7RTCD-MDVJX"
notes: "EOL Oct 2023; ESU available."
- family: "Windows Server 2012 R2"
edition: "Datacenter"
gvlk: "W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9"
- family: "Windows Server 2012 R2"
edition: "Essentials"
gvlk: "KNC87-3J2TX-XB4WP-VCPJV-M4FWM"
- family: "Windows Server 2012"
edition: "Standard"
gvlk: "XC9B7-NBPP2-83J2H-RHMBY-92BT4"
- family: "Windows Server 2012"
edition: "Datacenter"
gvlk: "48HP8-DN98B-MYWDG-T2DCC-8W83P"
- family: "Windows Server 2012"
edition: "Essentials"
gvlk: "HTDQM-NBMMG-KGYDT-2DTKT-J2MPV"
- family: "Windows Server 2012"
edition: "MultiPoint Standard"
gvlk: "HM7DN-YVMH3-46JC3-XYTG7-CYQJJ"
- family: "Windows Server 2012"
edition: "MultiPoint Premium"
gvlk: "XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G"
- family: "Windows Server 2008 R2"
edition: "Standard"
gvlk: "YC6KT-GKW9T-YTKYR-T4X34-R7VHC"
notes: "EOL Jan 2020 (ESU 2023)."
- family: "Windows Server 2008 R2"
edition: "Enterprise"
gvlk: "489J6-VHDMP-X63PK-3K798-CPX3Y"
- family: "Windows Server 2008 R2"
edition: "Datacenter"
gvlk: "74YFP-3QFB3-KQT8W-PMXWJ-7M648"
- family: "Windows Server 2008 R2"
edition: "Web"
gvlk: "6TPJF-RBVHG-WBW2R-86QPH-6RTM4"
- family: "Windows Server 2008 R2"
edition: "HPC"
gvlk: "TT8MH-CG224-D3D7Q-498W2-9QCTX"
- family: "Windows Server 2008 R2"
edition: "Itanium"
gvlk: "GT63C-RJFQ3-4GMB6-BRFB9-CB83V"
- family: "Windows Server 2008"
edition: "Standard"
gvlk: "TM24T-X9RMF-VWXK6-X8JC9-BFGM2"
notes: "EOL Jan 2020."
- family: "Windows Server 2008"
edition: "Enterprise"
gvlk: "YQGMW-MPWTJ-34KDK-48M3W-X4Q6V"
- family: "Windows Server 2008"
edition: "Datacenter"
gvlk: "7M67G-PC374-GR742-YH8V4-TCBY3"
- family: "Windows Server 2008"
edition: "Web"
gvlk: "WYR28-R7TFJ-3X2YQ-YCY4H-M249D"
- family: "Windows Server 2008"
edition: "HPC"
gvlk: "RCTX3-KWVHP-BR6TB-RB6DM-6X7HP"
- family: "Windows Server 2008"
edition: "Itanium"
gvlk: "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"
office:
# Office LTSC 2024 — current
- family: "Office LTSC 2024"
product: "ProPlus2024Volume"
edition: "Office LTSC Professional Plus 2024"
gvlk: "XJ2XN-FW8RK-P4HMP-DKDBV-GCVGB"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "Standard2024Volume"
edition: "Office LTSC Standard 2024"
gvlk: "V28N4-JG22K-W66P8-VTMGK-H6HGR"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "ProjectPro2024Volume"
edition: "Project Professional 2024"
gvlk: "FQQ23-N4YCY-73HQ3-FM9WC-76HF4"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "ProjectStd2024Volume"
edition: "Project Standard 2024"
gvlk: "PD3TT-NTHQQ-VC7CY-MFXK3-G87F8"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "VisioPro2024Volume"
edition: "Visio LTSC Professional 2024"
gvlk: "B7TN8-FJ8V3-7QYCP-HQPMV-YY89G"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "VisioStd2024Volume"
edition: "Visio LTSC Standard 2024"
gvlk: "JMMVY-XFNQC-KK4HK-9H7R3-WQQTV"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "Access2024Volume"
edition: "Access LTSC 2024"
gvlk: "82FTR-NCHR7-W3944-MGRHM-JMCWD"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "Excel2024Volume"
edition: "Excel LTSC 2024"
gvlk: "F4DYN-89BP2-WQTWJ-GR8YC-CKGJG"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "Outlook2024Volume"
edition: "Outlook LTSC 2024"
gvlk: "D2F8D-N3Q3B-J28PV-X27HD-RJWB9"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "PowerPoint2024Volume"
edition: "PowerPoint LTSC 2024"
gvlk: "CW94N-K6GJH-9CTXY-MG2VC-FYCWP"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "Word2024Volume"
edition: "Word LTSC 2024"
gvlk: "MQ84N-7VYDM-FXV7C-6K7CC-VFW9J"
channel: "PerpetualVL2024"
current: true
- family: "Office LTSC 2024"
product: "SkypeforBusiness2024Volume"
edition: "Skype for Business LTSC 2024"
gvlk: "4NKHF-9HBQF-Q3B6C-7YV34-F64P3"
channel: "PerpetualVL2024"
current: true
# Office LTSC 2021
- family: "Office LTSC 2021"
product: "ProPlus2021Volume"
edition: "Office LTSC Professional Plus 2021"
gvlk: "FXYTK-NJJ8C-GB6DW-3DYQT-6F7TH"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "Standard2021Volume"
edition: "Office LTSC Standard 2021"
gvlk: "KDX7X-BNVR8-TXXGX-4Q7Y8-78VT3"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "ProjectPro2021Volume"
edition: "Project Professional 2021"
gvlk: "FTNWT-C6WBT-8HMGF-K9PRX-QV9H8"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "ProjectStd2021Volume"
edition: "Project Standard 2021"
gvlk: "J2JDC-NJCYY-9RGQ4-YXWMH-T3D4T"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "VisioPro2021Volume"
edition: "Visio LTSC Professional 2021"
gvlk: "KNH8D-FGHT4-T8RK3-CTDYJ-K2HT4"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "VisioStd2021Volume"
edition: "Visio LTSC Standard 2021"
gvlk: "MJVNY-BYWPY-CWV6J-2RKRT-4M8QG"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "Access2021Volume"
edition: "Access LTSC 2021"
gvlk: "WM8YG-YNGDD-4JHDC-PG3F4-FC4T4"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "Excel2021Volume"
edition: "Excel LTSC 2021"
gvlk: "NWG3X-87C9K-TC7YY-BC2G7-G6RVC"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "Outlook2021Volume"
edition: "Outlook LTSC 2021"
gvlk: "C9FM6-3N72F-HFJXB-TM3V9-T86R9"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "PowerPoint2021Volume"
edition: "PowerPoint LTSC 2021"
gvlk: "TY7XF-NFRBR-KJ44C-G83KF-GX27K"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "Word2021Volume"
edition: "Word LTSC 2021"
gvlk: "TN8H9-M34D3-Y64V9-TR72V-X79KV"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "Publisher2021Volume"
edition: "Publisher LTSC 2021"
gvlk: "2MW9D-N4BXM-9VBPG-Q7W6M-KFBGQ"
channel: "PerpetualVL2021"
- family: "Office LTSC 2021"
product: "SkypeforBusiness2021Volume"
edition: "Skype for Business LTSC 2021"
gvlk: "HWCXN-K3WBT-WJBKY-R8BD9-XK29P"
channel: "PerpetualVL2021"
# Office 2019
- family: "Office 2019"
product: "ProPlus2019Volume"
edition: "Office Professional Plus 2019"
gvlk: "NMMKJ-6RK4F-KMJVX-8D9MJ-6MWKP"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "Standard2019Volume"
edition: "Office Standard 2019"
gvlk: "6NWWJ-YQWMR-QKGCB-6TMB3-9D9HK"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "ProjectPro2019Volume"
edition: "Project Professional 2019"
gvlk: "B4NPR-3FKK7-T2MBV-FRQ4W-PKD2B"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "ProjectStd2019Volume"
edition: "Project Standard 2019"
gvlk: "C4F7P-NCP8C-6CQPT-MQHV9-JXD2M"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "VisioPro2019Volume"
edition: "Visio Professional 2019"
gvlk: "9BGNQ-K37YR-RQHF2-38RQ3-7VCBB"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "VisioStd2019Volume"
edition: "Visio Standard 2019"
gvlk: "7TQNQ-K3YQQ-3PFH7-CCPPM-X4VQ2"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "Access2019Volume"
edition: "Access 2019"
gvlk: "9N9PT-27V4Y-VJ2PD-YXFMF-YTFQT"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "Excel2019Volume"
edition: "Excel 2019"
gvlk: "TMJWT-YYNMB-3BKTF-644FC-RVXBD"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "Outlook2019Volume"
edition: "Outlook 2019"
gvlk: "7HD7K-N4PVK-BHBCQ-YWQRW-XW4VK"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "PowerPoint2019Volume"
edition: "PowerPoint 2019"
gvlk: "RRNCX-C64HY-W2MM7-MCH9G-TJHMQ"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "Publisher2019Volume"
edition: "Publisher 2019"
gvlk: "G2KWX-3NW6P-PY93R-JXK2T-C9Y9V"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "Word2019Volume"
edition: "Word 2019"
gvlk: "PBX3G-NWMT6-Q7XBW-PYJGG-WXD33"
channel: "PerpetualVL2019"
- family: "Office 2019"
product: "SkypeforBusiness2019Volume"
edition: "Skype for Business 2019"
gvlk: "NCJ33-JHBBY-HTK98-MYCV8-HMKHJ"
channel: "PerpetualVL2019"
# Office 2016
- family: "Office 2016"
product: "ProPlus2016Volume"
edition: "Office Professional Plus 2016"
gvlk: "XQNVK-8JYDB-WJ9W3-YJ8YR-WFG99"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "Standard2016Volume"
edition: "Office Standard 2016"
gvlk: "JNRGM-WHDWX-FJJG3-K47QV-DRTFM"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "ProjectPro2016Volume"
edition: "Project Professional 2016"
gvlk: "YG9NW-3K39V-2T3HJ-93F3Q-G83KT"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "ProjectStd2016Volume"
edition: "Project Standard 2016"
gvlk: "GNFHQ-F6YQM-KQDGJ-327XX-KQBVC"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "VisioPro2016Volume"
edition: "Visio Professional 2016"
gvlk: "PD3PC-RHNGV-FXJ29-8JK7D-RJRJK"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "VisioStd2016Volume"
edition: "Visio Standard 2016"
gvlk: "7WHWN-4T7MP-G96JF-G33KR-W8GF4"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "Access2016Volume"
edition: "Access 2016"
gvlk: "GNH9Y-D2J4T-FJHGG-QRVH7-QPFDW"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "Excel2016Volume"
edition: "Excel 2016"
gvlk: "9C2PK-NWTVB-JMPW8-BFT28-7FTBF"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "OneNote2016Volume"
edition: "OneNote 2016"
gvlk: "DR92N-9HTF2-97XKM-XW2WJ-XW3J6"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "Outlook2016Volume"
edition: "Outlook 2016"
gvlk: "R69KK-NTPKF-7M3Q4-QYBHW-6MT9B"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "PowerPoint2016Volume"
edition: "PowerPoint 2016"
gvlk: "J7MQP-HNJ4Y-WJ7YM-PFYGF-BY6C6"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "Publisher2016Volume"
edition: "Publisher 2016"
gvlk: "F47MM-N3XJP-TQXJ9-BP99D-8K837"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "Word2016Volume"
edition: "Word 2016"
gvlk: "WXY84-JN2Q9-RBCCQ-3Q3J3-3PFJ6"
channel: "MSI / VL Click-to-Run"
- family: "Office 2016"
product: "SkypeforBusiness2016Volume"
edition: "Skype for Business 2016"
gvlk: "869NQ-FJ69K-466HW-QYCP2-DDBV6"
channel: "MSI / VL Click-to-Run"
downloads:
odt:
label: "Office Deployment Tool (ODT)"
url: "https://www.microsoft.com/en-us/download/details.aspx?id=49117"
notes: "Single setup.exe covers Office 2016, 2019, LTSC 2021, LTSC 2024. Configuration.xml controls product + channel."
windows:
- label: "Windows 11 (consumer media tool)"
url: "https://www.microsoft.com/software-download/windows11"
- label: "Windows 11 Enterprise (eval ISO)"
url: "https://www.microsoft.com/en-us/evalcenter/download-windows-11-enterprise"
- label: "Windows 11 IoT Enterprise LTSC 2024 (eval)"
url: "https://www.microsoft.com/en-us/evalcenter/download-windows-11-iot-enterprise-ltsc-eval"
- label: "Windows 10 (consumer media tool)"
url: "https://www.microsoft.com/software-download/windows10"
- label: "Windows 10 Enterprise (eval)"
url: "https://www.microsoft.com/en-us/evalcenter/download-windows-10-enterprise"
- label: "Windows 10 Enterprise LTSC 2021 (eval)"
url: "https://www.microsoft.com/en-us/evalcenter/download-windows-10-enterprise-ltsc"
- label: "Windows Server 2025 (eval ISO)"
url: "https://www.microsoft.com/en-us/evalcenter/download-windows-server-2025"
- label: "Windows Server 2022 (eval ISO)"
url: "https://www.microsoft.com/en-us/evalcenter/download-windows-server-2022"
- label: "Windows Server 2019 (eval ISO)"
url: "https://www.microsoft.com/en-us/evalcenter/download-windows-server-2019"
- label: "Windows Server 2016 (eval ISO)"
url: "https://www.microsoft.com/en-us/evalcenter/download-windows-server-2016"
- label: "Volume Licensing Service Center (VLSC)"
url: "https://www.microsoft.com/Licensing/servicecenter/default.aspx"
notes: "Source for VL media not on the Eval Center (e.g. legacy LTSB, retail-channel signed VL builds)."

15
hugo.toml Normal file
View file

@ -0,0 +1,15 @@
baseURL = "https://kms.viktorbarzin.me/"
languageCode = "en-us"
title = "kms.viktorbarzin.me"
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "404"]
[params]
description = "Self-hosted KMS activation reference for Windows, Office, Project, Visio."
kmsHost = "kms.viktorbarzin.lan"
kmsHostExt = "kms.viktorbarzin.me"
kmsPort = 1688
bootstrapURL = "https://nas.viktorbarzin.lan/Emo%20shared/kms-bootstrap.ps1"
[markup]
[markup.goldmark.renderer]
unsafe = true

312
layouts/index.html Normal file
View file

@ -0,0 +1,312 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{ .Site.Params.description }}">
<meta name="color-scheme" content="dark light">
<title>{{ .Site.Title }} — KMS activation reference</title>
<link rel="stylesheet" href="{{ "css/style.css" | relURL }}">
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
</head>
<body>
<header class="site-header">
<div class="wrap">
<div class="brand">
<span class="logo"></span>
<div>
<h1>kms.viktorbarzin.me</h1>
<p class="tag">Self-hosted KMS for Windows · Office · Project · Visio</p>
</div>
</div>
<nav>
<a href="#quickstart">Quick start</a>
<a href="#windows">Windows</a>
<a href="#server">Server</a>
<a href="#office">Office</a>
<a href="#downloads">Downloads</a>
<a href="#faq">FAQ</a>
</nav>
</div>
</header>
<section class="hero">
<div class="wrap">
<h2>Activate Microsoft Volume License products in seconds.</h2>
<p class="lede">
One private KMS host, every supported edition. Auto-discovery via DNS SRV from inside the network,
manual <code>/skms</code> from outside. No keys to remember — just install a Volume License edition
and it activates itself.
</p>
<div class="kms-target">
<div class="kms-target-item">
<span class="muted">Internal LAN host</span>
<code class="copy" data-copy="{{ .Site.Params.kmsHost }}:{{ .Site.Params.kmsPort }}">{{ .Site.Params.kmsHost }}:{{ .Site.Params.kmsPort }}</code>
</div>
<div class="kms-target-item">
<span class="muted">External / off-LAN host</span>
<code class="copy" data-copy="{{ .Site.Params.kmsHostExt }}:{{ .Site.Params.kmsPort }}">{{ .Site.Params.kmsHostExt }}:{{ .Site.Params.kmsPort }}</code>
</div>
<div class="kms-target-item">
<span class="muted">Auto-discovery SRV</span>
<code class="copy" data-copy="_vlmcs._tcp.viktorbarzin.lan">_vlmcs._tcp.viktorbarzin.lan</code>
</div>
</div>
</div>
</section>
<main class="wrap">
<section id="quickstart">
<h2>Quick start — fully automatic on a new machine</h2>
<p>
On the home / lab network, a freshly-installed Volume License edition will activate itself
once Windows can find the SRV record. The bootstrap script below sets the Primary DNS Suffix,
optionally installs Office + Project (LTSC 2024 VL), triggers <code>slmgr /ato</code> + <code>ospp.vbs /act</code>,
and prompts for a single reboot.
</p>
<div class="card terminal">
<div class="card-head"><span>PowerShell (run as Administrator)</span><button class="btn-copy" data-copy-target="quickstart-cmd">Copy</button></div>
<pre id="quickstart-cmd"><code>powershell -ExecutionPolicy Bypass -File &quot;\\nas.viktorbarzin.lan\Emo shared\kms-bootstrap.ps1&quot;</code></pre>
</div>
<p class="hint">Off-LAN? Skip auto-discovery and pin the host explicitly:</p>
<div class="card terminal">
<div class="card-head"><span>cmd.exe — explicit KMS host</span><button class="btn-copy" data-copy-target="explicit-cmd">Copy</button></div>
<pre id="explicit-cmd"><code>slmgr /skms kms.viktorbarzin.me
slmgr /ato
cscript "C:\Program Files\Microsoft Office\Office16\ospp.vbs" /sethst:kms.viktorbarzin.me
cscript "C:\Program Files\Microsoft Office\Office16\ospp.vbs" /act</code></pre>
</div>
</section>
{{/* helper: build product family groupings */}}
{{ $windows := .Site.Data.products.windows }}
{{ $servers := .Site.Data.products.windows_server }}
{{ $office := .Site.Data.products.office }}
<section id="windows">
<h2>Windows desktop</h2>
<p class="muted">
Per-edition GVLKs. <strong>Volume License only</strong> — Home and retail SKUs cannot KMS-activate.
Click any key to copy.
</p>
{{ $byFamily := dict }}
{{ range $windows }}
{{ $f := .family }}
{{ $arr := index $byFamily $f | default slice }}
{{ $arr = $arr | append . }}
{{ $byFamily = merge $byFamily (dict $f $arr) }}
{{ end }}
{{ range $family, $items := $byFamily }}
<h3 class="family-head">{{ $family }}</h3>
<table class="key-table">
<thead><tr><th>Edition</th><th>GVLK</th><th class="notes-col">Notes</th></tr></thead>
<tbody>
{{ range $items }}
<tr{{ if .current }} class="current"{{ end }}>
<td>{{ .edition }}{{ if .current }} <span class="badge">current</span>{{ end }}</td>
<td><code class="copy" data-copy="{{ .gvlk }}">{{ .gvlk }}</code></td>
<td class="notes-col">{{ with .notes }}{{ . }}{{ else }}—{{ end }}</td>
</tr>
{{ end }}
</tbody>
</table>
{{ end }}
<details class="card">
<summary>How to use a GVLK on Windows</summary>
<pre><code>:: open cmd.exe as Administrator
slmgr /upk :: remove existing key
slmgr /ipk &lt;PASTE-GVLK-HERE&gt; :: install GVLK
slmgr /skms kms.viktorbarzin.me :: optional: skip auto-discovery
slmgr /ato :: activate
slmgr /dlv :: verify (look for &quot;LICENSED&quot;)</code></pre>
</details>
</section>
<section id="server">
<h2>Windows Server</h2>
<p class="muted">Server SKUs need only 5 unique clients to satisfy the KMS activation count threshold (vs. 25 for desktop).</p>
{{ $byFamily := dict }}
{{ range $servers }}
{{ $f := .family }}
{{ $arr := index $byFamily $f | default slice }}
{{ $arr = $arr | append . }}
{{ $byFamily = merge $byFamily (dict $f $arr) }}
{{ end }}
{{ range $family, $items := $byFamily }}
<h3 class="family-head">{{ $family }}</h3>
<table class="key-table">
<thead><tr><th>Edition</th><th>GVLK</th><th class="notes-col">Notes</th></tr></thead>
<tbody>
{{ range $items }}
<tr{{ if .current }} class="current"{{ end }}>
<td>{{ .edition }}{{ if .current }} <span class="badge">current</span>{{ end }}</td>
<td><code class="copy" data-copy="{{ .gvlk }}">{{ .gvlk }}</code></td>
<td class="notes-col">{{ with .notes }}{{ . }}{{ else }}—{{ end }}</td>
</tr>
{{ end }}
</tbody>
</table>
{{ end }}
</section>
<section id="office">
<h2>Office · Project · Visio</h2>
<p class="muted">
All listed editions are Volume License. Install via the
<a href="#downloads">Office Deployment Tool</a> with the matching <code>Configuration.xml</code> below
(use <code>Channel</code> shown in each row), or manually swap a retail key with
<code>ospp.vbs /unpkey:&lt;LAST5&gt;</code> + <code>/inpkey:&lt;GVLK&gt;</code> and <code>/act</code>.
</p>
{{ $byFamily := dict }}
{{ range $office }}
{{ $f := .family }}
{{ $arr := index $byFamily $f | default slice }}
{{ $arr = $arr | append . }}
{{ $byFamily = merge $byFamily (dict $f $arr) }}
{{ end }}
{{ range $family, $items := $byFamily }}
<h3 class="family-head">{{ $family }}</h3>
<table class="key-table">
<thead><tr><th>Edition</th><th>Product ID</th><th>GVLK</th><th class="notes-col">Channel</th></tr></thead>
<tbody>
{{ range $items }}
<tr{{ if .current }} class="current"{{ end }}>
<td>{{ .edition }}{{ if .current }} <span class="badge">current</span>{{ end }}</td>
<td><code class="muted">{{ .product }}</code></td>
<td><code class="copy" data-copy="{{ .gvlk }}">{{ .gvlk }}</code></td>
<td class="notes-col">{{ .channel }}</td>
</tr>
{{ end }}
</tbody>
</table>
{{ end }}
<details class="card">
<summary>Sample Configuration.xml — Office Pro Plus 2024 + Project Pro 2024</summary>
<pre><code>&lt;Configuration&gt;
&lt;Add OfficeClientEdition=&quot;64&quot; Channel=&quot;PerpetualVL2024&quot;&gt;
&lt;Product ID=&quot;ProPlus2024Volume&quot; PIDKEY=&quot;XJ2XN-FW8RK-P4HMP-DKDBV-GCVGB&quot;&gt;
&lt;Language ID=&quot;en-us&quot;/&gt;
&lt;/Product&gt;
&lt;Product ID=&quot;ProjectPro2024Volume&quot; PIDKEY=&quot;FQQ23-N4YCY-73HQ3-FM9WC-76HF4&quot;&gt;
&lt;Language ID=&quot;en-us&quot;/&gt;
&lt;/Product&gt;
&lt;/Add&gt;
&lt;Updates Enabled=&quot;TRUE&quot; Channel=&quot;PerpetualVL2024&quot;/&gt;
&lt;Display Level=&quot;None&quot; AcceptEULA=&quot;TRUE&quot;/&gt;
&lt;Property Name=&quot;AUTOACTIVATE&quot; Value=&quot;1&quot;/&gt;
&lt;Property Name=&quot;FORCEAPPSHUTDOWN&quot; Value=&quot;TRUE&quot;/&gt;
&lt;/Configuration&gt;</code></pre>
</details>
<details class="card">
<summary>Activation — bare commands</summary>
<pre><code>cd "C:\Program Files\Microsoft Office\Office16"
cscript ospp.vbs /sethst:kms.viktorbarzin.me
cscript ospp.vbs /inpkey:&lt;PASTE-GVLK&gt;
cscript ospp.vbs /act
cscript ospp.vbs /dstatus :: verify --LICENSED--</code></pre>
</details>
</section>
<section id="downloads">
<h2>Downloads</h2>
<h3 class="family-head">Office Deployment Tool</h3>
<p>
Single binary that installs every Office VL edition listed above, driven by a
<code>Configuration.xml</code>.
</p>
<a class="dl" href="{{ .Site.Data.products.downloads.odt.url }}" target="_blank" rel="noopener">
{{ .Site.Data.products.downloads.odt.label }} ↗
</a>
<p class="hint">{{ .Site.Data.products.downloads.odt.notes }}</p>
<h3 class="family-head">Windows ISOs</h3>
<ul class="dl-list">
{{ range .Site.Data.products.downloads.windows }}
<li>
<a href="{{ .url }}" target="_blank" rel="noopener">{{ .label }} ↗</a>
{{ with .notes }}<span class="muted"> — {{ . }}</span>{{ end }}
</li>
{{ end }}
</ul>
<h3 class="family-head">Bootstrap script</h3>
<p>One-shot PowerShell that wires DNS suffix, Office install, and activation:</p>
<a class="dl" href="{{ .Site.Params.bootstrapURL }}" target="_blank" rel="noopener">
kms-bootstrap.ps1 ↗
</a>
</section>
<section id="faq">
<h2>FAQ &amp; gotchas</h2>
<div class="faq">
<details>
<summary>Why won't my retail Office activate?</summary>
<p>KMS only grants licenses to <strong>Volume License</strong> editions. Retail / OEM / Microsoft 365
keys reject KMS responses. Either reinstall as VL via ODT, or swap the key in place:
<code>cscript ospp.vbs /unpkey:&lt;LAST5&gt;</code> followed by
<code>cscript ospp.vbs /inpkey:&lt;GVLK&gt;</code> and <code>/act</code>.</p>
</details>
<details>
<summary>What about Windows Home?</summary>
<p>Home editions cannot be KMS-activated. Either upgrade to Pro/Enterprise/Education
(<code>DISM /Set-Edition</code> or in-place reinstall) or use a retail key.</p>
</details>
<details>
<summary>The first activation fails with 0xC004F038</summary>
<p>That's the KMS count threshold: 25 unique clients (or 5 servers) must contact the host before
desktop clients can activate. The host caches counts for 30 days. The vlmcsd implementation in
this server bypasses that — but real Microsoft KMS hosts enforce it. If you ever swap to a
hardware host you may need to pre-seed activations.</p>
</details>
<details>
<summary>Office LTSC vs Microsoft 365 — can they coexist?</summary>
<p>No. The Click-to-Run installer refuses side-by-side. Run
<code>setup.exe /configure Remove.xml</code> with <code>&lt;Remove All=&quot;TRUE&quot;/&gt;</code>
first, then install the VL edition.</p>
</details>
<details>
<summary>How does auto-discovery work?</summary>
<p>The Software Protection Service queries DNS for
<code>_vlmcs._tcp.&lt;primary-dns-suffix&gt;</code>. If the SRV resolves it connects to that
host on the returned port (1688 by default). On this network the suffix is
<code>viktorbarzin.lan</code> and the SRV resolves to <code>kms.viktorbarzin.lan:1688</code>.
Set the suffix once via System Properties → Computer Name → More → Primary DNS suffix.</p>
</details>
<details>
<summary>How long does a KMS license last?</summary>
<p>180 days. The client tries to renew every 7 days; one successful renewal resets the 180-day
clock. As long as a machine reaches the KMS once a quarter it stays activated indefinitely.</p>
</details>
<details>
<summary>Where can I see the GVLKs Microsoft publishes?</summary>
<p>Windows: <a href="https://learn.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys" target="_blank" rel="noopener">learn.microsoft.com/.../kms-client-activation-keys</a>.<br>
Office / Project / Visio: <a href="https://learn.microsoft.com/en-us/deployoffice/vlactivation/gvlks" target="_blank" rel="noopener">learn.microsoft.com/.../gvlks</a>.</p>
</details>
</div>
</section>
</main>
<footer>
<div class="wrap">
<p>
KMS host on this network · vlmcsd · Built with Hugo ·
<a href="https://forgejo.viktorbarzin.me/viktor/kms-website" target="_blank" rel="noopener">source</a>
</p>
</div>
</footer>
<script src="{{ "js/clipboard.js" | relURL }}"></script>
</body>
</html>

23
nginx.conf Normal file
View file

@ -0,0 +1,23 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Sensible static-file caching
location ~* \.(?:css|js|svg|png|jpg|jpeg|webp|woff2?)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
try_files $uri =404;
}
location / {
try_files $uri $uri/ $uri.html /index.html;
add_header Cache-Control "no-cache";
}
# Hide server token + minor security headers
server_tokens off;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

275
static/css/style.css Normal file
View file

@ -0,0 +1,275 @@
:root {
--bg: #0a0d12;
--surface: #11161e;
--surface-2: #161c26;
--border: #232a36;
--text: #e6ecf3;
--muted: #8a96a8;
--accent: #6ee7ff;
--accent-2: #a78bfa;
--good: #4ade80;
--warn: #fbbf24;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
--sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
--radius: 10px;
--shadow: 0 1px 0 rgba(255,255,255,0.04) inset, 0 24px 48px -24px rgba(0,0,0,0.6);
}
@media (prefers-color-scheme: light) {
:root {
--bg: #fafafa;
--surface: #ffffff;
--surface-2: #f3f5f8;
--border: #e3e8ef;
--text: #15181f;
--muted: #5b6573;
--accent: #0891b2;
--accent-2: #7c3aed;
--shadow: 0 1px 0 rgba(0,0,0,0.04) inset, 0 24px 48px -24px rgba(0,0,0,0.12);
}
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
font-family: var(--sans);
font-feature-settings: "cv11","ss01","ss03";
background: radial-gradient(1200px 600px at 50% -20%, rgba(110,231,255,0.08), transparent 60%), var(--bg);
color: var(--text);
line-height: 1.55;
-webkit-font-smoothing: antialiased;
}
@supports (font-variation-settings: normal) {
body { font-family: "Inter var", "Inter", system-ui, sans-serif; }
}
.wrap { max-width: 1100px; margin: 0 auto; padding: 0 24px; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code { font-family: var(--mono); font-size: 0.9em; }
.muted { color: var(--muted); }
.hint { color: var(--muted); margin-top: 12px; }
/* HEADER ----- */
.site-header {
position: sticky; top: 0; z-index: 50;
backdrop-filter: blur(12px);
background: rgba(10,13,18,0.7);
border-bottom: 1px solid var(--border);
}
@media (prefers-color-scheme: light) {
.site-header { background: rgba(255,255,255,0.78); }
}
.site-header .wrap { display: flex; align-items: center; justify-content: space-between; padding: 14px 24px; gap: 24px; }
.brand { display: flex; align-items: center; gap: 14px; }
.brand .logo {
width: 40px; height: 40px; display: grid; place-items: center;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
color: #061018; border-radius: 10px; font-size: 20px; font-weight: 800;
}
.brand h1 { font-size: 17px; margin: 0; letter-spacing: -0.01em; font-weight: 700; }
.brand .tag { margin: 0; color: var(--muted); font-size: 13px; }
.site-header nav { display: flex; gap: 18px; flex-wrap: wrap; }
.site-header nav a { color: var(--text); font-size: 14px; opacity: 0.78; }
.site-header nav a:hover { opacity: 1; text-decoration: none; }
/* HERO ----- */
.hero { padding: 64px 0 32px; }
.hero h2 {
font-size: clamp(28px, 4vw, 44px);
letter-spacing: -0.02em;
font-weight: 800;
line-height: 1.1;
margin: 0 0 16px;
background: linear-gradient(120deg, var(--text) 30%, var(--accent) 80%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero .lede { font-size: 18px; max-width: 720px; color: var(--muted); margin: 0 0 32px; }
.kms-target { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; }
.kms-target-item {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 14px 16px;
display: flex; flex-direction: column; gap: 6px;
box-shadow: var(--shadow);
}
.kms-target-item .muted { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; }
.kms-target-item code { font-size: 14px; }
/* SECTIONS ----- */
main { padding-bottom: 96px; }
section { padding: 56px 0 8px; scroll-margin-top: 80px; }
section h2 {
font-size: 26px; margin: 0 0 8px;
letter-spacing: -0.01em; font-weight: 700;
}
section > p:first-of-type { margin-top: 0; }
.family-head {
margin: 32px 0 12px;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--muted);
font-weight: 600;
}
/* CARD / TERMINAL ----- */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
margin: 16px 0;
overflow: hidden;
box-shadow: var(--shadow);
}
.card-head {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 16px;
background: var(--surface-2);
border-bottom: 1px solid var(--border);
font-size: 12px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.card pre { margin: 0; padding: 18px 20px; overflow-x: auto; }
.card pre code { color: var(--text); font-size: 13.5px; line-height: 1.6; }
.card details summary, details summary {
cursor: pointer; padding: 14px 18px; font-weight: 600; user-select: none;
list-style: none;
}
details summary::-webkit-details-marker { display: none; }
details summary::before { content: "▸"; display: inline-block; margin-right: 10px; transform: translateY(-1px); transition: transform 150ms ease; color: var(--muted); }
details[open] summary::before { transform: rotate(90deg); }
details pre { margin: 0; padding: 0 18px 18px; overflow-x: auto; background: transparent; }
.btn-copy {
background: rgba(110,231,255,0.12);
border: 1px solid rgba(110,231,255,0.3);
color: var(--accent);
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
font-family: var(--sans);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
transition: background 120ms ease, transform 120ms ease;
}
.btn-copy:hover { background: rgba(110,231,255,0.22); }
.btn-copy:active { transform: scale(0.96); }
.btn-copy.copied { background: rgba(74,222,128,0.18); border-color: rgba(74,222,128,0.4); color: var(--good); }
/* TABLES ----- */
.key-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
margin: 0 0 12px;
box-shadow: var(--shadow);
}
.key-table th, .key-table td {
text-align: left;
padding: 10px 14px;
font-size: 14px;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
.key-table thead th {
background: var(--surface-2);
font-weight: 600;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
.key-table tbody tr:last-child td { border-bottom: 0; }
.key-table tr.current td:first-child { color: var(--text); }
.key-table .notes-col { color: var(--muted); font-size: 13px; max-width: 380px; }
.badge {
display: inline-block;
background: rgba(74,222,128,0.16);
color: var(--good);
font-size: 10px;
padding: 2px 8px;
border-radius: 999px;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 700;
margin-left: 8px;
vertical-align: middle;
}
/* COPY-CODE INLINE ----- */
code.copy {
cursor: pointer;
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
background: var(--surface-2);
border: 1px solid var(--border);
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
font-family: var(--mono);
user-select: all;
}
code.copy:hover { background: rgba(110,231,255,0.1); border-color: rgba(110,231,255,0.4); color: var(--accent); }
code.copy.copied { background: rgba(74,222,128,0.12); border-color: rgba(74,222,128,0.5); color: var(--good); }
/* DOWNLOADS ----- */
.dl {
display: inline-block;
padding: 10px 16px;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
color: #061018;
border-radius: 8px;
font-weight: 700;
font-size: 14px;
letter-spacing: 0.01em;
margin: 8px 0;
}
.dl:hover { text-decoration: none; filter: brightness(1.06); }
.dl-list { padding-left: 18px; line-height: 1.9; }
.dl-list li::marker { color: var(--accent); }
/* FAQ ----- */
.faq details {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
margin: 10px 0;
box-shadow: var(--shadow);
}
.faq details p { margin: 0 18px 16px; color: var(--muted); }
.faq details code { color: var(--text); }
/* FOOTER ----- */
footer {
border-top: 1px solid var(--border);
margin-top: 48px;
padding: 24px 0;
font-size: 13px;
color: var(--muted);
}
/* MOBILE ----- */
@media (max-width: 760px) {
.site-header .wrap { flex-direction: column; align-items: flex-start; gap: 8px; padding: 12px 18px; }
.site-header nav { gap: 10px; }
.hero { padding: 36px 0 16px; }
.key-table th:nth-child(3), .key-table td:nth-child(3) { display: none; }
}

49
static/js/clipboard.js Normal file
View file

@ -0,0 +1,49 @@
(function () {
"use strict";
function flash(el, label) {
var orig = el.dataset.orig || el.textContent;
el.dataset.orig = orig;
el.classList.add("copied");
if (el.tagName === "BUTTON") el.textContent = label;
setTimeout(function () {
el.classList.remove("copied");
if (el.tagName === "BUTTON") el.textContent = orig;
}, 1200);
}
async function copyText(t) {
if (navigator.clipboard && window.isSecureContext) {
try { await navigator.clipboard.writeText(t); return true; } catch (e) { /* fall through */ }
}
var ta = document.createElement("textarea");
ta.value = t;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.focus(); ta.select();
var ok = false;
try { ok = document.execCommand("copy"); } catch (e) { ok = false; }
document.body.removeChild(ta);
return ok;
}
// 1. inline <code class="copy" data-copy="...">
document.querySelectorAll("code.copy[data-copy]").forEach(function (el) {
el.addEventListener("click", async function () {
var ok = await copyText(el.dataset.copy);
if (ok) flash(el, "Copied");
});
});
// 2. button.btn-copy[data-copy-target=elementId]
document.querySelectorAll(".btn-copy[data-copy-target]").forEach(function (btn) {
btn.addEventListener("click", async function () {
var target = document.getElementById(btn.dataset.copyTarget);
if (!target) return;
var text = target.innerText.trim();
var ok = await copyText(text);
if (ok) flash(btn, "Copied!");
});
});
})();