Skip to content

Add Modpack feature: bundle installed mods into a player-downloadable zip#64

Merged
AdaInTheLab merged 1 commit into
mainfrom
feature/modpack
May 13, 2026
Merged

Add Modpack feature: bundle installed mods into a player-downloadable zip#64
AdaInTheLab merged 1 commit into
mainfrom
feature/modpack

Conversation

@AdaInTheLab
Copy link
Copy Markdown
Collaborator

Summary

Like Minecraft modpacks: admin picks installed mods, names + versions a "pack," builds it, and publishes. Once published, an anonymous download CTA appears top-right on the login page so players can grab matching client-side mods without needing a panel account.

Spec (per your earlier answers)

  1. Single-server scope = exactly one modpack at a time
  2. Latest version only — re-publishing overwrites the previous zip
  3. Just the Mods/ folder contents — no config files, no other layers
  4. Top-right CTA on the login page (not a full panel)
  5. Three-state workflow: draft → published → archived

What it does

Admin flow (Mods view → new "Modpack" tab):

  • Pick mods from a MultiSelect populated by installed mods
  • Name + version + description
  • Save Draft → persists the configuration
  • Build Zip → atomically writes `/KitsuneModpacks/-.zip`
  • Publish → flips status so the public CTA appears
  • Archive / Unarchive / Delete for lifecycle management
  • Once published, shows the public download URL so admin can smoke-test or share

Public flow (login page, no auth):

  • `GET /api/modpack/public` returns metadata or 404
  • Top-right pill renders only if a pack is published, showing name + version
  • Click → streams `/api/modpack/public/download` directly to the browser
  • Download counter bumps on successful serve

Backend

File What
`Config/Migrations/010_modpack.sql` Singleton table
`Data/Entities/Modpack.cs` Plain entity
`Data/Repositories/ModpackRepository.cs` Singleton-enforced CRUD
`Services/ModpackService.cs` Validation, atomic zip build, status transitions, file streaming
`Web/Controllers/ModpackController.cs` 7 admin endpoints + 2 `[AllowAnonymous]` public ones
`Core/ServiceRegistry.cs` DI registration

Notable: zip builds use a `.building` temp file then atomic rename, so a public downloader can never grab a half-written archive mid-rebuild.

Frontend

File What
`api/modpack.ts` REST client (admin + public)
`types/index.ts` `Modpack`, `ModpackState`, `PublicModpack`, etc.
`views/ModsView.vue` New 3rd tab with the admin form
`views/LoginView.vue` Top-right CTA pill
`i18n/locales/*.ts` New `modpack` block in all 8 locales (de/fr/es translated, ja/ko/zh-* placeholders)

Test plan

  • Pull, build, deploy
  • Mods → Modpack tab loads with empty form
  • Pick 1–2 installed mods, set name + version, Save Draft → status shows "Draft"
  • Build Zip → row updates with filename + size
  • Publish → status flips to "Published", public download link revealed
  • Open login page in incognito → top-right CTA shows name + version
  • Click CTA → browser downloads the zip
  • Refresh admin view → `download_count` incremented
  • Archive → CTA disappears from login page; admin form shows "Restore from Archive"
  • Edit selection on a published pack → status flips back to "Draft" (forces a rebuild before re-publish; previous zip stays downloadable in the meantime)
  • Delete → row + zip removed from disk

🤖 Generated with Claude Code

… zip

New end-to-end feature: admin picks installed mods → names + versions a
"pack" → builds a zip → publishes it. Once published, an anonymous
download CTA appears top-right on the login page so players can grab
matching client-side mods without needing a panel account.

Spec (confirmed with Ada):
  1. Single-server scope = exactly one modpack at a time
  2. Latest version only — re-publishing overwrites the previous zip
  3. Just the Mods/ folder contents — no config files, no other layers
  4. Top-right CTA on the login page (not a full panel)
  5. Three-state workflow: draft → published → archived

