Commit graph

51 commits

Author SHA1 Message Date
Shekhar Vaidya
33f3a46777 chore(release): bump to v0.3.7 -- backend version string + winget manifests; fix(ci): winget submission needs a Windows runner
Version bump: backend/main.go latestCLIVersion and the local
winget/manifests/*.yaml mirror to 0.3.7, with the real
InstallerUrl/InstallerSha256 for the cli/v0.3.7 release asset.

CI fix: the first real winget-submission attempt (v0.3.7, now that
WINGET_TOKEN is finally set) failed immediately -- wingetcreate isn't
published as a NuGet/dotnet-tool package at all, it's a native Windows
binary requiring .NET 6 + VC++ Redistributable. `dotnet tool install
--global wingetcreate` on ubuntu-latest could never have worked; this
path was just never exercised before since the empty-token check
always short-circuited it first. Split winget submission into its own
job on windows-latest, downloading wingetcreate.exe directly from its
GitHub release instead of dotnet tool install.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-31 12:29:08 +05:30
Shekhar Vaidya
6ab849be5e fix(backend): back off Sentinel lockdown retries, validate product_id in /proxy
Log analysis over 17 days showed 181 Sentinel-block retry attempts on
/proxy, all 181 failed (0% success). Retrying every 90 minutes yields
zero value right now and just adds more blocked-request noise against
our own IP for nothing in return. Bumped lockdownTTL to 5h so it backs
off harder while still recovering same-day if Microsoft's block ever
eases.

Also found /proxy never validated product_id against the known
catalog, so a request for a nonexistent ID (e.g. product_id=2861,
never a real product) still burned a full outbound Microsoft session
attempt and a Sentinel-block hit for something that could never
succeed anyway. Now rejected with 404 before any Microsoft call,
reusing the existing validContributeProducts allow-list.

Verified locally: unknown product_id -> 404 (no MS fetch attempted in
logs), missing params -> 400 unchanged, known product_id -> proceeds
to a real MS session attempt as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-31 11:35:44 +05:30
Shekhar Vaidya
a831cddb8e chore(release): bump to v0.3.6 -- backend version string + winget manifests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 11:33:48 +05:30
Shekhar Vaidya
da8787e93a feat: add check-new-releases tools; fix msdls_v3.py; add missing 25H2 V2 China variants
Moves msdls_v3.py from repo root into scripts/check-new-releases/,
alongside a new Go tool, documented as Method 1 (Python brute-force
range scan, can write results to a catalog-shaped JSON) and Method 2
(Go auto-discovery via page-scrape + bounded adjacent-ID probe, no
range-guessing needed). Only Windows 11 is checked -- 8.1 is fully
frozen and Windows 10 is past end-of-life, so neither will produce a
new consumer ISO again.

Fixed msdls_v3.py's release-name extraction: it checked EditionName/
ReleaseName/FriendlyName, none of which exist in the real API
response -- the actual field is ProductDisplayName, confirmed live.
Also added the vlscppe session-permit call to setup_session(), which
was skipped entirely; higher-risk gap for this script's brute-force
range-scan usage pattern than for a single lookup.

Both tools' live runs surfaced a real catalog gap: product IDs
3322/3323/3325/3326 (Home/Pro China variants of the 25H2 "V2" refresh)
exist on Microsoft's side but weren't in our catalog. Added them to
cli/catalog.go, products.json, validContributeProducts, and
sitemap.xml, and renamed 3321/3324 from our own guessed "(Updated
Oct)" label to Microsoft's actual name for this refresh, "(V2)"
(their internal name is "Windows 11 25H2__V2"; cleaned up the
double-underscore for user-facing display).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 11:07:45 +05:30
Shekhar Vaidya
5eec0351e7 feat: add curl | bash installer for Linux/macOS without Homebrew
Adds a GET /install.sh endpoint on the backend (embedded via Go's
embed package, so the script stays a real, shellcheck-able .sh file
rather than a Go string literal). Auto-detects OS/arch (including
Termux on Android via $PREFIX) and always resolves the latest GitHub
release via the /releases/latest/download/ redirect, so unlike the
winget/brew/AUR manifests it needs no per-release maintenance.

Verified end-to-end in an ephemeral Ubuntu container (both the normal
/usr/local/bin path and the Termux $PREFIX path), and locally against
a running backend instance.

Documented alongside the existing winget/Homebrew instructions in
README.md, the release notes template, the web CLI page, and the
per-product CliHandoff card.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-13 22:28:23 +05:30
Shekhar Vaidya
388f115db6 chore(release): bump to v0.3.5 -- backend version string + winget manifests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-13 12:58:06 +05:30
Shekhar Vaidya
327c5e195d simplify(backend): drop date-scoping for Sentinel stats, use flat all-time fields
Matches the shape actually asked for and how every other telemetry
counter in this file already works (all-time cumulative via HIncrBy,
no expiry, no per-day key rotation): /metrics now exposes flat
sentinel_errors and sentinel_distinct_sources_est fields instead of a
nested, date-scoped sentinel_today object.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-13 12:43:31 +05:30
Shekhar Vaidya
b252ba1c30 feat(backend): approximate distinct-source count for Sentinel rejections
CLI telemetry showed a growing count of Sentinel rejections but no way
to tell whether they come from one user retrying repeatedly or many
distinct users independently getting blocked.

Adds a per-day Redis HyperLogLog (msdl:telemetry:sentinel_hll:<date>)
seeded from the reporting IP whenever a CLI error contains "Sentinel",
plus a parallel daily counter. /metrics now exposes sentinel_today:
{date, errors_today, distinct_sources_est}. No raw IPs are stored --
HLL is a one-way cardinality estimator, and both keys expire after 48h.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-13 12:37:01 +05:30
Shekhar Vaidya
a3f75c1ce5 chore(release): bump to v0.3.3 -- backend version string + winget manifests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 18:17:30 +05:30
Shekhar Vaidya
8b430fab5d fix(backend): don't mislabel fresh lockdown-gate cache hits as stale
The Sentinel lockdown gate served any linkCache hit as "stale"
regardless of whether the entry was still within its own TTL. A link
just contributed via CLI (still valid for ~24h) would get labeled
stale, causing the frontend to hide it and show the CLI-only banner
again instead of the working link it just received.

Now checks the entry's actual expiry: still-fresh entries are labeled
"cached" (frontend shows the link normally); only genuinely expired
entries are labeled "stale".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 19:47:09 +05:30
Shekhar Vaidya
ef64e06b99 chore(release): bump to v0.3.2 -- backend version string + winget manifests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 12:44:07 +05:30
Shekhar Vaidya
8ab322038b feat(telemetry): capture error message on failed CLI runs
Failure telemetry only recorded success=false with no context. Adds
a truncated error string (150 chars, CLI-side) forwarded to a new
msdl:telemetry:errors Redis hash so failure causes are diagnosable
from /metrics instead of guessed at.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 12:29:59 +05:30
Shekhar Vaidya
d275340c93 chore(release): bump to v0.3.1 — backend version string + winget manifests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 08:10:25 +05:30
Shekhar Vaidya
9e836503c0 Merge branch 'main' into feat/cli-telemetry 2026-06-30 06:40:46 +05:30
Shekhar Vaidya
d4973ab42a fix(backend): truncate HTTP error body in logs to 120 chars
Microsoft's 403 capacity page was logging hundreds of lines of HTML
per error. Both non-200 error paths now cap the body at 120 chars.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 10:33:03 +05:30
Shekhar Vaidya
3141635300 feat(backend+frontend): Sentinel lockdown mode + stale UX overhaul
Backend (main.go):
- negCacheEntry gains IsSentinel bool; lockdownTTL = 90min constant
- Lockdown gate at top of handleProxy: serves stale with X-MSDL-Lockdown:1
  header or 429 {"error":"...","lockdown":true} — zero MS API calls made
- Sentinel stale path: sets 90min lockdown neg cache, adds lockdown header,
  suppresses background refresh (retrying extends the Sentinel block timer)
- Non-Sentinel stale path: unchanged (60s neg cache, bg refresh fires)
- Expose X-MSDL-Lockdown in Access-Control-Expose-Headers

Frontend (ProductDetailPage.tsx):
- isLockdown state: set from X-MSDL-Lockdown header or data.lockdown field
- Stale state hides expired download links entirely; shows CLI recommendation
  inside the card instead of sending users to look for options elsewhere
- Lockdown: red banner explaining 90min cooldown + highlighted CLI card,
  no Refresh button (it cannot help while Sentinel block is active)
- Normal stale: amber banner + highlighted CLI card + secondary Refresh button
- Outer CliHandoff suppressed when already shown inside card (stale states)

Frontend (CliHandoff.tsx):
- CLI command uses msdl.exe (primary audience is Windows users)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 12:07:00 +05:30
Shekhar Vaidya
0525015ca9 fix(backend): detect Sentinel in JSON Errors field, evict session on block
Microsoft's WAF returns HTTP 200 with {"Errors":[{"Value":"Sentinel marked
this request as rejected."}]}. The existing Sentinel check only ran for
non-200 responses, so the session was never evicted — every subsequent
request reused the poisoned session and hit Sentinel again.

Add the same eviction + rateLimitError logic inside the errMap["Value"]
branch so the session is evicted regardless of the WAF's HTTP status code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 11:13:34 +05:30
Shekhar Vaidya
1e00dcee2e fix(backend): validate telemetry fields; add method guard to /cli/version
- Add platform allowlist validation (windows, darwin, linux) and length caps for version (≤20) and product_id (≤10) in handleTelemetry to prevent unbounded Redis hash growth from attacker-controlled field values.
- Add HTTP method guard to handleCLIVersion to reject non-GET requests with 405 Method Not Allowed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 21:14:21 +05:30
Shekhar Vaidya
2978012120 feat(backend): include telemetry counters in /metrics response 2026-06-21 20:58:11 +05:30
Shekhar Vaidya
470abbc94b feat(backend): Redis persistence for metrics (seed + 5min flush + graceful shutdown)
- seedMetricsFromRedis: loads persisted counters into atomics on startup
- flushMetricsToRedis: writes all 12 counters to 3 Redis hashes on demand
- startMetricsFlusher: goroutine that flushes every 5 minutes
- Graceful shutdown: SIGINT/SIGTERM flushes metrics then drains HTTP in 10s
- http.ListenAndServe replaced with http.Server for Shutdown support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 20:48:08 +05:30
Shekhar Vaidya
cbe5ca2f9b feat(backend): POST /telemetry endpoint with Redis HINCRBY counters 2026-06-21 20:43:27 +05:30
Shekhar Vaidya
f968942763 feat(backend): GET /cli/version returns latest CLI version 2026-06-21 20:40:13 +05:30
Shekhar Vaidya
2a41874ad4 fix: evict session and serve stale on Sentinel WAF block
When Microsoft Sentinel rejects a request (HTTP 502 with 'Sentinel'
in body), the session was being reused on every retry — hammering the
same poisoned session and making the block worse.

Now: detect Sentinel response, evict the session from cache, and
return a rateLimitError so stale-on-failure kicks in and users get
the last cached link instead of an error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:46:16 +05:30
Shekhar Vaidya
0bad3d1582 fix: update stale log message in contribute expiry validation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 19:39:49 +05:30
Shekhar
84429ebaaf fix: support P1 query parameter for consumer download link expiry 2026-06-21 09:09:54 +05:30
Shekhar Vaidya
bf5dca1fe4 feat: Phase 4 web-to-CLI handoff UX
- Add CliHandoff component: shows install link + pre-filled msdl command
- Highlighted card (Recommended) shown inline on Sentinel/WAF errors
- Collapsed version shown below OfficialFallback on stale links
- Rename instructions with platform-specific filenames (msdl.exe / msdl)
- Fix backend CORS to allow local network origins (100.x, 192.168.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 12:16:43 +05:30
Shekhar Vaidya
36c875ab23 fix: use extractRawExpiry for /contribute expiry validation
parseLinkExpiry has a 1h safety floor that causes it to treat already-
expired URLs as valid. Replace with extractRawExpiry + direct se
timestamp comparison so any URL expiring < 1h out is rejected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 16:12:36 +05:30
Shekhar Vaidya
9a5f41290d feat: Phase 3 crowdsourced cache contribution
- Redis L2 persistent cache: initRedis() from REDIS_URL env, seed
  in-memory linkCache on startup, write-through on every fresh fetch
  and background refresh (silent fallback to memory-only if unavailable)
- POST /contribute endpoint: validates product catalog, expiry > 1h,
  Microsoft CDN host allow-list, per-IP token bucket (~5/min),
  CONTRIBUTE_SECRET header; writes to in-memory + Redis; logs all
  accepts and rejections
- CLI --no-contribute flag (also MSDL_NO_CONTRIBUTE=1 env var):
  background goroutine posts raw MS JSON to /contribute after every
  consumer fetch; best-effort 5s timeout; opt-out message to stderr

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 14:07:35 +05:30
Shekhar Vaidya
77056cb50f feat: Phase 1 graceful degradation for WAF-blocked links
- Backend: emit X-MSDL-Link-Status (fresh/cached/stale) and
  X-MSDL-Link-Expires headers on every /proxy response; expose
  both via Access-Control-Expose-Headers
- Frontend: read headers in handleGetLinks/handleRefreshLinks;
  show amber stale warning when backend is WAF-blocked
- Frontend: new OfficialFallback component (collapsible) with
  step-by-step guide to Microsoft's official download page,
  pre-filled with the user's selected language; shown on stale
- Plan: add Redis/Valkey persistence section to Phase 3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 11:13:46 +05:30
Shekhar Vaidya
eb0fc51fe2 chore: remove debug cookie-count log from proxy handler
Diagnostic log added during Sentinel investigation — no longer needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 22:09:26 +05:30
Shekhar Vaidya
04a4d7e58c debug: log cookie count in jar before proxy request
Temporary diagnostic — tells us whether Set-Cookie headers from
Microsoft are actually reaching the backend through the CF Worker.
If count is 0, the Worker is stripping them or MS sets cookies via JS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 21:50:12 +05:30
Shekhar Vaidya
6f2c02d79b fix: forward cookies through CF Worker to satisfy Microsoft Sentinel
Sentinel started rejecting all /proxy requests because cookies set during
the fingerprinting flow (vlscppe/ov-df) were never reaching the
GetProductDownloadLinksBySku call.

Two gaps closed:
- CF Worker now forwards Cookie header to Microsoft and Set-Cookie headers
  back to the backend
- Backend uses a simpleCookieJar (domain-scoping disabled) shared across
  the full session: setup → warmup → download link fetch. Standard
  cookiejar rejects microsoft.com cookies when the request URL is the
  Worker domain.

Tested locally without Worker (direct MS calls): download links returned,
no Sentinel error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 21:36:43 +05:30
Shekhar Vaidya
1c54c36d03 feat: expiry countdown, refresh links, CLI tabs, recently viewed, file size support
- Backend: ?force=true on /proxy bypasses cache for fresh link fetch
- Frontend: parse `se` param for live expiry countdown on download links
- Frontend: show Refresh button when link expires in <6h, force-fetches fresh URL
- Frontend: CliCommand component replaces Aria2Tip — wget/curl/aria2 tabs, persists selection in localStorage
- Frontend: RecentlyViewed component — localStorage, shows active countdown or expired state
- Frontend: FileSize shown alongside arch if returned by Microsoft CDN

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 21:06:17 +05:30
Shekhar Vaidya
90c498233b fix: route background stale refresh through singleflight; cleanup logging
- Background refresh on stale serve now goes through sfGroup.Do so
  concurrent stale hits collapse into one Microsoft call instead of
  hammering the API from multiple goroutines simultaneously.

- cleanupCaches: only logs when entries are actually evicted (was
  logging every 30min regardless of whether anything changed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:12:59 +05:30
Shekhar Vaidya
a0a6341d5f feat: add /metrics endpoint, fix cache eviction and review issues
- /metrics: atomic counters for requests, cache hits, MS fetches, neg
  hits, stale serves, and cache sizes per endpoint. Auth via
  METRICS_SECRET env var (query param or Authorization: Bearer).

- Cache eviction: cleanupCaches() goroutine runs every 30min, removes
  expired SKU/neg entries and link entries past ExpiresAt + 4h stale
  window. Prevents unbounded map growth under sustained traffic.

- rand: replace global rand with package-level rand.Rand to avoid
  mutex contention at high QPS (jitter() uses local source).

- Singleflight keys: separate sfKey from negKey in both handlers for
  clarity — "sku:<id>" and "link:<id>:<sku>" vs neg cache keys.

- Background refresh on stale: when /proxy serves a stale entry after
  a failed refresh, fires a goroutine to retry and update the cache
  so the next request gets fresh data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:08:43 +05:30
Shekhar Vaidya
2f14ca6f22 fix: bump Dockerfile to golang:1.25-alpine to match go.mod requirement 2026-05-18 22:51:25 +05:30
Shekhar Vaidya
c6d8615cf1 feat: two-layer in-memory caching for backend (closes #12)
Reduces Microsoft API traffic from thousands/day to ~50-100/day,
eliminating the 715-123130 rate-limit block under real traffic.

1. Singleflight (golang.org/x/sync/singleflight) — wraps all Microsoft
   fetches so 500 concurrent cache misses collapse into 1 API hit.

2. SKU info cache — 7-day TTL keyed by product_id. Language lists are
   stable; no need to hit Microsoft on every product page load.

3. Download link cache — dynamic TTL keyed by product_id:sku_id. Expiry
   is derived from the signed URL's `se` query param minus 30min buffer,
   so we never serve an expired signed URL.

4. Negative response caching — 60s TTL for 429 / 715-123130 failures.
   Prevents thundering herd from retries worsening an existing block.
   Only stored when no stale data is available to fall back on.

5. Dynamic TTL — parses `se` (signed expiry) from Microsoft CDN URL:
   TTL = (se - now) - 30min. Falls back to 22h if unparseable.

6. Stale-on-failure — if a refresh fails (rate-limited or transient),
   the expired cache entry is served temporarily. Applies to both
   /proxy (download links) and /evallinks (eval ISOs).

7. Jitter — ±5min random offset on all TTLs prevents synchronised
   mass-expiry spikes when many entries are warmed at the same time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:41:58 +05:30
Shekhar Vaidya
a78059187f feat: add Enterprise & Server eval ISOs via Microsoft evalcenter
Adds /eval listing page and /product/<slug> detail pages for Windows
Server 2025/2022/2019/2016 and Windows 11 Enterprise evaluation ISOs,
sourced from Microsoft evalcenter without any session flow.

Backend: new /evallinks endpoint fetches evalcenter page, extracts
fwlinks, follows all redirects in parallel, detects arch + locale.
24h cache per product with startup warming so first hit is instant.

Frontend: EvalPage listing at /eval, EvalDetailPage at /product/<slug>
matching consumer structure with downloads, aria2 template, system
requirements, and also-available section. Smart ProductRouter in
App.tsx dispatches slug vs numeric productId. Dock highlights Products
tab for /product/* and /eval routes. CTA banner on home page and
footer link added. sitemap.xml updated with 6 new URLs.

Closes #13

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-18 21:56:57 +05:30
Shekhar Vaidya
106e2e2b31 fix(backend): allow non-numeric SKU IDs for legacy Windows products
Replaced strconv.Atoi on sku_id with a permissive allowlist regex
(alphanumeric, hyphens, underscores). Older products like Windows 8.1
(#48, #52) return non-integer SKU IDs from Microsoft API, causing the
Get Download Links button to fail with 'sku_id must be a numeric value'.

Closes #9
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:45:27 +05:30
Shekhar Vaidya
8b036e94ba feat: route Microsoft API calls through Cloudflare Worker to avoid IP blocks
- Add cloudflare-worker/worker.js: edge proxy forwarding requests to allowed
  Microsoft hosts from Cloudflare distributed IPs. Set CF_WORKER_URL env var
  to activate; omit to keep direct-to-Microsoft behaviour (self-hosters unaffected).
- Wrap all outbound Microsoft URLs in proxyURL() in main.go so the Worker
  sits transparently in front of every session and download-link request.
- Fix Back button navigating to external referrer when user lands directly
  on a product URL (#5) — use window.history.state?.idx to detect whether
  there is prior in-app history before calling navigate(-1).

Closes #5, Closes #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:01:03 +05:30
Shekhar Vaidya
37f947cb32 refactor(backend): validate numeric IDs and use url.Values for upstream URLs
- Reject non-numeric product_id and sku_id with HTTP 400 before any
  upstream request is made, preventing parameter injection into
  Microsoft API query strings.
- Replace all fmt.Sprintf URL construction with url.Values.Set +
  .Encode() across handleSkuInfo, handleProxy, and setupSession,
  ensuring values are always percent-encoded correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 19:00:09 +05:30
Shekhar Vaidya
2d688ae7e8 Implement inactive product state, restore backend tracker limits, and add dedicated catalog error UI panel 2026-04-11 13:01:56 +05:30
Shekhar Vaidya
aac8f5d990 Modernize UI, unify product catalog, and backend optimization
- Migrated products.json to structured metadata format.

- Refactored ProductDetailPage to eagerly reset state and validate product IDs against local catalog.

- Dynamic data fetching in HomePage and StatsBar for total release counts.

- Adjusted UI layout in App.tsx and UX states (404/Loading) for missing dependencies.

- Shortened backend tracking HTTP timeouts to prevent local Pi-Hole hangs.
2026-04-11 11:17:54 +05:30
Shekhar Vaidya
2b89aa3647 debug: add detailed MS API error logging 2026-04-10 22:03:02 +05:30
Shekhar Vaidya
f6cab53b77 fix: remove public folder copy from Dockerfile 2026-04-10 20:56:08 +05:30
Shekhar Vaidya
3b18b08c0a security: restrict CORS and remove test UI from root 2026-04-10 20:52:56 +05:30
Shekhar Vaidya
722d211197 chore: remove test UI and static file server for production 2026-04-10 20:50:05 +05:30
Shekhar Vaidya
74c06b0a8e fix: downgrade go version to 1.22 and ensure go.sum is tracked 2026-04-10 20:40:01 +05:30
Shekhar Vaidya
acd9f94e5f fix: remove go.sum from ignore list 2026-04-10 20:33:36 +05:30
Shekhar Vaidya
56a43b2ff0 feat: v3 release with Coolify support and UI polish 2026-04-10 20:23:59 +05:30