[{"data":1,"prerenderedAt":730},["ShallowReactive",2],{"doc-\u002Fen\u002Farchitecture":3,"docnav-en":134},{"id":4,"title":5,"body":6,"description":125,"extension":126,"meta":127,"nav":128,"navigation":129,"path":130,"seo":131,"stem":132,"__hash__":133},"docs\u002Fen\u002Farchitecture.md","Architecture",{"type":7,"value":8,"toc":116},"minimark",[9,14,18,41,45,79,83,102,106,109,113],[10,11,13],"h2",{"id":12},"winget-source-api","winget Source API",[15,16,17],"p",{},"kvellman implements the winget Source REST contract exactly, so the winget CLI treats it like any\nother source:",[19,20,21,29,35],"ul",{},[22,23,24,28],"li",{},[25,26,27],"code",{},"GET \u002Fapi\u002F{siteToken}\u002Finformation"," — source metadata and supported versions.",[22,30,31,34],{},[25,32,33],{},"POST \u002Fapi\u002F{siteToken}\u002FmanifestSearch"," — full-text search.",[22,36,37,40],{},[25,38,39],{},"GET \u002Fapi\u002F{siteToken}\u002FpackageManifests\u002F{id}"," — manifest delivery with server-side resolution.",[10,42,44],{"id":43},"site-context","Site context",[15,46,47,48,52,53,56,57,60,61,64,65,68,69,68,72,68,75,78],{},"winget sends no custom headers, so site context is carried in the URL ",[49,50,51],"strong",{},"site token"," (primary),\nwith ",[49,54,55],{},"subnet"," and ",[49,58,59],{},"mTLS client certificate"," as additional signals. The resolved context drives\nserver-side ",[25,62,63],{},"InstallerUrl"," placeholders (",[25,66,67],{},"$REPO_URL",", ",[25,70,71],{},"$SITE",[25,73,74],{},"$LOCATION",[25,76,77],{},"$LANG",") — so the same\nmanifest can point at a site-local SMB\u002FDFS share without being forked.",[10,80,82],{"id":81},"manifests-overlays-storage","Manifests, overlays & storage",[15,84,85,86,89,90,93,94,97,98,101],{},"Manifests are versioned YAML, validated against the official winget JSON schemas on upload. Local\nedits are kept as an ",[49,87,88],{},"overlay"," on top of the untouched upstream, and per-package ",[49,91,92],{},"overlay\ntemplates"," make repeatable edits (e.g. an internal mirror URL with ",[25,95,96],{},"$VERSION","\u002F",[25,99,100],{},"$ARCH",") easy across\nversions. Installers can be mirrored to S3\u002FMinIO or SMB; hashes are recomputed on delivery.",[10,103,105],{"id":104},"edge-mirror-nodes","Edge \u002F mirror nodes",[15,107,108],{},"Edge nodes are thin caching proxies that serve the winget API + downloads locally and lazily fetch\nfrom the origin. They keep an offline search index, enroll via mTLS, and are centrally scoped\n(which packages, which installer architectures\u002Fscopes) and observable from the origin.",[10,110,112],{"id":111},"stack","Stack",[15,114,115],{},"Nuxt 4 (Vue 3 + Nitro), TypeScript, PostgreSQL + Drizzle ORM, S3-compatible storage, BullMQ\u002FRedis\nfor background jobs. On-premise only — fully self-hostable, including air-gapped operation.",{"title":117,"searchDepth":118,"depth":118,"links":119},"",2,[120,121,122,123,124],{"id":12,"depth":118,"text":13},{"id":43,"depth":118,"text":44},{"id":81,"depth":118,"text":82},{"id":104,"depth":118,"text":105},{"id":111,"depth":118,"text":112},"How the winget Source API, site context, mirroring and edge nodes fit together.","md",{},4,true,"\u002Fen\u002Farchitecture",{"title":5,"description":125},"en\u002Farchitecture","Eqw1cDRa0-CLXwr65192sIi5s3k47eNKWIDs5A9qIiE",[135,346,473,660],{"id":136,"title":137,"body":138,"description":340,"extension":126,"meta":341,"nav":174,"navigation":129,"path":342,"seo":343,"stem":344,"__hash__":345},"docs\u002Fen\u002Fgetting-started.md","Getting started",{"type":7,"value":139,"toc":333},[140,144,151,155,162,245,252,256,266,270,277,299,302,306,329],[10,141,143],{"id":142},"what-is-kvellman","What is kvellman?",[15,145,146,147,150],{},"kvellman is a self-hosted, ",[49,148,149],{},"winget-compatible"," package repository. You register it as an\nadditional winget source — the winget CLI queries it exactly like the official Microsoft source.\nNo client modifications, no custom installers.",[10,152,154],{"id":153},"_1-run-the-origin-docker","1. Run the origin (Docker)",[15,156,157,158,161],{},"The Community edition runs from a single ",[25,159,160],{},"docker compose"," stack (app + PostgreSQL). With a domain\npointing at your host and ports 80\u002F443 open:",[163,164,168],"pre",{"className":165,"code":166,"language":167,"meta":117,"style":117},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","git clone \u003Copen-core-repo> kvellman && cd kvellman\ncp .env.deploy.example .env.deploy   # set DOMAIN, NUXT_SESSION_PASSWORD, POSTGRES_PASSWORD\ndocker compose --env-file .env.deploy up -d\n","bash",[25,169,170,210,225],{"__ignoreMap":117},[171,172,175,179,183,187,190,194,197,200,203,207],"span",{"class":173,"line":174},"line",1,[171,176,178],{"class":177},"sBMFI","git",[171,180,182],{"class":181},"sfazB"," clone",[171,184,186],{"class":185},"sMK4o"," \u003C",[171,188,189],{"class":181},"open-core-rep",[171,191,193],{"class":192},"sTEyZ","o",[171,195,196],{"class":185},">",[171,198,199],{"class":181}," kvellman",[171,201,202],{"class":185}," &&",[171,204,206],{"class":205},"s2Zo4"," cd",[171,208,209],{"class":181}," kvellman\n",[171,211,212,215,218,221],{"class":173,"line":118},[171,213,214],{"class":177},"cp",[171,216,217],{"class":181}," .env.deploy.example",[171,219,220],{"class":181}," .env.deploy",[171,222,224],{"class":223},"sHwdD","   # set DOMAIN, NUXT_SESSION_PASSWORD, POSTGRES_PASSWORD\n",[171,226,228,231,234,237,239,242],{"class":173,"line":227},3,[171,229,230],{"class":177},"docker",[171,232,233],{"class":181}," compose",[171,235,236],{"class":181}," --env-file",[171,238,220],{"class":181},[171,240,241],{"class":181}," up",[171,243,244],{"class":181}," -d\n",[15,246,247,248,251],{},"Database migrations run automatically on start. Open ",[25,249,250],{},"https:\u002F\u002Fyour-domain"," to create the first\nadmin account.",[10,253,255],{"id":254},"_2-provide-a-package","2. Provide a package",[15,257,258,259,261,262,265],{},"In the web UI: create a ",[49,260,51],{}," (Admin → Site tokens), then ",[49,263,264],{},"upload a manifest"," or import\none from the upstream winget catalog. Optionally store the installer locally so it is served from\nyour origin.",[10,267,269],{"id":268},"_3-add-the-source-on-a-client","3. Add the source on a client",[15,271,272,273,276],{},"winget requires ",[49,274,275],{},"HTTPS"," for a REST source. On a Windows client (PowerShell):",[163,278,282],{"className":279,"code":280,"language":281,"meta":117,"style":117},"language-powershell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","winget source add --name kvellman --type Microsoft.Rest --arg \"https:\u002F\u002Fyour-domain\u002Fapi\u002F\u003Csite-token>\"\nwinget search --source kvellman \u003Cterm>\nwinget install --source kvellman \u003CPackage.Identifier>\n","powershell",[25,283,284,289,294],{"__ignoreMap":117},[171,285,286],{"class":173,"line":174},[171,287,288],{},"winget source add --name kvellman --type Microsoft.Rest --arg \"https:\u002F\u002Fyour-domain\u002Fapi\u002F\u003Csite-token>\"\n",[171,290,291],{"class":173,"line":118},[171,292,293],{},"winget search --source kvellman \u003Cterm>\n",[171,295,296],{"class":173,"line":227},[171,297,298],{},"winget install --source kvellman \u003CPackage.Identifier>\n",[15,300,301],{},"That's it — clients now install internal software through your own winget source.",[10,303,305],{"id":304},"next-steps","Next steps",[19,307,308,316,322],{},[22,309,310,315],{},[311,312,314],"a",{"href":313},"\u002Fdocs\u002Fdeployment","Deployment"," — Docker, reverse proxies (Caddy\u002FTraefik), edge nodes, TLS.",[22,317,318,321],{},[311,319,5],{"href":320},"\u002Fdocs\u002Farchitecture"," — how the winget API, site tokens and mirroring fit together.",[22,323,324,328],{},[311,325,327],{"href":326},"\u002Fdocs\u002Feditions-licensing","Editions & licensing"," — Community vs Enterprise.",[330,331,332],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":117,"searchDepth":118,"depth":118,"links":334},[335,336,337,338,339],{"id":142,"depth":118,"text":143},{"id":153,"depth":118,"text":154},{"id":254,"depth":118,"text":255},{"id":268,"depth":118,"text":269},{"id":304,"depth":118,"text":305},"Run a kvellman origin and add it as a winget source in minutes.",{},"\u002Fen\u002Fgetting-started",{"title":137,"description":340},"en\u002Fgetting-started","kK4dTunMMifwqdzNjiI5Y6UPFNawYYWerxVCiGSe-fI",{"id":347,"title":314,"body":348,"description":467,"extension":126,"meta":468,"nav":118,"navigation":129,"path":469,"seo":470,"stem":471,"__hash__":472},"docs\u002Fen\u002Fdeployment.md",{"type":7,"value":349,"toc":461},[350,354,365,369,372,393,423,430,434,441,445,448,458],[10,351,353],{"id":352},"winget-requires-https","winget requires HTTPS",[15,355,356,357,360,361,364],{},"A winget REST source must be served over ",[49,358,359],{},"HTTPS with a trusted certificate"," (this also applies to\n",[25,362,363],{},"localhost"," on recent winget versions). Plan TLS for every endpoint clients talk to.",[10,366,368],{"id":367},"origin-via-docker","Origin via Docker",[15,370,371],{},"The Community stack is app + PostgreSQL. Two reverse-proxy options ship out of the box:",[19,373,374,384],{},[22,375,376,379,380,383],{},[49,377,378],{},"Caddy"," (",[25,381,382],{},"docker-compose.yml",") — automatic Let's Encrypt for a public domain.",[22,385,386,379,389,392],{},[49,387,388],{},"Existing Traefik",[25,390,391],{},"docker-compose.traefik.yml",") — no Caddy; the app joins Traefik's network\nand is routed by labels. Use this when port 80\u002F443 is already taken by Traefik.",[163,394,396],{"className":165,"code":395,"language":167,"meta":117,"style":117},"# behind an existing Traefik:\ndocker compose -f docker-compose.traefik.yml --env-file .env.deploy up -d\n",[25,397,398,403],{"__ignoreMap":117},[171,399,400],{"class":173,"line":174},[171,401,402],{"class":223},"# behind an existing Traefik:\n",[171,404,405,407,409,412,415,417,419,421],{"class":173,"line":118},[171,406,230],{"class":177},[171,408,233],{"class":181},[171,410,411],{"class":181}," -f",[171,413,414],{"class":181}," docker-compose.traefik.yml",[171,416,236],{"class":181},[171,418,220],{"class":181},[171,420,241],{"class":181},[171,422,244],{"class":181},[15,424,425,426,429],{},"Set ",[25,427,428],{},"KVELLMAN_IMAGE"," to a prebuilt image (built elsewhere and pushed to a registry) to avoid\nbuilding on a small server; migrations run automatically at container start.",[10,431,433],{"id":432},"edge-mirror-nodes-enterprise","Edge \u002F mirror nodes (Enterprise)",[15,435,436,437,440],{},"A site can run a headless ",[49,438,439],{},"edge node"," that serves the winget API and installer downloads locally,\ncaching from the origin on demand. Clients add the node as their source; the first install pulls\nthrough the node, the rest hit the LAN. Nodes enroll with the origin (one-time key → durable mTLS\nidentity) and are centrally controlled (package scope, installer filter, push\u002Fpre-stage, health).",[10,442,444],{"id":443},"telemetry-toggles","Telemetry & toggles",[15,446,447],{},"Repository-usage telemetry (searches, manifest fetches, downloads) is off by default and enabled via\nenvironment. The same pattern applies to the upstream-catalog sync and a GitHub token for imports.",[449,450,451],"blockquote",{},[15,452,453,454,457],{},"For the full, step-by-step guide (origin, edge node under WSL, TLS for LAN\u002Flocalhost, the registry\nworkflow) see ",[25,455,456],{},"DEPLOYMENT.md"," in the open-core repository.",[330,459,460],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":117,"searchDepth":118,"depth":118,"links":462},[463,464,465,466],{"id":352,"depth":118,"text":353},{"id":367,"depth":118,"text":368},{"id":432,"depth":118,"text":433},{"id":443,"depth":118,"text":444},"Deploy the origin with Docker behind Caddy or an existing Traefik, plus edge nodes.",{},"\u002Fen\u002Fdeployment",{"title":314,"description":467},"en\u002Fdeployment","bkkfra33ona6FLJByyaPAXxM4d1aUfGtARgD9iJ6v08",{"id":474,"title":327,"body":475,"description":654,"extension":126,"meta":655,"nav":227,"navigation":129,"path":656,"seo":657,"stem":658,"__hash__":659},"docs\u002Fen\u002Feditions-licensing.md",{"type":7,"value":476,"toc":649},[477,481,496,572,580,584,591,625,628,632,635],[10,478,480],{"id":479},"open-core","Open core",[15,482,483,484,487,488,491,492,495],{},"kvellman is ",[49,485,486],{},"open core",". The platform is open source under ",[49,489,490],{},"Apache-2.0","; advanced features ship\nas ",[49,493,494],{},"commercial plugins"," (e.g. SSO, MFA) that activate only with a valid Enterprise license.",[497,498,499,514],"table",{},[500,501,502],"thead",{},[503,504,505,508,511],"tr",{},[506,507],"th",{},[506,509,510],{},"Community",[506,512,513],{},"Enterprise",[515,516,517,528,539,550,561],"tbody",{},[503,518,519,523,525],{},[520,521,522],"td",{},"License",[520,524,490],{},[520,526,527],{},"Commercial subscription",[503,529,530,533,536],{},[520,531,532],{},"Users",[520,534,535],{},"Single \u002F local accounts",[520,537,538],{},"Multi-user, RBAC",[503,540,541,544,547],{},[520,542,543],{},"Auth",[520,545,546],{},"Local",[520,548,549],{},"SSO (OIDC) + MFA (TOTP)",[503,551,552,555,558],{},[520,553,554],{},"Scale",[520,556,557],{},"Single node",[520,559,560],{},"High availability, edge nodes",[503,562,563,566,569],{},[520,564,565],{},"Operations",[520,567,568],{},"Core",[520,570,571],{},"Approval workflow + audit, air-gapped, release scraper",[15,573,574,575,579],{},"See ",[311,576,578],{"href":577},"\u002Fpricing","pricing"," for the full comparison.",[10,581,583],{"id":582},"how-licensing-works","How licensing works",[15,585,586,587,590],{},"Enterprise features are gated by an ",[49,588,589],{},"offline, signature-based"," license — no activation server, so\nit works air-gapped:",[592,593,594,601,608,622],"ol",{},[22,595,596,597,600],{},"A license is an ",[49,598,599],{},"Ed25519-signed token"," issued by the vendor.",[22,602,603,604,607],{},"The product ships only the ",[49,605,606],{},"public key"," and verifies the token locally.",[22,609,610,611,614,615,68,618,621],{},"The token lists the customer and the active ",[49,612,613],{},"entitlements"," (e.g. ",[25,616,617],{},"sso",[25,619,620],{},"mfa",") and an expiry.",[22,623,624],{},"An admin pastes the token in the UI; matching plugins activate immediately — no restart, no\ninternet required.",[15,626,627],{},"Because verification is local, even the full open-source code cannot forge a valid license without\nthe vendor's private key.",[10,629,631],{"id":630},"developer-packages","Developer packages",[15,633,634],{},"The shared, public building blocks are on npm under Apache-2.0:",[19,636,637,643],{},[22,638,639,642],{},[25,640,641],{},"@kvellman\u002Fwinget-contract"," — the winget REST\u002Ftypes contract shared by origin and nodes.",[22,644,645,648],{},[25,646,647],{},"@kvellman\u002Fplugin-sdk"," — types for building auth\u002Fentitlement plugins.",{"title":117,"searchDepth":118,"depth":118,"links":650},[651,652,653],{"id":479,"depth":118,"text":480},{"id":582,"depth":118,"text":583},{"id":630,"depth":118,"text":631},"Open-core model — Community (Apache-2.0) vs Enterprise, and how licensing works.",{},"\u002Fen\u002Feditions-licensing",{"title":327,"description":654},"en\u002Feditions-licensing","GG0PkuJPsApFCUuFyP9GP_yXQMJjDVLXffyEuQ3kbmI",{"id":4,"title":5,"body":661,"description":125,"extension":126,"meta":728,"nav":128,"navigation":129,"path":130,"seo":729,"stem":132,"__hash__":133},{"type":7,"value":662,"toc":721},[663,665,667,681,683,701,703,713,715,717,719],[10,664,13],{"id":12},[15,666,17],{},[19,668,669,673,677],{},[22,670,671,28],{},[25,672,27],{},[22,674,675,34],{},[25,676,33],{},[22,678,679,40],{},[25,680,39],{},[10,682,44],{"id":43},[15,684,47,685,52,687,56,689,60,691,64,693,68,695,68,697,68,699,78],{},[49,686,51],{},[49,688,55],{},[49,690,59],{},[25,692,63],{},[25,694,67],{},[25,696,71],{},[25,698,74],{},[25,700,77],{},[10,702,82],{"id":81},[15,704,85,705,89,707,93,709,97,711,101],{},[49,706,88],{},[49,708,92],{},[25,710,96],{},[25,712,100],{},[10,714,105],{"id":104},[15,716,108],{},[10,718,112],{"id":111},[15,720,115],{},{"title":117,"searchDepth":118,"depth":118,"links":722},[723,724,725,726,727],{"id":12,"depth":118,"text":13},{"id":43,"depth":118,"text":44},{"id":81,"depth":118,"text":82},{"id":104,"depth":118,"text":105},{"id":111,"depth":118,"text":112},{},{"title":5,"description":125},1782195177568]