From 94cb6d6d443a77c86d9e5eb5bc6345ff62aa52bc Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 21:45:09 +0000 Subject: [PATCH 01/11] Fix deadlocking issue --- README.md | 3 +++ torrential/src/downloads/download.rs | 8 ++++---- torrential/src/downloads/serve.rs | 24 ++++++++++++------------ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 74af78cf6..e9e9d14cf 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ Drop is an open-source game distribution platform, similar to GameVault or Steam. It's designed to distribute and share DRM-free games quickly, all while being incredibly flexible, beautiful, and fast. +> [!NOTE] +> Drop is in maintenance mode. Maintainers will not contribute new large features, but bugs will continue to be fixed and external contributions will continue to be merged. +
Drop Screenshot
diff --git a/torrential/src/downloads/download.rs b/torrential/src/downloads/download.rs index d8cd4551a..8cebf7b26 100644 --- a/torrential/src/downloads/download.rs +++ b/torrential/src/downloads/download.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, time::Instant}; +use std::{path::PathBuf, sync::Arc, time::Instant}; use droplet_rs::{ manifest::Manifest, @@ -18,11 +18,11 @@ use crate::{ pub struct DownloadContext { pub(crate) manifest: Manifest, - pub(crate) backend: Box, + pub(crate) backend: Arc, last_access: Instant, } impl DownloadContext { - #[must_use] + #[must_use] pub fn last_access(&self) -> Instant { self.last_access } @@ -42,7 +42,7 @@ pub async fn create_download_context( let download_context = DownloadContext { manifest: convert_protobuf_manifest(version_data.manifest.unwrap()), - backend, + backend: Arc::from(backend), last_access: Instant::now(), }; diff --git a/torrential/src/downloads/serve.rs b/torrential/src/downloads/serve.rs index 5847826c8..64234f95a 100644 --- a/torrential/src/downloads/serve.rs +++ b/torrential/src/downloads/serve.rs @@ -14,7 +14,7 @@ use bytes::Bytes; use dashmap::{DashMap, mapref::one::RefMut}; use droplet_rs::{ manifest::ChunkData, - versions::types::{MinimumFileObject, VersionFile}, + versions::types::{MinimumFileObject, VersionBackend, VersionFile}, }; use futures_util::{Stream, StreamExt, stream}; use log::{error, info}; @@ -72,12 +72,13 @@ pub async fn serve_file( State(state): State>, Path((game_id, version_name, chunk_id)): Path<(String, String, String)>, ) -> Result { - let context_cache = &state.context_cache; - - let mut context = get_or_create_context(&state, context_cache, game_id, version_name).await?; - context.reset_last_access(); - - let chunk_data = lookup_chunk(&chunk_id, &context)?; + let (backend, key, chunk_data) = { + let mut context = + get_or_create_context(&state, &state.context_cache, game_id, version_name).await?; + context.reset_last_access(); + let chunk_data = lookup_chunk(&chunk_id, &context)?; + (context.backend.clone(), context.manifest.key, chunk_data) + }; if chunk_data.files.len() >= *SEMPAHORE_COUNT { return Err(StatusCode::INSUFFICIENT_STORAGE); } @@ -90,7 +91,7 @@ pub async fn serve_file( for file_entry in &chunk_data.files { let reader = get_file_reader( - &mut context, + &backend, file_entry.filename.clone(), file_entry.start, file_entry.start + file_entry.length, @@ -103,7 +104,7 @@ pub async fn serve_file( } let stream = stream::iter(streams).flatten(); - let mut cipher = Aes128Ctr64LE::new(&context.manifest.key.into(), &chunk_data.iv.into()); + let mut cipher = Aes128Ctr64LE::new(&key.into(), &chunk_data.iv.into()); let encrypted_stream = stream.chunks(16).map(move |raw| -> Result { let data: Result, Error> = raw.into_iter().collect(); let mut data = data?.concat(); @@ -142,13 +143,12 @@ fn lookup_chunk( .ok_or(StatusCode::NOT_FOUND) } async fn get_file_reader( - context: &mut RefMut<'_, (String, String), DownloadContext>, + backend: &Arc, relative_filename: String, start: usize, end: usize, ) -> Result, StatusCode> { - context - .backend + backend .reader( &VersionFile { relative_filename: relative_filename.clone(), From 0f8b7adc502e7cc200d12dec44ef44dd9e760864 Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 21:49:34 +0000 Subject: [PATCH 02/11] Sync admin group with other groups --- server/server/internal/auth/oidc/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/server/internal/auth/oidc/index.ts b/server/server/internal/auth/oidc/index.ts index 014df02a3..cfec3da6f 100644 --- a/server/server/internal/auth/oidc/index.ts +++ b/server/server/internal/auth/oidc/index.ts @@ -519,11 +519,18 @@ export class OIDCManager { }) ).map((g) => ({ id: g.id })); + const data = { + groups: { set: groupsToSet }, + ...(this.adminGroup !== undefined && { + admin: oidcGroups.includes(this.adminGroup), + }), + }; + // SAFETY: existence verified above via findUnique // eslint-disable-next-line drop/no-prisma-delete await prisma.user.update({ where: { id: userId }, - data: { groups: { set: groupsToSet } }, + data, }); } From 07207d02151ee9dbda857b329093d584dc049ee6 Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 21:57:28 +0000 Subject: [PATCH 03/11] Remove .gitlab-ci.yml --- desktop/.gitlab-ci.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 desktop/.gitlab-ci.yml diff --git a/desktop/.gitlab-ci.yml b/desktop/.gitlab-ci.yml deleted file mode 100644 index 0fc3abe6f..000000000 --- a/desktop/.gitlab-ci.yml +++ /dev/null @@ -1,31 +0,0 @@ -stages: - - build - -build-linux: - stage: build - image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/rustlang/rust:nightly - script: - - apt-get update -y - - apt-get install yarnpkg libsoup-3.0-0 libsoup-3.0-dev libatk-adaptor libgtk-3-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev -y - - yarnpkg - - export - - export RUST_LOG=warn - - yarnpkg tauri build - - cp src-tauri/target/release/bundle/deb/*.deb . - - cp src-tauri/target/release/bundle/rpm/*.rpm . - - cp src-tauri/target/release/bundle/appimage/*.AppImage . - artifacts: - paths: - - "*.{deb,rpm,AppImage}" - -build-windows: - stage: build - tags: - - windows - script: - - yarn - - yarn tauri build - - cp src-tauri/target/release/bundle/nsis/*.exe . - artifacts: - paths: - - "*.exe" From 07a6feeabbae728345d7a99c530d45fba537b04c Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 22:01:59 +0000 Subject: [PATCH 04/11] Fix desktop client publish --- .github/workflows/client-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/client-release.yml b/.github/workflows/client-release.yml index 6dc41a2db..cb6b8c755 100644 --- a/.github/workflows/client-release.yml +++ b/.github/workflows/client-release.yml @@ -130,6 +130,7 @@ jobs: APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} NO_STRIP: true with: + releaseId: ${{ github.event.release.id || '' }} tagName: ${{ github.event.release.tag_name || inputs.tagName || 'v__VERSION__' }} # the action automatically replaces \_\_VERSION\_\_ with the app version. releaseName: "Auto-release v__VERSION__" releaseBody: "See the assets to download this version and install. This release was created automatically." From c096607ee3bc4ea7f45d4791105c5ceaba610bff Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 22:12:07 +0000 Subject: [PATCH 05/11] Fix PCGamingWiki metadata provider --- server/.env.example | 4 + .../server/internal/metadata/pcgamingwiki.ts | 143 +++++++++++++++++- .../docs/admin/metadata/pcgamingwiki.md | 5 - .../docs/admin/metadata/pcgamingwiki.mdx | 30 ++++ 4 files changed, 176 insertions(+), 6 deletions(-) delete mode 100644 sites/docs/src/content/docs/admin/metadata/pcgamingwiki.md create mode 100644 sites/docs/src/content/docs/admin/metadata/pcgamingwiki.mdx diff --git a/server/.env.example b/server/.env.example index dbd70e578..5567a5666 100644 --- a/server/.env.example +++ b/server/.env.example @@ -6,6 +6,10 @@ GIANT_BOMB_API_KEY="" IGDB_CLIENT_ID="" IGDB_CLIENT_SECRET="" +# PCGamingWiki bot account credentials (create a bot password in MediaWiki) +PCGAMINGWIKI_BOT_USERNAME="" +PCGAMINGWIKI_BOT_PASSWORD="" + EXTERNAL_URL="http://localhost:3000" NUXT_PORT=4000 diff --git a/server/server/internal/metadata/pcgamingwiki.ts b/server/server/internal/metadata/pcgamingwiki.ts index 401854491..3a8adcaf7 100644 --- a/server/server/internal/metadata/pcgamingwiki.ts +++ b/server/server/internal/metadata/pcgamingwiki.ts @@ -1,5 +1,6 @@ import type { CompanyModel } from "~/prisma/client/models"; import { MetadataSource } from "~/prisma/client/enums"; +import { MissingMetadataProviderConfig } from "."; import type { MetadataProvider } from "."; import type { GameMetadataSearchResult, @@ -16,6 +17,8 @@ import { type } from "arktype"; import type { TaskRunContext } from "../tasks"; import { logger } from "~/server/internal/logging"; import type { NitroFetchOptions, NitroFetchRequest } from "nitropack"; +import cacheHandler from "../cache"; +import { systemConfig } from "../config/sys-conf"; interface PCGamingWikiParseRawPage { parse: { @@ -83,6 +86,21 @@ interface PCGamingWikiCargoResult { }; } +interface PCGamingWikiLoginToken { + query: { + tokens: { + logintoken: string; + }; + }; +} + +interface PCGamingWikiLoginResult { + login: { + result: string; + reason?: string; + }; +} + type StringArrayKeys = { [K in keyof T]: T[K] extends string | string[] | null ? K : never; }[keyof T]; @@ -94,6 +112,32 @@ const ratingProviderReview = type({ // Api Docs: https://www.pcgamingwiki.com/wiki/PCGamingWiki:API // Good tool for helping build cargo queries: https://www.pcgamingwiki.com/wiki/Special:CargoQuery export class PCGamingWikiProvider implements MetadataProvider { + private apiCache = cacheHandler.createCache< + PCGamingWikiParseRawPage | PCGamingWikiCargoResult + >("PCGamingWiki"); + private requestQueue: Promise = Promise.resolve(); + private nextRequestAt = 0; + private cookies = new Map(); + private loginPromise: Promise | undefined; + private username: string; + private password: string; + private userAgent = `Drop/${systemConfig.getDropVersion()} (https://github.com/Drop-OSS/drop)`; + + constructor() { + this.username = process.env.PCGAMINGWIKI_BOT_USERNAME ?? ""; + this.password = process.env.PCGAMINGWIKI_BOT_PASSWORD ?? ""; + if (!this.username) + throw new MissingMetadataProviderConfig( + "PCGAMINGWIKI_BOT_USERNAME", + this.name(), + ); + if (!this.password) + throw new MissingMetadataProviderConfig( + "PCGAMINGWIKI_BOT_PASSWORD", + this.name(), + ); + } + name() { return "PCGamingWiki"; } @@ -106,16 +150,113 @@ export class PCGamingWikiProvider implements MetadataProvider { options?: NitroFetchOptions, ) { const finalURL = `https://www.pcgamingwiki.com/w/api.php?${query.toString()}`; + const cacheKey = `response:${finalURL}`; + const cacheable = + query.get("action") !== "login" && + !(query.get("action") === "query" && query.get("meta") === "tokens"); + if (cacheable) { + const cached = await this.apiCache.get(cacheKey); + if (cached !== null) return cached as T; + } - const response = await $fetch(finalURL, options); + const response = await this.enqueueRequest(finalURL, options); + const hasApiError = + typeof response === "object" && response !== null && "error" in response; + if (cacheable && !hasApiError) { + await this.apiCache.set( + cacheKey, + response as PCGamingWikiParseRawPage | PCGamingWikiCargoResult, + { + ttl: 6 * 60 * 60 * 1000, + }, + ); + } return response; } + private async enqueueRequest( + url: string, + options?: NitroFetchOptions, + ) { + const request = this.requestQueue.then(async () => { + const delay = this.nextRequestAt - Date.now(); + if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay)); + + const headers = new Headers(options?.headers); + headers.set("User-Agent", this.userAgent); + if (this.cookies.size > 0) { + headers.set( + "Cookie", + [...this.cookies.entries()] + .map(([name, value]) => `${name}=${value}`) + .join("; "), + ); + } + this.nextRequestAt = Date.now() + 1000; + + const response = await $fetch.raw(url, { ...options, headers }); + for (const setCookie of response.headers.getSetCookie()) { + const cookie = setCookie.split(";", 1)[0] ?? ""; + if (cookie === "") continue; + const separator = cookie.indexOf("="); + if (separator > 0) + this.cookies.set( + cookie.slice(0, separator), + cookie.slice(separator + 1), + ); + } + return response._data as T; + }); + + this.requestQueue = request.then( + () => undefined, + () => undefined, + ); + return request; + } + + private async authenticate() { + if (!this.loginPromise) { + this.loginPromise = this.login().catch((error: unknown) => { + this.loginPromise = undefined; + throw error; + }); + } + await this.loginPromise; + } + + private async login() { + const tokenResponse = await this.request( + new URLSearchParams({ + action: "query", + meta: "tokens", + type: "login", + format: "json", + }), + ); + const loginResponse = await this.request( + new URLSearchParams({ action: "login", format: "json" }), + { + method: "post", + body: new URLSearchParams({ + lgname: this.username, + lgpassword: this.password, + lgtoken: tokenResponse.query.tokens.logintoken, + }), + }, + ); + if (loginResponse.login.result !== "Success") + throw new Error( + `PCGamingWiki login failed: ${loginResponse.login.reason ?? loginResponse.login.result}`, + ); + } + private async cargoQuery( query: URLSearchParams, options?: NitroFetchOptions, ) { + await this.authenticate(); const response = await this.request>( query, options, diff --git a/sites/docs/src/content/docs/admin/metadata/pcgamingwiki.md b/sites/docs/src/content/docs/admin/metadata/pcgamingwiki.md deleted file mode 100644 index 296cc3839..000000000 --- a/sites/docs/src/content/docs/admin/metadata/pcgamingwiki.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: PCGamingWiki ---- - -PCGamingWiki is a community run game database. There no additional setup required to use PCGamingWiki. diff --git a/sites/docs/src/content/docs/admin/metadata/pcgamingwiki.mdx b/sites/docs/src/content/docs/admin/metadata/pcgamingwiki.mdx new file mode 100644 index 000000000..8c583a491 --- /dev/null +++ b/sites/docs/src/content/docs/admin/metadata/pcgamingwiki.mdx @@ -0,0 +1,30 @@ +--- +title: PCGamingWiki +--- + +import { Steps } from "@astrojs/starlight/components"; + +PCGamingWiki is a community-run game database. Drop uses a MediaWiki bot password to access its API. + + + +1. **Create a bot password** + + Sign in to PCGamingWiki and open [Special:BotPasswords](https://www.pcgamingwiki.com/wiki/Special:BotPasswords). Create a bot password and copy the bot username and generated password. Use these credentials, not your account's normal password. + +2. **Add the credentials to your `compose.yaml`** + + Set `PCGAMINGWIKI_BOT_USERNAME` and `PCGAMINGWIKI_BOT_PASSWORD` in the Drop service environment: + + ```diff + drop: + image: ghcr.io/drop-oss/drop:latest + ... + environment: + - DATABASE_URL=postgres://drop:drop@postgres:5432/drop + - EXTERNAL_URL=http://localhost:3000 # default, customise if accessing from another computer or behind a reverse proxy + + - PCGAMINGWIKI_BOT_USERNAME=[bot username] + + - PCGAMINGWIKI_BOT_PASSWORD=[bot password] + ``` + + \ No newline at end of file From 7274b3ae947cb035f368aa6faf83406d5d88f6bf Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 22:46:19 +0000 Subject: [PATCH 06/11] Fix PCGamingWiki import issue + types --- server/server/internal/auth/oidc/index.ts | 2 +- server/server/internal/metadata/igdb.ts | 35 +++++++++++-------- .../server/internal/metadata/pcgamingwiki.ts | 4 ++- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/server/server/internal/auth/oidc/index.ts b/server/server/internal/auth/oidc/index.ts index cfec3da6f..5ef0e23d4 100644 --- a/server/server/internal/auth/oidc/index.ts +++ b/server/server/internal/auth/oidc/index.ts @@ -517,7 +517,7 @@ export class OIDCManager { where: { name: { in: oidcGroups } }, select: { id: true }, }) - ).map((g) => ({ id: g.id })); + ).map((group: { id: string }) => ({ id: group.id })); const data = { groups: { set: groupsToSet }, diff --git a/server/server/internal/metadata/igdb.ts b/server/server/internal/metadata/igdb.ts index 306622d51..1837344e3 100644 --- a/server/server/internal/metadata/igdb.ts +++ b/server/server/internal/metadata/igdb.ts @@ -88,7 +88,7 @@ interface IGDBSearchStub extends IGDBItem { name: string; cover?: IGDBID; first_release_date?: number; // unix timestamp - summary: string; + summary?: string; } interface IGDBAgeRating extends IGDBItem { @@ -315,11 +315,12 @@ export class IGDBProvider implements MetadataProvider { const body = `where id = ${mediaID}; fields image_id;`; const response = await this.request(type, body); - if (!response.length || !response[0].image_id) { + const media = response[0]; + if (!media?.image_id) { throw new Error(`No image_id found for ${type} with id ${mediaID}`); } - const imageId = response[0].image_id; + const imageId = media.image_id; const result = `https://images.igdb.com/igdb/image/upload/${size}/${imageId}.jpg`; return result; @@ -395,7 +396,9 @@ export class IGDBProvider implements MetadataProvider { results.push({ organization, rating, - ratingCoverUrl: ar.rating_cover_url, + ...(ar.rating_cover_url !== undefined && { + ratingCoverUrl: ar.rating_cover_url, + }), }); } } @@ -415,21 +418,21 @@ export class IGDBProvider implements MetadataProvider { const response = await this.request("games", body); const results: GameMetadataSearchResult[] = []; - for (let i = 0; i < response.length; i++) { + for (const game of response) { let icon = ""; - const cover = response[i].cover; + const cover = game.cover; if (cover !== undefined) { icon = await this.getIconURL(cover); } else { icon = ""; } - const firstReleaseDate = response[i].first_release_date; + const firstReleaseDate = game.first_release_date; results.push({ - id: "" + response[i].id, - name: response[i].name, + id: "" + game.id, + name: game.name, icon, - description: response[i].summary, + description: game.summary ?? "", year: firstReleaseDate === undefined ? 0 @@ -547,18 +550,20 @@ export class IGDBProvider implements MetadataProvider { const genres = await this.getGenres(currentGame.genres); const ageRatings = await this.getAgeRatings(currentGame.age_ratings); + const summary = currentGame.summary ?? ""; + const storyline = currentGame.storyline ?? ""; let description = ""; let shortDescription = ""; - if (currentGame.summary.length > (currentGame.storyline?.length ?? 0)) { - description = currentGame.summary; + if (summary.length > storyline.length) { + description = summary; shortDescription = this.trimMessage( - currentGame.storyline ?? currentGame.summary, + storyline || summary, 280, ); } else { - description = currentGame.storyline ?? currentGame.summary; - shortDescription = this.trimMessage(currentGame.summary, 280); + description = storyline || summary; + shortDescription = this.trimMessage(summary, 280); } const metadata = { diff --git a/server/server/internal/metadata/pcgamingwiki.ts b/server/server/internal/metadata/pcgamingwiki.ts index 3a8adcaf7..32600fe47 100644 --- a/server/server/internal/metadata/pcgamingwiki.ts +++ b/server/server/internal/metadata/pcgamingwiki.ts @@ -621,7 +621,9 @@ export class PCGamingWikiProvider implements MetadataProvider { const icon = createObject(jdenticon.toPng(query, 512)); for (let i = 0; i < res.cargoquery.length; i++) { - const company = res.cargoquery[i].title; + const row = res.cargoquery[i]; + if (!row) continue; + const company = row.title; const fixedCompanyName = this.parseWikiStringArray(company.PageName)[0] ?? company.PageName; From 19a8c38c6dbfc7e5f2527642ddfcd99a22de7fae Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 22:46:34 +0000 Subject: [PATCH 07/11] Add AUR deployment --- .github/workflows/client-release.yml | 65 ++++++++++++++++++++++++++++ packages/drop-oss-app-bin/.SRCINFO | 20 +++++++++ packages/drop-oss-app-bin/PKGBUILD | 24 ++++++++++ packages/drop-oss-app/.SRCINFO | 22 ++++++++++ packages/drop-oss-app/PKGBUILD | 48 ++++++++++++++++++++ packages/update-aur-packages.mjs | 52 ++++++++++++++++++++++ 6 files changed, 231 insertions(+) create mode 100644 packages/drop-oss-app-bin/.SRCINFO create mode 100644 packages/drop-oss-app-bin/PKGBUILD create mode 100644 packages/drop-oss-app/.SRCINFO create mode 100644 packages/drop-oss-app/PKGBUILD create mode 100644 packages/update-aur-packages.mjs diff --git a/.github/workflows/client-release.yml b/.github/workflows/client-release.yml index cb6b8c755..620e64fc3 100644 --- a/.github/workflows/client-release.yml +++ b/.github/workflows/client-release.yml @@ -138,3 +138,68 @@ jobs: prerelease: true args: ${{ matrix.args }} projectPath: "./desktop" + + publish-aur: + needs: publish-tauri + runs-on: ubuntu-22.04 + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + + - name: Update and publish AUR packages + env: + AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tagName }} + run: | + # stop the script on errors and unset variables + set -euo pipefail + + # require the ssh key used to publish to aur + if [[ -z "$AUR_SSH_PRIVATE_KEY" ]]; then + echo "AUR_SSH_PRIVATE_KEY is required to publish the AUR packages." >&2 + exit 1 + fi + + # read the desktop version and choose the release tag + version="$(jq -r '.version' desktop/src-tauri/tauri.conf.json)" + tag="${RELEASE_TAG:-v${version}}" + [[ "$version" =~ ^[0-9]+(\.[0-9]+)*$ ]] + [[ "$tag" =~ ^[A-Za-z0-9._-]+$ ]] + + # download the x86_64 deb package from the github release + deb_name="Drop.Desktop.Client_${version}_amd64.deb" + gh release download "$tag" \ + --repo "$GITHUB_REPOSITORY" \ + --pattern "$deb_name" \ + --dir "$RUNNER_TEMP" + deb_path="$RUNNER_TEMP/$deb_name" + + # configure ssh access to the aur git server + mkdir -p "$HOME/.ssh" + chmod 700 "$HOME/.ssh" + printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > "$HOME/.ssh/aur" + chmod 600 "$HOME/.ssh/aur" + ssh-keyscan -H aur.archlinux.org >> "$HOME/.ssh/known_hosts" + eval "$(ssh-agent -s)" + ssh-add "$HOME/.ssh/aur" + + # fill the release-specific values in both package recipes + node packages/update-aur-packages.mjs "$tag" "$version" "$deb_path" + + # publish each updated recipe to its aur repository + for package in drop-oss-app-bin drop-oss-app; do + package_dir="$RUNNER_TEMP/$package" + git clone "ssh://aur@aur.archlinux.org/$package.git" "$package_dir" + cp "packages/$package/PKGBUILD" "$package_dir/PKGBUILD" + cp "packages/$package/.SRCINFO" "$package_dir/.SRCINFO" + git -C "$package_dir" config user.name "github-actions[bot]" + git -C "$package_dir" config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git -C "$package_dir" add PKGBUILD .SRCINFO + if ! git -C "$package_dir" diff --cached --quiet; then + # create and push a commit only when the recipe changed + git -C "$package_dir" commit -m "Update to $version" + git -C "$package_dir" push origin HEAD:master + fi + done diff --git a/packages/drop-oss-app-bin/.SRCINFO b/packages/drop-oss-app-bin/.SRCINFO new file mode 100644 index 000000000..0d6abe417 --- /dev/null +++ b/packages/drop-oss-app-bin/.SRCINFO @@ -0,0 +1,20 @@ +pkgbase = drop-oss-app-bin + pkgdesc = The client application for the open-source, self-hosted game distribution platform Drop + pkgver = DROP_PKG_VER + pkgrel = DROP_PKG_REL + url = https://droposs.org/ + arch = any + license = LicenseRef-GPL-3.0 + makedepends = libarchive + depends = glib2 + depends = glibc + depends = gcc-libs + depends = webkit2gtk-4.1 + depends = umu-launcher + depends = libappindicator + depends = libayatana-appindicator + conflicts = drop-oss-app + source = https://github.com/Drop-OSS/drop/releases/download/DROP_RELEASE_TAG/Drop.Desktop.Client_DROP_PKG_VER_amd64.deb + sha256sums = DROP_DEB_SHA256 + +pkgname = drop-oss-app-bin diff --git a/packages/drop-oss-app-bin/PKGBUILD b/packages/drop-oss-app-bin/PKGBUILD new file mode 100644 index 000000000..a448a5e8f --- /dev/null +++ b/packages/drop-oss-app-bin/PKGBUILD @@ -0,0 +1,24 @@ +# Maintainer: DecDuck +# Maintainer: quexeky + +pkgname=drop-oss-app-bin +pkgver=DROP_PKG_VER +pkgrel=DROP_PKG_REL +pkgdesc="The client application for the open-source, self-hosted game distribution platform Drop" +arch=(any) +conflicts=('drop-oss-app') +url="https://droposs.org/" +license=('LicenseRef-GPL-3.0') +depends=('glib2' 'glibc' 'gcc-libs' 'webkit2gtk-4.1' 'umu-launcher' 'libappindicator' 'libayatana-appindicator') +makedepends=('libarchive') +source=("https://github.com/Drop-OSS/drop/releases/download/DROP_RELEASE_TAG/Drop.Desktop.Client_DROP_PKG_VER_amd64.deb") +sha256sums=('DROP_DEB_SHA256') + +prepare() { + bsdtar -xf ./*.deb + bsdtar -xf data.tar.* +} + +package() { + cp -r "$srcdir/usr" "$pkgdir" +} diff --git a/packages/drop-oss-app/.SRCINFO b/packages/drop-oss-app/.SRCINFO new file mode 100644 index 000000000..41eee28c1 --- /dev/null +++ b/packages/drop-oss-app/.SRCINFO @@ -0,0 +1,22 @@ +pkgbase = drop-oss-app + pkgdesc = Desktop client for the Drop OSS project. + pkgver = DROP_PKG_VER + pkgrel = DROP_PKG_REL + url = https://droposs.org/ + arch = any + license = AGPL-3.0-only + makedepends = pnpm + makedepends = cargo-nightly + makedepends = gcc + makedepends = pkgconf + depends = glib2 + depends = glibc + depends = gcc-libs + depends = webkit2gtk-4.1 + depends = umu-launcher + depends = libappindicator + depends = libayatana-appindicator + source = git+https://github.com/Drop-OSS/drop.git#tag=DROP_RELEASE_TAG + sha256sums = SKIP + +pkgname = drop-oss-app diff --git a/packages/drop-oss-app/PKGBUILD b/packages/drop-oss-app/PKGBUILD new file mode 100644 index 000000000..a935184a1 --- /dev/null +++ b/packages/drop-oss-app/PKGBUILD @@ -0,0 +1,48 @@ +# Maintainer: quexeky +# Maintainer: DecDuck + +pkgname=drop-oss-app +pkgver=DROP_PKG_VER +pkgrel=DROP_PKG_REL +pkgdesc="Desktop client for the Drop OSS project." +arch=(any) +url="https://droposs.org/" +license=('AGPL-3.0-only') +depends=('glib2' 'glibc' 'gcc-libs' 'webkit2gtk-4.1' 'umu-launcher' 'libappindicator' 'libayatana-appindicator') +makedepends=('pnpm' 'cargo-nightly' 'gcc' 'pkgconf') +source=("git+https://github.com/Drop-OSS/drop.git#tag=DROP_RELEASE_TAG") +sha256sums=('SKIP') + +_desktop=" +[Desktop Entry]\n +Type=Application\n +Version=DROP_PKG_VER\n +Name=Drop Desktop App\n +Comment=Drop client for Linux\n +Path=/usr/bin/\n +Exec=drop-app\n +Icon=drop-oss-app\n +Terminal=false\n +" + +prepare() { + echo "$_desktop" > drop-oss-app.desktop +} + +build() { + cd drop + pnpm install + cd desktop + export RUSTUP_TOOLCHAIN=nightly + CFLAGS+=' -ffat-lto-objects' + pnpm tauri build --no-bundle +} + +package() { + echo -e "$_desktop" > drop-oss-app.desktop + install -Dm0755 -t "$pkgdir/usr/share/applications" "drop-oss-app.desktop" + cd drop/desktop + cp src-tauri/icons/icon.png ./drop-oss-app.png + install -Dm0755 -t "$pkgdir/usr/share/pixmaps" "./drop-oss-app.png" + install -Dm0755 -t "$pkgdir/usr/bin" "src-tauri/target/release/drop-app" +} diff --git a/packages/update-aur-packages.mjs b/packages/update-aur-packages.mjs new file mode 100644 index 000000000..58e1d316d --- /dev/null +++ b/packages/update-aur-packages.mjs @@ -0,0 +1,52 @@ +import { createHash } from "node:crypto"; +import { readFile, writeFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +function replaceToken(text, token, value, expectedCount) { + const actualCount = text.split(token).length - 1; + if (actualCount !== expectedCount) { + throw new Error(`Expected ${expectedCount} ${token} tokens, found ${actualCount}`); + } + return text.replaceAll(token, value); +} + +async function updateFile(path, transform) { + const original = await readFile(path, "utf8"); + await writeFile(path, transform(original)); +} + +const [tag, version, debPath, ...extraArgs] = process.argv.slice(2); +if (!tag || !version || !debPath || extraArgs.length > 0) { + throw new Error("usage: update-aur-packages.mjs TAG VERSION DEB_PATH"); +} +if (!/^[A-Za-z0-9._-]+$/.test(tag)) { + throw new Error("Release tag contains unsupported characters"); +} +if (!/^[0-9]+(?:\.[0-9]+)*$/.test(version)) { + throw new Error("Package version must contain numeric dot-separated components"); +} + +const packagesDir = dirname(fileURLToPath(import.meta.url)); +const checksum = createHash("sha256").update(await readFile(debPath)).digest("hex"); +const tokens = [ + ["DROP_PKG_VER", version], + ["DROP_PKG_REL", "1"], + ["DROP_RELEASE_TAG", tag], + ["DROP_DEB_SHA256", checksum], +]; +const files = [ + { packageName: "drop-oss-app", fileName: "PKGBUILD", tokenCounts: [2, 1, 1, 0] }, + { packageName: "drop-oss-app", fileName: ".SRCINFO", tokenCounts: [1, 1, 1, 0] }, + { packageName: "drop-oss-app-bin", fileName: "PKGBUILD", tokenCounts: [2, 1, 1, 1] }, + { packageName: "drop-oss-app-bin", fileName: ".SRCINFO", tokenCounts: [2, 1, 1, 1] }, +]; + +for (const { packageName, fileName, tokenCounts } of files) { + await updateFile(join(packagesDir, packageName, fileName), (text) => + tokens.reduce( + (updated, [token, value], index) => replaceToken(updated, token, value, tokenCounts[index]), + text, + ), + ); +} \ No newline at end of file From 1c0de9f2765323ff3841771751244c4421c09a1e Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 22:51:28 +0000 Subject: [PATCH 08/11] Use default of umu-default for umu --- desktop/src-tauri/process/src/process_handlers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/src-tauri/process/src/process_handlers.rs b/desktop/src-tauri/process/src/process_handlers.rs index d3f343f25..9704e8b19 100644 --- a/desktop/src-tauri/process/src/process_handlers.rs +++ b/desktop/src-tauri/process/src/process_handlers.rs @@ -303,7 +303,7 @@ impl ProcessHandler for UMUNativeLauncher { .map_or("", |v| v); let game_id = if umu_id_override.is_empty() { - &game_version.version_id + "umu-default" } else { umu_id_override }; @@ -365,7 +365,7 @@ impl ProcessHandler for UMUCompatLauncher { .map_or("", |v| v); let game_id = if umu_id_override.is_empty() { - &game_version.version_id + "umu-default" } else { umu_id_override }; From 209a6e1ade586fefaccc4fa09c3a866e8a46ce25 Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 23:03:28 +0000 Subject: [PATCH 09/11] Fix server library performance issues --- .../migration.sql | 5 +++++ server/prisma/models/content.prisma | 4 ++++ server/server/internal/library/index.ts | 13 +++++++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 server/prisma/migrations/20261018000000_add_version_game_id_indexes/migration.sql diff --git a/server/prisma/migrations/20261018000000_add_version_game_id_indexes/migration.sql b/server/prisma/migrations/20261018000000_add_version_game_id_indexes/migration.sql new file mode 100644 index 000000000..6b9011b74 --- /dev/null +++ b/server/prisma/migrations/20261018000000_add_version_game_id_indexes/migration.sql @@ -0,0 +1,5 @@ +-- CreateIndex +CREATE INDEX "UnimportedGameVersion_gameId_idx" ON "UnimportedGameVersion"("gameId"); + +-- CreateIndex +CREATE INDEX "GameVersion_gameId_idx" ON "GameVersion"("gameId"); \ No newline at end of file diff --git a/server/prisma/models/content.prisma b/server/prisma/models/content.prisma index e3635d479..cb206e8f3 100644 --- a/server/prisma/models/content.prisma +++ b/server/prisma/models/content.prisma @@ -123,6 +123,8 @@ model UnimportedGameVersion { manifest Json fileList String[] + + @@index([gameId]) } // A particular set of files that relate to the version @@ -150,6 +152,8 @@ model GameVersion { requiredContent GameVersion[] @relation(name: "requiredContent") requiringContent GameVersion[] @relation(name: "requiredContent") + + @@index([gameId]) } model SetupConfiguration { diff --git a/server/server/internal/library/index.ts b/server/server/internal/library/index.ts index 3aafa6efc..aea7a50f4 100644 --- a/server/server/internal/library/index.ts +++ b/server/server/internal/library/index.ts @@ -219,8 +219,17 @@ class LibraryManager { ...where, include: { library: true, - versions: true, - unimportedGameVersions: true, + versions: { + select: { + versionPath: true, + }, + }, + unimportedGameVersions: { + select: { + id: true, + versionName: true, + }, + }, }, }); From f79d9d72c3f84b35efe579475efd46be9be3c145 Mon Sep 17 00:00:00 2001 From: decduck Date: Sun, 18 Oct 2026 23:14:20 +0000 Subject: [PATCH 10/11] Fix library pagination --- server/pages/admin/library/index.vue | 66 ++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/server/pages/admin/library/index.vue b/server/pages/admin/library/index.vue index 2f0f542b8..d36476744 100644 --- a/server/pages/admin/library/index.vue +++ b/server/pages/admin/library/index.vue @@ -496,19 +496,28 @@