Compare commits

...

20 Commits

Author SHA1 Message Date
DecDuck 38c11567ef Variety of bug fixes (#432)
* Fix #414

* Implement #268

* Add #269
2026-06-21 20:07:59 +10:00
DecDuck cbecd1161d Publish docs, update links (#431)
* Publish docs, update links

* Fix sitemap gen

* Migrate to Astro v6

* Fix server lint
2026-06-21 16:39:34 +10:00
DecDuck 9185089c99 Fix v0.4.0 process handler, add override menu (#430)
* Fix Windows and Linux launch

* Add process handler selector, pin Prisma

* Regenerate lcofkiel

* Fix torrential inclusion in image

* Fix layouting

* Implement tree kill for Windows

* Fix server lint
2026-06-21 15:24:33 +10:00
DecDuck 0290718ee0 Fix droposs.org build, finish website (#429)
* Fix compile issues

* Finish up website
2026-06-21 11:31:21 +10:00
DecDuck 2e86422004 Add lints, new website publish (#428)
* Add lints and new website

* Fix droplet CI

* Fix droplet ci again

* Fix clippy lints
2026-06-21 11:16:39 +10:00
DecDuck 796abf478f Fix GitHub Actions build (#427)
* Fix server build

* Remove server drop-base submod

* Update lockfile

* Use debian images for build

* Fix pino errors, lint

* Fix macOS keychain lookup
2026-06-21 10:37:54 +10:00
dependabot[bot] 062ddc0c24 chore(deps): bump next from 15.4.4 to 15.5.18 in /sites/promo (#413)
Bumps [next](https://github.com/vercel/next.js) from 15.4.4 to 15.5.18.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.4.4...v15.5.18)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 15.5.18
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 08:13:45 +10:00
dependabot[bot] ac1e0230ae chore(deps): bump nuxt from 3.20.1 to 3.21.6 in /desktop/main (#416)
Bumps [nuxt](https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt) from 3.20.1 to 3.21.6.
- [Release notes](https://github.com/nuxt/nuxt/releases)
- [Commits](https://github.com/nuxt/nuxt/commits/v3.21.6/packages/nuxt)

---
updated-dependencies:
- dependency-name: nuxt
  dependency-version: 3.21.6
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 08:13:01 +10:00
dependabot[bot] 8637ff52ef chore(deps): bump nuxt from 3.21.2 to 3.21.6 (#417)
Bumps [nuxt](https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt) from 3.21.2 to 3.21.6.
- [Release notes](https://github.com/nuxt/nuxt/releases)
- [Commits](https://github.com/nuxt/nuxt/commits/v3.21.6/packages/nuxt)

---
updated-dependencies:
- dependency-name: nuxt
  dependency-version: 3.21.6
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 08:12:37 +10:00
dependabot[bot] 18471a1d35 chore(deps): bump openssl from 0.10.75 to 0.10.80 in /desktop/src-tauri (#419)
Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.75 to 0.10.80.
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.75...openssl-v0.10.80)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.80
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 08:12:17 +10:00
dependabot[bot] c8bb84e0d8 chore(deps): bump tar from 0.4.44 to 0.4.46 in /desktop/src-tauri (#420)
Bumps [tar](https://github.com/composefs/tar-rs) from 0.4.44 to 0.4.46.
- [Release notes](https://github.com/composefs/tar-rs/releases)
- [Commits](https://github.com/composefs/tar-rs/compare/0.4.44...0.4.46)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 0.4.46
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 08:11:47 +10:00
dependabot[bot] a3974f6137 chore(deps-dev): bump nitropack from 2.13.3 to 2.13.4 (#410)
Bumps [nitropack](https://github.com/nitrojs/nitro) from 2.13.3 to 2.13.4.
- [Release notes](https://github.com/nitrojs/nitro/releases)
- [Changelog](https://github.com/nitrojs/nitro/blob/main/changelog.config.ts)
- [Commits](https://github.com/nitrojs/nitro/compare/v2.13.3...v2.13.4)

---
updated-dependencies:
- dependency-name: nitropack
  dependency-version: 2.13.4
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 12:28:18 +10:00
dependabot[bot] 0b4e20bd0f chore(deps): bump vite from 7.2.2 to 7.3.2 in /desktop/main (#388)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.2.2 to 7.3.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 12:27:20 +10:00
dependabot[bot] 546f47e40e chore(deps): bump defu from 6.1.4 to 6.1.6 in /desktop/main (#386)
Bumps [defu](https://github.com/unjs/defu) from 6.1.4 to 6.1.6.
- [Release notes](https://github.com/unjs/defu/releases)
- [Changelog](https://github.com/unjs/defu/blob/main/CHANGELOG.md)
- [Commits](https://github.com/unjs/defu/compare/v6.1.4...v6.1.6)

---
updated-dependencies:
- dependency-name: defu
  dependency-version: 6.1.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 12:26:57 +10:00
dependabot[bot] 516eaade4f chore(deps): bump time from 0.3.44 to 0.3.47 in /torrential (#381)
Bumps [time](https://github.com/time-rs/time) from 0.3.44 to 0.3.47.
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.44...v0.3.47)

---
updated-dependencies:
- dependency-name: time
  dependency-version: 0.3.47
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 12:26:43 +10:00
dependabot[bot] 784e42f177 chore(deps): bump bytes from 1.11.0 to 1.11.1 in /torrential (#380)
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 12:26:27 +10:00
dependabot[bot] 758baa9bbb chore(deps): bump quinn-proto from 0.11.13 to 0.11.14 in /torrential (#379)
Bumps [quinn-proto](https://github.com/quinn-rs/quinn) from 0.11.13 to 0.11.14.
- [Release notes](https://github.com/quinn-rs/quinn/releases)
- [Commits](https://github.com/quinn-rs/quinn/compare/quinn-proto-0.11.13...quinn-proto-0.11.14)

---
updated-dependencies:
- dependency-name: quinn-proto
  dependency-version: 0.11.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 12:26:12 +10:00
DecDuck bf7ce5927f Attempt fix monorepo build (#404)
* add latest changes and fix launcher

* add optional tag specify

* fix client release

* empty commit
2026-04-27 15:38:05 +10:00
Husky ff1144e016 Improve repo tooling (#398)
* add basic git files to root

* make server part of monorepo

* import promo

* import libraries base

* import docs

* import desktop

* move docs and promo
2026-04-20 11:44:38 +10:00
DecDuck 5bbe406e4c disable proxy buffering 2026-04-19 09:38:42 +10:00
236 changed files with 27877 additions and 40644 deletions
+6
View File
@@ -0,0 +1,6 @@
/sites
/cli
/desktop
/backend # go backend
node_modules
@@ -1,7 +1,12 @@
name: "publish" name: "Build and release desktop"
on: on:
workflow_dispatch: {} workflow_dispatch:
inputs:
tagName:
required: false
type: string
description: "tagName to be associated with this release."
release: release:
types: [published] types: [published]
# This can be used to automatically publish nightlies at UTC nighttime # This can be used to automatically publish nightlies at UTC nighttime
@@ -33,13 +38,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
submodules: true
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: setup pnpm - name: setup pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with: with:
version: 10
run_install: false run_install: false
- name: setup node - name: setup node
@@ -58,7 +61,7 @@ jobs:
- name: Rust cache - name: Rust cache
uses: swatinem/rust-cache@v2 uses: swatinem/rust-cache@v2
with: with:
workspaces: './src-tauri -> target' workspaces: './desktop/src-tauri -> target'
- name: install dependencies (ubuntu only) - name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm' # This must match the platform value defined above. if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm' # This must match the platform value defined above.
@@ -80,6 +83,10 @@ jobs:
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain security set-keychain-settings -t 3600 -u build.keychain
# Add build.keychain to the user keychain search list so that codesign
# (invoked later by tauri-action WITHOUT an explicit --keychain) can
# resolve the signing identity from it.
security list-keychains -d user -s build.keychain $(security list-keychains -d user | tr -d '"')
echo "Created keychain" echo "Created keychain"
@@ -115,14 +122,19 @@ jobs:
- uses: tauri-apps/tauri-action@v0 - uses: tauri-apps/tauri-action@v0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} # Do NOT set APPLE_CERTIFICATE / APPLE_CERTIFICATE_PASSWORD here. Doing so
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} # makes tauri-action import the cert into its own throwaway keychain and
# look up the identity by Apple-only name prefixes (e.g.
# "Developer ID Application:"), which never matches our "Drop OSS" cert
# and fails with "failed to resolve signing identity". Instead we rely on
# the build.keychain prepared above and only pass the resolved identity.
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
NO_STRIP: true NO_STRIP: true
with: with:
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. tagName: ${{ inputs.print_tags || 'v__VERSION__' }} # the action automatically replaces \_\_VERSION\_\_ with the app version.
releaseName: "Auto-release v__VERSION__" releaseName: "Auto-release v__VERSION__"
releaseBody: "See the assets to download this version and install. This release was created automatically." releaseBody: "See the assets to download this version and install. This release was created automatically."
releaseDraft: false releaseDraft: false
prerelease: true prerelease: true
args: ${{ matrix.args }} args: ${{ matrix.args }}
projectPath: './desktop'
+56
View File
@@ -0,0 +1,56 @@
name: Droplet CI
on:
push:
branches: [develop]
paths:
- "libraries/droplet/**"
- "libraries/droplet_types/**"
- "libraries/libarchive/**"
- ".github/workflows/droplet-ci.yml"
pull_request:
branches: [develop]
paths:
- "libraries/droplet/**"
- "libraries/droplet_types/**"
- "libraries/libarchive/**"
- ".github/workflows/droplet-ci.yml"
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
ci:
name: Build, Test, Lint
runs-on: ubuntu-latest
defaults:
run:
working-directory: libraries/droplet
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt, clippy
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: "./libraries/droplet -> target"
- name: Install libarchive
run: |
sudo apt-get update
sudo apt-get install -y libarchive-dev
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run Clippy (lint)
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Run tests
run: cargo test --all-features --all --verbose
+100
View File
@@ -0,0 +1,100 @@
name: Deploy website to GitHub Pages
on:
# Runs on pushes targeting the default branch
push:
branches: [develop]
paths:
- "sites/promo/**"
- "sites/docs/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- ".github/workflows/pages.yml"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment per the "pages" group, skipping runs queued
# between the in-progress run and the latest queued one. cancel-in-progress defaults
# to false, so in-flight production deployments are allowed to complete.
concurrency: "pages"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
# Only install the promo site (radiant) and docs site (docs-next) and their
# dependencies so the public website deploy stays decoupled from the
# server/desktop build pipelines.
- name: Install dependencies
run: pnpm install --filter radiant... --filter docs-next...
- name: Setup Pages
id: setup_pages
uses: actions/configure-pages@v5
- name: Restore cache
uses: actions/cache@v4
with:
path: |
sites/promo/.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('sites/promo/**.[jt]s', 'sites/promo/**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml') }}-
- name: Build promo site with Next.js
working-directory: sites/promo
run: pnpm run build
env:
PAGES_BASE_PATH: ${{ steps.setup_pages.outputs.base_path }}
- name: Build docs site with Astro
working-directory: sites/docs
run: pnpm run build
# Nest the Starlight docs (built with base: "/docs") inside the promo export
# so both ship from a single GitHub Pages deployment at /docs.
- name: Assemble docs into /docs
run: |
rm -rf sites/promo/out/docs
mkdir -p sites/promo/out/docs
cp -r sites/docs/dist/. sites/promo/out/docs/
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: sites/promo/out
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
@@ -1,12 +1,24 @@
name: CI name: Server CI
on: on:
push: push:
branches: branches: [develop]
- develop paths:
- "server/**"
- "libraries/base/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- ".github/workflows/server-ci.yml"
pull_request: pull_request:
branches: branches: [develop]
- develop paths:
- "server/**"
- "libraries/base/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- ".github/workflows/server-ci.yml"
permissions: permissions:
contents: read contents: read
@@ -18,8 +30,6 @@ jobs:
steps: steps:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
@@ -34,6 +44,7 @@ jobs:
run: pnpm install run: pnpm install
- name: Typecheck - name: Typecheck
working-directory: server
run: pnpm run typecheck run: pnpm run typecheck
lint: lint:
@@ -42,8 +53,6 @@ jobs:
steps: steps:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
@@ -58,4 +67,5 @@ jobs:
run: pnpm install run: pnpm install
- name: Lint - name: Lint
working-directory: server
run: pnpm run lint run: pnpm run lint
@@ -1,4 +1,4 @@
name: Release Workflow name: Build and release server
on: on:
workflow_dispatch: {} workflow_dispatch: {}
@@ -29,7 +29,6 @@ jobs:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: true
fetch-depth: 3 # fix for when this gets triggered by tag fetch-depth: 3 # fix for when this gets triggered by tag
fetch-tags: true fetch-tags: true
ref: ${{ github.ref }} ref: ${{ github.ref }}
+2
View File
@@ -0,0 +1,2 @@
dist/
node_modules/
+105
View File
@@ -0,0 +1,105 @@
# syntax=docker/dockerfile:1
# Pinned to bookworm so the glibc here matches the torrential build stage
# and the libarchive runtime package is named `libarchive13` (trixie renames it to libarchive13t64).
FROM node:lts-bookworm-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app
## so corepack knows pnpm's version
COPY . .
## prevent prompt to download
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
## setup for offline
RUN corepack pack
## don't call out to network anymore
ENV COREPACK_ENABLE_NETWORK=0
### INSTALL DEPS ONCE
FROM base AS deps
RUN pnpm install --frozen-lockfile --ignore-scripts
### BUILD TORRENTIAL
# Bookworm-pinned to match the runtime image's glibc (a trixie build would not run on bookworm).
FROM rustlang/rust:nightly-bookworm-slim AS torrential-build
## libarchive-dev + pkg-config let libarchive3-sys link libarchive dynamically (glibc).
## protobuf-compiler is kept for parity (torrential's build.rs uses a vendored protoc).
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config \
libarchive-dev \
protobuf-compiler \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
COPY . .
RUN cargo build --release --manifest-path ./torrential/Cargo.toml
### BUILD APP
FROM base AS build-system
ENV NODE_ENV=production
ENV NUXT_TELEMETRY_DISABLED=1
## add git so drop can determine its git ref at build
RUN apt-get update && apt-get install -y --no-install-recommends git \
&& rm -rf /var/lib/apt/lists/*
## copy deps and rest of project files
COPY . .
COPY --from=deps /app/node_modules ./node_modules
ARG BUILD_DROP_VERSION
ARG BUILD_GIT_REF
## build
RUN pnpm run --filter=drop postinstall && pnpm run --filter=drop build
# create run environment for Drop
FROM base AS run-system
ENV NODE_ENV=production
ENV NUXT_TELEMETRY_DISABLED=1
# The base stage's `COPY . .` puts the whole repo into the runtime WORKDIR (/app),
# but at runtime only the artifacts copied explicitly below are needed. Drop the
# inherited `torrential` source dir: the service resolves the binary by scanning
# the cwd for `torrential`, and a directory there is spawned as ./torrential and
# fails with EACCES. With it gone, resolution falls through to the `torrential`
# binary installed on PATH (/usr/bin/torrential) below.
RUN rm -rf /app/torrential
# RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn add --network-timeout 1000000 --no-lockfile --ignore-scripts prisma@6.11.1
## runtime deps:
## - libarchive13: torrential now links libarchive dynamically (glibc build)
## - p7zip-full: provides the 7z CLI
## - nginx: front-end proxy
## - openssl + ca-certificates: required by Prisma's query engine on Debian
## pnpm itself is provided by corepack (enabled in the base stage)
RUN apt-get update && apt-get install -y --no-install-recommends \
libarchive13 \
p7zip-full \
nginx \
openssl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN pnpm install prisma@7.7.0 --global
# init prisma to download all required files
RUN pnpm prisma init
COPY --from=build-system /app/server/prisma.config.ts ./
COPY --from=build-system /app/server/.output ./app
COPY --from=build-system /app/server/prisma ./prisma
COPY --from=build-system /app/server/build ./startup
COPY --from=build-system /app/server/build/nginx.conf /nginx.conf
COPY --from=torrential-build /build/torrential/target/release/torrential /usr/bin/
ENV LIBRARY="/library"
ENV DATA="/data"
ENV NGINX_CONFIG="/nginx.conf"
# Nuxt's port
ENV PORT=4000
CMD ["sh", "/app/startup/launch.sh"]
+2 -2
View File
@@ -6,7 +6,7 @@
# Drop # Drop
[![Website](https://img.shields.io/badge/website-000000?style=for-the-badge&logo=About.me&logoColor=white)](https://droposs.org) [![Website](https://img.shields.io/badge/website-000000?style=for-the-badge&logo=About.me&logoColor=white)](https://droposs.org)
[![Docs](https://img.shields.io/badge/DOCS-black?style=for-the-badge&logo=docusaurus)](https://docs.droposs.org/) [![Docs](https://img.shields.io/badge/DOCS-black?style=for-the-badge&logo=docusaurus)](https://droposs.org/docs)
[![Static Badge](https://img.shields.io/badge/FORUM-blue?style=for-the-badge)](https://forum.droposs.org) [![Static Badge](https://img.shields.io/badge/FORUM-blue?style=for-the-badge)](https://forum.droposs.org)
[![GitHub License](https://img.shields.io/badge/AGPL--3.0-red?style=for-the-badge)](LICENSE) [![GitHub License](https://img.shields.io/badge/AGPL--3.0-red?style=for-the-badge)](LICENSE)
[![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/ACq4qZp4a9) [![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/ACq4qZp4a9)
@@ -28,7 +28,7 @@ Drop is an open-source game distribution platform, similar to GameVault or Steam
## Deployment ## Deployment
See our documentation on how to [deploy Drop](https://docs.droposs.org/docs/guides/quickstart) for more information. See our documentation on how to [deploy Drop](https://droposs.org/docs/admin/quickstart) for more information.
## Contributing ## Contributing
+8
View File
@@ -611,6 +611,7 @@ version = "0.16.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"droplet_types",
"dyn-clone", "dyn-clone",
"futures", "futures",
"getrandom 0.3.4", "getrandom 0.3.4",
@@ -630,6 +631,13 @@ dependencies = [
"x509-parser 0.17.0", "x509-parser 0.17.0",
] ]
[[package]]
name = "droplet_types"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.5" version = "1.0.5"
-31
View File
@@ -1,31 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Arch Linux, Windows]
- App Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-23
View File
@@ -1,23 +0,0 @@
on: push
name: Clippy check
jobs:
clippy_check:
runs-on: ubuntu-24.04
permissions:
checks: write
steps:
- uses: actions/checkout@v1
- name: install dependencies (ubuntu only)
run: |
sudo apt-get update
sudo apt-get install -y libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
override: true
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --manifest-path ./src-tauri/Cargo.toml
@@ -0,0 +1,141 @@
<template>
<Listbox
as="div"
v-model="model.overrideHandler"
class="mt-6"
v-if="handlers.length > 1"
>
<ListboxLabel class="block text-sm/6 font-medium text-white"
>Launch method</ListboxLabel
>
<div class="relative mt-2">
<ListboxButton
class="grid w-full cursor-default grid-cols-1 rounded-md bg-white/5 py-1.5 pr-2 pl-3 text-left text-white outline-1 -outline-offset-1 outline-white/10 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-500 sm:text-sm/6"
>
<span
v-if="currentHandler"
class="col-start-1 row-start-1 truncate pr-6"
>{{ currentHandler.name }}</span
>
<span
v-else
class="col-start-1 row-start-1 truncate pr-6 italic text-zinc-400"
>Automatic</span
>
<ChevronUpDownIcon
class="col-start-1 row-start-1 size-5 self-center justify-self-end text-zinc-400 sm:size-4"
aria-hidden="true"
/>
</ListboxButton>
<transition
leave-active-class="transition ease-in duration-100"
leave-from-class=""
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-800 py-1 text-base outline-1 -outline-offset-1 outline-white/10 sm:text-sm"
>
<ListboxOption
as="template"
:value="undefined"
v-slot="{ active, selected }"
>
<li
:class="[
active ? 'bg-blue-500 text-white outline-hidden' : 'text-white',
'relative cursor-default py-2 pr-9 pl-3 select-none',
]"
>
<span
:class="[
selected ? 'font-semibold' : 'font-normal',
'block truncate italic',
]"
>Automatic</span
>
<span class="block truncate text-xs text-zinc-400"
>Pick the best method for this game.</span
>
<span
v-if="selected"
:class="[
active ? 'text-white' : 'text-blue-400',
'absolute inset-y-0 right-0 flex items-center pr-4',
]"
>
<CheckIcon class="size-5" aria-hidden="true" />
</span>
</li>
</ListboxOption>
<ListboxOption
as="template"
v-for="handler in handlers"
:key="handler.id"
:value="handler.id"
v-slot="{ active, selected }"
>
<li
:class="[
active ? 'bg-blue-500 text-white outline-hidden' : 'text-white',
'relative cursor-default py-2 pr-9 pl-3 select-none',
]"
>
<span
:class="[
selected ? 'font-semibold' : 'font-normal',
'block truncate',
]"
>{{ handler.name }}</span
>
<span class="block truncate text-xs text-zinc-400">{{
handler.description
}}</span>
<span
v-if="selected"
:class="[
active ? 'text-white' : 'text-blue-400',
'absolute inset-y-0 right-0 flex items-center pr-4',
]"
>
<CheckIcon class="size-5" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
<p class="mt-2 text-sm text-zinc-400">
Override how this game is launched.
</p>
</Listbox>
</template>
<script setup lang="ts">
import { invoke } from "@tauri-apps/api/core";
import {
Listbox,
ListboxButton,
ListboxLabel,
ListboxOption,
ListboxOptions,
} from "@headlessui/vue";
import { ChevronUpDownIcon } from "@heroicons/vue/16/solid";
import { CheckIcon } from "@heroicons/vue/20/solid";
import type { GameVersion } from "~/types";
type ProcessHandlerOption = { id: string; name: string; description: string };
const model = defineModel<GameVersion["userConfiguration"]>({ required: true });
const props = defineProps<{ gameId: string }>();
const handlers = await invoke<ProcessHandlerOption[]>("get_process_handlers", {
id: props.gameId,
});
const currentHandler = computed(() =>
handlers.find((v) => v.id == model.value.overrideHandler),
);
</script>
@@ -23,16 +23,19 @@
</p> </p>
<ProtonSelector v-model="model" v-if="$props.protonEnabled" /> <ProtonSelector v-model="model" v-if="$props.protonEnabled" />
<HandlerSelector v-model="model" :game-id="$props.gameId" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { GameVersion } from "~/types"; import type { GameVersion } from "~/types";
import ProtonSelector from "./ProtonSelector.vue"; import ProtonSelector from "./ProtonSelector.vue";
import HandlerSelector from "./HandlerSelector.vue";
const model = defineModel<GameVersion["userConfiguration"]>({ required: true }); const model = defineModel<GameVersion["userConfiguration"]>({ required: true });
const props = defineProps<{ const props = defineProps<{
protonEnabled: boolean; protonEnabled: boolean;
gameId: string;
}>(); }>();
</script> </script>
+3 -2
View File
@@ -1,7 +1,7 @@
<template> <template>
<ModalTemplate size-class="max-w-4xl" v-model="open"> <ModalTemplate size-class="max-w-4xl" v-model="open">
<template #default> <template #default>
<div class="flex flex-row gap-x-4 h-96"> <div class="flex flex-row gap-x-4 min-h-96">
<nav class="flex flex-1 flex-col" aria-label="Sidebar"> <nav class="flex flex-1 flex-col" aria-label="Sidebar">
<ul role="list" class="-mx-2 space-y-1"> <ul role="list" class="-mx-2 space-y-1">
<li v-for="(tab, tabIdx) in tabs" :key="tab.name"> <li v-for="(tab, tabIdx) in tabs" :key="tab.name">
@@ -29,11 +29,12 @@
</li> </li>
</ul> </ul>
</nav> </nav>
<div class="border-l-2 border-zinc-800 w-full grow pl-4 overflow-y-scroll"> <div class="border-l-2 border-zinc-800 w-full grow pl-4">
<component <component
v-model="configuration" v-model="configuration"
:is="tabs[currentTabIndex]?.page" :is="tabs[currentTabIndex]?.page"
:proton-enabled="protonEnabled" :proton-enabled="protonEnabled"
:game-id="props.gameId"
/> />
</div> </div>
</div> </div>
+1 -1
View File
@@ -22,7 +22,7 @@
"koa": "^2.16.1", "koa": "^2.16.1",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"micromark": "^4.0.1", "micromark": "^4.0.1",
"nuxt": "^3.16.0", "nuxt": "^4.4.8",
"scss": "^0.2.4", "scss": "^0.2.4",
"vue-router": "latest", "vue-router": "latest",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
+2957 -2333
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -53,6 +53,7 @@ export type GameVersion = {
userConfiguration: { userConfiguration: {
launchTemplate: string; launchTemplate: string;
overrideProtonPath: string; overrideProtonPath: string;
overrideHandler: string | undefined;
enableUpdates: boolean enableUpdates: boolean
}; };
setups: Array<{ platform: string }>; setups: Array<{ platform: string }>;
+1 -1
View File
@@ -7,7 +7,7 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"pino": "^9.7.0", "pino": "^9.14.0",
"pino-pretty": "^13.1.1", "pino-pretty": "^13.1.1",
"tauri": "^0.15.0" "tauri": "^0.15.0"
}, },
-5583
View File
File diff suppressed because it is too large Load Diff
-21
View File
@@ -1,21 +0,0 @@
onlyBuiltDependencies:
- sharp
overrides:
cross-spawn@<6.0.6: '>=6.0.6'
cross-spawn@>=7.0.0 <7.0.5: '>=7.0.5'
form-data@<2.5.4: '>=2.5.4'
got@<11.8.5: '>=11.8.5'
http-cache-semantics@<4.1.1: '>=4.1.1'
lodash@<4.17.21: '>=4.17.21'
lodash@>=4.0.0 <4.17.21: '>=4.17.21'
minimist@>=1.0.0 <1.2.6: '>=1.2.6'
nth-check@<2.0.1: '>=2.0.1'
semver-regex@<3.1.3: '>=3.1.3'
semver-regex@<3.1.4: '>=3.1.4'
semver@>=7.0.0 <7.5.2: '>=7.5.2'
sharp@<0.30.5: '>=0.30.5'
sharp@<0.32.6: '>=0.32.6'
tmp@<=0.2.3: '>=0.2.4'
tough-cookie@<4.1.3: '>=4.1.3'
trim-newlines@<3.0.1: '>=3.0.1'
+7 -7
View File
@@ -1392,6 +1392,7 @@ dependencies = [
"http-serde 2.1.1", "http-serde 2.1.1",
"humansize", "humansize",
"known-folders", "known-folders",
"libloading",
"log", "log",
"log4rs", "log4rs",
"md5 0.7.0", "md5 0.7.0",
@@ -3932,15 +3933,14 @@ dependencies = [
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.75" version = "0.10.80"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"cfg-if", "cfg-if",
"foreign-types 0.3.2", "foreign-types 0.3.2",
"libc", "libc",
"once_cell",
"openssl-macros", "openssl-macros",
"openssl-sys", "openssl-sys",
] ]
@@ -3970,9 +3970,9 @@ checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.111" version = "0.9.116"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -6241,9 +6241,9 @@ dependencies = [
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.44" version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [ dependencies = [
"filetime", "filetime",
"libc", "libc",
+4 -1
View File
@@ -82,7 +82,7 @@ sha1 = "0.10.6"
shared_child = "1.0.1" shared_child = "1.0.1"
slice-deque = "0.3.0" slice-deque = "0.3.0"
sysinfo = "0.36.1" sysinfo = "0.36.1"
tar = "0.4.44" tar = "0.4.46"
tauri-plugin-autostart = "*" tauri-plugin-autostart = "*"
tauri-plugin-deep-link = "*" tauri-plugin-deep-link = "*"
tauri-plugin-dialog = "*" tauri-plugin-dialog = "*"
@@ -136,6 +136,9 @@ tauri-build = { version = "*", features = [] }
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies] [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies]
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] } tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
[target."cfg(target_os = \"linux\")".dependencies]
libloading = "0.7"
[profile.release] [profile.release]
lto = true lto = true
panic = "abort" panic = "abort"
+1 -1
View File
@@ -12,7 +12,7 @@ rustix = "1.1.2"
serde = "1.0.228" serde = "1.0.228"
serde_json = "1.0.145" serde_json = "1.0.145"
serde_with = "3.15.0" serde_with = "3.15.0"
tar = "0.4.44" tar = "0.4.46"
tempfile = "3.23.0" tempfile = "3.23.0"
uuid = "1.18.1" uuid = "1.18.1"
whoami = "1.6.1" whoami = "1.6.1"
+3
View File
@@ -79,6 +79,7 @@ pub mod data {
UserConfiguration { UserConfiguration {
launch_template: "{}".to_owned(), launch_template: "{}".to_owned(),
override_proton_path: None, override_proton_path: None,
override_handler: None,
enable_updates: false, enable_updates: false,
} }
} }
@@ -88,6 +89,8 @@ pub mod data {
pub struct UserConfiguration { pub struct UserConfiguration {
pub launch_template: String, pub launch_template: String,
pub override_proton_path: Option<String>, pub override_proton_path: Option<String>,
#[serde(default)]
pub override_handler: Option<String>,
pub enable_updates: bool, pub enable_updates: bool,
} }
@@ -1,11 +1,15 @@
use std::{fs::create_dir_all, path::PathBuf, process::Command}; use std::{
fs::create_dir_all,
path::{Path, PathBuf},
process::Command,
};
use client::compat::{COMPAT_INFO, UMU_LAUNCHER_EXECUTABLE}; use client::compat::{COMPAT_INFO, UMU_LAUNCHER_EXECUTABLE};
use database::{ use database::{
Database, DownloadableMetadata, GameVersion, db::DATA_ROOT_DIR, platform::Platform, Database, DownloadableMetadata, GameVersion, db::DATA_ROOT_DIR, platform::Platform,
}; };
use crate::{error::ProcessError, process_manager::ProcessHandler}; use crate::{error::ProcessError, parser::ParsedCommand, process_manager::ProcessHandler};
pub struct MacLauncher; pub struct MacLauncher;
impl ProcessHandler for MacLauncher { impl ProcessHandler for MacLauncher {
@@ -25,11 +29,89 @@ impl ProcessHandler for MacLauncher {
} }
fn modify_command(&self, _command: &mut Command) {} fn modify_command(&self, _command: &mut Command) {}
fn id(&self) -> &'static str {
"macos"
}
fn name(&self) -> &'static str {
"Direct"
}
fn description(&self) -> &'static str {
"Launches the game directly on macOS."
}
} }
#[allow(dead_code)] #[allow(dead_code)]
const CREATE_NO_WINDOW: u32 = 0x08000000; const CREATE_NO_WINDOW: u32 = 0x08000000;
#[cfg_attr(not(target_os = "windows"), allow(unused_variables))]
fn apply_no_window(command: &mut Command) {
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
command.creation_flags(CREATE_NO_WINDOW);
}
}
enum WindowsLaunchStrategy {
Direct,
Cmd,
Powershell,
}
// Wrap a launch command for Windows; with no strategy, detect it from the file extension.
fn windows_launch_command(
launch_command: String,
current_dir: &str,
strategy: Option<WindowsLaunchStrategy>,
) -> Result<String, ProcessError> {
let mut parsed = ParsedCommand::parse(launch_command)?;
let strategy = strategy.unwrap_or_else(|| {
let extension = Path::new(&parsed.command)
.extension()
.and_then(|ext| ext.to_str())
.map(str::to_ascii_lowercase);
match extension.as_deref() {
Some("ps1") => WindowsLaunchStrategy::Powershell,
Some("exe") | Some("com") => WindowsLaunchStrategy::Direct,
_ => WindowsLaunchStrategy::Cmd,
}
});
match strategy {
// PowerShell scripts
WindowsLaunchStrategy::Powershell => {
parsed.make_absolute(PathBuf::from(current_dir));
let script = std::mem::replace(&mut parsed.command, "powershell".to_owned());
let mut args = vec![
"-NoProfile".to_owned(),
"-ExecutionPolicy".to_owned(),
"Bypass".to_owned(),
"-File".to_owned(),
script,
];
args.append(&mut parsed.args);
parsed.args = args;
}
// Direct executables
WindowsLaunchStrategy::Direct => {
parsed.make_absolute(PathBuf::from(current_dir));
}
// cmd.exe, for batch files, builtins, PATHEXT resolution, %VAR% expansion, etc.
WindowsLaunchStrategy::Cmd => {
let command = std::mem::replace(&mut parsed.command, "cmd".to_owned());
let mut args = vec!["/C".to_owned(), command];
args.append(&mut parsed.args);
parsed.args = args;
}
}
Ok(parsed.reconstruct())
}
pub struct WindowsLauncher; pub struct WindowsLauncher;
impl ProcessHandler for WindowsLauncher { impl ProcessHandler for WindowsLauncher {
fn create_launch_process( fn create_launch_process(
@@ -37,22 +119,169 @@ impl ProcessHandler for WindowsLauncher {
_meta: &DownloadableMetadata, _meta: &DownloadableMetadata,
launch_command: String, launch_command: String,
_game_version: &GameVersion, _game_version: &GameVersion,
_current_dir: &str, current_dir: &str,
_database: &Database, _database: &Database,
) -> Result<String, ProcessError> { ) -> Result<String, ProcessError> {
Ok(format!("cmd /C \"{}\"", launch_command)) windows_launch_command(launch_command, current_dir, None)
} }
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool { fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
true true
} }
#[allow(unused_variables)]
fn modify_command(&self, command: &mut Command) { fn modify_command(&self, command: &mut Command) {
#[cfg(target_os = "windows")] apply_no_window(command);
use std::os::windows::process::CommandExt; }
#[cfg(target_os = "windows")]
command.creation_flags(CREATE_NO_WINDOW); fn id(&self) -> &'static str {
"windows-auto"
}
fn name(&self) -> &'static str {
"Automatic"
}
fn description(&self) -> &'static str {
"Detects the file type and launches it directly, or through cmd or PowerShell."
}
}
pub struct WindowsDirectLauncher;
impl ProcessHandler for WindowsDirectLauncher {
fn create_launch_process(
&self,
_meta: &DownloadableMetadata,
launch_command: String,
_game_version: &GameVersion,
current_dir: &str,
_database: &Database,
) -> Result<String, ProcessError> {
windows_launch_command(launch_command, current_dir, Some(WindowsLaunchStrategy::Direct))
}
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
true
}
fn modify_command(&self, command: &mut Command) {
apply_no_window(command);
}
fn id(&self) -> &'static str {
"windows-direct"
}
fn name(&self) -> &'static str {
"Direct executable"
}
fn description(&self) -> &'static str {
"Runs the executable directly, without a shell."
}
}
pub struct WindowsCmdLauncher;
impl ProcessHandler for WindowsCmdLauncher {
fn create_launch_process(
&self,
_meta: &DownloadableMetadata,
launch_command: String,
_game_version: &GameVersion,
current_dir: &str,
_database: &Database,
) -> Result<String, ProcessError> {
windows_launch_command(launch_command, current_dir, Some(WindowsLaunchStrategy::Cmd))
}
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
true
}
fn modify_command(&self, command: &mut Command) {
apply_no_window(command);
}
fn id(&self) -> &'static str {
"windows-cmd"
}
fn name(&self) -> &'static str {
"Command Prompt (cmd)"
}
fn description(&self) -> &'static str {
"Launches through cmd.exe. Supports batch files, builtins and %VAR% expansion."
}
}
pub struct WindowsPowershellLauncher;
impl ProcessHandler for WindowsPowershellLauncher {
fn create_launch_process(
&self,
_meta: &DownloadableMetadata,
launch_command: String,
_game_version: &GameVersion,
current_dir: &str,
_database: &Database,
) -> Result<String, ProcessError> {
windows_launch_command(
launch_command,
current_dir,
Some(WindowsLaunchStrategy::Powershell),
)
}
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
true
}
fn modify_command(&self, command: &mut Command) {
apply_no_window(command);
}
fn id(&self) -> &'static str {
"windows-powershell"
}
fn name(&self) -> &'static str {
"PowerShell"
}
fn description(&self) -> &'static str {
"Runs the command as a PowerShell script (-File)."
}
}
pub struct LinuxNativeLauncher;
impl ProcessHandler for LinuxNativeLauncher {
fn create_launch_process(
&self,
_meta: &DownloadableMetadata,
launch_command: String,
_game_version: &GameVersion,
_current_dir: &str,
_database: &Database,
) -> Result<String, ProcessError> {
// Run native Linux games directly, no umu-run wrapper
Ok(launch_command)
}
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
true
}
fn modify_command(&self, _command: &mut Command) {}
fn id(&self) -> &'static str {
"linux-native"
}
fn name(&self) -> &'static str {
"Native (direct)"
}
fn description(&self) -> &'static str {
"Runs the native Linux game directly on the host."
} }
} }
@@ -101,6 +330,18 @@ impl ProcessHandler for UMUNativeLauncher {
} }
fn modify_command(&self, _command: &mut Command) {} fn modify_command(&self, _command: &mut Command) {}
fn id(&self) -> &'static str {
"linux-umu"
}
fn name(&self) -> &'static str {
"Steam Linux Runtime (umu-run)"
}
fn description(&self) -> &'static str {
"Runs the native Linux game inside umu-run's Steam Linux Runtime."
}
} }
pub struct UMUCompatLauncher; pub struct UMUCompatLauncher;
@@ -168,6 +409,18 @@ impl ProcessHandler for UMUCompatLauncher {
} }
fn modify_command(&self, _command: &mut Command) {} fn modify_command(&self, _command: &mut Command) {}
fn id(&self) -> &'static str {
"proton-umu"
}
fn name(&self) -> &'static str {
"Proton (umu-run)"
}
fn description(&self) -> &'static str {
"Runs the Windows game through Proton using umu-run."
}
} }
pub struct AsahiMuvmLauncher; pub struct AsahiMuvmLauncher;
@@ -228,4 +481,16 @@ impl ProcessHandler for AsahiMuvmLauncher {
} }
fn modify_command(&self, _command: &mut Command) {} fn modify_command(&self, _command: &mut Command) {}
fn id(&self) -> &'static str {
"proton-muvm"
}
fn name(&self) -> &'static str {
"Proton + muvm (Asahi)"
}
fn description(&self) -> &'static str {
"Runs through Proton inside a muvm microVM, for Apple Silicon / Asahi Linux."
}
} }
@@ -28,7 +28,8 @@ use crate::{
format::DropFormatArgs, format::DropFormatArgs,
parser::{LaunchParameters, ParsedCommand}, parser::{LaunchParameters, ParsedCommand},
process_handlers::{ process_handlers::{
AsahiMuvmLauncher, MacLauncher, UMUCompatLauncher, UMUNativeLauncher, WindowsLauncher, AsahiMuvmLauncher, LinuxNativeLauncher, MacLauncher, UMUCompatLauncher, UMUNativeLauncher,
WindowsCmdLauncher, WindowsDirectLauncher, WindowsLauncher, WindowsPowershellLauncher,
}, },
}; };
@@ -54,6 +55,13 @@ pub struct LaunchOption {
name: String, name: String,
} }
#[derive(Serialize)]
pub struct ProcessHandlerOption {
id: String,
name: String,
description: String,
}
impl ProcessManager<'_> { impl ProcessManager<'_> {
pub fn new(app_handle: AppHandle) -> Self { pub fn new(app_handle: AppHandle) -> Self {
let log_output_dir = DATA_ROOT_DIR.join("logs"); let log_output_dir = DATA_ROOT_DIR.join("logs");
@@ -76,6 +84,22 @@ impl ProcessManager<'_> {
(Platform::Windows, Platform::Windows), (Platform::Windows, Platform::Windows),
&WindowsLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static), &WindowsLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
), ),
(
(Platform::Windows, Platform::Windows),
&WindowsDirectLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
),
(
(Platform::Windows, Platform::Windows),
&WindowsCmdLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
),
(
(Platform::Windows, Platform::Windows),
&WindowsPowershellLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
),
(
(Platform::Linux, Platform::Linux),
&LinuxNativeLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
),
( (
(Platform::Linux, Platform::Linux), (Platform::Linux, Platform::Linux),
&UMUNativeLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static), &UMUNativeLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
@@ -101,7 +125,7 @@ impl ProcessManager<'_> {
match self.processes.get_mut(&game_id) { match self.processes.get_mut(&game_id) {
Some(process) => { Some(process) => {
process.manually_killed = true; process.manually_killed = true;
process.handle.kill()?; kill_process_tree(&process.handle)?;
let exit_status = process.handle.wait()?; let exit_status = process.handle.wait()?;
info!("exit status: {:?}", exit_status); info!("exit status: {:?}", exit_status);
Ok(()) Ok(())
@@ -188,7 +212,21 @@ impl ProcessManager<'_> {
&self, &self,
db_lock: &Database, db_lock: &Database,
target_platform: &Platform, target_platform: &Platform,
override_id: Option<&str>,
) -> Result<&(dyn ProcessHandler + Send + Sync), ProcessError> { ) -> Result<&(dyn ProcessHandler + Send + Sync), ProcessError> {
// An explicit override wins, as long as it's valid for the current platform.
if let Some(override_id) = override_id
&& let Some(handler) = self.game_launchers.iter().find(|e| {
let (e_current, e_target) = e.0;
e_current == self.current_platform
&& e_target == *target_platform
&& e.1.id() == override_id
&& e.1.valid_for_platform(db_lock, target_platform)
})
{
return Ok(handler.1);
}
Ok(self Ok(self
.game_launchers .game_launchers
.iter() .iter()
@@ -204,10 +242,44 @@ impl ProcessManager<'_> {
pub fn valid_platform(&self, platform: &Platform) -> bool { pub fn valid_platform(&self, platform: &Platform) -> bool {
let db_lock = borrow_db_checked(); let db_lock = borrow_db_checked();
let process_handler = self.fetch_process_handler(&db_lock, platform); let process_handler = self.fetch_process_handler(&db_lock, platform, None);
process_handler.is_ok() process_handler.is_ok()
} }
pub fn get_process_handlers(
&self,
game_id: String,
) -> Result<Vec<ProcessHandlerOption>, ProcessError> {
let db_lock = borrow_db_checked();
let meta = db_lock
.applications
.installed_game_version
.get(&game_id)
.cloned()
.ok_or(ProcessError::NotInstalled)?;
let target_platform = meta.target_platform;
let handlers = self
.game_launchers
.iter()
.filter(|e| {
let (e_current, e_target) = e.0;
e_current == self.current_platform
&& e_target == target_platform
&& e.1.valid_for_platform(&db_lock, &target_platform)
})
.map(|e| ProcessHandlerOption {
id: e.1.id().to_string(),
name: e.1.name().to_string(),
description: e.1.description().to_string(),
})
.collect();
Ok(handlers)
}
pub fn get_launch_options(game_id: String) -> Result<Vec<LaunchOption>, ProcessError> { pub fn get_launch_options(game_id: String) -> Result<Vec<LaunchOption>, ProcessError> {
let db_lock = borrow_db_checked(); let db_lock = borrow_db_checked();
@@ -310,7 +382,12 @@ impl ProcessManager<'_> {
let target_platform = meta.target_platform; let target_platform = meta.target_platform;
let process_handler = self.fetch_process_handler(&db_lock, &target_platform)?; let process_handler = self.fetch_process_handler(
&db_lock,
&target_platform,
game_version.user_configuration.override_handler.as_deref(),
)?;
debug!("using process handler {:?}", process_handler.id());
let (target_command, emulator) = match game_status { let (target_command, emulator) = match game_status {
GameDownloadStatus::Installed { GameDownloadStatus::Installed {
@@ -516,6 +593,30 @@ impl ProcessManager<'_> {
} }
} }
fn kill_process_tree(handle: &SharedChild) -> io::Result<()> {
#[cfg(target_os = "windows")]
{
// handle.kill() only terminates the launched process (often a cmd or
// powershell wrapper), orphaning the actual game. taskkill /T kills the
// whole process tree.
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000;
let pid = handle.id().to_string();
let killed = Command::new("taskkill")
.args(["/F", "/T", "/PID", pid.as_str()])
.creation_flags(CREATE_NO_WINDOW)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|status| status.success())
.unwrap_or(false);
if killed {
return Ok(());
}
}
handle.kill()
}
pub trait ProcessHandler: Send + 'static { pub trait ProcessHandler: Send + 'static {
fn create_launch_process( fn create_launch_process(
&self, &self,
@@ -529,4 +630,8 @@ pub trait ProcessHandler: Send + 'static {
fn valid_for_platform(&self, db: &Database, target: &Platform) -> bool; fn valid_for_platform(&self, db: &Database, target: &Platform) -> bool;
fn modify_command(&self, command: &mut Command); fn modify_command(&self, command: &mut Command);
fn id(&self) -> &'static str;
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
} }
+50 -10
View File
@@ -8,8 +8,17 @@
#![deny(clippy::all)] #![deny(clippy::all)]
use std::{ use std::{
env, fs::File, io::Write, panic::PanicHookInfo, path::Path, str::FromStr, env,
sync::nonpoison::Mutex, time::SystemTime, fs::File,
io::Write,
panic::PanicHookInfo,
path::Path,
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering},
nonpoison::Mutex,
},
time::SystemTime,
}; };
use ::client::{ use ::client::{
@@ -260,6 +269,7 @@ pub fn run() {
get_autostart_enabled, get_autostart_enabled,
open_process_logs, open_process_logs,
get_launch_options, get_launch_options,
get_process_handlers,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
::process::compat::fetch_proton_paths, ::process::compat::fetch_proton_paths,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@@ -359,8 +369,17 @@ pub fn run() {
) )
.expect("Failed to generate menu"); .expect("Failed to generate menu");
if env::var("NO_TRAY_ICON").is_ok_and(|value| value.to_lowercase() == "true") {
TRAY_DISABLED.store(true, Ordering::Relaxed);
} else if !tray_icon_supported() {
warn!(
"appindicator library not available at runtime, disabling system tray icon"
);
TRAY_DISABLED.store(true, Ordering::Relaxed);
}
run_on_tray(|| { run_on_tray(|| {
TrayIconBuilder::new() let tray = TrayIconBuilder::new()
.icon( .icon(
app.default_window_icon() app.default_window_icon()
.expect("Failed to get default window icon") .expect("Failed to get default window icon")
@@ -383,8 +402,12 @@ pub fn run() {
warn!("menu event not handled: {:?}", event.id); warn!("menu event not handled: {:?}", event.id);
} }
}) })
.build(app) .build(app);
.expect("error while setting up tray menu");
if let Err(e) = tray {
warn!("failed to set up system tray icon, disabling tray: {e}");
TRAY_DISABLED.store(true, Ordering::Relaxed);
}
}); });
{ {
@@ -445,13 +468,30 @@ pub fn run() {
}); });
} }
static TRAY_DISABLED: AtomicBool = AtomicBool::new(false);
#[cfg(target_os = "linux")]
fn tray_icon_supported() -> bool {
[
"libayatana-appindicator3.so.1",
"libappindicator3.so.1",
"libayatana-appindicator3.so",
"libappindicator3.so",
]
.iter()
.any(|name| unsafe { libloading::Library::new(name) }.is_ok())
}
#[cfg(not(target_os = "linux"))]
fn tray_icon_supported() -> bool {
true
}
fn run_on_tray<T: FnOnce()>(f: T) { fn run_on_tray<T: FnOnce()>(f: T) {
if match std::env::var("NO_TRAY_ICON") { if TRAY_DISABLED.load(Ordering::Relaxed) {
Ok(s) => s.to_lowercase() != "true", return;
Err(_) => true,
} {
(f)();
} }
(f)();
} }
// TODO: Refactor // TODO: Refactor
+6 -1
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use process::{ use process::{
PROCESS_MANAGER, PROCESS_MANAGER,
error::ProcessError, error::ProcessError,
process_manager::{LaunchOption, ProcessManager}, process_manager::{LaunchOption, ProcessHandlerOption, ProcessManager},
}; };
use serde::Serialize; use serde::Serialize;
use tauri::AppHandle; use tauri::AppHandle;
@@ -16,6 +16,11 @@ pub fn get_launch_options(id: String) -> Result<Vec<LaunchOption>, ProcessError>
Ok(launch_options) Ok(launch_options)
} }
#[tauri::command]
pub fn get_process_handlers(id: String) -> Result<Vec<ProcessHandlerOption>, ProcessError> {
PROCESS_MANAGER.lock().get_process_handlers(id)
}
#[derive(Serialize)] #[derive(Serialize)]
#[serde(tag = "result", content = "data")] #[serde(tag = "result", content = "data")]
pub enum LaunchResult { pub enum LaunchResult {
Submodule desktop/src-tauri/tailscale/libtailscale deleted from 78294ac1d6
-4205
View File
File diff suppressed because it is too large Load Diff
-3
View File
@@ -1,3 +0,0 @@
onlyBuiltDependencies:
- esbuild
- sharp
+1 -1
View File
@@ -14,7 +14,7 @@
"devDependencies": { "devDependencies": {
"@nuxt/eslint": "latest", "@nuxt/eslint": "latest",
"eslint": "^9.17.0", "eslint": "^9.17.0",
"nuxt": "^3.14.1592", "nuxt": "^3.21.6",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"vue": "latest" "vue": "latest"
}, },
-8465
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -1,4 +0,0 @@
onlyBuiltDependencies:
- '@parcel/watcher'
- esbuild
- unrs-resolver
-53
View File
@@ -1,53 +0,0 @@
name: Rust CI
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
env:
CARGO_TERM_COLOR: always
jobs:
ci:
name: Build, Test, Lint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 3 # fix for when this gets triggered by tag
fetch-tags: true
ref: ${{ github.ref }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt, clippy
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install libarchive
run: |
sudo apt-get install libarchive-dev -y
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run Clippy (lint)
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Run tests
run: cargo test --all-features --all --verbose
-2
View File
@@ -1,7 +1,5 @@
#![deny(clippy::all)] #![deny(clippy::all)]
#![feature(impl_trait_in_bindings)] #![feature(impl_trait_in_bindings)]
#![feature(nonpoison_mutex)]
#![feature(sync_nonpoison)]
pub mod file_utils; pub mod file_utils;
pub mod manifest; pub mod manifest;
pub mod ssl; pub mod ssl;
+1 -1
View File
@@ -1,6 +1,6 @@
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
use droplet_rs::manifest::{ManifestWriterFactory, generate_manifest_rusty}; use droplet_rs::manifest::{generate_manifest_rusty, ManifestWriterFactory};
use tokio::runtime::Handle; use tokio::runtime::Handle;
struct SinkFactory {} struct SinkFactory {}
+2 -3
View File
@@ -2,6 +2,7 @@ use std::{collections::HashMap, ops::Not, path::Path};
use anyhow::anyhow; use anyhow::anyhow;
use async_trait::async_trait; use async_trait::async_trait;
pub use droplet_types::{ChunkData, FileEntry, Manifest};
use futures::StreamExt; use futures::StreamExt;
use hex::ToHex as _; use hex::ToHex as _;
use humansize::{format_size, BINARY}; use humansize::{format_size, BINARY};
@@ -9,8 +10,6 @@ use sha2::{Digest as _, Sha256};
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::io::{AsyncReadExt as _, AsyncWrite}; use tokio::io::{AsyncReadExt as _, AsyncWrite};
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
pub use droplet_types::{ChunkData, FileEntry, Manifest};
pub const CHUNK_SIZE: u64 = 1024 * 1024 * 64; pub const CHUNK_SIZE: u64 = 1024 * 1024 * 64;
pub const MAX_FILE_COUNT: usize = 512; pub const MAX_FILE_COUNT: usize = 512;
@@ -44,7 +43,7 @@ where
"Could not create backend for path. Is this structure supported?" "Could not create backend for path. Is this structure supported?"
))?()?; ))?()?;
let mut files = backend.list_files().await?; let mut files = backend.list_files().await?;
files.sort_by(|a, b| b.size.cmp(&a.size)); files.sort_by_key(|b| std::cmp::Reverse(b.size));
log_sfn("organising files into chunks...".to_string()); log_sfn("organising files into chunks...".to_string());
+6 -4
View File
@@ -34,9 +34,11 @@ const SUPPORTED_FILE_EXTENSIONS: [&str; 11] = [
]; ];
pub mod types; pub mod types;
pub fn create_backend_constructor<'a, P>(
path: P, type BackendConstructor<'a> =
) -> Option<Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + Sync + 'a>>>> Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + Sync + 'a>>>;
pub fn create_backend_constructor<'a, P>(path: P) -> Option<BackendConstructor<'a>>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
@@ -53,7 +55,7 @@ where
})); }));
}; };
let file_extension = path.extension().map(|v| v.to_str()).flatten()?; let file_extension = path.extension().and_then(|v| v.to_str())?;
if SUPPORTED_FILE_EXTENSIONS.contains(&file_extension) { if SUPPORTED_FILE_EXTENSIONS.contains(&file_extension) {
let buf = path.to_path_buf(); let buf = path.to_path_buf();
return Some(Box::new(move || Ok(Box::new(ZipVersionBackend::new(buf)?)))); return Some(Box::new(move || Ok(Box::new(ZipVersionBackend::new(buf)?))));
-1
View File
@@ -24,4 +24,3 @@ pub struct Manifest {
pub size: u64, pub size: u64,
pub key: [u8; 16], pub key: [u8; 16],
} }
+5
View File
@@ -0,0 +1,5 @@
{
"name": "drop",
"private": true,
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
}
+23576
View File
File diff suppressed because it is too large Load Diff
+72
View File
@@ -0,0 +1,72 @@
packages:
- "server/"
- "libraries/base/"
- "sites/*"
- "desktop/"
onlyBuiltDependencies:
- "@bufbuild/buf"
- "@parcel/watcher"
- "@prisma/client"
- "@prisma/engines"
- "@tailwindcss/oxide"
- argon2
- esbuild
- prisma
- sharp
- unrs-resolver
- contentlayer
- contentlayer2
- protobufjs
overrides:
"@isaacs/brace-expansion@<=5.0.0": ">=5.0.1"
ajv@<6.14.0: ">=6.14.0"
devalue@<=5.6.2: ">=5.6.3"
devalue@>=5.1.0 <5.6.2: ">=5.6.2"
devalue@>=5.3.0 <=5.6.1: ">=5.6.2"
diff@>=6.0.0 <8.0.3: ">=8.0.3"
hono@<4.11.10: ">=4.11.10"
hono@<4.11.7: ">=4.11.7"
lodash-es@>=4.0.0 <=4.17.22: ">=4.17.23"
lodash@>=4.0.0 <=4.17.22: ">=4.17.23"
minimatch@<3.1.3: ">=3.1.3"
minimatch@<3.1.4: ">=3.1.4"
minimatch@>=10.0.0 <10.2.1: ">=10.2.1"
minimatch@>=10.0.0 <10.2.3: ">=10.2.3"
minimatch@>=5.0.0 <5.1.7: ">=5.1.7"
minimatch@>=5.0.0 <5.1.8: ">=5.1.8"
minimatch@>=9.0.0 <9.0.6: ">=9.0.6"
minimatch@>=9.0.0 <9.0.7: ">=9.0.7"
node-forge@<1.3.2: ">=1.3.2"
qs@<6.14.1: ">=6.14.1"
qs@>=6.7.0 <=6.14.1: ">=6.14.2"
rollup@>=4.0.0 <4.59.0: ">=4.59.0"
serialize-javascript@<=7.0.2: ">=7.0.3"
seroval@<1.4.1: ">=1.4.1"
seroval@<=1.4.0: ">=1.4.1"
tar@<7.5.7: ">=7.5.7"
tar@<7.5.8: ">=7.5.8"
tar@<=7.5.2: ">=7.5.3"
tar@<=7.5.3: ">=7.5.4"
undici@>=7.0.0 <7.18.2: ">=7.18.2"
cross-spawn@<6.0.6: ">=6.0.6"
cross-spawn@>=7.0.0 <7.0.5: ">=7.0.5"
form-data@<2.5.4: ">=2.5.4"
got@<11.8.5: ">=11.8.5"
http-cache-semantics@<4.1.1: ">=4.1.1"
lodash@<4.17.21: ">=4.17.21"
lodash@>=4.0.0 <4.17.21: ">=4.17.21"
minimist@>=1.0.0 <1.2.6: ">=1.2.6"
nth-check@<2.0.1: ">=2.0.1"
semver-regex@<3.1.3: ">=3.1.3"
semver-regex@<3.1.4: ">=3.1.4"
semver@>=7.0.0 <7.5.2: ">=7.5.2"
sharp@<0.30.5: ">=0.30.5"
sharp@<0.32.6: ">=0.32.6"
tmp@<=0.2.3: ">=0.2.4"
tough-cookie@<4.1.3: ">=4.1.3"
trim-newlines@<3.0.1: ">=3.0.1"
shamefullyHoist: true
-80
View File
@@ -1,80 +0,0 @@
name: Deploy Next.js site to Pages
on:
# Runs on pushes targeting the main branch
push:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 10
run_install: false
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Setup Pages
id: setup_pages
uses: actions/configure-pages@v5
- name: Restore cache
uses: actions/cache@v4
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- name: Build with Next.js
run: pnpm run build
env:
PAGES_BASE_PATH: ${{ steps.setup_pages.outputs.base_path }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./out
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
-5985
View File
File diff suppressed because it is too large Load Diff
-8
View File
@@ -1,8 +0,0 @@
onlyBuiltDependencies:
- '@tailwindcss/oxide'
- contentlayer
- contentlayer2
- esbuild
- protobufjs
- sharp
- unrs-resolver
-75
View File
@@ -1,75 +0,0 @@
# syntax=docker/dockerfile:1
FROM node:lts-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app
## so corepack knows pnpm's version
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
## prevent prompt to download
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
## setup for offline
RUN corepack pack
## don't call out to network anymore
ENV COREPACK_ENABLE_NETWORK=0
### INSTALL DEPS ONCE
FROM base AS deps
RUN pnpm install --frozen-lockfile --ignore-scripts
### BUILD TORRENTIAL
FROM rustlang/rust:nightly-alpine AS torrential-build
RUN apk add musl-dev
WORKDIR /build
COPY torrential .
RUN apk add protoc
RUN cargo build --release
### BUILD APP
FROM base AS build-system
ENV NODE_ENV=production
ENV NUXT_TELEMETRY_DISABLED=1
## add git so drop can determine its git ref at build
RUN apk add --no-cache git
## copy deps and rest of project files
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ARG BUILD_DROP_VERSION
ARG BUILD_GIT_REF
## build
RUN pnpm run postinstall && pnpm run build
# create run environment for Drop
FROM base AS run-system
ENV NODE_ENV=production
ENV NUXT_TELEMETRY_DISABLED=1
# RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn add --network-timeout 1000000 --no-lockfile --ignore-scripts prisma@6.11.1
RUN apk add --no-cache pnpm 7zip nginx
RUN pnpm install prisma@7.3.0
# init prisma to download all required files
RUN pnpm prisma init
COPY --from=build-system /app/prisma.config.ts ./
COPY --from=build-system /app/.output ./app
COPY --from=build-system /app/prisma ./prisma
COPY --from=build-system /app/build ./startup
COPY --from=build-system /app/build/nginx.conf /nginx.conf
COPY --from=torrential-build /build/target/release/torrential /usr/bin/
ENV LIBRARY="/library"
ENV DATA="/data"
ENV NGINX_CONFIG="/nginx.conf"
# NGINX's port
ENV PORT=4000
CMD ["sh", "/app/startup/launch.sh"]
+1 -1
View File
@@ -1,3 +1,3 @@
# Server # Server
The hosted, accessible portion of Drop. Exposes a web UI and API for applications to use. The hosted, accessible portion of Drop. Exposes a web UI and API for applications to use.
+1 -1
View File
@@ -5,4 +5,4 @@ plugins:
opt: target=ts opt: target=ts
inputs: inputs:
- directory: ../ - directory: ../
+2
View File
@@ -21,6 +21,8 @@ http {
scgi_temp_path scgi_temp; scgi_temp_path scgi_temp;
uwsgi_temp_path uwsgi_temp; uwsgi_temp_path uwsgi_temp;
proxy_buffering off;
server { server {
listen 3000; listen 3000;
server_name localhost; server_name localhost;
+2 -2
View File
@@ -19,7 +19,7 @@
</p> </p>
<NuxtLink <NuxtLink
class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500" class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
href="https://docs.droposs.org/docs/authentication/simple" href="https://droposs.org/docs/admin/authentication/simple"
target="_blank" target="_blank"
> >
<i18n-t <i18n-t
@@ -74,7 +74,7 @@
</p> </p>
<NuxtLink <NuxtLink
class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500" class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
href="https://docs.droposs.org/docs/authentication/oidc" href="https://droposs.org/docs/admin/authentication/oidc"
target="_blank" target="_blank"
> >
<i18n-t <i18n-t
+3 -2
View File
@@ -174,13 +174,14 @@ const optionsMetadata: {
Filesystem: { Filesystem: {
title: t("library.admin.sources.fsTitle"), title: t("library.admin.sources.fsTitle"),
description: t("library.admin.sources.fsDesc"), description: t("library.admin.sources.fsDesc"),
docsLink: "https://docs.droposs.org/docs/library#drop-style", docsLink: "https://droposs.org/docs/reference/library-sources#drop-style",
icon: DropLogo, icon: DropLogo,
}, },
FlatFilesystem: { FlatFilesystem: {
title: t("library.admin.sources.fsFlatTitle"), title: t("library.admin.sources.fsFlatTitle"),
description: t("library.admin.sources.fsFlatDesc"), description: t("library.admin.sources.fsFlatDesc"),
docsLink: "https://docs.droposs.org/docs/library#flat-style-or-compat", docsLink:
"https://droposs.org/docs/reference/library-sources#compatibility-flat-style",
icon: BackwardIcon, icon: BackwardIcon,
}, },
}; };
+2 -2
View File
@@ -134,11 +134,11 @@ const navigation = computed(() => ({
// { name: t("footer.api"), href: "https://api.droposs.org/" }, // { name: t("footer.api"), href: "https://api.droposs.org/" },
{ {
name: t("footer.docs.server"), name: t("footer.docs.server"),
href: "https://docs.droposs.org/docs/guides/quickstart", href: "https://droposs.org/docs/admin/quickstart",
}, },
{ {
name: t("footer.docs.client"), name: t("footer.docs.client"),
href: "https://docs.droposs.org/docs/guides/client", href: "https://droposs.org/docs/user",
}, },
], ],
about: [ about: [
+6 -1
View File
@@ -9,7 +9,12 @@ export const updateUser = async () => {
const user = useUser(); const user = useUser();
if (user.value === null) return; if (user.value === null) return;
user.value = await $dropFetch<UserModel | null>("/api/v1/user"); user.value = await $dropFetch<UserModel | null>("/api/v1/user", {
// Forward headers manually when called outside a component
headers: import.meta.server
? useRequestHeaders(["cookie", "authorization"])
: undefined,
});
}; };
export async function completeSignin() { export async function completeSignin() {
Submodule server/drop-base deleted from dad3487be6
+5
View File
@@ -11,7 +11,9 @@ export default withNuxt([
eslintConfigPrettier, eslintConfigPrettier,
// vue-i18n plugin // vue-i18n plugin
// @ts-expect-error
...vueI18n.configs.recommended, ...vueI18n.configs.recommended,
// @ts-expect-error
{ {
rules: { rules: {
// Optional. // Optional.
@@ -34,6 +36,9 @@ export default withNuxt([
messageSyntaxVersion: "^11.0.0", messageSyntaxVersion: "^11.0.0",
}, },
}, },
},
// @ts-expect-error
{
plugins: { plugins: {
drop: { rules: { "no-prisma-delete": noPrismaDelete } }, drop: { rules: { "no-prisma-delete": noPrismaDelete } },
}, },
+3
View File
@@ -547,6 +547,9 @@
"sources": { "sources": {
"create": "Create source", "create": "Create source",
"createDesc": "Drop will use this source to access your game library, and make them available.", "createDesc": "Drop will use this source to access your game library, and make them available.",
"deleteButton": "Delete source",
"deleteDesc": "Deleting \"{0}\" will cascade delete the library, all of its games, all of their versions, and all of their metadata. This action cannot be undone.",
"deleteTitle": "Delete library source?",
"desc": "Configure your library sources, where Drop will look for new games and versions to import.", "desc": "Configure your library sources, where Drop will look for new games and versions to import.",
"documentationLink": "Documentation {arrow}", "documentationLink": "Documentation {arrow}",
"edit": "Edit source", "edit": "Edit source",
+51 -14
View File
@@ -126,16 +126,50 @@
</div> </div>
<div <div
class="sticky top-0 z-40 flex items-center gap-x-6 bg-zinc-900 px-4 py-4 shadow-sm sm:px-6 lg:hidden" class="sticky top-0 z-40 lg:pl-20 border-b border-zinc-800 bg-zinc-950 shadow-sm"
> >
<button <div class="flex items-center gap-x-4 px-4 py-2 sm:px-6 lg:px-8">
type="button" <button
class="-m-2.5 p-2.5 text-zinc-400 lg:hidden" type="button"
@click="sidebarOpen = true" class="-m-2.5 p-2.5 text-zinc-400 lg:hidden"
> @click="sidebarOpen = true"
<span class="sr-only">{{ $t("header.openSidebar") }}</span> >
<Bars3Icon class="h-6 w-6" aria-hidden="true" /> <span class="sr-only">{{ $t("header.openSidebar") }}</span>
</button> <Bars3Icon class="h-6 w-6" aria-hidden="true" />
</button>
<div class="flex-1" />
<ol class="inline-flex items-center gap-3">
<li>
<Menu as="div" class="relative inline-block">
<MenuButton>
<UserHeaderWidget :notifications="unreadNotifications.length">
<BellIcon class="h-5" />
</UserHeaderWidget>
</MenuButton>
<transition
enter-active-class="transition ease-out duration-100"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95"
>
<MenuItems
class="absolute right-0 top-10 z-50 w-96 focus:outline-none shadow-md"
>
<UserHeaderNotificationWidgetPanel
:notifications="unreadNotifications"
/>
</MenuItems>
</transition>
</Menu>
</li>
<UserHeaderUserWidget />
</ol>
</div>
</div> </div>
<main class="lg:pl-20 min-h-screen bg-zinc-900 flex flex-col"> <main class="lg:pl-20 min-h-screen bg-zinc-900 flex flex-col">
@@ -156,6 +190,9 @@ import {
DialogPanel, DialogPanel,
TransitionChild, TransitionChild,
TransitionRoot, TransitionRoot,
Menu,
MenuButton,
MenuItems,
} from "@headlessui/vue"; } from "@headlessui/vue";
import { import {
Bars3Icon, Bars3Icon,
@@ -168,7 +205,7 @@ import {
} from "@heroicons/vue/24/outline"; } from "@heroicons/vue/24/outline";
import type { NavigationItem } from "~/composables/types"; import type { NavigationItem } from "~/composables/types";
import { useCurrentNavigationIndex } from "~/composables/current-page-engine"; import { useCurrentNavigationIndex } from "~/composables/current-page-engine";
import { ArrowLeftIcon } from "@heroicons/vue/16/solid"; import { ArrowLeftIcon, BellIcon } from "@heroicons/vue/16/solid";
import { XMarkIcon } from "@heroicons/vue/24/solid"; import { XMarkIcon } from "@heroicons/vue/24/solid";
import type { Settings } from "~/server/internal/utils/types"; import type { Settings } from "~/server/internal/utils/types";
@@ -219,10 +256,10 @@ const navigation: Array<NavigationItem & { icon: Component }> = [
}, },
]; ];
// const notifications = useNotifications(); const notifications = useNotifications();
// const unreadNotifications = computed(() => const unreadNotifications = computed(() =>
// notifications.value.filter((e) => !e.read) notifications.value.filter((e) => !e.read),
// ); );
const currentNavigationIndex = useCurrentNavigationIndex(navigation); const currentNavigationIndex = useCurrentNavigationIndex(navigation);
+1 -2
View File
@@ -2,14 +2,13 @@ const whitelistedPrefixes = ["/auth", "/api", "/setup"];
const requireAdmin = ["/admin"]; const requireAdmin = ["/admin"];
export default defineNuxtRouteMiddleware(async (to, _from) => { export default defineNuxtRouteMiddleware(async (to, _from) => {
if (import.meta.server) return;
const error = useError(); const error = useError();
if (error.value !== undefined) return; if (error.value !== undefined) return;
if (whitelistedPrefixes.findIndex((e) => to.fullPath.startsWith(e)) != -1) if (whitelistedPrefixes.findIndex((e) => to.fullPath.startsWith(e)) != -1)
return; return;
const user = useUser(); const user = useUser();
if (user === undefined) { if (user.value === undefined) {
await updateUser(); await updateUser();
} }
if (!user.value) { if (!user.value) {
+8 -9
View File
@@ -29,8 +29,8 @@
"@nuxt/image": "^1.10.0", "@nuxt/image": "^1.10.0",
"@nuxt/kit": "^3.20.1", "@nuxt/kit": "^3.20.1",
"@nuxtjs/i18n": "^9.5.5", "@nuxtjs/i18n": "^9.5.5",
"@prisma/adapter-pg": "^7.3.0", "@prisma/adapter-pg": "7.7.0",
"@prisma/client": "^7.3.0", "@prisma/client": "7.7.0",
"@simplewebauthn/browser": "^13.2.2", "@simplewebauthn/browser": "^13.2.2",
"@simplewebauthn/server": "^13.2.2", "@simplewebauthn/server": "^13.2.2",
"@tailwindcss/vite": "^4.0.6", "@tailwindcss/vite": "^4.0.6",
@@ -52,13 +52,13 @@
"luxon": "^3.6.1", "luxon": "^3.6.1",
"micromark": "^4.0.1", "micromark": "^4.0.1",
"normalize-url": "^8.0.2", "normalize-url": "^8.0.2",
"nuxt": "^3.20.1", "nuxt": "^3.21.6",
"nuxt-security": "2.2.0", "nuxt-security": "2.2.0",
"otp-io": "^1.2.7", "otp-io": "^1.2.7",
"parse-cosekey": "^1.0.2", "parse-cosekey": "^1.0.2",
"pino": "^9.7.0", "pino": "^9.14.0",
"pino-pretty": "^13.0.0", "pino-pretty": "^13.1.1",
"prisma": "7.3.0", "prisma": "7.7.0",
"sanitize-filename": "^1.6.3", "sanitize-filename": "^1.6.3",
"semver": "^7.7.1", "semver": "^7.7.1",
"shescape": "^2.1.10", "shescape": "^2.1.10",
@@ -90,7 +90,7 @@
"eslint-config-prettier": "^10.1.1", "eslint-config-prettier": "^10.1.1",
"golar": "^0.0.13", "golar": "^0.0.13",
"h3": "^1.15.5", "h3": "^1.15.5",
"nitropack": "^2.11.12", "nitropack": "^2.13.4",
"ofetch": "^1.4.1", "ofetch": "^1.4.1",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-sort-json": "^4.1.1",
@@ -103,6 +103,5 @@
"vue3-carousel-nuxt": { "vue3-carousel-nuxt": {
"vue3-carousel": "^0.16.0" "vue3-carousel": "^0.16.0"
} }
}, }
"packageManager": "pnpm@10.29.1+sha512.48dae233635a645768a3028d19545cacc1688639eeb1f3734e42d6d6b971afbf22aa1ac9af52a173d9c3a20c15857cfa400f19994d79a2f626fcc73fccda9bbc"
} }
+1 -1
View File
@@ -438,7 +438,7 @@
<NuxtLink <NuxtLink
class="transition text-xs text-zinc-600 hover:underline hover:text-zinc-400" class="transition text-xs text-zinc-600 hover:underline hover:text-zinc-400"
href="https://docs.droposs.org/docs/library" href="https://droposs.org/docs/reference/library-sources"
target="_blank" target="_blank"
> >
<i18n-t <i18n-t
+39 -23
View File
@@ -279,13 +279,14 @@ const optionsMetadata: {
Filesystem: { Filesystem: {
title: t("library.admin.sources.fsTitle"), title: t("library.admin.sources.fsTitle"),
description: t("library.admin.sources.fsDesc"), description: t("library.admin.sources.fsDesc"),
docsLink: "https://docs.droposs.org/docs/library#drop-style", docsLink: "https://droposs.org/docs/reference/library-sources#drop-style",
icon: DropLogo, icon: DropLogo,
}, },
FlatFilesystem: { FlatFilesystem: {
title: t("library.admin.sources.fsFlatTitle"), title: t("library.admin.sources.fsFlatTitle"),
description: t("library.admin.sources.fsFlatDesc"), description: t("library.admin.sources.fsFlatDesc"),
docsLink: "https://docs.droposs.org/docs/library#flat-style-or-compat", docsLink:
"https://droposs.org/docs/reference/library-sources#compatibility-flat-style",
icon: BackwardIcon, icon: BackwardIcon,
}, },
}; };
@@ -346,30 +347,45 @@ function edit(index: number) {
actionSourceOpen.value = true; actionSourceOpen.value = true;
} }
async function deleteSource(index: number) { function deleteSource(index: number) {
const source = sources.value[index]; const source = sources.value[index];
if (!source) return; if (!source) return;
try { createModal(
await $dropFetch("/api/v1/admin/library/sources", { ModalType.Confirmation,
method: "DELETE", {
body: { id: source.id }, title: t("library.admin.sources.deleteTitle"),
headers, description: t("library.admin.sources.deleteDesc", [source.name]),
}); buttonText: t("library.admin.sources.deleteButton"),
} catch (e) { },
createModal( async (event, close) => {
ModalType.Notification, if (event !== "confirm") return close();
{
title: t("errors.library.source.delete.title"),
description: t("errors.library.source.delete.desc", [
// @ts-expect-error attempt to display statusMessage on error
e?.statusMessage ?? t("errors.unknown"),
]),
},
(_, c) => c(),
);
}
sources.value.splice(index, 1); try {
await $dropFetch("/api/v1/admin/library/sources", {
method: "DELETE",
body: { id: source.id },
headers,
});
} catch (e) {
createModal(
ModalType.Notification,
{
title: t("errors.library.source.delete.title"),
description: t("errors.library.source.delete.desc", [
// @ts-expect-error attempt to display statusMessage on error
e?.statusMessage ?? t("errors.unknown"),
]),
},
(_, c) => c(),
);
return close();
}
const currentIndex = sources.value.findIndex((s) => s.id === source.id);
if (currentIndex !== -1) sources.value.splice(currentIndex, 1);
close();
},
);
} }
</script> </script>
-13503
View File
File diff suppressed because it is too large Load Diff
-45
View File
@@ -1,45 +0,0 @@
onlyBuiltDependencies:
- '@bufbuild/buf'
- '@parcel/watcher'
- '@prisma/client'
- '@prisma/engines'
- '@tailwindcss/oxide'
- argon2
- esbuild
- prisma
- sharp
- unrs-resolver
overrides:
'@isaacs/brace-expansion@<=5.0.0': '>=5.0.1'
ajv@<6.14.0: '>=6.14.0'
devalue@<=5.6.2: '>=5.6.3'
devalue@>=5.1.0 <5.6.2: '>=5.6.2'
devalue@>=5.3.0 <=5.6.1: '>=5.6.2'
diff@>=6.0.0 <8.0.3: '>=8.0.3'
hono@<4.11.10: '>=4.11.10'
hono@<4.11.7: '>=4.11.7'
lodash-es@>=4.0.0 <=4.17.22: '>=4.17.23'
lodash@>=4.0.0 <=4.17.22: '>=4.17.23'
minimatch@<3.1.3: '>=3.1.3'
minimatch@<3.1.4: '>=3.1.4'
minimatch@>=10.0.0 <10.2.1: '>=10.2.1'
minimatch@>=10.0.0 <10.2.3: '>=10.2.3'
minimatch@>=5.0.0 <5.1.7: '>=5.1.7'
minimatch@>=5.0.0 <5.1.8: '>=5.1.8'
minimatch@>=9.0.0 <9.0.6: '>=9.0.6'
minimatch@>=9.0.0 <9.0.7: '>=9.0.7'
node-forge@<1.3.2: '>=1.3.2'
qs@<6.14.1: '>=6.14.1'
qs@>=6.7.0 <=6.14.1: '>=6.14.2'
rollup@>=4.0.0 <4.59.0: '>=4.59.0'
serialize-javascript@<=7.0.2: '>=7.0.3'
seroval@<1.4.1: '>=1.4.1'
seroval@<=1.4.0: '>=1.4.1'
tar@<7.5.7: '>=7.5.7'
tar@<7.5.8: '>=7.5.8'
tar@<=7.5.2: '>=7.5.3'
tar@<=7.5.3: '>=7.5.4'
undici@>=7.0.0 <7.18.2: '>=7.18.2'
shamefullyHoist: true
@@ -14,9 +14,9 @@ export const FilesystemProviderConfig = type({
baseDir: "string", baseDir: "string",
}); });
export class FilesystemProvider export class FilesystemProvider implements LibraryProvider<
implements LibraryProvider<typeof FilesystemProviderConfig.infer> typeof FilesystemProviderConfig.infer
{ > {
private config: typeof FilesystemProviderConfig.infer; private config: typeof FilesystemProviderConfig.infer;
private myId: string; private myId: string;
@@ -11,9 +11,9 @@ export const FlatFilesystemProviderConfig = type({
baseDir: "string", baseDir: "string",
}); });
export class FlatFilesystemProvider export class FlatFilesystemProvider implements LibraryProvider<
implements LibraryProvider<typeof FlatFilesystemProviderConfig.infer> typeof FlatFilesystemProviderConfig.infer
{ > {
private config: typeof FlatFilesystemProviderConfig.infer; private config: typeof FlatFilesystemProviderConfig.infer;
private myId: string; private myId: string;
@@ -188,7 +188,10 @@ export class PCGamingWikiProvider implements MetadataProvider {
return url.pathname.replace("/games/", "").replace(/\/$/, ""); return url.pathname.replace("/games/", "").replace(/\/$/, "");
} }
default: { default: {
logger.warn("Pcgamingwiki, unknown host", url.hostname); logger.warn(
{ hostname: url.hostname },
"Pcgamingwiki, unknown host",
);
return undefined; return undefined;
} }
} }
@@ -222,8 +225,8 @@ export class PCGamingWikiProvider implements MetadataProvider {
}); });
if (ratingObj instanceof type.errors) { if (ratingObj instanceof type.errors) {
logger.info( logger.info(
{ summary: ratingObj.summary },
"pcgamingwiki: failed to properly get review rating", "pcgamingwiki: failed to properly get review rating",
ratingObj.summary,
); );
return undefined; return undefined;
} }
@@ -327,7 +330,7 @@ export class PCGamingWikiProvider implements MetadataProvider {
* @returns * @returns
*/ */
private parseTS(isoStr: string): DateTime { private parseTS(isoStr: string): DateTime {
return DateTime.fromISO(isoStr.split(";")[0]); return DateTime.fromISO(isoStr.split(";")[0]!);
} }
private parseWebsitesGetFirst(websiteStr?: string | null): string { private parseWebsitesGetFirst(websiteStr?: string | null): string {
@@ -429,7 +432,7 @@ export class PCGamingWikiProvider implements MetadataProvider {
); );
const released = game.Released const released = game.Released
? DateTime.fromISO(game.Released.split(";")[0]).toJSDate() ? DateTime.fromISO(game.Released.split(";")[0]!).toJSDate()
: new Date(); : new Date();
const metadata: GameMetadata = { const metadata: GameMetadata = {
+13 -4
View File
@@ -306,7 +306,8 @@ export class SteamProvider implements MetadataProvider {
"https://store.steampowered.com/publisher/", "https://store.steampowered.com/publisher/",
), ),
) )
.map((v) => v.attribs.href); .map((v) => v.attribs.href)
.filter((v) => v !== undefined);
const companies: { const companies: {
[key: string]: { [key: string]: {
@@ -320,6 +321,8 @@ export class SteamProvider implements MetadataProvider {
.substring("https://store.steampowered.com/".length, v.indexOf("?")) .substring("https://store.steampowered.com/".length, v.indexOf("?"))
.split("/"); .split("/");
if (!type || !name) return;
companies[name] ??= { pub: false, dev: false }; companies[name] ??= { pub: false, dev: false };
switch (type) { switch (type) {
case "publisher": case "publisher":
@@ -546,7 +549,9 @@ export class SteamProvider implements MetadataProvider {
let titleMatch = ogTitleRegex.exec(html); let titleMatch = ogTitleRegex.exec(html);
titleMatch ??= titleTagRegex.exec(html); titleMatch ??= titleTagRegex.exec(html);
return titleMatch ? this._decodeHtmlEntities(titleMatch[1]) : undefined; return titleMatch && titleMatch[1]
? this._decodeHtmlEntities(titleMatch[1])
: undefined;
} }
private _extractDescription(html: string): string | undefined { private _extractDescription(html: string): string | undefined {
@@ -558,7 +563,9 @@ export class SteamProvider implements MetadataProvider {
let descMatch = ogDescRegex.exec(html); let descMatch = ogDescRegex.exec(html);
descMatch ??= nameDescRegex.exec(html); descMatch ??= nameDescRegex.exec(html);
return descMatch ? this._decodeHtmlEntities(descMatch[1]) : undefined; return descMatch && descMatch[1]
? this._decodeHtmlEntities(descMatch[1])
: undefined;
} }
private _extractImage(html: string): string | undefined { private _extractImage(html: string): string | undefined {
@@ -583,6 +590,7 @@ export class SteamProvider implements MetadataProvider {
curatorUrlMatch ??= linkfilterRegex.exec(html); curatorUrlMatch ??= linkfilterRegex.exec(html);
if (!curatorUrlMatch) return undefined; if (!curatorUrlMatch) return undefined;
if (!curatorUrlMatch[1]) return undefined;
try { try {
return decodeURIComponent(curatorUrlMatch[1]); return decodeURIComponent(curatorUrlMatch[1]);
@@ -601,11 +609,12 @@ export class SteamProvider implements MetadataProvider {
bannerMatch ??= backgroundImageRegex.exec(html); bannerMatch ??= backgroundImageRegex.exec(html);
if (!bannerMatch) return undefined; if (!bannerMatch) return undefined;
if (!bannerMatch[1]) return undefined;
let bannerUrl = bannerMatch[1].replace(/['"]/g, ""); let bannerUrl = bannerMatch[1].replace(/['"]/g, "");
// Clean up the URL // Clean up the URL
if (bannerUrl.includes("?")) { if (bannerUrl.includes("?")) {
bannerUrl = bannerUrl.split("?")[0]; bannerUrl = bannerUrl.split("?")[0]!;
} }
return bannerUrl; return bannerUrl;
} }
+5 -2
View File
@@ -123,7 +123,10 @@ export class FsObjectBackend extends ObjectBackend {
const metadataRaw = JSON.parse(fs.readFileSync(metadataPath, "utf-8")); const metadataRaw = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
const metadata = objectMetadata(metadataRaw); const metadata = objectMetadata(metadataRaw);
if (metadata instanceof type.errors) { if (metadata instanceof type.errors) {
logger.error("FsObjectBackend#fetchMetadata", metadata.summary); logger.error(
{ summary: metadata.summary },
"FsObjectBackend#fetchMetadata",
);
return undefined; return undefined;
} }
await this.metadataCache.set(id, metadata); await this.metadataCache.set(id, metadata);
@@ -198,8 +201,8 @@ export class FsObjectBackend extends ObjectBackend {
); );
} catch (error) { } catch (error) {
cleanupLogger.error( cleanupLogger.error(
{ error },
`[FsObjectBackend#cleanupMetadata]: Failed to remove ${file}`, `[FsObjectBackend#cleanupMetadata]: Failed to remove ${file}`,
error,
); );
} }
} }
+1 -1
View File
@@ -190,7 +190,7 @@ class TaskHandler {
parentTask?.progress ?? parentTask?.progress ??
((progress: number) => { ((progress: number) => {
if (progress < 0 || progress > 100) { if (progress < 0 || progress > 100) {
logger.error("Progress must be between 0 and 100", { progress }); logger.error({ progress }, "Progress must be between 0 and 100");
return; return;
} }
const taskEntry = this.taskPool.get(task.id); const taskEntry = this.taskPool.get(task.id);
@@ -49,10 +49,13 @@ export default defineDropTask({
// if response failed somehow // if response failed somehow
if (!response.ok) { if (!response.ok) {
logger.info("Failed to check for update ", { logger.info(
status: response.status, {
body: response.body, status: response.status,
}); body: response.body,
},
"Failed to check for update ",
);
throw new Error( throw new Error(
`Failed to check for update: ${response.status} ${response.body}`, `Failed to check for update: ${response.status} ${response.body}`,
@@ -41,7 +41,7 @@ export default defineConfig({
{ slug: "user" }, { slug: "user" },
{ {
label: "Install", label: "Install",
autogenerate: { directory: "user/install" }, items: [{ autogenerate: { directory: "user/install" } }],
}, },
{ {
label: "Usage", label: "Usage",
@@ -65,25 +65,26 @@ export default defineConfig({
}, },
{ {
label: "Going further", label: "Going further",
autogenerate: { directory: "admin/going-further" }, items: [{ autogenerate: { directory: "admin/going-further" } }],
}, },
{ {
label: "Metadata", label: "Metadata",
autogenerate: { directory: "admin/metadata" }, items: [{ autogenerate: { directory: "admin/metadata" } }],
}, },
{ {
label: "Authentication", label: "Authentication",
autogenerate: { directory: "admin/authentication" }, items: [{ autogenerate: { directory: "admin/authentication" } }],
}, },
], ],
}, },
{ {
label: "Reference", label: "Reference",
autogenerate: { directory: "reference" }, items: [{ autogenerate: { directory: "reference" } }],
}, },
], ],
customCss: ["./src/styles/drop.css"], customCss: ["./src/styles/drop.css"],
}), }),
], ],
site: "https://docs-next.droposs.org/", site: "https://droposs.org",
base: "/docs",
}); });
@@ -10,11 +10,11 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/starlight": "^0.37.4", "@astrojs/starlight": "^0.40.0",
"astro": "^5.6.1", "astro": "^6.4.8",
"sharp": "^0.34.2", "sharp": "^0.35.2",
"starlight-image-zoom": "^0.13.2", "starlight-image-zoom": "^0.14.2",
"starlight-links-validator": "^0.19.2", "starlight-links-validator": "^0.24.1",
"starlight-theme-rapide": "^0.5.2" "starlight-theme-rapide": "^0.5.2"
} }
} }

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 436 B

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

@@ -28,7 +28,7 @@ For convenience's sake, we can also specify file extensions for Drop's auto-dete
Add a launch executable for every platform you want to support. The options are fairly self-explanatory, but make sure to use the `{rom}` placeholder, and optionally add the file extensions. Add a launch executable for every platform you want to support. The options are fairly self-explanatory, but make sure to use the `{rom}` placeholder, and optionally add the file extensions.
Read the [Command Parsing](/reference/command-parsing/) article to understand how it's parsed and substituted. Read the [Command Parsing](/docs/reference/command-parsing/) article to understand how it's parsed and substituted.
3. ## Import your game 3. ## Import your game
@@ -18,7 +18,7 @@ If you're using a library source that supports versioning, you can add and impor
2. ### Follow the import guide again 2. ### Follow the import guide again
Follow the [import guide again](/admin/guides/import-version/), but this time for your new version folder. Follow the [import guide again](/docs/admin/guides/import-version/), but this time for your new version folder.
</Steps> </Steps>
@@ -39,7 +39,7 @@ You can stack many "update mode" versions on top of each other, and they will pi
2. ### Follow the import guide again 2. ### Follow the import guide again
Follow the [import guide again](/admin/guides/import-version/), but this time for your new version folder. Follow the [import guide again](/docs/admin/guides/import-version/), but this time for your new version folder.
3. ### Before import, enable update mode 3. ### Before import, enable update mode
@@ -3,7 +3,7 @@ title: Setting up OIDC
--- ---
:::note :::note
You can find reference information in the [OIDC authentication docs](/admin/authentication/oidc/). You can find reference information in the [OIDC authentication docs](/docs/admin/authentication/oidc/).
::: :::
## Authentik ## Authentik
@@ -10,7 +10,7 @@ To import games and start using Drop, you must first create a library to import
1. **Decide on a library layout.** 1. **Decide on a library layout.**
Drop supports different layouts for your files on disk, you can read more about them in the [Library Sources](/reference/library-sources) reference section. Drop supports different layouts for your files on disk, you can read more about them in the [Library Sources](/docs/reference/library-sources) reference section.
2. **Mount your library in the Docker container.** 2. **Mount your library in the Docker container.**
@@ -28,7 +28,7 @@ To import games and start using Drop, you must first create a library to import
- `/mnt/media/my-drop-library` is the path to your library. - `/mnt/media/my-drop-library` is the path to your library.
- `/library` is a **unique** path inside the container. **Use something else if another volume mounts to `/library`**. - `/library` is a **unique** path inside the container. **Use something else if another volume mounts to `/library`**.
If you followed the [Quickstart](/admin/quickstart/) guide, you'll have already set up a library at `./library` pointing to `/library` within the container. You may want to instead edit that line in the `volumes` section to point to where your library is located. If you followed the [Quickstart](/docs/admin/quickstart/) guide, you'll have already set up a library at `./library` pointing to `/library` within the container. You may want to instead edit that line in the `volumes` section to point to where your library is located.
3. **Open library source interface in Admin Dashboard.** 3. **Open library source interface in Admin Dashboard.**
@@ -16,7 +16,7 @@ Drop automatically parses and formats the URL, so there are no requirements on t
## LAN ## LAN
The `compose.yaml` provided in the [Quickstart guide](/admin/quickstart/) already exposes the Drop instance on port 3000. If you're on the same LAN as your Drop instance, you can find it's IP and then use: The `compose.yaml` provided in the [Quickstart guide](/docs/admin/quickstart/) already exposes the Drop instance on port 3000. If you're on the same LAN as your Drop instance, you can find it's IP and then use:
``` ```
http://[instance IP]:3000 http://[instance IP]:3000
@@ -60,7 +60,7 @@ Once you've got a library set up, and have imported a game, you can import a ver
A installer version uses "setup mode". Enable the option, and then add the installer executable in setup commands. A installer version uses "setup mode". Enable the option, and then add the installer executable in setup commands.
:::note :::note
Setup and launch commands are parsed in a cross-platform, POSIX style. It's not relevant for simple setups, but useful to know. Read more about it in [Command Parsing](/reference/command-parsing/). Setup and launch commands are parsed in a cross-platform, POSIX style. It's not relevant for simple setups, but useful to know. Read more about it in [Command Parsing](/docs/reference/command-parsing/).
::: :::
6. ### **Wait for import.** 6. ### **Wait for import.**

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Some files were not shown because too many files have changed in this diff Show More