Compare commits

...

24 Commits

Author SHA1 Message Date
DecDuck 032cbda498 Fix server lint 2026-06-21 15:12:16 +10:00
DecDuck 6290826c4d Implement tree kill for Windows 2026-06-21 15:11:25 +10:00
DecDuck 1430d2ef3d Fix layouting 2026-06-21 13:40:44 +10:00
DecDuck e3a1824e7d Fix torrential inclusion in image 2026-06-21 13:23:05 +10:00
DecDuck 27502bf31f Regenerate lcofkiel 2026-06-21 13:06:56 +10:00
DecDuck 564a4aa6f0 Add process handler selector, pin Prisma 2026-06-21 13:03:15 +10:00
DecDuck a028db7288 Fix Windows and Linux launch 2026-06-21 11:56:45 +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
225 changed files with 27500 additions and 40503 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
@@ -1,10 +1,15 @@
name: Deploy Next.js site to Pages name: Deploy website to GitHub Pages
on: on:
# Runs on pushes targeting the main branch # Runs on pushes targeting the default branch
push: push:
branches: branches: [develop]
- main paths:
- "sites/promo/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- ".github/workflows/pages.yml"
# Allows you to run this workflow manually from the Actions tab # Allows you to run this workflow manually from the Actions tab
workflow_dispatch: workflow_dispatch:
@@ -15,11 +20,10 @@ permissions:
pages: write pages: write
id-token: write id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # Allow only one concurrent deployment per the "pages" group, skipping runs queued
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. # between the in-progress run and the latest queued one. cancel-in-progress defaults
concurrency: # to false, so in-flight production deployments are allowed to complete.
group: "pages" concurrency: "pages"
cancel-in-progress: false
jobs: jobs:
build: build:
@@ -28,20 +32,21 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 - name: Install pnpm
name: Install pnpm uses: pnpm/action-setup@v4
with: with:
version: 10
run_install: false run_install: false
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: "22"
cache: 'pnpm' cache: "pnpm"
# Only install the promo site (radiant) and its dependencies so the public
# website deploy stays decoupled from the server/desktop build pipelines.
- name: Install dependencies - name: Install dependencies
run: pnpm install run: pnpm install --filter radiant...
- name: Setup Pages - name: Setup Pages
id: setup_pages id: setup_pages
@@ -51,14 +56,15 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
.next/cache sites/promo/.next/cache
# Generate a new cache whenever packages or source files change. # Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} 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. # If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: | restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml') }}-
- name: Build with Next.js - name: Build with Next.js
working-directory: sites/promo
run: pnpm run build run: pnpm run build
env: env:
PAGES_BASE_PATH: ${{ steps.setup_pages.outputs.base_path }} PAGES_BASE_PATH: ${{ steps.setup_pages.outputs.base_path }}
@@ -66,7 +72,7 @@ jobs:
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v3 uses: actions/upload-pages-artifact@v3
with: with:
path: ./out path: sites/promo/out
deploy: deploy:
environment: environment:
@@ -77,4 +83,4 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v4 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"]
+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"
}
+23416
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
-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;
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 } },
}, },
+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"
} }
-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}`,

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

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

@@ -89,7 +89,7 @@ Drop's [dockerfile](https://github.com/Drop-OSS/drop/blob/develop/Dockerfile) pr
::: :::
```bash ```bash
npm install prisma@7.3.0 dotenv # dotenv is required npm install prisma@7.7.0 dotenv # dotenv is required
``` ```
Then, with your database running: Then, with your database running:
@@ -21,12 +21,40 @@ Then, what happens with this, depends on the type of game we're launching:
## Normal (no emulator) ## Normal (no emulator)
Drop reconstructs the original shell string, and passes it into platform-specific command wrappers. For Windows, this means nothing. For Linux, it gets wrapped in `umu-run`. Drop reconstructs the original shell string, and passes it into a platform-specific command wrapper, called a **launch method**. Drop picks a sensible launch method automatically, but you can override it per-game for troubleshooting — see [Launch methods](#launch-methods) below.
By default, on Windows the command is launched based on its file type: `.exe` files run directly, `.bat` and `.cmd` files run through `cmd`, `.ps1` files run through PowerShell, and anything else is handed to `cmd` so builtins, `PATHEXT` resolution and `%VAR%` expansion all work. On Linux, native games run directly on the host, while games targeting Windows are wrapped in `umu-run` (with Proton).
It is then parsed again, and then passed into process creation, mapping the environment variable, command, and arguments into their respective platform-dependent places. It is then parsed again, and then passed into process creation, mapping the environment variable, command, and arguments into their respective platform-dependent places.
Drop logs out it's final parsed command, if you want to look at it in the client logs. Drop logs out it's final parsed command, if you want to look at it in the client logs.
## Launch methods
The wrapper Drop uses to start a game is called a **launch method** (a *process handler* internally). Drop automatically selects the best available method for each game, but if a game won't launch you can override it under **Game Options → Launch → Launch method**.
Only methods supported by your current platform (and the game's target platform) are listed, each with a short description in the client.
### Windows
| Method | Description |
| ------ | ----------- |
| **Automatic** *(default)* | Detects the file type and launches it directly, or through `cmd` or PowerShell. |
| **Direct executable** | Runs the executable directly, without a shell. |
| **Command Prompt (cmd)** | Launches through `cmd.exe`. Supports batch files, builtins and `%VAR%` expansion. |
| **PowerShell** | Runs the command as a PowerShell script (`-File`). |
### Linux
| Method | Description |
| ------ | ----------- |
| **Native (direct)** *(default for Linux games)* | Runs the native Linux game directly on the host. |
| **Steam Linux Runtime (umu-run)** | Runs the native Linux game inside `umu-run`'s Steam Linux Runtime. Requires [UMU launcher](/user/usage/proton/). |
| **Proton (umu-run)** *(default for Windows games)* | Runs a Windows game through Proton, using `umu-run`. Requires [Proton](/user/usage/proton/). |
| **Proton + muvm (Asahi)** | Runs a Windows game through Proton inside a muvm microVM, for Apple Silicon / Asahi Linux. |
On macOS, games are always launched directly.
## Emulators ## Emulators
For emulators, we have the "emulator version" (version containing the emulator), and the "emulated version" (version containing the ROM). For emulators, we have the "emulator version" (version containing the emulator), and the "emulated version" (version containing the ROM).

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