Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
28e75e5
test(host): add two-host relay probe to unblock #46 make-before-break
paulocorcino Jun 19, 2026
d766a7f
fix(host): stop infinite "authorizing" loop on a deleted tunnel
paulocorcino Jun 26, 2026
98e6ec1
fix(cache): skip stale startup row cache to avoid phantom tunnels
paulocorcino Jun 26, 2026
80dcb7a
fix(i18n): use the loaded FTL's langid so plurals match (0 ports, not…
paulocorcino Jun 26, 2026
aa65c46
feat(ui): open tunnel URL in browser on click, drop open button
paulocorcino Jun 26, 2026
a97427a
fix(log): implement non-blocking stderr writer to prevent UI thread s…
paulocorcino Jul 2, 2026
97dc3b2
feat(update): implement in-app update checker and banner for new rele…
paulocorcino Jul 2, 2026
8f53e82
feat(store): add MSIX `store` build feature for Microsoft Store
paulocorcino Jul 6, 2026
2bb2c53
docs(store): add Microsoft Store packaging, listing, and runbook
paulocorcino Jul 6, 2026
2964a08
feat(store): add Pages privacy site, screenshot helper, and .env-driv…
paulocorcino Jul 6, 2026
921ab37
fix(store): resolve a relative CERT_PATH against repo root
paulocorcino Jul 7, 2026
438d3f4
fix(store): locate appcert in App Certification Kit folder; require e…
paulocorcino Jul 7, 2026
fd332ec
fix(store): keep packaging scripts ASCII-only for Windows PowerShell 5.1
paulocorcino Jul 7, 2026
baba6e2
feat(ui): rename in-app branding to TunnelDeck to match the Store name
paulocorcino Jul 7, 2026
090a9a3
fix(store): make screenshot capture reliable (correct window, raise, …
paulocorcino Jul 7, 2026
5bbd450
style: cargo fmt (expand gen_msix_assets tables; pre-existing drift)
paulocorcino Jul 7, 2026
f850c89
fix(clippy): allow match_single_binding in ftl_source locale scaffold
paulocorcino Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Publishes the privacy policy (and any other static pages under docs/store/site)
# to GitHub Pages. One-time setup: repo Settings > Pages > Source = "GitHub Actions".
# After that, this deploys on every push to main that touches the site folder.
#
# Public URL: https://paulocorcino.github.io/devtunnel_gui/
name: Deploy Pages

on:
push:
branches: [main]
paths:
- "docs/store/site/**"
- ".github/workflows/pages.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

# Allow one concurrent deployment; don't cancel an in-progress run.
concurrency:
group: pages
cancel-in-progress: false

jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: docs/store/site
- id: deployment
uses: actions/deploy-pages@v4
36 changes: 32 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ log = "0.4"
tunnels = { git = "https://github.com/microsoft/dev-tunnels", features = ["connections", "vendored-openssl"], optional = true }
tokio = { version = "1", features = ["full"], optional = true }
env_logger = { version = "0.11", optional = true }
# Blocking HTTP client for the health probe (issue #4). rustls avoids a native
# OpenSSL dependency for the probe itself. Optional: only pulled by `hosting`.
ureq = { version = "2", default-features = false, features = ["tls"], optional = true }
# Blocking HTTP client. Used by the startup update checker (GitHub Releases) in
# every build, and by the health probe in the `hosting` build. rustls avoids a
# native OpenSSL dependency, keeping the default build light.
ureq = { version = "2", default-features = false, features = ["tls"] }
# PNG encoder for the MSIX visual assets. Only pulled in by the `store` feature,
# which builds the `gen_msix_assets` helper bin; the GUI itself never links it.
ico = { version = "0.3", optional = true }

[target.'cfg(windows)'.dependencies]
# Initial dark-mode detection: read the Windows "apps use light theme" setting.
Expand All @@ -48,7 +52,19 @@ spike = ["dep:tunnels", "dep:tokio", "dep:env_logger"]
# `cargo build` stays light (no vendored OpenSSL / heavy toolchain).
# env_logger is no longer needed here: the GUI installs its own capturing
# logger (src/logbuf.rs) in every build. The spike bin still uses env_logger.
hosting = ["dep:tunnels", "dep:tokio", "dep:ureq"]
hosting = ["dep:tunnels", "dep:tokio"]
# Microsoft Store (MSIX) build. The MSIX container virtualizes the registry and
# filesystem and manages install/update itself, so the self-install relocation,
# the HKCU Run-key auto-start, and the GitHub-Releases update checker are all
# either broken or against Store policy inside the package. This feature compiles
# them out: install/update are handled by the MSIX package, and auto-start is
# declared via the `windows.startupTask` manifest extension (user-managed in
# Windows Settings > Startup apps). See docs/store/README.md.
#
# `store` always pulls in `hosting`: the Host button is core to the product, so a
# Store build without it makes no sense. Building it needs NASM + Strawberry Perl
# on PATH (vendored OpenSSL) — see CLAUDE.md.
store = ["dep:ico", "hosting"]

[[bin]]
name = "devtunnel_gui"
Expand All @@ -59,6 +75,18 @@ name = "host_spike"
path = "src/bin/host_spike.rs"
required-features = ["spike"]

[[bin]]
name = "two_host_probe"
path = "src/bin/two_host_probe.rs"
required-features = ["spike"]

# Renders the MSIX visual assets (tile/logo PNGs) from the procedural app icon so
# the Store package's Assets\ folder is reproducible. Run via packaging/msix/build-msix.ps1.
[[bin]]
name = "gen_msix_assets"
path = "src/bin/gen_msix_assets.rs"
required-features = ["store"]

[build-dependencies]
slint-build = "1.13"

Expand Down
127 changes: 127 additions & 0 deletions docs/store/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Publishing to the Microsoft Store

End-to-end runbook for shipping **TunnelDeck for Dev Tunnels** to the Microsoft
Store as an MSIX package. Work top to bottom; each step links to the artifact that
implements it.

| Artifact | Purpose |
|---|---|
| `store` cargo feature | Compiles out self-install, the GitHub update checker, and the HKCU auto-start (all MSIX-incompatible / against policy). Auto-start moves to the manifest. |
| `packaging/msix/AppxManifest.xml` | Package manifest: identity placeholders, full-trust app, `windows.startupTask`. |
| `packaging/msix/build-msix.ps1` | Builds the exe, renders assets, packs the `.msix`, optional sign + WACK. |
| `src/bin/gen_msix_assets.rs` | Renders the tile/logo PNGs from the app icon (run by the script). |
| `docs/store/listing.md` | Store listing copy: name, description, features, keywords, screenshots, age rating. |
| `docs/store/privacy-policy.md` | Privacy policy to publish and link (required). |

---

## Step 1 — Partner Center account & app name

1. Create a **Microsoft Partner Center** developer account (one-time fee: ~US$19
individual / US$99 company): https://partner.microsoft.com/dashboard/registration
2. **Apps and games → New product → MSIX or PWA app.**
3. **Reserve the name** `TunnelDeck for Dev Tunnels`.
- The `<YourApp> for Dev Tunnels` form is used deliberately: it avoids a
trademark rejection for leading with Microsoft's product name. Do **not**
reserve just "Dev Tunnels …".
4. Open **Product → Product identity** and copy these three values — you'll pass
them to `build-msix.ps1`:
- **Package/Identity/Name** → `-IdentityName`
- **Package/Identity/Publisher** (`CN=…`) → `-PublisherId`
- **Publisher display name** → `-PublisherDisplayName`

## Step 2 — Build the store executable

The `store` feature strips the MSIX-incompatible bits and **pulls in `hosting`**
(the Host button is core to the product). That builds the `tunnels` SDK + vendored
OpenSSL, which needs **NASM** and **Strawberry Perl** on `PATH` (see the repo
`CLAUDE.md`). On this machine, prepend before building:

```powershell
$env:PATH = "C:\Strawberry\perl\bin;C:\Strawberry\c\bin;C:\Users\PICHAU\AppData\Local\bin\NASM;$env:PATH"
cargo build --release --features store --bin devtunnel_gui
```

`build-msix.ps1` runs this for you.

## Step 3 — Fill in the manifest identity & package

Put the three Partner Center identity values into a `.env` file (gitignored), then
run the script with no arguments. `build-msix.ps1` builds the exe, renders
`Assets\`, substitutes the identity into the manifest, and packs the `.msix`.

```powershell
cd packaging\msix
Copy-Item .env.example .env
notepad .env # fill IDENTITY_NAME, PUBLISHER_ID, PUBLISHER_DISPLAY_NAME
.\build-msix.ps1
```

(You can still override any value on the command line, e.g. `-Version 0.2.0.0`.)

Output: `packaging\msix\out\TunnelDeck-0.1.0.0.msix` (**unsigned** — correct for
submission; the Store re-signs it).

## Step 4 — Test locally + certify (WACK)

The submission package is unsigned, but to **install and test locally** you need a
self-signed cert whose subject exactly equals `Identity/@Publisher`:

```powershell
# One-time: create a test cert (subject must match your -PublisherId)
$cert = New-SelfSignedCertificate -Type Custom -Subject "CN=Paulo Corcino" `
-KeyUsage DigitalSignature -CertStoreLocation "Cert:\CurrentUser\My" `
-TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}")
Export-PfxCertificate -Cert $cert -FilePath .\TunnelDeck-test.pfx `
-Password (ConvertTo-SecureString -String "test" -Force -AsPlainText)

# Build a signed test package (identity comes from .env):
.\build-msix.ps1 -Sign

# Install it (self-signed → first trust the cert; needs an ELEVATED prompt):
Import-PfxCertificate -FilePath .\TunnelDeck-test.pfx `
-CertStoreLocation Cert:\LocalMachine\TrustedPeople `
-Password (ConvertTo-SecureString "test" -Force -AsPlainText)
Add-AppxPackage .\out\TunnelDeck-0.1.0.0.msix

# Run the certification kit — WACK requires an ELEVATED (Administrator) prompt:
.\build-msix.ps1 -Sign -Wack
```

Fix any **WACK** failures before submitting. Then rebuild **without** `-Sign` to
produce the clean unsigned package for upload.

Smoke-test the installed app:
- Launches to the tray; window opens; tunnels list loads.
- Settings → General shows **no** "Start with Windows" toggle (managed by the
package). Settings → Status shows **no** install/uninstall rows.
- No "update available" banner appears (checker compiled out).
- Enable auto-start via **Windows Settings → Apps → Startup** and confirm it
launches at logon.

## Step 5 — Create the submission

In Partner Center, on the reserved product:

1. **Packages** — upload the unsigned `.msix`. Set device family to **Desktop**.
2. **Store listing** — paste everything from [`listing.md`](listing.md):
name, short + full description, features, search terms, category
(Developer tools), copyright, support email, and screenshots (≥ 1, 1366×768+).
3. **Privacy policy URL** — `https://paulocorcino.github.io/devtunnel_gui/`.
The `Deploy Pages` workflow publishes [`site/index.html`](site/index.html)
(mirror of [`privacy-policy.md`](privacy-policy.md)). One-time: repo
**Settings → Pages → Source = GitHub Actions**. Required field.
4. **Age ratings** — complete the IARC questionnaire (see `listing.md`; expected
result: Everyone / PEGI 3).
5. **Pricing and availability** — Free; pick markets.
6. **Submit for certification.** Microsoft's automated + manual review typically
takes hours to a couple of days. If rejected, the report says why — the most
likely notes here are name/trademark or the CLI dependency; address and
resubmit.

## Recurring: shipping an update

1. Bump the version (e.g. `-Version 0.2.0.0`; the 4th part must stay `0`).
2. Re-run `build-msix.ps1`, re-test, upload the new unsigned `.msix`.
3. Update **What's new** and submit. The Store delivers the update to users; the
in-app updater stays disabled in this build by design.
Loading