42 Commits

Author SHA1 Message Date
Philipp Neumann
c9e6ac42ee Merge d501b66c11 into 93a9f2e5c9 2025-11-21 12:16:37 +08:00
DatCaptainHorse
93a9f2e5c9 fix: Remove old CI comment and allow manual trigger 2025-11-20 19:14:11 +02:00
DatCaptainHorse
d5916ac2be fix: Add v2 suffix to make tags sane 2025-11-20 19:04:17 +02:00
DatCaptainHorse
7d515bcd94 fix: CI base permission to push packages 2025-11-20 15:46:02 +02:00
Kristian Ollikainen
49cc5e1ab9 feat: Runner image variants, CI improvements (#306)
## Description

Next step would be having full DE environment variant I guess? I'll see
later if it's doable in this PR or if I'll do separate one for keeping
things small and manageable for once 😅

- Added easily doable variants for runners, with simple CI build matrix.
- Added playsite in CI builds finally.
- Some CI formatting and naming fixes.
- Removed PR full runner builds as they kept failing due to lack of disk
space on GH runner.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* New dedicated runner images for Steam, Heroic, and Minecraft plus a
common runtime and builder images.
* **Chores**
* CI/workflow reorganization to build and publish more runner variants
and base images.
* Installer and package tweaks (package manager flags, CUDA enablement)
and updated build tooling.
* Unified startup to use a constructed launch command; removed two
default environment exports.
  * Added container ignore patterns.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-11-20 15:40:50 +02:00
DatCaptainHorse
c9a0e6ee29 fix: Specify MangoHud config for gamescope 2025-11-11 11:45:00 +02:00
Kristian Ollikainen
d87a0b35dd feat: Fully use protobuf, fix controller issues and cleanup (#305)
## Description
### First commit
Restructured protobuf schemas to make them easier to use across
languages, switched to using them in-place of JSON for signaling as
well, so there's no 2 different message formats flying about. Few new
message types to deal with clients and nestri-servers better (not final
format, may see changes still).

General cleanup of dead/unused code along some bug squashing and package
updates.

TODO for future commits:
- [x] Fix additional controllers not doing inputs (possibly needs
vimputti changes)
- [x] ~~Restructure relay protocols code a bit, to reduce bloatiness of
the currently single file for them, more code re-use.~~
- Gonna keep this PR somewhat manageable without poking more at relay..
- [x] ~~Try to fix issue where with multiple clients, static stream
content causes video to freeze until there's some movement.~~
- Was caused by server tuned profile being `throughput-performance`,
causing CPU latency to be too high.
- [x] Ponder the orb


### Second + third commit
Redid the controller polling handling and fixed multi-controller
handling in vimputti and nestri code sides. Remove some dead relay code
as well to clean up the protocol source file, we'll revisit the meshing
functionality later.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added software rendering option and MangoHud runtime config;
controller sessions now support reconnection and batched state updates
with persistent session IDs.

* **Bug Fixes**
* Restored previously-filtered NES-like gamepads so they connect
correctly.

* **Chores**
* Modernized dependencies and protobuf tooling, migrated to
protobuf-based messaging and streaming, and removed obsolete CUDA build
steps.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-11-08 13:10:28 +02:00
DatCaptainHorse
32341574dc fix: Disable v4 runner build in GHA workflow
- Turns out GHA doesn't use AVX-512 CPUs..
2025-10-20 20:04:01 +03:00
Kristian Ollikainen
c62a22b552 feat: Controller support, performance enchancements, multi-stage images, fixes (#304)
## Description
Oops.. another massive PR 🥲 

This PR contains multiple improvements and changes.

Firstly, thanks gst-wayland-display's PR
[here](https://github.com/games-on-whales/gst-wayland-display/pull/20).
NVIDIA path is now way more efficient than before.

Secondly, adding controller support was a massive hurdle, requiring me
to start another project
[vimputti](https://github.com/DatCaptainHorse/vimputti) - which allows
simple virtual controller inputs in isolated containers. Well, it's not
simple, it includes LD_PRELOAD shims and other craziness, but the
library API is simple to use..

Thirdly, split runner image into 3 separate stages, base + build +
runtime, should help keep things in check in future, also added GitHub
Actions CI builds for v2 to v4 builds (hopefully they pass..).

Fourth, replaced the runner's runtime Steam patching with better and
simpler bubblewrap patch, massive thanks to `games-on-whales` to
figuring it out better!

Fifth, relay for once needed some changes, the new changes are still
mostly WIP, but I'll deal with them next time I have energy.. I'm spent
now. Needed to include these changes as relay needed a minor change to
allow rumble events to flow back to client peer.

Sixth.. tons of package updates, minor code improvements and the usual. 

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* End-to-end gamepad/controller support (attach/detach, buttons, sticks,
triggers, rumble) with client/server integration and virtual controller
plumbing.
  * Optional Prometheus metrics endpoint and WebTransport support.
  * Background vimputti manager process added for controller handling.

* **Improvements**
  * Multi-variant container image builds and streamlined runtime images.
  * Zero-copy video pipeline and encoder improvements for lower latency.
  * Updated Steam compat mapping and dependency/toolchain refreshes.

* **Bug Fixes**
* More robust GPU detection, input/fullscreen lifecycle,
startup/entrypoint, and container runtime fixes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-10-20 11:20:05 +03:00
Wanjohi
a3ee9aadd9 feat: Make sure worker runs close to db 2025-10-12 03:03:44 +03:00
Wanjohi
9b1dc054d0 fix: Remove package-lock 2025-09-27 12:12:16 +03:00
Wanjohi
278c1043f5 feat: Move API to CF workers (WIP) 2025-09-27 10:25:13 +03:00
Kristian Ollikainen
5705029972 feat(play-standalone): Add PEER_URL env variable (#302)
## Description

Adds PEER_URL env variable for setting peer URL (query param still takes
priority if set).

- Useful for self-hosters
- Was a pain to figure out



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Support configuring the peer server URL via an environment variable,
with automatic fallback to the URL parameter or a default.
- Server-provided configuration is securely passed to the client to
simplify deployment setup.

- Chores
- Excluded common build artifacts and IDE directories from container
contexts to reduce image size and speed up builds.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-09-24 20:08:20 +03:00
Kristian Ollikainen
590fe5e196 feat(runner): Container detection and handling, video bit-depth flags and script updates (#303)
## Description

Works in apptainer now.. podman is still the goat since apptainer needs
docker treatment and even more..

- Added container detection so podman can be used to it's fullest, the
non-sane ones are handled separately..
- Added video bit-depth option, cuz AV1 and 10-bit encoding go well
together.
- Some other package updates to nestri-server.
- General tidying up of scripts to make multi-container-engine handling
less of a pain.
- Updated old wireplumber lua script to new json format.

Further changes:

- Removed unused debug arg from nestri-server.
- Moved configs to config file folder rather than keeping them in
containerfile.
- Improved audio configs, moved some into wireplumber to keep things
tidy.
- Bit better arg handling in nestri-server.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Optional 10‑bit video support and auto‑launch of an app after display
setup.

* **Changes**
* Standardized runtime/user env to NESTRI_* with updated home/cache
paths and explicit LANG; password generation now logged.
* Improved container/GPU detection and startup logging; reduced blanket
root usage during startup; SSH setup surfaced.
* WirePlumber/PipeWire moved to JSON configs; low‑latency clock and
loopback audio policies added; audio capture defaults to PipeWire.
  
* **Chores**
* GStreamer/libp2p dependency upgrades and Rust toolchain pinned; NVIDIA
driver capability exposed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-09-24 20:08:04 +03:00
Wanjohi
aba0bc3be1 chore: Migrate to namespace 2025-09-20 17:20:16 +03:00
Wanjohi
eb41fdc754 feat: Use drizzle studio to view changes in realtime 2025-09-20 07:01:30 +03:00
Wanjohi
73a6d9546a feat: Push schema to remote db 2025-09-20 06:54:57 +03:00
Wanjohi
49286d7e72 feat: Add auth endpoint 2025-09-20 04:49:31 +03:00
Wanjohi
a045fc9c91 fix: Clean up and add Neon DB 2025-09-20 04:38:16 +03:00
Wanjohi
df4af84d55 fix: Resolve database issues 2025-09-20 03:56:51 +03:00
Wanjohi
ecfdb5156e feat: Cloud WIP 2025-09-20 01:05:03 +03:00
Wanjohi
16004c87a9 chore: Migrate auth to CF Workers Pt 2 2025-09-20 00:44:25 +03:00
Wanjohi
d01e9945de chore: Migrate auth to CF Workers 2025-09-19 23:56:03 +03:00
Wanjohi
33407d8df5 feat: Start working on Auth 2025-09-19 23:35:37 +03:00
Wanjohi
639616ce73 feat: Add database 2025-09-19 01:25:29 +03:00
Wanjohi
6bd17a4d5f feat: Add DNS configuration 2025-09-08 05:37:42 +03:00
Wanjohi
199c021797 Update LICENSE 2025-09-08 04:45:36 +03:00
Wanjohi
ad71c6c26a fix: Remove Neon DB support 2025-09-07 23:02:48 +03:00
Wanjohi
93bfe4ef24 fix: Fix SST infra directory 2025-09-07 23:00:19 +03:00
Wanjohi
ebddef4eb2 fix: Add cloud/packages/* workspace 2025-09-07 22:57:59 +03:00
Wanjohi
18942dc26b fix: Remove vscode files 2025-09-06 17:14:20 +03:00
Wanjohi
bb6c54e55a Update the LICENSE 2025-09-06 17:10:47 +03:00
Wanjohi
9818165a90 fix: Move more directories 2025-09-06 16:50:44 +03:00
Wanjohi
1c1c73910b fix: Remove unnecessary workflows 2025-09-06 04:38:13 +03:00
Wanjohi
d409a8dc95 fix merge error 2025-09-06 04:35:36 +03:00
Wanjohi
8643608ae4 chore: Rename containers to containerfiles 2025-09-06 04:29:44 +03:00
Kristian Ollikainen
dc6a53e18d chore: Fix up the directories
Adds a basic standalone "play site" that mimics current one in apps/www.
This is so self-hosters don't need to host whole site, but can just use
small version of it.

Yet to test so marking as draft, not at home currently so may take some
time. Also might be good idea to make Caddy-powered container out of
this later?

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

- New Features
- Introduces a standalone Play site with server output, accessible on
0.0.0.0:3000.
- Streams video via WebRTC into a canvas with continuous frame
rendering.
- Fullscreen and pointer lock support with optional keyboard lock for
navigation keys.
  - Room-based routing with offline and loading states.
  - Responsive 16:9 canvas and improved default layout styling.

- Chores
- Adds a multi-stage container build for efficient runtime images and a
lightweight init process.
  - Includes configuration and project setup for the standalone package.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <datcaptainhorse@users.noreply.github.com>
2025-09-06 04:25:32 +03:00
Kristian Ollikainen
51941e6560 feat: Standalone play site (#298)
## Description
Adds a basic standalone "play site" that mimics current one in apps/www.
This is so self-hosters don't need to host whole site, but can just use
small version of it.

Yet to test so marking as draft, not at home currently so may take some
time. Also might be good idea to make Caddy-powered container out of
this later?

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Introduces a standalone Play site with server output, accessible on
0.0.0.0:3000.
- Streams video via WebRTC into a canvas with continuous frame
rendering.
- Fullscreen and pointer lock support with optional keyboard lock for
navigation keys.
  - Room-based routing with offline and loading states.
  - Responsive 16:9 canvas and improved default layout styling.

- Chores
- Adds a multi-stage container build for efficient runtime images and a
lightweight init process.
  - Includes configuration and project setup for the standalone package.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <datcaptainhorse@users.noreply.github.com>
2025-08-24 16:13:07 +03:00
Wanjohi
85d6fdd213 ci: Update workflows to use dev 2025-07-20 23:46:05 +03:00
Wanjohi
5315daa932 🐜 fix(readme): Switch main branch to dev 2025-07-20 23:06:55 +03:00
Philipp Neumann
d501b66c11 latency test blog entry 2025-05-28 16:49:45 +02:00
Philipp Neumann
dc1b552ac1 added initial blog tryings 2025-05-17 20:39:14 +02:00
561 changed files with 13287 additions and 76366 deletions

View File

@@ -1,2 +1,2 @@
CLOUDFLARE_API_TOKEN=
NEON_API_KEY=
NEON_API_TOKEN=

4
.github/CODEOWNERS vendored
View File

@@ -3,13 +3,13 @@
/apps/ @victorpahuus @AquaWolf
/packages/ui/ @wanjohiryan @victorpahuus @AquaWolf
/protobuf/ @AquaWolf
/protobufs/ @AquaWolf @DatCaptainHorse
/infra/ @wanjohiryan
/packages/core/ @wanjohiryan
/packages/functions/ @wanjohiryan
/containers/ @DatCaptainHorse
/containerfiles/ @DatCaptainHorse
/packages/server/ @DatCaptainHorse
/packages/relay/ @DatCaptainHorse
/packages/scripts/ @DatCaptainHorse

32
.github/workflows/docker-bake.hcl vendored Normal file
View File

@@ -0,0 +1,32 @@
variable "BASE_IMAGE" {
default = "docker.io/cachyos/cachyos:latest"
}
group "default" {
targets = ["runner-base", "runner-builder"]
}
target "runner-base" {
dockerfile = "containerfiles/runner-base.Containerfile"
context = "."
args = {
BASE_IMAGE = BASE_IMAGE
}
cache-from = ["type=gha,scope=runner-base-pr"]
cache-to = ["type=gha,scope=runner-base-pr,mode=max"]
tags = ["runner-base:latest"]
}
target "runner-builder" {
dockerfile = "containerfiles/runner-builder.Containerfile"
context = "."
args = {
RUNNER_BASE_IMAGE = "runner-base:latest"
}
cache-from = ["type=gha,scope=runner-builder-pr"]
cache-to = ["type=gha,scope=runner-builder-pr,mode=max"]
tags = ["runner-builder:latest"]
contexts = {
runner-base = "target:runner-base"
}
}

View File

@@ -1,40 +0,0 @@
name: Build docs
on:
pull_request:
paths:
- "apps/docs/**"
- ".github/workflows/docs.yml"
push:
branches: [main]
paths:
- "apps/docs/**"
- ".github/workflows/docs.yml"
jobs:
deploy-docs:
name: Build and deploy docs
runs-on: ubuntu-latest
defaults:
run:
working-directory: "apps/docs"
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Build Project Artifacts
run: bun run build
- name: Deploy Project Artifacts to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
packageManager: bun
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
wranglerVersion: "3.93.0"
workingDirectory: "apps/docs"
command: pages deploy ./dist --project-name=${{ vars.CF_DOCS_PAGES_PROJECT_NAME }} --commit-dirty=true
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

81
.github/workflows/play-standalone.yml vendored Normal file
View File

@@ -0,0 +1,81 @@
name: Build Nestri standalone playsite
on:
pull_request:
paths:
- "containerfiles/playsite.Containerfile"
- ".github/workflows/play-standalone.yml"
- "packages/play-standalone/**"
push:
branches: [ dev, production ]
paths:
- "containerfiles/playsite.Containerfile"
- ".github/workflows/play-standalone.yml"
- "packages/play-standalone/**"
tags:
- v*.*.*
release:
types: [ created ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: nestrilabs/nestri
BASE_TAG_PREFIX: playsite
jobs:
build-docker-pr:
name: Build image on PR
runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request' }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v5
with:
file: containerfiles/playsite.Containerfile
context: ./
push: false
load: true
tags: nestri:playsite
build-and-push-docker:
name: Build and push image
if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/production' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Extract Container metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.BASE_TAG_PREFIX }}
#
#tag on release, and a nightly build for 'dev'
tags: |
type=raw,value=nightly,enable={{is_default_branch}}
type=raw,value={{branch}}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'production') }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build Docker image
uses: docker/build-push-action@v5
with:
file: containerfiles/playsite.Containerfile
context: ./
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,16 +1,15 @@
#Tabs not spaces, you moron :)
name: Build Nestri relay
name: Build nestri:relay
on:
pull_request:
paths:
- "containers/relay.Containerfile"
- "containerfiles/relay.Containerfile"
- "packages/relay/**"
- ".github/workflows/relay.yml"
push:
branches: [main]
branches: [dev, production]
paths:
- "containers/relay.Containerfile"
- "containerfiles/relay.Containerfile"
- ".github/workflows/relay.yml"
- "packages/relay/**"
tags:
@@ -39,15 +38,15 @@ jobs:
name: Build Docker image
uses: docker/build-push-action@v5
with:
file: containers/relay.Containerfile
file: containerfiles/relay.Containerfile
context: ./
push: false
load: true
tags: nestri:relay
build-docker-main:
name: Build image on main
if: ${{ github.ref == 'refs/heads/main' }}
build-and-push-docker:
name: Build and push image
if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/production' }}
runs-on: ubuntu-latest
permissions:
contents: read
@@ -73,7 +72,8 @@ jobs:
#tag on release, and a nightly build for 'dev'
tags: |
type=raw,value=nightly,enable={{is_default_branch}}
type=ref,event=tag
type=raw,value={{branch}}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'production') }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
@@ -81,8 +81,8 @@ jobs:
name: Build Docker image
uses: docker/build-push-action@v5
with:
file: containers/relay.Containerfile
file: containerfiles/relay.Containerfile
context: ./
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}

73
.github/workflows/runner-bases.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
name: Build Nestri runner base images
on: [ workflow_call ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: nestrilabs/nestri
BASE_IMAGE: docker.io/cachyos/cachyos:latest
jobs:
build-and-push-bases:
name: Build and push images
if: ${{ github.ref == 'refs/heads/production' || github.ref == 'refs/heads/dev' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
variant:
- { suffix: "v2", base: "docker.io/cachyos/cachyos:latest" }
- { suffix: "v3", base: "docker.io/cachyos/cachyos-v3:latest" }
#- { suffix: "v4", base: "docker.io/cachyos/cachyos-v4:latest" } # Disabled until GHA has this
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set Swap Space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 20
- name: Build and push runner-base image
uses: docker/build-push-action@v6
with:
file: containerfiles/runner-base.Containerfile
context: ./
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-base:latest-${{ matrix.variant.suffix }}
build-args: |
BASE_IMAGE=${{ matrix.variant.base }}
cache-from: type=gha,scope=runner-base-${{ matrix.variant.suffix }},mode=max
cache-to: type=gha,scope=runner-base-${{ matrix.variant.suffix }},mode=max
pull: true
- name: Build and push runner-builder image
uses: docker/build-push-action@v6
with:
file: containerfiles/runner-builder.Containerfile
context: ./
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-builder:latest-${{ matrix.variant.suffix }}
build-args: |
RUNNER_BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-base:latest-${{ matrix.variant.suffix }}
cache-from: type=gha,scope=runner-builder-${{ matrix.variant.suffix }},mode=max
cache-to: type=gha,scope=runner-builder-${{ matrix.variant.suffix }},mode=max
- name: Build and push runner-common image
uses: docker/build-push-action@v6
with:
file: containerfiles/runner-common.Containerfile
context: ./
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-common:latest-${{ matrix.variant.suffix }}
build-args: |
RUNNER_BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-base:latest-${{ matrix.variant.suffix }}
RUNNER_BUILDER_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-builder:latest-${{ matrix.variant.suffix }}
cache-from: type=gha,scope=runner-common-${{ matrix.variant.suffix }},mode=max
cache-to: type=gha,scope=runner-common-${{ matrix.variant.suffix }},mode=max

86
.github/workflows/runner-variants.yml vendored Normal file
View File

@@ -0,0 +1,86 @@
name: Build Nestri runner image variants
on:
workflow_dispatch:
schedule:
- cron: 7 0 * * 1,3,6 # Nightlies
push:
branches: [ dev, production ]
paths:
- "containerfiles/*runner.Containerfile"
- ".github/workflows/runner-variants.yml"
- "packages/scripts/**"
- "packages/configs/**"
tags:
- v*.*.*
release:
types: [ created ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: nestrilabs/nestri
jobs:
bases:
uses: ./.github/workflows/runner-bases.yml
permissions:
contents: read
packages: write
build-and-push-variants:
needs: [ bases ]
name: Build and push images
if: ${{ github.ref == 'refs/heads/production' || github.ref == 'refs/heads/dev' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
variant:
- { suffix: "v2", base: "docker.io/cachyos/cachyos:latest" }
- { suffix: "v3", base: "docker.io/cachyos/cachyos-v3:latest" }
#- { suffix: "v4", base: "docker.io/cachyos/cachyos-v4:latest" } # Disabled until GHA has this
runner:
- steam
- heroic
- minecraft
# ADD MORE HERE AS NEEDED #
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Extract runner metadata
id: meta-runner
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner
tags: |
type=raw,value=nightly-${{ matrix.runner }}-${{ matrix.variant.suffix }},enable={{is_default_branch}}
type=raw,value={{branch}}-${{ matrix.runner }}-${{ matrix.variant.suffix }}
type=raw,value=latest-${{ matrix.runner }}-${{ matrix.variant.suffix }},enable=${{ github.ref == format('refs/heads/{0}', 'production') }}
type=semver,pattern={{version}}-${{ matrix.runner }}-${{ matrix.variant.suffix }}
type=semver,pattern={{major}}.{{minor}}-${{ matrix.runner }}-${{ matrix.variant.suffix }}
type=semver,pattern={{major}}-${{ matrix.runner }}-${{ matrix.variant.suffix }}
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set Swap Space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 20
- name: Build and push runner image
uses: docker/build-push-action@v6
with:
file: containerfiles/${{ matrix.runner }}-runner.Containerfile
context: ./
push: true
tags: ${{ steps.meta-runner.outputs.tags }}
labels: ${{ steps.meta-runner.outputs.labels }}
build-args: |
RUNNER_COMMON_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-common:latest-${{ matrix.variant.suffix }}
cache-from: type=gha,scope=runner-${{ matrix.runner }}-${{ matrix.variant.suffix }},mode=max
cache-to: type=gha,scope=runner-${{ matrix.runner }}-${{ matrix.variant.suffix }},mode=max

View File

@@ -1,120 +0,0 @@
#Tabs not spaces, you moron :)
name: Build nestri:runner
on:
pull_request:
paths:
- "containers/runner.Containerfile"
- "packages/scripts/**"
- "packages/server/**"
- ".github/workflows/runner.yml"
schedule:
- cron: 7 0 * * 1,3,6 # Regularly to keep that build cache warm
push:
branches: [main]
paths:
- "containers/runner.Containerfile"
- ".github/workflows/runner.yml"
- "packages/scripts/**"
- "packages/server/**"
tags:
- v*.*.*
release:
types: [created]
env:
REGISTRY: ghcr.io
IMAGE_NAME: nestrilabs/nestri
BASE_TAG_PREFIX: runner
BASE_IMAGE: docker.io/cachyos/cachyos:latest
# This makes our release ci quit prematurely
# concurrency:
# group: ci-${{ github.ref }}
# cancel-in-progress: true
jobs:
build-docker-pr:
name: Build image on PR
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
if: ${{ github.event_name == 'pull_request' }}
steps:
-
name: Checkout repo
uses: actions/checkout@v4
-
name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Set Swap Space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 20
-
name: Build Docker image
uses: docker/build-push-action@v6
with:
file: containers/runner.Containerfile
context: ./
push: false
load: true
tags: nestri:runner
cache-from: type=gha,mode=max
cache-to: type=gha,mode=max
build-docker-main:
name: Build image on main
if: ${{ github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
-
name: Checkout repo
uses: actions/checkout@v4
-
name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
-
name: Extract Container metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.BASE_TAG_PREFIX }}
#
#tag on release, and a nightly build for 'dev'
tags: |
type=raw,value=nightly,enable={{is_default_branch}}
type=ref,event=tag
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
-
name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Set Swap Space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 20
-
name: Build Docker image
uses: docker/build-push-action@v6
with:
file: containers/runner.Containerfile
context: ./
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,mode=max
cache-to: type=gha,mode=max
pull: ${{ github.event_name == 'schedule' }} # Pull base image for scheduled builds

View File

@@ -1,40 +0,0 @@
name: Build www
on:
pull_request:
paths:
- "apps/www/**"
- ".github/workflows/www.yml"
push:
branches: [main]
paths:
- "apps/www/**"
- ".github/workflows/www.yml"
jobs:
deploy-www:
name: Build and deploy www
runs-on: ubuntu-latest
defaults:
run:
working-directory: "apps/www"
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Build Project Artifacts
run: bun run build
- name: Deploy Project Artifacts to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
packageManager: bun
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
wranglerVersion: "3.93.0"
workingDirectory: "apps/www"
command: pages deploy ./dist --project-name=${{ vars.CF_WWW_PAGES_PROJECT_NAME }} --commit-dirty=true
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

View File

@@ -1,3 +0,0 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

12
LICENSE
View File

@@ -1,3 +1,13 @@
Copyright (c) 2023-present Nestri Labs, Inc.
Portions of this software are licensed as follows:
* All content that resides under https://github.com/nestrilabs/nestri/tree/dev/cloud and
https://github.com/nestrilabs/nestri/tree/production/cloud directory of this repository (Commercial License) is licensed under the license defined in "cloud/LICENSE".
* All third party components incorporated into the Nestri Software are licensed under the original license provided by the owner of the applicable component.
* Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
@@ -658,4 +668,4 @@ specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
<https://www.gnu.org/licenses/>.

View File

@@ -10,8 +10,8 @@
<p align="center">Deploy and stream games/apps in the cloud. Use our GPUs or bring your own.</p>
<p align="center">
<a href="https://discord.com/invite/Y6etn3qKZ3"><img alt="Discord" src="https://img.shields.io/discord/1080111004698021909?style=flat-square&label=discord" /></a>
<a href="https://github.com/nestrilabs/nestri/blob/main/LICENSE"><img alt="Nestri License" src="https://img.shields.io/github/license/nestriness/nestri?style=flat-square" /></a>
<a href="https://github.com/nestrilabs/nestri/actions/workflows/runner.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/nestrilabs/nestri/runner.yml?style=flat-square&branch=main" /></a>
<a href="https://github.com/nestrilabs/nestri/blob/dev/LICENSE"><img alt="Nestri License" src="https://img.shields.io/github/license/nestriness/nestri?style=flat-square" /></a>
<a href="https://github.com/nestrilabs/nestri/actions/workflows/runner.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/nestrilabs/nestri/runner.yml?style=flat-square&branch=dev" /></a>
<!-- <a href="https://nestri.io" style="text-decoration: none;">
<img src="https://img.shields.io/badge/Start%20Playing%20Now-For%20$1/hour-brightgreen?style=flat-square" alt="Umami Demo" />
</a> -->

24
apps/blog/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/

4
apps/blog/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"],
"unwantedRecommendations": []
}

11
apps/blog/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

68
apps/blog/README.md Normal file
View File

@@ -0,0 +1,68 @@
# Astro Starter Kit: Blog
```sh
bun create astro@latest -- --template blog
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/blog)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/blog)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/blog/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
![blog](https://github.com/withastro/astro/assets/2244813/ff10799f-a816-4703-b967-c78997e8323d)
Features:
- ✅ Minimal styling (make it your own!)
- ✅ 100/100 Lighthouse performance
- ✅ SEO-friendly with canonical URLs and OpenGraph data
- ✅ Sitemap support
- ✅ RSS Feed support
- ✅ Markdown & MDX support
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
├── public/
├── src/
│   ├── components/
│   ├── content/
│   ├── layouts/
│   └── pages/
├── astro.config.mjs
├── README.md
├── package.json
└── tsconfig.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `bun install` | Installs dependencies |
| `bun dev` | Starts local dev server at `localhost:4321` |
| `bun build` | Build your production site to `./dist/` |
| `bun preview` | Preview your build locally, before deploying |
| `bun astro ...` | Run CLI commands like `astro add`, `astro check` |
| `bun astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
## Credit
This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/).

View File

@@ -0,0 +1,18 @@
// @ts-check
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import solidJs from '@astrojs/solid-js';
import tailwindcss from '@tailwindcss/vite';
// https://astro.build/config
export default defineConfig({
site: 'https://example.com',
integrations: [mdx(), sitemap(), solidJs()],
vite: {
plugins: [tailwindcss()],
},
});

1022
apps/blog/bun.lock Normal file

File diff suppressed because it is too large Load Diff

21
apps/blog/package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^4.2.6",
"@astrojs/rss": "^4.0.11",
"@astrojs/sitemap": "^3.4.0",
"@astrojs/solid-js": "^5.0.10",
"@tailwindcss/vite": "^4.1.7",
"astro": "^5.7.13",
"solid-js": "^1.9.7",
"tailwindcss": "^4.1.7"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View File

@@ -0,0 +1,55 @@
---
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import '../styles/global.css';
import { SITE_TITLE } from '../consts';
interface Props {
title: string;
description: string;
image?: string;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props;
---
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="sitemap" href="/sitemap-index.xml" />
<link
rel="alternate"
type="application/rss+xml"
title={SITE_TITLE}
href={new URL('rss.xml', Astro.site)}
/>
<meta name="generator" content={Astro.generator} />
<!-- Font preloads -->
<link rel="preload" href="/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin />
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.url)} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, Astro.url)} />

View File

@@ -0,0 +1,53 @@
---
import "../styles/global.css"
const today = new Date();
---
<footer>
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
<span class="hover:text-primary-500 transition-colors duration-200">
<a rel="noreferrer" href="https://nestri.io/terms" >Terms of Service</a></span>
<span class="text-gray-400 dark:text-gray-600">•</span>
<span class="hover:text-primary-500 transition-colors duration-200">
<a href="https://nestri.io/privacy">Privacy Policy</a>
</span>
</div>
<div class="mt-6 w-full justify-center flex items-center space-x-4">
<a href="https://discord.gg/6um5K6jrYj" target="_blank">
<span class="sr-only">Join our Discord Server</span>
<svg width="59" height="44" viewBox="0 0 59 44" aria-hidden="true" astro-icon="social/discord" style="height:28px">
<path d="M37.1937 0C36.6265 1.0071 36.1172 2.04893 35.6541 3.11392C31.2553 2.45409 26.7754 2.45409 22.365 3.11392C21.9136 2.04893 21.3926 1.0071 20.8254 0C16.6928 0.70613 12.6644 1.94475 8.84436 3.69271C1.27372 14.9098 -0.775214 25.8374 0.243466 36.6146C4.67704 39.8906 9.6431 42.391 14.9333 43.9884C16.1256 42.391 17.179 40.6893 18.0819 38.9182C16.3687 38.2815 14.7133 37.4828 13.1274 36.5567C13.5442 36.2557 13.9493 35.9432 14.3429 35.6422C23.6384 40.0179 34.4039 40.0179 43.711 35.6422C44.1046 35.9663 44.5097 36.2789 44.9264 36.5567C43.3405 37.4943 41.6852 38.2815 39.9604 38.9298C40.8633 40.7009 41.9167 42.4025 43.109 44C48.3992 42.4025 53.3653 39.9137 57.7988 36.6377C59.0027 24.1358 55.7383 13.3007 49.1748 3.70429C45.3663 1.95633 41.3379 0.717706 37.2053 0.0231518L37.1937 0ZM19.3784 29.9816C16.5192 29.9816 14.1461 27.3886 14.1461 24.1821C14.1461 20.9755 16.4266 18.371 19.3669 18.371C22.3071 18.371 24.6455 20.9871 24.5992 24.1821C24.5529 27.377 22.2956 29.9816 19.3784 29.9816ZM38.6639 29.9816C35.7931 29.9816 33.4431 27.3886 33.4431 24.1821C33.4431 20.9755 35.7236 18.371 38.6639 18.371C41.6042 18.371 43.9309 20.9871 43.8846 24.1821C43.8383 27.377 41.581 29.9816 38.6639 29.9816Z" fill="white"/>
</svg>
</a>
<a href="https://github.com/nestrilabs/nestri/" target="_blank">
<span class="sr-only">Go to Nestri's GitHub repo</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/github"
><path
fill="currentColor"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
/>
</svg>
</a>
</div>
</footer>
<style>
footer {
padding: 2em 1em 6em 1em;
background: linear-gradient(var(--gray-gradient)) no-repeat;
color: rgb(var(--gray));
text-align: center;
}
.social-links {
display: flex;
justify-content: center;
gap: 1em;
margin-top: 1em;
}
.social-links a {
text-decoration: none;
color: rgb(var(--gray));
}
.social-links a:hover {
color: rgb(var(--gray-dark));
}
</style>

View File

@@ -0,0 +1,17 @@
---
interface Props {
date: Date;
}
const { date } = Astro.props;
---
<time datetime={date.toISOString()}>
{
date.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
</time>

View File

@@ -0,0 +1,57 @@
---
import HeaderLink from './HeaderLink.astro';
import { SITE_TITLE } from '../consts';
import "../styles/global.css";
---
<header>
<nav>
<h2><a href="/">{SITE_TITLE}</a></h2>
<div class="internal-links">
<HeaderLink href="https://nestri.io/">Nestri Home</HeaderLink>
<HeaderLink href="/blog">Blog</HeaderLink>
<HeaderLink href="https://nestri.io/about">About us</HeaderLink>
</div>
</nav>
</header>
<style>
header {
margin: 0;
padding: 0 1em;
border-bottom: solid;
box-: 0 2px 8px rgba(var(--black), 5%);
}
h2 {
margin: 0;
font-size: 1em;
}
h2 a,
h2 a.active {
text-decoration: none;
}
nav {
display: flex;
align-items: center;
justify-content: space-between;
}
nav a {
padding: 1em 0.5em;
color: var(--black);
border-bottom: 4px solid transparent;
text-decoration: none;
}
nav a.active {
text-decoration: none;
border-bottom-color: var(--accent);
}
.social-links,
.social-links a {
display: flex;
}
@media (max-width: 720px) {
.social-links {
display: none;
}
}
</style>

View File

@@ -0,0 +1,24 @@
---
import type { HTMLAttributes } from 'astro/types';
type Props = HTMLAttributes<'a'>;
const { href, class: className, ...props } = Astro.props;
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, '');
const subpath = pathname.match(/[^\/]+/g);
const isActive = href === pathname || href === '/' + (subpath?.[0] || '');
---
<a href={href} class:list={[className, { active: isActive }]} {...props}>
<slot />
</a>
<style>
a {
display: inline-block;
text-decoration: none;
}
a.active {
font-weight: bolder;
text-decoration: underline;
}
</style>

5
apps/blog/src/consts.ts Normal file
View File

@@ -0,0 +1,5 @@
// Place any global data in this file.
// You can import this data from anywhere in your site by using the `import` keyword.
export const SITE_TITLE = 'Nestri Blog';
export const SITE_DESCRIPTION = 'Welcome to Nestri\'s Blog - This Blog is about the current status of and about intresting facts about Nestri';

View File

@@ -0,0 +1,18 @@
import { glob } from 'astro/loaders';
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
}),
});
export const collections = { blog };

View File

@@ -0,0 +1,16 @@
---
title: 'First post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 08 2022'
heroImage: '/blog-placeholder-3.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

View File

@@ -0,0 +1,65 @@
---
title: 'Technical Deep Dive into Latency'
description: "Why It's High and How to Reduce It"
pubDate: 'May 18 2025'
heroImage: '/pexels-brett-sayles-2881224.jpg'
---
### Why It's High and How to Reduce It
First, let's start with the basics of the Internet.
The Internet connects clients and servers. Webpages primarily use the Application Layer protocol HTTP(S) to communicate with servers. HTTP is widely adopted for various applications, including mobile apps and other services requiring server communication.
There are also other client protocols like WebRTC (Web Real-Time Communication), which mainly powers streaming services needing a back channel. Nestri utilizes WebRTC, and we'll delve deeper into that later.
Imagine using a client protocol like WebRTC to send messages. Common formats for these messages include XML, HTML, or JSON.
While HTML contains significant duplicate symbols (e.g., `<a href="example.com">Some Link</a> <a href="example.com/subpage">Some nested Link</a>`), the modern web employs techniques to reduce its size. For instance, using modern zipping algorithms like gzip, this data can be compressed, resulting in a smaller size for transmission over the HTTP protocol.
In computer science, the more dense the information in a message (achieved through compression, for example), the higher its message entropy. Therefore, sending messages with high entropy is beneficial as it allows for the transfer of more information in a smaller package. Pure HTTP has relatively low entropy, similar to XML. JSON offers higher entropy, which can be further increased by removing whitespace and shortening attribute names. However, in modern client-server applications, JSON is often compressed.
So, we compress JSON traffic for efficiency. Have you ever compressed a large file? Modern systems make this process incredibly fast! But this requires computing power on both the client and server sides, which directly influences latency.
"Well, if I have a fiber connection, I don't need to worry about that..."
While a fiber connection offers significant bandwidth, this statement is somewhat misleading.
Latency also depends on your local network. A modern and stable Wi-Fi connection might seem sufficient, but the physical layer of the internet also contributes to latency. Wireless protocols, in particular, operate on a shared medium the air. This medium is utilized by various devices, commonly on frequencies around 2.4 or 5 GHz. This spectrum is divided among all these devices. Mechanisms like scheduling and signal modulation are used to manage this shared resource. In essence, to avoid a deeper dive into wireless communication, a wired connection is generally superior to a wireless connection due to the absence of a shared physical medium.
Okay, but what about Ethernet or fiber cables? Aren't we sharing those as well, with multiple applications or other internet users?
Yes, this also impacts latency. If many users in your local area are utilizing the same uplinks to a backbone (a high-speed part of the internet), you'll have to share that bandwidth. While fiber optic cables have substantial capacity due to advanced modulation techniques, consider the journey these data packets undertake across the internet.
Sometimes, if a data center is located nearby, your connection will involve fewer routers (fewer hops) between you and the server. Fewer hops generally translate to lower latency. Each router needs to queue your messages and determine the next destination. Modern routing protocols facilitate this process. However, even routers have to process messages in their queues. Thus, higher message entropy means fewer or smaller packets need to be sent.
What happens when your messages are too large for transmission? They are split into multiple parts and sent using protocols like TCP. TCP ensures reliable packet exchange by retransmitting any packets that are likely lost during internet transit. Packet loss can occur if a router's queue overflows, forcing it to drop packets, potentially prioritizing other traffic. This retransmission significantly increases latency as a packet might need to be sent multiple times.
UDP offers a different approach: it sends all packets without the overhead of retransmission. In this case, the application protocol is responsible for handling any lost packets. Fortunately, there's an application protocol that manages this quite effectively: WebRTC.
WebRTC is an open-source project providing APIs for real-time communication of audio, video, and generic data between peers via a browser. It leverages protocols like ICE, STUN, and TURN to handle NAT traversal and establish peer-to-peer connections, enabling low-latency media streaming and data exchange directly within web applications.
Sending raw video streams over WebRTC is inefficient; they require compression using modern codecs. A GPU is the optimal choice for this task because it has dedicated hardware (hardware encoder) to accelerate video encoding, significantly speeding up the process compared to software encoding on a CPU. Therefore, your GPU also plays a crucial role in reducing latency during video encoding and decoding.
So, why is all this relevant to Nestri?
We aim to deliver a cutting-edge, low-latency cloud gaming experience. Here's what we've implemented to combat bad latency:
**1. Reducing Mouse and Keyboard Latency**
1. Reduce package size by using the Protobuf protocol instead of JSON.
2. Avoid wasting compute power by not compressing these already optimized messages.
3. Minimize message flooding by bundling multiple mouse events into fewer messages through aggregation.
4. Implement all of this within WebRTC for a super lightweight communication over UDP.
**2. Reducing Video Latency**
1. Utilize cutting-edge encoder-decoders on a GPU instead of a CPU.
**3. Reducing Network Latency in the Backbone**
1. Bring servers closer to users to reduce the hop count.
Here's a glimpse of the results of these improvements, comparing the experience before and after implementation:
![[nestri footage video](/nestri-footage-latency.png)](https://fs.dathorse.com/w/ad2bee7e322b942491044fcffcccc899)
**Latency Test and comparison to the old Nestri**
Did you enjoy this blog post? Join our Discord and share your thoughts!

View File

@@ -0,0 +1,214 @@
---
title: 'Markdown Style Guide'
description: 'Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.'
pubDate: 'Jun 19 2024'
heroImage: '/blog-placeholder-1.jpg'
---
Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.
## Headings
The following HTML `<h1>``<h6>` elements represent six levels of section headings. `<h1>` is the highest section level while `<h6>` is the lowest.
# H1
## H2
### H3
#### H4
##### H5
###### H6
## Paragraph
Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.
Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.
## Images
### Syntax
```markdown
![Alt text](./full/or/relative/path/of/image)
```
### Output
![blog placeholder](/blog-placeholder-about.jpg)
## Blockquotes
The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations.
### Blockquote without attribution
#### Syntax
```markdown
> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
> **Note** that you can use _Markdown syntax_ within a blockquote.
```
#### Output
> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
> **Note** that you can use _Markdown syntax_ within a blockquote.
### Blockquote with attribution
#### Syntax
```markdown
> Don't communicate by sharing memory, share memory by communicating.<br>
> — <cite>Rob Pike[^1]</cite>
```
#### Output
> Don't communicate by sharing memory, share memory by communicating.<br>
> — <cite>Rob Pike[^1]</cite>
[^1]: The above quote is excerpted from Rob Pike's [talk](https://www.youtube.com/watch?v=PAAkCSZUG1c) during Gopherfest, November 18, 2015.
## Tables
### Syntax
```markdown
| Italics | Bold | Code |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |
```
### Output
| Italics | Bold | Code |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |
## Code Blocks
### Syntax
we can use 3 backticks ``` in new line and write snippet and close with 3 backticks on new line and to highlight language specific syntax, write one word of language name after first 3 backticks, for eg. html, javascript, css, markdown, typescript, txt, bash
````markdown
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Example HTML5 Document</title>
</head>
<body>
<p>Test</p>
</body>
</html>
```
````
### Output
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Example HTML5 Document</title>
</head>
<body>
<p>Test</p>
</body>
</html>
```
## List Types
### Ordered List
#### Syntax
```markdown
1. First item
2. Second item
3. Third item
```
#### Output
1. First item
2. Second item
3. Third item
### Unordered List
#### Syntax
```markdown
- List item
- Another item
- And another item
```
#### Output
- List item
- Another item
- And another item
### Nested list
#### Syntax
```markdown
- Fruit
- Apple
- Orange
- Banana
- Dairy
- Milk
- Cheese
```
#### Output
- Fruit
- Apple
- Orange
- Banana
- Dairy
- Milk
- Cheese
## Other Elements — abbr, sub, sup, kbd, mark
### Syntax
```markdown
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.
H<sub>2</sub>O
X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>
Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.
Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.
```
### Output
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.
H<sub>2</sub>O
X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>
Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.
Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.

View File

@@ -0,0 +1,16 @@
---
title: 'Second post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 15 2022'
heroImage: '/blog-placeholder-4.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

View File

@@ -0,0 +1,16 @@
---
title: 'Third post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 22 2022'
heroImage: '/blog-placeholder-2.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

View File

@@ -0,0 +1,31 @@
---
title: 'Using MDX'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jun 01 2024'
heroImage: '/blog-placeholder-5.jpg'
---
This theme comes with the [@astrojs/mdx](https://docs.astro.build/en/guides/integrations-guide/mdx/) integration installed and configured in your `astro.config.mjs` config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file.
## Why MDX?
MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax. This unlocks the ability to [mix JavaScript and UI Components into your Markdown content](https://docs.astro.build/en/guides/markdown-content/#mdx-features) for things like interactive charts or alerts.
If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze.
## Example
Here is how you import and use a UI component inside of MDX.
When you open this page in the browser, you should see the clickable button below.
import HeaderLink from '../../components/HeaderLink.astro';
<HeaderLink href="#" onclick="alert('clicked!')">
Embedded component in MDX
</HeaderLink>
## More Links
- [MDX Syntax Documentation](https://mdxjs.com/docs/what-is-mdx)
- [Astro Usage Documentation](https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages)
- **Note:** [Client Directives](https://docs.astro.build/en/reference/directives-reference/#client-directives) are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default.

View File

@@ -0,0 +1,92 @@
---
import type { CollectionEntry } from 'astro:content';
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import FormattedDate from '../components/FormattedDate.astro';
import "../styles/global.css"
type Props = CollectionEntry<'blog'>['data'];
const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<style>
main {
width: calc(100% - 2em);
max-width: 100%;
margin: 0;
}
.hero-image {
width: 100%;
}
.hero-image img {
display: block;
margin: 0 auto;
border-radius: 12px;
}
.prose {
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
padding: 1em;
color: rgb(var(--gray-dark));
}
.title {
margin-bottom: 1em;
padding: 1em 0;
text-align: center;
line-height: 1;
}
.title h1 {
margin: 0 0 0.5em 0;
}
.date {
margin-bottom: 0.5em;
color: rgb(var(--gray));
}
.last-updated-on {
font-style: italic;
}
</style>
</head>
<body>
<Header />
<main>
<article>
<div class="grid gap-8 items-start justify-center">
<div class="relative group">
<div class="absolute -inset-0.5 bg-radial-gradient opacity-40 group-hover:opacity-80 transition duration-1000 group-hover:duration-200 animate-tilt" />
<div class="relative bg-black rounded-lg leading-none flex items-center divide-x divide-gray-600">
{heroImage && <img width={1020} height={510} src={heroImage} alt="" />}
</div>
</div>
</div>
</div>
<div class="prose">
<div class="title">
<div class="date">
<FormattedDate date={pubDate} />
{
updatedDate && (
<div class="last-updated-on">
Last updated on <FormattedDate date={updatedDate} />
</div>
)
}
</div>
<h1>{title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</main>
<Footer />
</body>
</html>

View File

@@ -0,0 +1,62 @@
---
import Layout from '../layouts/BlogPost.astro';
---
<Layout
title="About Me"
description="Lorem ipsum dolor sit amet"
pubDate={new Date('August 08 2021')}
heroImage="/blog-placeholder-about.jpg"
>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo
viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam
adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus
et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus
vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque
sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
</p>
<p>
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non
tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non
blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna
porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis
massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc.
Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis
bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra
massa massa ultricies mi.
</p>
<p>
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl
suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet
nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae
turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem
dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat
semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus
vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum
facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam
vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla
urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
</p>
<p>
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper
viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc
scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur
gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus
pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim
blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id
cursus metus aliquam eleifend mi.
</p>
<p>
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta
nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam
tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci
ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar
proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.
</p>
</Layout>

View File

@@ -0,0 +1,21 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'blog'>;
const post = Astro.props;
const { Content } = await render(post);
---
<BlogPost {...post.data}>
<Content />
</BlogPost>

View File

@@ -0,0 +1,120 @@
---
import BaseHead from '../../components/BaseHead.astro';
import Header from '../../components/Header.astro';
import Footer from '../../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
import { getCollection } from 'astro:content';
import FormattedDate from '../../components/FormattedDate.astro';
import "../../styles/global.css"
const posts = (await getCollection('blog')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
main {
width: 960px;
}
ul {
display: flex;
flex-wrap: wrap;
gap: 2rem;
list-style-type: none;
margin: 0;
padding: 0;
}
ul li {
width: calc(50% - 1rem);
}
ul li * {
text-decoration: none;
transition: 0.2s ease;
}
ul li:first-child {
width: 100%;
margin-bottom: 1rem;
text-align: center;
}
ul li:first-child img {
width: 100%;
}
ul li:first-child .title {
font-size: 2.369rem;
}
ul li img {
}
ul li a {
display: block;
}
.title {
margin: 0;
color: #d9d9d9;
line-height: 1;
}
.date {
margin: 0;
color: #c0c0c0;
}
ul li a:hover h4,
ul li a:hover .date {
color: #f2f2f2;
}
ul a:hover img {
box-shadow: var(--box-shadow);
}
@media (max-width: 720px) {
ul {
gap: 0.5em;
}
ul li {
width: 100%;
text-align: center;
}
ul li:first-child {
margin-bottom: 0;
}
ul li:first-child .title {
font-size: 1.563em;
}
}
</style>
</head>
<body>
<Header />
<main>
<section>
<ul>
{
posts.map((post) => (
<li>
<a href={`/blog/${post.id}/`}>
<div class="grid gap-8 items-start justify-center">
<div class="relative group">
<div class="absolute -inset-0.5 bg-radial-gradient opacity-0 group-hover:opacity-80 transition duration-1000 group-hover:duration-200 animate-tilt" />
<div class="relative bg-black rounded-lg leading-none flex items-center divide-x divide-gray-600">
<img width={720} height={360} src={post.data.heroImage} alt="" />
</div>
</div>
</div>
</div>
<h4 class="title py-4">{post.data.title}</h4>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
</ul>
</section>
</main>
<Footer />
</body>
</html>

View File

@@ -0,0 +1,49 @@
---
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
</head>
<body>
<Header />
<main>
<h1>🧑‍🚀 Hello, Astronaut!</h1>
<p>
Welcome to the official <a href="https://astro.build/">Astro</a> blog starter template. This
template serves as a lightweight, minimally-styled starting point for anyone looking to build
a personal website, blog, or portfolio with Astro.
</p>
<p>
This template comes with a few integrations already configured in your
<code>astro.config.mjs</code> file. You can customize your setup with
<a href="https://astro.build/integrations">Astro Integrations</a> to add tools like Tailwind,
React, or Vue to your project.
</p>
<p>Here are a few ideas on how to get started with the template:</p>
<ul>
<li>Edit this page in <code>src/pages/index.astro</code></li>
<li>Edit the site header items in <code>src/components/Header.astro</code></li>
<li>Add your name to the footer in <code>src/components/Footer.astro</code></li>
<li>Check out the included blog posts in <code>src/content/blog/</code></li>
<li>Customize the blog post page layout in <code>src/layouts/BlogPost.astro</code></li>
</ul>
<p>
Have fun! If you get stuck, remember to
<a href="https://docs.astro.build/">read the docs</a>
or <a href="https://astro.build/chat">join us on Discord</a> to ask questions.
</p>
<p>
Looking for a blog template with a bit more personality? Check out
<a href="https://github.com/Charca/astro-blog-template">astro-blog-template</a>
by <a href="https://twitter.com/Charca">Maxi Ferreira</a>.
</p>
</main>
<Footer />
</body>
</html>

View File

@@ -0,0 +1,16 @@
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/blog/${post.id}/`,
})),
});
}

View File

@@ -0,0 +1,178 @@
/*
The CSS in this style tag is based off of Bear Blog's default CSS.
https://github.com/HermanMartinus/bearblog/blob/297026a877bc2ab2b3bdfbd6b9f7961c350917dd/templates/styles/blog/default.css
License MIT: https://github.com/HermanMartinus/bearblog/blob/master/LICENSE.md
*/
@import "tailwindcss";
:root {
/*--accent: rgb(255, 79, 1);*/
/*--accent-dark: #fafafa;*/
/*--black: 15, 18, 25;*/
/*--gray: 96, 1, 159;*/
/*--gray-light: 82, 82, 82;*/
--gray-dark: 250, 250, 250;
--gray-gradient: rgba(var(--gray-light), 50%), #fff;
--box-shadow:
0 2px 6px rgba(var(--gray), 25%), 0 8px 24px rgba(var(--gray), 33%),
0 16px 32px rgba(var(--gray), 33%);
}
@font-face {
font-family: 'Atkinson';
src: url('/fonts/atkinson-regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Atkinson';
src: url('/fonts/atkinson-bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
body {
font-family: 'Atkinson', sans-serif;
margin: 0;
padding: 0;
text-align: left;
background: linear-gradient(var(--gray-gradient)) no-repeat;
background-color: #171717;
background-size: 100% 600px;
word-wrap: break-word;
overflow-wrap: break-word;
color: rgb(var(--gray-dark));
font-size: 20px;
line-height: 1.7;
}
main {
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
padding: 3em 1em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0 0 0.5rem 0;
color: rgb(var(--black));
line-height: 1.2;
}
h1 {
font-size: 3.052em;
}
h2 {
font-size: 2.441em;
}
h3 {
font-size: 1.953em;
}
h4 {
font-size: 1.563em;
}
h5 {
font-size: 1.25em;
}
strong,
b {
font-weight: 700;
}
a {
color: var(--accent);
}
a:hover {
color: var(--accent);
}
p {
margin-bottom: 1em;
}
.prose p {
margin-bottom: 2em;
}
textarea {
width: 100%;
font-size: 16px;
}
input {
font-size: 16px;
}
table {
width: 100%;
}
img {
max-width: 100%;
height: auto;
border-radius: 8px;
}
code {
padding: 2px 5px;
background-color: rgb(var(--gray-light));
border-radius: 2px;
}
pre {
padding: 1.5em;
border-radius: 8px;
}
pre > code {
all: unset;
}
blockquote {
border-left: 4px solid var(--accent);
padding: 0 0 0 20px;
margin: 0px;
font-size: 1.333em;
}
hr {
border: none;
border-top: 1px solid rgb(var(--gray-light));
}
@media (max-width: 720px) {
body {
font-size: 18px;
}
main {
padding: 1em;
}
}
.sr-only {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
/* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px 1px 1px 1px);
/* maybe deprecated but we need to support legacy browsers */
clip: rect(1px, 1px, 1px, 1px);
/* modern browsers, clip-path works inwards from each corner */
clip-path: inset(50%);
/* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */
white-space: nowrap;
}
.bg-radial-gradient {
filter: blur(32px);
background-image: linear-gradient(
90deg,
rgb(239, 118, 70),
rgb(251, 91, 88),
rgb(255, 61, 116),
rgb(249, 33, 149),
rgb(227, 34, 188),
rgb(181, 94, 230),
rgb(118, 128, 252),
rgb(0, 150, 255),
rgb(0, 183, 255),
rgb(0, 208, 242),
rgb(0, 227, 184),
rgb(70, 239, 111)
);
}

15
apps/blog/tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"extends": "astro/tsconfigs/strict",
"include": [
".astro/types.d.ts",
"**/*"
],
"exclude": [
"dist"
],
"compilerOptions": {
"strictNullChecks": true,
"jsx": "preserve",
"jsxImportSource": "solid-js"
}
}

25
apps/docs/.gitignore vendored
View File

@@ -1,25 +0,0 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
.vscode
# Local env files
.env
.env.*
!.env.example

View File

@@ -1,2 +0,0 @@
shamefully-hoist=true
strict-peer-dependencies=false

View File

@@ -1,49 +0,0 @@
# shadcn-docs-nuxt Minimal Starter
Starter template for [shadcn-docs-nuxt](https://github.com/ZTL-UwU/shadcn-docs-nuxt).
## Setup
Make sure to install the dependencies:
```bash
# yarn
yarn install
# npm
npm install
# pnpm
pnpm install
# bun
bun install
```
## Development Server
Start the development server on http://localhost:3000
```bash
npm run dev
```
## Production
[![Deploy to NuxtHub](https://hub.nuxt.com/button.svg)](https://hub.nuxt.com/new?repo=ZTL-UwU/shadcn-docs-nuxt-starter)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FZTL-UwU%2Fshadcn-docs-nuxt-starter)
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https%3A%2F%2Fgithub.com%2FZTL-UwU%2Fshadcn-docs-nuxt-starter)
Build the application for production:
```bash
npm run build
```
Locally preview production build:
```bash
npm run preview
```
Checkout the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

View File

@@ -1,79 +0,0 @@
export default defineAppConfig({
shadcnDocs: {
site: {
name: 'Nestri Docs',
description: 'Beautifully designed Nuxt Content template built with shadcn-vue. Customizable. Compatible. Open Source.',
},
theme: {
customizable: false,
color: 'orange',
radius: 0.5,
},
header: {
title: 'Nestri Docs',
showTitle: true,
darkModeToggle: true,
logo: {
light: '/logo.webp',
dark: '/logo.webp',
},
nav: [{
title: 'Star on GitHub',
icon: 'lucide:star',
to: 'https://github.com/nestrilabs/nestri',
target: '_blank',
}, {
title: 'Create Issues',
icon: 'lucide:circle-dot',
to: 'https://github.com/nestrilabs/nestri/issues',
target: '_blank',
}],
links: [
{
icon: 'lucide:github',
to: 'https://github.com/nestrilabs/nestri',
target: '_blank',
}],
},
aside: {
useLevel: true,
collapse: false,
},
main: {
breadCrumb: true,
showTitle: true,
},
footer: {
credits: 'Copyright © 2025',
links: [{
icon: 'lucide:github',
to: 'https://github.com/nestrilabs/nestri',
target: '_blank',
},
{
icon: 'ri:discord-line',
to: 'https://discord.com/invite/Y6etn3qKZ3',
target: '_blank',
}],
},
toc: {
enable: true,
title: 'On This Page',
links: [{
title: 'Star on GitHub',
icon: 'lucide:star',
to: 'https://github.com/nestrilabs/nestri',
target: '_blank',
}, {
title: 'Create Issues',
icon: 'lucide:circle-dot',
to: 'https://github.com/nestrilabs/nestri/issues',
target: '_blank',
}],
},
search: {
enable: true,
inAside: false,
}
}
});

View File

@@ -1,88 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border:214.3 31.8% 91.4%;
--input:214.3 31.8% 91.4%;
--ring:221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background:222.2 84% 4.9%;
--foreground:210 40% 98%;
--card:222.2 84% 4.9%;
--card-foreground:210 40% 98%;
--popover:222.2 84% 4.9%;
--popover-foreground:210 40% 98%;
--primary:217.2 91.2% 59.8%;
--primary-foreground:222.2 47.4% 11.2%;
--secondary:217.2 32.6% 17.5%;
--secondary-foreground:210 40% 98%;
--muted:217.2 32.6% 17.5%;
--muted-foreground:215 20.2% 65.1%;
--accent:217.2 32.6% 17.5%;
--accent-foreground:210 40% 98%;
--destructive:0 62.8% 30.6%;
--destructive-foreground:210 40% 98%;
--border:217.2 32.6% 17.5%;
--input:217.2 32.6% 17.5%;
--ring:224.3 76.3% 48%;
}
}
@layer utilities {
.step {
counter-increment: step;
}
.step:before {
@apply absolute w-9 h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background;
@apply -ml-[50px] -mt-1;
content: counter(step);
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -1,70 +0,0 @@
<template>
<div class="py-8">
<h2 class="text-3xl lg:text-4xl font-bold mb-12 text-gray-900 dark:text-white">
Contributors made <span class="text-orange-500">Nestri</span>
</h2>
<div class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-8 gap-4 sm:gap-5 lg:gap-6">
<div
v-for="(contributor, index) in contributors"
:key="index"
class="pt-[100%] relative"
>
<NuxtLink
v-if="contributor.login"
:key="contributor.login"
:to="`https://github.com/${contributor.login}`"
class="absolute inset-0 flex transition-all"
:style="{
'transition-delay': `${(index % 8 + Math.floor(index / 8)) * 20}ms`
}"
>
<UTooltip class="w-full text-orange-500" :text="contributor.login">
<img
:src="contributor.avatar_url"
provider="ipx"
densities="x1 x2"
height="80px"
width="80px"
:alt="contributor.login"
loading="lazy"
class="rounded-xl w-full h-full transition lg:hover:scale-110"
/>
</UTooltip>
<span class="inline-block rounded-t px-1 bg-gray-950 dark:bg-white text-white dark:text-gray-950 absolute -bottom-2 right-0 font-medium text-sm">
<span class="font-light text-xs text-gray-400">#</span>{{ index + 1 }}
</span>
</NuxtLink>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const contributors = ref([])
// Fetch contributors data from GitHub without authentication
const fetchContributors = async () => {
try {
const response = await fetch('https://api.github.com/repos/nestriness/nestri/contributors')
if (!response.ok) throw new Error('Failed to fetch contributors')
contributors.value = await response.json()
} catch (error) {
console.error('Error fetching contributors:', error)
}
}
// Fetch contributors when component is mounted
onMounted(fetchContributors)
</script>
<style>
:hover.
</style>

View File

@@ -1,15 +0,0 @@
<template>
<div>
<NuxtLink v-if="logo.light && logo.dark" class="flex">
<img :src="logo.light" class="h-7 dark:hidden" />
<img :src="logo.dark" class="hidden h-7 dark:block" />
<span v-if="showTitle && title" class="ml-3 self-center font-bold">
{{ title }}
</span>
</NuxtLink>
</div>
</template>
<script setup lang="ts">
const { logo, title, showTitle } = useConfig().value.header;
</script>

View File

@@ -1,25 +0,0 @@
---
title: What is Nestri?
description: Learn about Nestri, an open-source, self-hostable cloud gaming platform that gives you full control over your gaming server, streaming, and setup.
icon: 'lucide:gamepad'
---
Nestri is a self-hosted cloud gaming platform that enables you to spin up dedicated gaming sessions remotely and play your own games from any device with a browser. Unlike remote desktop solutions like Parsec, which focus on streaming a desktop environment, Nestri is designed specifically for cloud gaming. It works similarly to services like NVIDIA GeForce Now, allowing you to enjoy high-performance gaming without needing to be physically near your gaming PC.
The key difference with Nestri is that its open-source and can be self-hosted, so you have full control over the server, the games you install, and the entire setup. Nestri is ideal for gamers who prioritize privacy, flexibility, and control, offering a way to manage your own gaming infrastructure rather than relying on third-party services. As long as you have a stable internet connection and access to a web browser, you can game from virtually anywhere.
## Nestri Architecture
Nestri is composed of the following key components:
#### Nestri Node
The **Nestri Node** (also referred to as *Instance*) is the core of your Nestri setup. It acts as the game server where you install and run your games. The Nestri Node streams gameplay from the machine its installed on, allowing you to access your games remotely. It runs on most Linux-based systems and major vendor's GPUs (Intel, AMD, NVIDIA).
**Nestri Node** runs within a container, which isolates it from the host system, keeping the host environment clean and secure. This containerization also allows for easy updates, management and recovery of your gaming environment.
#### Nestri Relay
The **Nestri Relay** is responsible for taking the audio-video stream from your **Nestri Node** and sending that forward to the device you're gaming on with minimal latency. This is essentially a WebRTC SFU (Selective Forwarding Unit) that splits single incoming stream to multiple potential players, allowing multiple devices to connect to the same game session without overwhelming the **Nestri Node** with multiple outgoing streams.
**Nestri Relay** runs within a container, similar to the **Nestri Node**, and can be deployed on the same machine or a different one.

View File

@@ -1,56 +0,0 @@
---
title: FAQ
description: Got questions about Nestri? This FAQ covers everything from pricing and setup to game compatibility and system requirements. Whether you're exploring the free self-hosted version, the Bring Your Own GPU (BYOG) option, or the hosted service, youll find all the details here.
icon: 'lucide:message-circle-question'
---
## Is Nestri free?
Yes! Nestri offers three options: a free, self-hosted version, a free and paid **Bring Your Own GPU (BYOG)** version, and a paid, hosted version.
- **Self-Hosted Version (Free):**
If you have your own server, you can install and run Nestri for free. Since Nestri is open-source, you have full access to the codebase, allowing for transparency and flexibility in your setup.
- **Bring Your Own GPU (BYOG):**
With BYOG, you can use your own server with a GPU to play your games while avoiding the hassle of setting up relays, web interfaces, port forwarding, and other technical configurations. BYOG is available in both a free and a paid package:
- The **Free BYOG package** lets you get started with basic functionality.
- The **Paid BYOG package** unlocks exclusive features only available in BYOG and Hosted versions.
- **[Hosted Version (Paid)](https://nestri.io/pricing):**
The hosted version of Nestri operates similarly to services like NVIDIA GeForce Now. With a subscription, you can play your games on Nestris infrastructure without needing any technical knowledge—just sign up, log in, and start gaming!
## Does Nestri require a high-speed internet connection?
Yes, a stable and fast internet connection is essential for a smooth gaming experience. While you dont need extremely high speeds (like 1 Gbps fiber), low latency is critical. Since cloud gaming is sensitive to delay, your device needs to connect to one of our relays with minimal lag. Ensuring a strong, stable network connection close to a relay server is important to avoid delays in gameplay, especially during fast-paced action sequences.
## Where are Nestris relays located?
Currently, we have one relay deployed in Helsinki, Finland. As we grow, we plan to add more relays to reduce latency and improve connection quality for users in different regions. Our next planned relay is in Copenhagen, Denmark. Since were a startup with a limited budget, well continue to roll out more relays as demand grows and more users join the platform.
## Is Nestri like Parsec?
No, Nestri is not like Parsec, which is used to access and game on an existing desktop remotely. Nestri is a server application designed specifically for cloud gaming. Rather than connecting to a physical Windows desktop, Nestri runs your games within a Docker or Podman container, allowing you to play remotely without needing to access a traditional desktop environment.
## Do I need a high-end server with a 4090 GPU and a 64-core CPU?
Not necessarily! Nestri doesnt have strict hardware requirements in terms of having the latest or most powerful CPU or GPU. Just as with traditional gaming, better hardware will enhance your experience with improved graphics and higher FPS. The exact specs you need will depend on the games you want to play and the performance youre aiming for. Keep in mind that, because Nestri has to use a GPU to encode the game stream for lowest possible latency, there will be a bit of additional processing required.
## Do you have an app for phone or TV?
Not yet! At the moment, we dont have a dedicated app. However, since the Nestri interface works on most devices with a Chromium-based browser, you can play your games that way on your phone, TV, or other devices.
Were actively working on developing an app that will make it even easier to play your games on mobile, your TV, or install a client directly on your PC. Stay tuned for updates!
## Do I need to port forward to use Nestri?
No! If youre using Nestri BYOG, you wont need to port forward anything on your router or firewall.
Since Nestri is built with WebRTC, the Nestri node connects directly with the client via our relays. All you need to do is install Nestri on your server and start your game through our web interface — no complicated networking setup required!
## What games can I play on Nestri?
Currently, Nestri only supports Steam games that are compatible with Proton, as Nestri is Linux-based.
When you launch Nestri, youll have access to Steam Big Picture mode, just like on your PC. You can check which games are supported by Proton and their ratings on [ProtonDB](https://www.protondb.com/).
This ensures a smooth gaming experience for a wide range of titles, and were continually working to expand compatibility!
## Do I need my own server?
No! We also offer a **[Hosted version](https://nestri.io/pricing)**, where you can use our infrastructure. All you need to do is start your game through our interface, and well handle the rest.
If you dont have your own physical server, you can also run Nestri in the cloud. Simply use a dedicated server with a GPU or platforms like AWS, Digital Ocean, or similar services that offer GPU solutions.
Whether you prefer using your own setup or a hassle-free hosted solution, Nestri has you covered!

View File

@@ -1,3 +0,0 @@
title: Getting started
icon: lucide:rocket
navigation.redirect: /introduction/what-is-nestri

View File

@@ -1,7 +0,0 @@
---
title: What is Nestri Node?
description: What is Nestri Node and how does it powers the Nestri eco-system and your self-hosted cloud gaming experience.
icon: 'lucide:message-circle-question'
---
**Nestri Node** is the core component of Nestri's self-hosted cloud-gaming solution. It is the actual server where you install your games. Once set up, you can stream and play your games remotely from any compatible device. It runs on most Linux-based systems and requires a NVIDIA, AMD or Intel graphics card.

View File

@@ -1,27 +0,0 @@
---
title: Prerequisites
description: Essential system and software requirements for setting up Nestri on your server, including GPU compatibility, OS recommendations, and necessary configurations.
icon: 'lucide:check-circle'
---
To run Nestri on your own server, there are several essential preparations required before installing Nestri Node. This page outlines the key requirements to get Nestri up and running smoothly.
Nestri Node supports AMD, NVIDIA, and Intel graphics cards.
While it might be tempting to skip this setup, we advise against it. Taking the time to prepare now will help you avoid potential issues and wasted hours later.
## Recommended host configuration
::list{type="primary"}
- **AMD, NVIDIA or Intel GPU**
- **CPU with AVX2 support**
- **Fedora or Arch** based distribution
::
## Software Requirements
::list{type="primary"}
- **GPU Drivers** (if not provided by the kernel)
- **Podman or Docker** (Podman is recommended for better compatibility)
::

View File

@@ -1,72 +0,0 @@
---
title: Getting Started
description: Follow this guide to set up and run your own Nestri Node for cloud gaming.
icon: 'lucide:message-circle-question'
---
::alert{type="danger"}
Nestri is in a **very early phase**, so errors and bugs may occur.
::
::alert{type="info"}
You can pull the docker image from GitHub Container Registry with:
```bash [pull image command]
podman pull ghcr.io/nestrilabs/nestri/runner:nightly
```
::
### Step 1: Create a home directory for your Nestri Node
This will be the directory where Steam, games and other persistent files will be saved.
You may use any directory you like, but for simplicity, we will use `~/nestri` as the home directory in this guide.
```bash [create home directory command]
mkdir -p ~/nestri
sudo chmod 777 ~/nestri
```
The above will create a directory called `nestri` in your home directory and set the permissions to allow read, write, and execute for all users.
This is important for the Nestri Node to function properly.
### Step 2: Launch the Nestri Runner
With your home directory ready, insert it into the command below, replacing `<relay_url>` with the relay's URL you want to use.
You will also need to replace `<room_name>` with an unique name for the room you will be using to play your games.
```bash [run container (nvidia)]
podman run --replace -d --name=nestri --shm-size=6g --cap-add=SYS_NICE --device /dev/dri/ -e RELAY_URL='<relay_url>' -e NESTRI_ROOM='<room_name>' -e RESOLUTION=1920x1080 -e FRAMERATE=60 -e NESTRI_PARAMS='--verbose=true --dma-buf=true --audio-rate-control=cbr --video-codec=h264 --video-rate-control=cbr --video-bitrate=8000' -v ~/nestri:/home/nestri --device /dev/nvidia-uvm --device /dev/nvidia-uvm-tools --device /dev/nvidiactl --device /dev/nvidia0 --device /dev/nvidia-modeset ghcr.io/nestrilabs/nestri/runner:nightly
```
```bash [run container (amd/intel)]
podman run --replace -d --name=nestri --shm-size=6g --cap-add=SYS_NICE --device /dev/dri/ -e RELAY_URL='<relay_url>' -e NESTRI_ROOM='<room_name>' -e RESOLUTION=1920x1080 -e FRAMERATE=60 -e NESTRI_PARAMS='--verbose=true --dma-buf=true --audio-rate-control=cbr --video-codec=h264 --video-rate-control=cbr --video-bitrate=8000' -v ~/nestri:/home/nestri ghcr.io/nestrilabs/nestri/runner:nightly
```
### Step 3: Begin Playing
Finally, construct the play URL with your room name and relay URL:
`https://nestri.io/play/<room_name>?peerURL=<relay_url>`
Navigate to this URL in your browser, click on the button to capture your mouse pointer and keyboard, and start playing!
### Stop the Nestri Container
If you want to stop the Nestri container, you can use the following command:
```bash [stop container command]
podman stop nestri
```
### Start the Nestri Container
If you want to start the Nestri container after stopping it, you can use the following command:
```bash [start container command]
podman start nestri
```
### Remove the Nestri Container
To remove the container, you can use the following command:
```bash [remove container command]
podman rm nestri
```
### Update Nestri Container
To update the Nestri container, you can use the following command:
```bash [update container command]
podman pull ghcr.io/nestrilabs/nestri/runner:nightly
```
After which, you can recreate the container with the latest image using the same command you used in Step 2.

View File

@@ -1 +0,0 @@
# Troubleshooting

View File

@@ -1,36 +0,0 @@
---
title: Container CLI
description: Configure and manage your Nestri container environment using CLI parameters for relay settings, video resolution, GPU selection, and encoding options.
icon: 'lucide:terminal'
---
The Container CLI for Nestri provides parameters to configure and manage your container environment. Use these options to set values like `relay-url`, `video resolution`, and `frame rate`. Additionally, activate `verbose` mode and logging to assist in debugging and error tracking. This documentation details each parameter to help you optimize your container setup effectively
| **Parameter** | **Type** | **Default** | **Description** |
|--------------------------|----------|-------------|-----------------------------------------------------------------------------------|
| `-v, --verbose` | `string` | false | Enable verbose output. Set to `true` for detailed logs. |
| `-d, --debug` | `string` | false | Enable additional debugging features. Set to `true` for extra debug information. |
| `-u, --relay-url` | `string` | | Nestri relay URL. Specify the URL for the Nestri relay server. |
| `-r, --resolution` | `string` | 1280x720 | Display/stream resolution in 'WxH' format. Default is 1280x720. |
| `-f, --framerate` | `string` | 60 | Display/stream framerate. Default is 60 FPS. |
| `--room` | `string` | | Nestri room name/identifier. Specify the room for your Nestri session. |
| `-g, --gpu-vendor` | `string` | | GPU vendor to use (e.g., NVIDIA, AMD, Intel). |
| `-n, --gpu-name` | `string` | | GPU name to use. Specify the exact GPU model. |
| `-i, --gpu-index` | `string` | -1 | GPU index to use. Default is -1 (auto-select). |
| `--gpu-card-path` | `string` | | Force a specific GPU by `/dev/dri/` card or render path. |
| `-c, --video-codec` | `string` | h264 | Preferred video codec. Options: h264, h265, av1. Default is h264. |
| `--video-encoder` | `string` | | Override video encoder (e.g., `nvenc`, `libx264`). |
| `--video-rate-control` | `string` | cbr | Rate control method. Options: cqp, vbr, cbr. Default is cbr. |
| `--video-cqp` | `string` | 26 | Constant Quantization Parameter (CQP) quality. Default is 26. |
| `--video-bitrate` | `string` | 6000 | Target bitrate in kbps. Default is 6000 kbps. |
| `--video-bitrate-max` | `string` | 8000 | Maximum bitrate in kbps. Default is 8000 kbps. |
| `--video-encoder-type` | `string` | hardware | Encoder type. Options: software, hardware. Default is hardware. |
| `--audio-capture-method` | `string` | pulseaudio | Audio capture method. Options: pulseaudio, pipewire, alsa. Default is pulseaudio. |
| `--audio-codec` | `string` | opus | Preferred audio codec. Default is opus. |
| `--audio-encoder` | `string` | | Override audio encoder (e.g., `opusenc`). |
| `--audio-rate-control` | `string` | cbr | Audio rate control method. Options: cqp, vbr, cbr. Default is cbr. |
| `--audio-bitrate` | `string` | 128 | Target audio bitrate in kbps. Default is 128 kbps. |
| `--audio-bitrate-max` | `string` | 192 | Maximum audio bitrate in kbps. Default is 192 kbps. |
| `--dma-buf` | `string` | false | Use DMA-BUF for pipeline. Set to `true` to enable DMA-BUF support. |
| `-h, --help` | | | Print help information for the CLI parameters. |

View File

@@ -1,14 +0,0 @@
---
title: Node FAQ
description: This FAQ is made to address common questions about Nestri Node, the container which runs your games. Whether you're curious about compatibility, setup, or performance, you'll find answers to help you get started.
icon: 'lucide:info'
---
## Can I run Nestri Node on Debian/Ubuntu?
Yes, this is now possible, but not recommended due to several issues from those distributions.
## Can I run Nestri Node in a virtualized environment like Proxmox?
Yes, you can run Nestri Node in a virtualized environment, provided you passthrough your GPU to the virtual machine.
## Can I run Nestri Node on Windows-based systems?
No, the Nestri Node service does not support Windows-based systems. It can only be deployed on Linux-based systems.

View File

@@ -1,36 +0,0 @@
---
title: Developer Notes and Tips
description: This is a collection of developer notes for Nestri Node.
icon: 'lucide:wrench'
---
### Construct The Nestri Runner Docker Image
Checkout your branch with the latest version of nestri and build the image `<your-nestri-image>` within git root folder:
```bash [build docker image command]
podman build -t <your-nestri-image>:latest -f containers/Containerfile.runner .
```
### Running other applications besides Steam
When you followed the getting started guide, you already have a container running. You can get into your container to start your games or other applications:
```bash [get into container command]
podman exec -it nestri /bin/bash
```
For most games that are not DRM free you need a launcher. In this case use the umu launcher:
```bash [install umu and mangohud command]
pacman -S umu-launcher
```
You have to execute your game with the nestri user. If you have a linux game execute it like so:
```bash [execute game command]
su nestri
source /etc/nestri/envs.sh
GAMEID=0 PROTONPATH=GE-Proton mangohud umu-run <your-game.exe>
```
You could also use other launchers like Lutris to run other games.
::alert{type="danger"}
**Warning:** Running other applications besides Steam is not supported and may cause issues. We cannot provide support for this.
::

View File

@@ -1,3 +0,0 @@
title: Nestri Node
navigation.redirect: /nestri-node/what-is-nestri-node
icon: lucide:box

View File

@@ -1,8 +0,0 @@
---
title: What is Nestri Relay?
description: This FAQ is made to address common questions about Nestri Node, the container which runs your games. Whether you're curious about compatibility, setup, or performance, you'll find answers to help you get started.
icon: 'lucide:info'
---
Nestri Relay is an essential component in the Nestri cloud-gaming ecosystem, responsible for taking the audio-video stream from your Nestri Node and further forwarding that to the device youre playing on.
It is built using WebRTC, for lowest latency streaming.

View File

@@ -1,166 +0,0 @@
## Should I Self-Host a Nestri Relay?
If you want to use and enjoy the simplicity of the Nestri ecosystem, then you should not set up the Nestri Relay locally. Our free BYOG (Bring Your Own GPU) plan includes free shared relay access, which we highly recommend for those who want to start playing quickly on their own hardware without additional setup.
However, if you prefer to install and manage the Nestri Relay yourself, there are some important considerations to keep in mind.
### Important Considerations for Self-Hosting Nestri Relay
1. WebRTC and Firewall Issues
* WebRTC, by default, attempts to access your public IP even if both the relay and Nestri Node are on the same local network.
* This behavior can cause firewalls to block traffic, as the connection may attempt to access itself, resulting in connection failures.
* Unordered Third
2. Recommended Deployment Strategy
* Instead of hosting the relay on your local network, we strongly recommend deploying the Nestri Relay on a VPS (Virtual Private Server) in the cloud.
* Using a cloud-based VPS minimizes potential firewall conflicts and ensures a more stable connection between your Nestri Node and the relay.
If you're set on self-hosting despite the potential challenges, proceed with caution and ensure you have a proper understanding of firewall configurations and networking setups to mitigate connectivity issues.
## Self-hosted Nestri Relay
For those who prefer full control over the Nestri stack, it is possible to self-host the Nestri Relay. However, setting this up can be a bit complex, as it requires SSL Certificates for secure communication between your Nestri Node and your gaming devices. There are three main options:
- **Let's Encrypt Certificate**: This is the most common certificates for self-hosting and requires a domain name. You can generate a certificate using tools like **certbot** or **acme.sh**. Let's Encrypt provides free SSL certificates that are trusted by most browsers and are relatively straightforward to set up.
- **Purchased SSL Certificate**: The **easiest option** for most users is to buy an SSL certificate from a trusted Certificate Authority (CA). This option eliminates much of the hassle involved with certificate generation and renewals, as these certificates are already trusted by browsers and dont require as much manual setup.
While self-hosting offers more flexibility, most users will find the **Nestri-hosted Relay** to be the easiest and most reliable option for getting started with cloud gaming on Nestri. This hosted relay is available to everyone using the BYOG package and requires no configuration.
## Prerequisites
1. **Server Requirements:**
- Ensure **port 443** is open for both **TCP and UDP** (`:443/udp & :443/tcp`).
- The server should have at least **6-8GB RAM** and **2 vCPUs**.
- Supports both ARM or AMD64 architecture.
2. **Software Requirements:**
- Docker and `docker-compose` must be installed on the server. You can use [this installation script](https://github.com/docker/docker-install) to set up Docker.
- Git must be installed to clone the necessary repository.
3. **Certificates:**
- You will need both private and public SSL certificates. It is recommended to use certificates from a **trusted Certificate Authority** (CA), either by using **Let's Encrypt** or purchasing a commercial SSL certificate, for secure communication. Avoid using self-signed certificates, as they can lead to compatibility issues and security warnings in browsers.
## Self-hosted Nestri Relay with an Reverse Proxy
### Caddy
As caddy user you can use the following docker-compose.yml file:
```yaml [docker-compose.caddy.yml]
services:
caddy:
image: caddy:latest
container_name: caddy
ports:
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile # your caddyfile
- ./cert:/etc/caddy/certs
depends_on:
- relay
networks:
- relay_network
restart: unless-stopped
relay:
#image: ghcr.io/nestrilabs/nestri/relay:nightly # Offical relay image
image: ghcr.io/datcaptainhorse/nestri-relay:latest # Most current relay image
container_name: relay
environment:
#- AUTO_ADD_LOCAL_IP=false # use with WEBRTC_NAT_IPS
#- WEBRTC_NAT_IPS=1.2.3.4 # Add the LAN IP of your container here if connections fail
- VERBOSE=true
- DEBUG=true
ports:
- "8088:8088/udp"
networks:
- relay_network
restart:
unless-stopped
networks:
relay_network:
driver: bridge
```
The Caddyfile should look like this:
```caddyfile [Caddyfile]
relay.example.com {
@ws {
header Connection Upgrade
header Upgrade websocket
}
tls you@example.com
reverse_proxy @ws relay:8088
reverse_proxy relay:8088
}
```
Please modify it to your needs and replace the placeholder values with your own.
You should also setup the Caddyfile to match your domain.
### Traefik
As traefik user you can use the following docker-compose.yml file:
```yaml [docker-compose.relay.traefik.yml]
services:
traefik:
image: "traefik:v2.3"
restart: always
container_name: "traefik"
networks:
- traefik
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.network=traefik"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=web-secure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web-secure.address=:443"
- "--certificatesresolvers.default.acme.tlschallenge=true"
- "--certificatesresolvers.default.acme.email=foo@example.com" # Your email for tls challenge
- "--certificatesresolvers.default.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "./letsencrypt:/letsencrypt" # Your letsencrypt folder for certificate persistence
- "/var/run/docker.sock:/var/run/docker.sock:ro"
restart:
unless-stopped
relay:
#image: ghcr.io/nestrilabs/nestri/relay:nightly # Offical relay image
image: ghcr.io/datcaptainhorse/nestri-relay:latest # Most current relay image
container_name: relay
environment:
- AUTO_ADD_LOCAL_IP=false # Use with WEBRTC_NAT_IPS
#- WEBRTC_NAT_IPS=1.2.3.4 # Add the LAN IP of your container here if connections fail
- VERBOSE=true
- DEBUG=true
ports:
- "8088:8088/udp"
networks:
- traefik
restart:
unless-stopped
labels:
- traefik.enable=true
- traefik.http.routers.relay.rule=Host(`relay.example.com`) # Your domain for tls challenge
- traefik.http.routers.relay.tls=true
- traefik.http.routers.relay.tls.certresolver=default
- traefik.http.routers.relay.entrypoints=web-secure
- traefik.http.services.relay.loadbalancer.server.port=8088
networks:
traefik:
external: true
```
Please modify it to your needs and replace the placeholder values with your own.
### Where to find the relay compose files?
You will also find the relay compose files in our [github repository](https://github.com/nestrilabs/nestri/tree/main/containers).

View File

@@ -1,25 +0,0 @@
---
title: Container CLI
description: Configure and manage your Nestri Relay environment using CLI parameters for WebRTC settings, STUN servers, local IP handling, and TLS options.
icon: 'lucide:terminal'
---
The Nestri Relay CLI provides configuration parameters to manage your relay environment. These options allow you to set values like `WebRTC ports`, `STUN servers`, and `TLS certificates`. Additionally, you can enable `verbose` mode and debugging for troubleshooting purposes. This documentation details each parameter to help you optimize your relay setup effectively.
## Parameters
| **Parameter** | **Type** | **Default** | **Description** |
|----------------------------------|-----------|------------------------------------|------------------------------------------------------------------------------------------------------|
| `-v, --verbose` | `boolean` | false | Shows more logs; useful for debugging issues. Recommended before reporting problems. |
| `-d, --debug` | `boolean` | false | Enables debugging mode for additional logs and troubleshooting insights. |
| `-p, --endpointPort` | `integer` | 8088 | Specifies the main port for the relay endpoint. |
| **WebRTC Settings** | | | |
| `--webrtcUDPStart` | `integer` | 10000 | Defines the starting UDP port for WebRTC connections. |
| `--webrtcUDPEnd` | `integer` | 20000 | Defines the ending UDP port for WebRTC connections. |
| `--webrtcUDPMux` | `integer` | 8088 | Specifies the WebRTC UDP multiplexing port. |
| `--stunServer` | `string` | stun.l.google.com:19302 | Defines the STUN server address for NAT traversal. |
| `--autoAddLocalIP` | `boolean` | true | Automatically adds local IP addresses to WebRTC candidates. |
| `--WEBRTC_NAT_IPS` | `string` | "" | Comma-separated list of public IPs for WebRTC NAT traversal (e.g., `"192.168.0.1,192.168.0.2"`). |
| **TLS Configuration** | | | |
| `--tlsCert` | `string` | "" | Path to the TLS certificate file for secure connections. |
| `--tlsKey` | `string` | "" | Path to the TLS private key file for secure connections. |

View File

@@ -1,3 +0,0 @@
title: Nestri Relay
navigation.redirect: /nestri-relay/what-is-nestri-relay
icon: lucide:box

View File

@@ -1,30 +0,0 @@
---
title: Home
navigation: false
---
::hero
---
announcement:
title: 'We are launching soon!'
icon: '🎉'
to: https://github.com/nestrilabs/nestri/releases/latest
target: _blank
actions:
- name: Documentation
to: /introduction/what-is-nestri
- name: GitHub
variant: outline
to: https://github.com/nestrilabs/nestri
leftIcon: 'lucide:github'
---
#title
Welcome to Nestri Docs
#description
Play your favorite games on the go or with your friends on your own game cloud.
::
::contributors
::

View File

@@ -1,6 +0,0 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
extends: ['shadcn-docs-nuxt'],
compatibilityDate: '2024-07-06',
});

15358
apps/docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
{
"name": "shadcn-docs-nuxt-starter",
"private": true,
"type": "module",
"scripts": {
"nestri.dev": "nuxi dev",
"build": "nuxi build --preset=cloudflare_pages",
"generate": "nuxi generate",
"preview": "nuxi preview",
"lint": "eslint ."
},
"dependencies": {
"nuxt": "^3.15.4",
"shadcn-docs-nuxt": "^0.8.14",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 68 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><use id="Hintergrund" xlink:href="#_Image1" x="0" y="0" width="90px" height="69px" transform="matrix(0.992647,0,0,0.995192,0,0)"/><defs><image id="_Image1" width="68px" height="52px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEQAAAA0CAYAAAAzMZ5zAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACxElEQVRoge1aTa7UMAz+HAxCIJBYIBachBuwgj0n5TIc4C3fSE9saPwW03Yc122TUWfaF/JJI02TL67j+qdNQv9+vn5EDxIAEIA6BMi5UbAAWup8kWAAH/dW4kgIwx9a9IT/B6wNEQQQWokSUWFSX8SAk+kTAMS9dDkEGNQBGEImgGA8pEIvWAKP1QQESAQICBDEGUsEt7UeXOY3JhMvg9D4k55Raw7mZGZOqRECSCVSUZwaKxNPHjXZCxs6OmjqS8DTlFDhUy8Bz1eRpZxikBM7slKutpBRIsvK68dMDCIJT87dnh4CxKE9S88VJbeQUSTLyOvHMASnRBYpA2vBS7pcy9dj5oqb7VuziX6RytVn6CeAIfR92mME5wgt4UuGplbeGMFrj39D/RsaGtZA8oPe763EkcAAHvZW4khgAM1DFGr/mi9GM4hBM4hBM4hBM4hBM4hBM4hBM4gBu63XrF6tjTka344ZVsyiN7B09eoWfBJ/IWkr/oxOHLxV93utmF2xonVrPj+8/fSUdJAAiL0wJSVxScf8ZLav1viQ1K1L+dC78lvwAVAEn968+zIV1PX/9b6Lyr/i5GKKN+YLgE413Jrf0NCQAfrz6+u3tGkl1oA+/g0kYHbfdxM+AXiVwbdj9f40meSt7k8dAAF//vv4+zLQCBC72T3sedgDEdRP0KtzOtvLHfj6vzHGWGm8Tfx4rjIfuid1CnHOO4K5GaZPcfLE+5vsws+pXlZWB5CAu+TNbOZ4w3h+1XsSlqcuNuVLAT93TlpWBCDgxHHEE0QY67PM3XS4jun1pnxy+Jo382mwOCcyPHEOzEwUyt3wjFaT/fjZc5rK8792r0bpbvGt+eXy2nqIAU/Lz7LV6jtVlsL7yrm/FgdCCxmDJKleXkl6L3FW00LuwbcXimUPqXzyHlrIGHCQcEqbnNfmqDylcqd5BirQM2zm+G7BAAAAAElFTkSuQmCC"/></defs></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 B

View File

@@ -1,88 +0,0 @@
import animate from 'tailwindcss-animate';
export default {
darkMode: 'class',
safelist: ['dark'],
prefix: '',
content: [
'./content/**/*',
],
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px',
},
},
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
xl: 'calc(var(--radius) + 4px)',
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
'collapsible-down': {
from: { height: '0' },
to: { height: 'var(--radix-collapsible-content-height)' },
},
'collapsible-up': {
from: { height: 'var(--radix-collapsible-content-height)' },
to: { height: '0' },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'collapsible-down': 'collapsible-down 0.2s ease-in-out',
'collapsible-up': 'collapsible-up 0.2s ease-in-out',
},
},
},
plugins: [animate],
};

View File

@@ -1,4 +0,0 @@
{
// https://v3.nuxtjs.org/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

View File

@@ -1,38 +0,0 @@
**/*.log
**/.DS_Store
*.
.vscode/settings.json
.history
.yarn
bazel-*
bazel-bin
bazel-out
bazel-qwik
bazel-testlogs
dist
dist-dev
lib
lib-types
etc
external
node_modules
temp
tsc-out
tsdoc-metadata.json
target
output
rollup.config.js
build
.cache
.vscode
.rollup.cache
dist
tsconfig.tsbuildinfo
vite.config.ts
*.spec.tsx
*.spec.ts
.netlify
pnpm-lock.yaml
package-lock.json
yarn.lock
server

View File

@@ -1,42 +0,0 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:qwik/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json"],
ecmaVersion: 2021,
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
plugins: ["@typescript-eslint"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
"prefer-spread": "off",
"no-case-declarations": "off",
"no-console": "off",
"@typescript-eslint/no-unused-vars": ["error"],
"@typescript-eslint/consistent-type-imports": "warn",
"@typescript-eslint/no-unnecessary-condition": "warn",
},
};

48
apps/www/.gitignore vendored
View File

@@ -1,48 +0,0 @@
# Build
/dist
/lib
/lib-types
/server
# Development
node_modules
*.local
# Cache
.cache
.mf
.rollup.cache
tsconfig.tsbuildinfo
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Editor
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Yarn
.yarn/*
!.yarn/releases
# Cloudflare
functions/**/*.js
#Typescript
*.tsbuildinfo
tmp

View File

@@ -1,37 +0,0 @@
**/*.log
**/.DS_Store
*.
.vscode/settings.json
.history
.yarn
bazel-*
bazel-bin
bazel-out
bazel-qwik
bazel-testlogs
dist
dist-dev
lib
lib-types
etc
external
node_modules
temp
tsc-out
tsdoc-metadata.json
target
output
rollup.config.js
build
.cache
.vscode
.rollup.cache
tsconfig.tsbuildinfo
vite.config.ts
*.spec.tsx
*.spec.ts
.netlify
pnpm-lock.yaml
package-lock.json
yarn.lock
server

View File

@@ -1,112 +0,0 @@
# Qwik City App ⚡️
- [Qwik Docs](https://qwik.dev/)
- [Discord](https://qwik.dev/chat)
- [Qwik GitHub](https://github.com/QwikDev/qwik)
- [@QwikDev](https://twitter.com/QwikDev)
- [Vite](https://vitejs.dev/)
---
## Project Structure
This project is using Qwik with [QwikCity](https://qwik.dev/qwikcity/overview/). QwikCity is just an extra set of tools on top of Qwik to make it easier to build a full site, including directory-based routing, layouts, and more.
Inside your project, you'll see the following directory structure:
```
├── public/
│ └── ...
└── src/
├── components/
│ └── ...
└── routes/
└── ...
```
- `src/routes`: Provides the directory-based routing, which can include a hierarchy of `layout.tsx` layout files, and an `index.tsx` file as the page. Additionally, `index.ts` files are endpoints. Please see the [routing docs](https://qwik.dev/qwikcity/routing/overview/) for more info.
- `src/components`: Recommended directory for components.
- `public`: Any static assets, like images, can be placed in the public directory. Please see the [Vite public directory](https://vitejs.dev/guide/assets.html#the-public-directory) for more info.
## Add Integrations and deployment
Use the `bun qwik add` command to add additional integrations. Some examples of integrations includes: Cloudflare, Netlify or Express Server, and the [Static Site Generator (SSG)](https://qwik.dev/qwikcity/guides/static-site-generation/).
```shell
bun qwik add # or `bun qwik add`
```
## Development
Development mode uses [Vite's development server](https://vitejs.dev/). The `dev` command will server-side render (SSR) the output during development.
```shell
npm start # or `bun start`
```
> Note: during dev mode, Vite may request a significant number of `.js` files. This does not represent a Qwik production build.
## Preview
The preview command will create a production build of the client modules, a production build of `src/entry.preview.tsx`, and run a local server. The preview server is only for convenience to preview a production build locally and should not be used as a production server.
```shell
bun preview # or `bun preview`
```
## Production
The production build will generate client and server modules by running both client and server build commands. The build command will use Typescript to run a type check on the source code.
```shell
bun build # or `bun build`
```
## Cloudflare Pages
Cloudflare's [wrangler](https://github.com/cloudflare/wrangler) CLI can be used to preview a production build locally. To start a local server, run:
```
bun serve
```
Then visit [http://localhost:8787/](http://localhost:8787/)
### Deployments
[Cloudflare Pages](https://pages.cloudflare.com/) are deployable through their [Git provider integrations](https://developers.cloudflare.com/pages/platform/git-integration/).
If you don't already have an account, then [create a Cloudflare account here](https://dash.cloudflare.com/sign-up/pages). Next go to your dashboard and follow the [Cloudflare Pages deployment guide](https://developers.cloudflare.com/pages/framework-guides/deploy-anything/).
Within the projects "Settings" for "Build and deployments", the "Build command" should be `bun build`, and the "Build output directory" should be set to `dist`.
### Function Invocation Routes
Cloudflare Page's [function-invocation-routes config](https://developers.cloudflare.com/pages/platform/functions/routing/#functions-invocation-routes) can be used to include, or exclude, certain paths to be used by the worker functions. Having a `_routes.json` file gives developers more granular control over when your Function is invoked.
This is useful to determine if a page response should be Server-Side Rendered (SSR) or if the response should use a static-site generated (SSG) `index.html` file.
By default, the Cloudflare pages adaptor _does not_ include a `public/_routes.json` config, but rather it is auto-generated from the build by the Cloudflare adaptor. An example of an auto-generate `dist/_routes.json` would be:
```
{
"include": [
"/*"
],
"exclude": [
"/_headers",
"/_redirects",
"/build/*",
"/favicon.ico",
"/manifest.json",
"/service-worker.js",
"/about"
],
"version": 1
}
```
In the above example, it's saying _all_ pages should be SSR'd. However, the root static files such as `/favicon.ico` and any static assets in `/build/*` should be excluded from the Functions, and instead treated as a static file.
In most cases the generated `dist/_routes.json` file is ideal. However, if you need more granular control over each path, you can instead provide you're own `public/_routes.json` file. When the project provides its own `public/_routes.json` file, then the Cloudflare adaptor will not auto-generate the routes config and instead use the committed one within the `public` directory.

View File

@@ -1,15 +0,0 @@
import { cloudflarePagesAdapter } from "@builder.io/qwik-city/adapters/cloudflare-pages/vite";
import { extendConfig } from "@builder.io/qwik-city/vite";
import baseConfig from "../../vite.config";
export default extendConfig(baseConfig, () => {
return {
build: {
ssr: true,
rollupOptions: {
input: ["src/entry.cloudflare-pages.tsx", "@qwik-city-plan"],
},
},
plugins: [cloudflarePagesAdapter()],
};
});

View File

@@ -1,23 +0,0 @@
import { denoServerAdapter } from "@builder.io/qwik-city/adapters/deno-server/vite";
import { extendConfig } from "@builder.io/qwik-city/vite";
import baseConfig from "../../vite.config";
export default extendConfig(baseConfig, () => {
return {
build: {
ssr: true,
rollupOptions: {
input: ["src/entry.deno.ts", "@qwik-city-plan"],
},
minify: false,
},
plugins: [
denoServerAdapter({
ssg: {
include: ["/*"],
origin: "https://yoursite.dev",
},
}),
],
};
});

View File

@@ -1,78 +0,0 @@
{
"name": "@nestri/web",
"description": "Your games. Your rules.",
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"engines-annotation": "Mostly required by sharp which needs a Node-API v9 compatible runtime",
"private": true,
"trustedDependencies": [
"sharp"
],
"trustedDependencies-annotation": "Needed for bun to allow running install scripts",
"type": "module",
"scripts": {
"build": "qwik build",
"build.client": "vite build",
"build.preview": "vite build --ssr src/entry.preview.tsx",
"build.server": "vite build -c adapters/cloudflare-pages/vite.config.ts",
"deno:build.server": "vite build -c adapters/deno/vite.config.ts",
"build.types": "tsc --incremental --noEmit",
"deploy": "wrangler pages deploy ./dist",
"dev": "vite --mode ssr",
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
"fmt": "prettier --write .",
"fmt.check": "prettier --check .",
"lint": "eslint \"src/**/*.ts*\"",
"preview": "qwik build preview && vite preview --open",
"serve": "wrangler pages dev ./dist --compatibility-flags=nodejs_als",
"deno:serve": "deno run --allow-net --allow-read --allow-env server/entry.deno.js",
"start": "vite --open --mode ssr",
"qwik": "qwik"
},
"devDependencies": {
"@builder.io/partytown": "^0.8.1",
"@builder.io/qwik": "^1.8.0",
"@builder.io/qwik-city": "^1.8.0",
"@builder.io/qwik-react": "0.5.0",
"@fontsource-variable/bricolage-grotesque": "^5.0.1",
"@fontsource/geist-mono": "^5.1.0",
"@fontsource/geist-sans": "^5.1.0",
"@fontsource-variable/mona-sans": "^5.0.1",
"@modular-forms/qwik": "^0.29.0",
"@nestri/input": "*",
"@nestri/libmoq": "*",
"@nestri/sdk": "0.1.0-alpha.14",
"@nestri/ui": "*",
"@openauthjs/openauth": "*",
"@polar-sh/checkout": "^0.1.8",
"@polar-sh/sdk": "^0.21.1",
"@qwik-ui/headless": "^0.6.4",
"@types/eslint": "8.56.10",
"@types/howler": "^2.2.12",
"@types/node": "^22.5.1",
"@types/react": "^18.2.28",
"@types/react-dom": "^18.2.13",
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"ajv": "^8.17.1",
"eslint": "8.57.0",
"eslint-plugin-qwik": "^1.8.0",
"howler": "^2.2.4",
"posthog-js": "^1.207.0",
"prettier": "3.3.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"semver": "^7.7.1",
"typescript": "5.4.5",
"undici": "*",
"valibot": "^0.42.1",
"vite": "6.1.6",
"vite-tsconfig-paths": "^4.2.1",
"wrangler": "^3.0.0"
},
"dependencies": {
"@types/pako": "^2.0.3",
"pako": "^2.1.0"
}
}

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@@ -1,9 +0,0 @@
# https://developers.cloudflare.com/pages/platform/headers/
/*service-worker.js
Cache-Control: no-store
Content-Type: application/javascript
X-Content-Type-Options: nosniff
/build/*
Cache-Control: public, max-age=31536000, s-maxage=31536000, immutable

View File

@@ -1 +0,0 @@
# https://developers.cloudflare.com/pages/platform/redirects/

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

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