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