Backend surface:

  Config/Migrations/010_modpack.sql
    Singleton 'modpack' table. status, filename, size, mod_count, mod_list
    (JSON array of folder names), description, download_count.

  Data/Entities/Modpack.cs
    Plain entity mirroring the table.

  Data/Repositories/ModpackRepository.cs
    GetCurrent / GetPublished / Upsert / IncrementDownloadCount / Delete.
    Repo enforces singleton semantics (the table can structurally hold
    many rows; this layer keeps it to one).

  Services/ModpackService.cs
    SaveDraft (validates mods exist on disk), BuildZip (atomic temp-file
    pattern so partial writes can't leak to public downloaders),
    Publish / Archive / Unarchive / Delete, OpenPublishedForDownload
    (FileStream for the controller to pipe through StreamContent).
    Zips land in <game-root>/KitsuneModpacks/ — same convention as
    KitsuneBackups.

  Web/Controllers/ModpackController.cs
    [Authorize] for the admin endpoints, [AllowAnonymous] for the two
    public-facing ones (GET /api/modpack/public for metadata, GET
    /api/modpack/public/download for the zip stream).

  Core/ServiceRegistry.cs
    Registers ModpackService alongside BackupService.

Frontend surface:

  api/modpack.ts
    REST client for the 7 admin endpoints + 1 public metadata fetch.
    Public download URL exported as a constant so anchor tags can hit
    it directly without going through axios.

  types/index.ts
    Modpack, ModpackState, PublicModpack, ModpackStatus, etc.

  views/ModsView.vue
    Third tab on the existing Mods page ("Modpack"). Form fields for
    name + version + mod picker (MultiSelect from installedMods) +
    description. Status badge, action buttons gated by current state
    (Save Draft / Build Zip / Publish / Archive / Unarchive / Delete).
    Reveals the public download URL when published so admin can share
    or smoke-test it.

  views/LoginView.vue
    Top-right CTA pill that renders only when getPublishedModpack()
    resolves to a record. Shows name + version, opens the public
    download endpoint in a new tab. Hidden cleanly when no pack is
    published (silent failure on the anonymous metadata call).

i18n: full English in en.ts, native translations in de/fr/es, English
placeholders in ja/ko/zh-CN/zh-TW (consistent with prior locale
rollout pattern — translations TBD by community native speakers).

Tested locally: full build clean, vue-tsc structural check across all
8 locales passes, 0 C# errors, all expected files in dist/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AdaInTheLab AdaInTheLab merged commit 1e078d5 into main May 13, 2026
2 checks passed
@AdaInTheLab AdaInTheLab deleted the feature/modpack branch May 13, 2026 00:09
AdaInTheLab added a commit that referenced this pull request May 13, 2026
Rolls up everything since v2.6.2 into a single release tag. v2.6.3
was bumped on main (#63) but never tagged or released — folding it
into v2.6.4 keeps the public release history clean.

Marquee additions since v2.6.2:

- Modpack (#64) — Bundle installed mods into a single zip players can
  download from the login page. No panel account required. Three-state
  workflow (draft → published → archived), top-right CTA on the login
  page, anonymous metadata + download endpoints. Atomic temp-file zip
  build so public downloaders can't grab a half-written archive.

- Graceful Restart (#57, #58) — Scheduled daily restarts with
  player-friendly in-game countdown warnings. Configurable warning
  ladder, IANA-timezone schedule (DST-aware), krestart console command,
  REST endpoints, panel Settings tab.

- Backups bug fix (#56) — BackupService had a redundant conn.Open()
  call that silently dropped writes due to a custom System.Data.SQLite
  quirk. Reads worked, so the bug went unnoticed until first hands-on
  test. Six call sites fixed, class doc comment added so future-me
  doesn't reintroduce it.

- German / French / Spanish locales (#59, #61, #62) — Full polite-form
  translations across ~250 keys / 30 namespaces. Browser auto-detect
  picks the right one for de-* / fr-* / es-* visitors. Tooltip on the
  language switcher noting that in-game broadcast messages don't
  auto-translate.

- Favicon (#60) — Regenerated all four favicon variants (svg / 16 /
  32 / ico) from kitsune-command-logo-transparent.png so the browser
  tab matches the panel's sidebar logo.

- README updates: features list now reflects all the above.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant