Skip to content

About

Host your own WinGet packages

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

winget-pkgs-selfhost

Host your own WinGet source and keep your apps up-to-date. Based on winget-extras.

Features

  • Automated package updates, powered by Anthelion and Komac. New versions are submitted as pull requests
  • Automated validation, using GitHub-hosted Windows runners (x64 and arm64) with Attack Surface Analyzer reports, screenshots, and installer logs
  • Serve the source from GitHub, Azure Blob Storage, or any S3-compatible bucket (AWS S3, Cloudflare R2, MinIO...)
  • Sign the source package with Azure Trusted Signing, or an Azure Key Vault certificate via AzureSignTool

Getting started

  1. Create a repository from this template
  2. Update the Identity, Properties, and display names in index/AppxManifest.xml. The Publisher must exactly match the subject of your signing certificate. Optionally replace the logos in index/Assets
  3. Configure a signing backend - WinGet requires preindexed sources to be signed by a certificate the client trusts
  4. Configure a storage backend (defaults to GitHub, no setup required)
  5. Add packages
  6. Add the source on your machines:
winget source add --name selfhost --type Microsoft.PreIndexed.Package --arg <cache URL for your storage backend>

Signing backends

Both backends authenticate to Azure with OIDC:

  1. Create an app registration
  2. Add a federated credential for the repository (entity type Branch, branch main)
  3. Create these repository variables:
Variable Value
AZURE_TENANT_ID App registration tenant ID
AZURE_CLIENT_ID App registration client ID
AZURE_SUBSCRIPTION_ID Subscription ID (only needed for Azure Blob Storage)

Azure Trusted Signing

  1. Set up an Azure Trusted Signing certificate profile (currently closed to new users)
  2. Assign Trusted Signing Certificate Profile Signer to the app registration
  3. Create these repository variables:
Variable Value
SIGNING_BACKEND trusted-signing
AZURE_SIGNING_ENDPOINT Trusted Signing endpoint, like https://eus.codesigning.azure.net/
AZURE_SIGNING_ACCOUNT Trusted Signing account name
AZURE_SIGNING_PROFILE Certificate profile name

AzureSignTool (Azure Key Vault)

  1. Import or generate a code signing certificate in an Azure Key Vault
  2. Assign the app registration the Key Vault Crypto User and Key Vault Certificate User roles (or equivalent access policies) on the vault
  3. Create these repository variables:
Variable Value
SIGNING_BACKEND azuresigntool
KEY_VAULT_URL Key Vault URL, like https://myvault.vault.azure.net/
KEY_VAULT_CERT Certificate name in the vault

Note

If the certificate is self-signed, it must be deployed to the Local Machine\Trusted People or Trusted Root Certification Authorities store on client machines.

Storage backends

GitHub (default)

No configuration needed. The generated cache directory is committed back to the repository and served from raw.githubusercontent.com:

winget source add --name selfhost --type Microsoft.PreIndexed.Package --arg https://github.com/OWNER/REPO/raw/main/cache

Best for small sources - every rebuild adds the source package to the repository's history, and GitHub raw serving isn't a CDN.

Azure Blob Storage

  1. Create a storage account and a blob container with anonymous read access for blobs (or front it with Azure CDN / Front Door)
  2. Assign the app registration the Storage Blob Data Contributor role on the container
  3. Create these repository variables:
Variable Value
STORAGE_BACKEND azure-blob
AZURE_STORAGE_ACCOUNT Storage account name
AZURE_STORAGE_CONTAINER Container name (defaults to cache)
AZURE_SUBSCRIPTION_ID Subscription containing the storage account
winget source add --name selfhost --type Microsoft.PreIndexed.Package --arg https://ACCOUNT.blob.core.windows.net/CONTAINER

S3-compatible (AWS S3, Cloudflare R2, MinIO...)

The cache is synced with rclone, so any S3-compatible provider works. Serve the bucket over HTTPS (e.g. an R2 custom domain, CloudFront, or public bucket hosting).

  1. Create a bucket and an access key with write permission
  2. Create these repository variables and secrets:
Variable Value
STORAGE_BACKEND s3
S3_BUCKET Bucket name
S3_ENDPOINT Endpoint URL for non-AWS providers, like https://<ACCOUNT_ID>.r2.cloudflarestorage.com
S3_PROVIDER rclone provider name, like AWS, Cloudflare, or Minio (defaults to AWS)
Secret Value
AWS_ACCESS_KEY_ID Access key ID
AWS_SECRET_ACCESS_KEY Secret access key
winget source add --name selfhost --type Microsoft.PreIndexed.Package --arg https://your-domain.example.com/cache

Adding packages

Manifests

Add standard WinGet manifests under manifests/<first letter>/<Publisher>/<Package>/<version>/, the same layout as winget-pkgs. Font packages can live in an optional fonts/ directory with the same layout.

The easiest way to author or update a manifest is the Anthelion fork of Komac, which can target your own repository instead of microsoft/winget-pkgs. Download a binary for your platform from unpn-org/Komac releases, then point it at your repo with these environment variables (the same ones the update workflow uses):

Variable Value
GITHUB_TOKEN A token with contents and pull-requests write scope
KOMAC_GITHUB_OWNER Target repository owner (your account, not microsoft)
KOMAC_GITHUB_REPO Target repository name
KOMAC_FORK_OWNER Where Komac pushes its branch (usually the same owner)
# add a new package
komac new Publisher.Package

# update an existing package to a new version
komac update Publisher.Package --version 1.2.3 --urls https://example.com/setup-1.2.3.exe

Komac downloads the installers, fills in the hashes and metadata, and opens a pull request against $KOMAC_GITHUB_OWNER/$KOMAC_GITHUB_REPO instead of the default microsoft/winget-pkgs. Add --dry-run --output <dir> to write the manifests locally without opening a pull request. wingetcreate also works if you prefer to author manifests by hand.

On merge to main, the publish workflow merges the manifests, resolves winget-pkgs dependencies, builds the preindexed source package with IndexCreationTool, signs it, and uploads it to your storage backend.

Automated updates (Anthelion)

Add a shard at shards/json/<PackageIdentifier>.json describing how to detect new versions, and the update workflow runs Anthelion on a schedule to open a pull request via Komac when a new version is found. See Anthelion's CONTRIBUTING.md for the shard format, the available strategies, and how to test a shard locally with bun test:shard <PackageIdentifier> --dry-run. Suffix a shard filename with .disabled to skip it.

Anthelion authenticates as a GitHub App so its pull requests trigger CI:

  1. Create a GitHub App with Contents and Pull requests write permissions, and install it on your repository
  2. Create the GH_CLIENT_ID repository variable (the app's client ID) and the GH_PRIVATE_KEY repository secret (a private key for the app)

Validation

Changed manifests in pull requests are validated automatically by the validate workflow: each installer is installed on a GitHub-hosted runner matching its architecture, with logs, an Attack Surface Analyzer SARIF report, and a desktop screenshot attached to the job summary. Limitations:

  • Interactive installation is only tested if silent installation fails
  • The arm architecture (32-bit ARM) is not tested, because arm32 GitHub runners aren't available

You can also validate manually with SandboxTest from winget-pkgs:

git clone https://github.com/microsoft/winget-pkgs
cd winget-pkgs\Tools
.\SandboxTest.ps1 -Manifest ..\..\REPO\manifests\p\Publisher\Package\1.0\

Enterprise deployment

The source can be deployed to managed devices via the EnableAdditionalSources policy.

The Identifier is the package family name of your source package: the Identity Name from index/AppxManifest.xml plus a hash of the publisher, e.g. Winget.Source.Selfhost_ggk937h18f62r. Get it from a machine with the source installed via Get-AppxPackage in PowerShell.

Intune

In the Settings Catalog, enable Administrative Templates > Windows Components > Desktop App Installer > Enable App Installer Additional Sources and set the value to:

{"Arg":"<cache URL>","Data":"<Identifier>","Explicit":false,"Identifier":"<Identifier>","Name":"selfhost","TrustLevel":["Trusted"],"Type":"Microsoft.PreIndexed.Package"}

Group Policy

Enable Computer Configuration > Administrative Templates > Windows Components > Desktop App Installer > Enable App Installer Additional Sources and set the same value as above.

Credits

Inspired by ScoopInstaller/Extras. See a real deployment at pl4nty/winget-extras.

About

Host your own WinGet packages

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages