32 Commits

Author SHA1 Message Date
Wanjohi
edb70ead62 feat(web): Use a better theme colors 2025-07-25 15:20:38 +03:00
Wanjohi
7156980063 feat(web): Use a better grid system 2025-07-22 17:14:10 +03:00
Wanjohi
7ebf0644f1 feat(web): Add Grid component 2025-07-22 15:53:38 +03:00
Wanjohi
41b85d6436 feat(web): Use reusable components 2025-07-22 15:52:21 +03:00
Wanjohi
ece6d74e1f feat(web): Add more stuff 2025-07-22 15:35:00 +03:00
Wanjohi
05b25b237d feat(web): Add more Grid columns 2025-07-22 06:47:53 +03:00
Wanjohi
fedcad7695 fix(web): Fix responsiveness issues 2025-07-21 21:44:40 +03:00
Wanjohi
67ba04eced feat(web): Add grid component 2025-07-21 21:20:37 +03:00
Wanjohi
9cf3ad5397 feat(web): Add landing page 2025-07-21 17:55:55 +03:00
Wanjohi
43103e2914 fix: Add pretter plugins 2025-07-20 03:52:13 +03:00
Wanjohi
4c9cc49a64 fix: Remove NEON_API_KEY reference 2025-07-19 20:22:44 +03:00
Wanjohi
b10532ab83 fix: Remove stuff 2025-07-19 20:20:10 +03:00
Wanjohi
2ff8d260fd fix: Align center goddamn it 2025-07-19 20:18:28 +03:00
Wanjohi
28765bea9a fix: Align logo to the center 2025-07-19 20:17:13 +03:00
Wanjohi
cb8cf33536 fix: Merge stuff 2025-07-19 20:15:59 +03:00
Wanjohi
032236f294 fix: Add stuff 2025-07-19 20:14:08 +03:00
Wanjohi
87d424b058 fix: Add some more stuff 2025-07-19 20:09:28 +03:00
Wanjohi
17feb15a55 feat: Add more stuff 2025-07-19 19:11:41 +03:00
Wanjohi
55e413d6c7 fix: Remove Windows stuff 2025-07-19 17:10:05 +03:00
Wanjohi
53833c7368 feat: Add tailwindcss 2025-07-19 17:09:24 +03:00
Wanjohi
a98d906fa6 feat: Add some web stuff 2025-07-19 15:17:01 +03:00
Wanjohi
130c1ed314 remove old config 2025-07-19 12:30:58 +03:00
dependabot[bot]
8d18ac9044 build(deps-dev): bump the npm_and_yarn group across 2 directories with 1 update (#297)
Bumps the npm_and_yarn group with 1 update in the / directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /apps/www directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).

Updates `vite` from 6.0.15 to 6.1.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.1.6</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.3</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>create-vite@6.1.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@6.1.1/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@6.1.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@6.1.1/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>create-vite@6.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@6.1.0/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@6.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@6.1.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.0-beta.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.0-beta.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.0-beta.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.0-beta.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.0-beta.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.0-beta.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.1.6/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.1.6 (2025-04-30)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: check static serve file inside sirv (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19965">#19965</a>)
(<a
href="42079a078e">42079a0</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19965">#19965</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.5 (2025-04-10)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)
(<a
href="87cff1215b">87cff12</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19830">#19830</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.4 (2025-04-03)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths (<a
href="0aeccef739">0aeccef</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19782">#19782</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.3 (2025-03-31)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)
(<a
href="45b00c987c">45b00c9</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19761">#19761</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.2 (2025-03-24)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)
(<a
href="80381c38d6">80381c3</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19702">#19702</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.1 (2025-02-19)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: ensure <code>.[cm]?[tj]sx?</code> static assets are JS mime (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19453">#19453</a>)
(<a
href="e7ba55e7d5">e7ba55e</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19453">#19453</a></li>
<li>fix: ignore <code>*.ipv4</code> address in cert (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19416">#19416</a>)
(<a
href="973283bf84">973283b</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19416">#19416</a></li>
<li>fix(css): run rewrite plugin if postcss plugin exists (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19371">#19371</a>)
(<a
href="bcdb51a1ac">bcdb51a</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19371">#19371</a></li>
<li>fix(deps): bump tsconfck (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19375">#19375</a>)
(<a
href="746a583d42">746a583</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19375">#19375</a></li>
<li>fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19392">#19392</a>)
(<a
href="60456a54fe">60456a5</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19392">#19392</a></li>
<li>fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19440">#19440</a>)
(<a
href="ccac73d9d0">ccac73d</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19440">#19440</a></li>
<li>fix(html): ignore malformed src attrs (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19397">#19397</a>)
(<a
href="aff7812f0a">aff7812</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19397">#19397</a></li>
<li>fix(worker): fix web worker type detection (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19462">#19462</a>)
(<a
href="edc65eafa3">edc65ea</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19462">#19462</a></li>
<li>refactor: remove custom .jxl mime (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19457">#19457</a>)
(<a
href="0c854645bd">0c85464</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19457">#19457</a></li>
<li>feat: add support for injecting debug IDs (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18763">#18763</a>)
(<a
href="0ff556a6d9">0ff556a</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18763">#18763</a></li>
<li>chore: update 6.1.0 changelog (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19363">#19363</a>)
(<a
href="fa7c211bf3">fa7c211</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19363">#19363</a></li>
</ul>
<h2>6.1.0 (2025-02-05)</h2>
<h3>Features</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3b2299f6f5"><code>3b2299f</code></a>
release: v6.1.6</li>
<li><a
href="42079a078e"><code>42079a0</code></a>
fix: check static serve file inside sirv (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19965">#19965</a>)</li>
<li><a
href="c279023cc3"><code>c279023</code></a>
release: v6.1.5</li>
<li><a
href="87cff1215b"><code>87cff12</code></a>
fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)</li>
<li><a
href="f4d34dc4cd"><code>f4d34dc</code></a>
release: v6.1.4</li>
<li><a
href="0aeccef739"><code>0aeccef</code></a>
fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths</li>
<li><a
href="98d066a355"><code>98d066a</code></a>
release: v6.1.3</li>
<li><a
href="45b00c987c"><code>45b00c9</code></a>
fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)</li>
<li><a
href="0044d54c6d"><code>0044d54</code></a>
release: v6.1.2</li>
<li><a
href="80381c38d6"><code>80381c3</code></a>
fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v6.1.6/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 6.0.15 to 6.1.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.1.6</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.3</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>create-vite@6.1.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@6.1.1/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@6.1.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@6.1.1/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>create-vite@6.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@6.1.0/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@6.1.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@6.1.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.0-beta.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.0-beta.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.0-beta.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.0-beta.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.1.0-beta.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.1.0-beta.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.1.6/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.1.6 (2025-04-30)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: check static serve file inside sirv (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19965">#19965</a>)
(<a
href="42079a078e">42079a0</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19965">#19965</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.5 (2025-04-10)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)
(<a
href="87cff1215b">87cff12</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19830">#19830</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.4 (2025-04-03)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths (<a
href="0aeccef739">0aeccef</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19782">#19782</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.3 (2025-03-31)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)
(<a
href="45b00c987c">45b00c9</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19761">#19761</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.2 (2025-03-24)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)
(<a
href="80381c38d6">80381c3</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19702">#19702</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.1.1 (2025-02-19)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: ensure <code>.[cm]?[tj]sx?</code> static assets are JS mime (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19453">#19453</a>)
(<a
href="e7ba55e7d5">e7ba55e</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19453">#19453</a></li>
<li>fix: ignore <code>*.ipv4</code> address in cert (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19416">#19416</a>)
(<a
href="973283bf84">973283b</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19416">#19416</a></li>
<li>fix(css): run rewrite plugin if postcss plugin exists (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19371">#19371</a>)
(<a
href="bcdb51a1ac">bcdb51a</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19371">#19371</a></li>
<li>fix(deps): bump tsconfck (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19375">#19375</a>)
(<a
href="746a583d42">746a583</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19375">#19375</a></li>
<li>fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19392">#19392</a>)
(<a
href="60456a54fe">60456a5</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19392">#19392</a></li>
<li>fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19440">#19440</a>)
(<a
href="ccac73d9d0">ccac73d</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19440">#19440</a></li>
<li>fix(html): ignore malformed src attrs (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19397">#19397</a>)
(<a
href="aff7812f0a">aff7812</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19397">#19397</a></li>
<li>fix(worker): fix web worker type detection (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19462">#19462</a>)
(<a
href="edc65eafa3">edc65ea</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19462">#19462</a></li>
<li>refactor: remove custom .jxl mime (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19457">#19457</a>)
(<a
href="0c854645bd">0c85464</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19457">#19457</a></li>
<li>feat: add support for injecting debug IDs (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18763">#18763</a>)
(<a
href="0ff556a6d9">0ff556a</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/18763">#18763</a></li>
<li>chore: update 6.1.0 changelog (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19363">#19363</a>)
(<a
href="fa7c211bf3">fa7c211</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19363">#19363</a></li>
</ul>
<h2>6.1.0 (2025-02-05)</h2>
<h3>Features</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3b2299f6f5"><code>3b2299f</code></a>
release: v6.1.6</li>
<li><a
href="42079a078e"><code>42079a0</code></a>
fix: check static serve file inside sirv (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19965">#19965</a>)</li>
<li><a
href="c279023cc3"><code>c279023</code></a>
release: v6.1.5</li>
<li><a
href="87cff1215b"><code>87cff12</code></a>
fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)</li>
<li><a
href="f4d34dc4cd"><code>f4d34dc</code></a>
release: v6.1.4</li>
<li><a
href="0aeccef739"><code>0aeccef</code></a>
fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths</li>
<li><a
href="98d066a355"><code>98d066a</code></a>
release: v6.1.3</li>
<li><a
href="45b00c987c"><code>45b00c9</code></a>
fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)</li>
<li><a
href="0044d54c6d"><code>0044d54</code></a>
release: v6.1.2</li>
<li><a
href="80381c38d6"><code>80381c3</code></a>
fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v6.1.6/packages/vite">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nestrilabs/nestri/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-19 12:25:21 +03:00
Wanjohi
a2461cf59d 🐜 fix(readme): Fix wording 2025-07-19 12:23:59 +03:00
Wanjohi
8a02f019e9 🐜ðŸ fix(readme): Removing unnecessary filetype annotation fixes media query 2025-07-19 00:12:03 +03:00
Wanjohi
2db52f107e 📝 docs: Start work on the README 2025-07-19 00:08:57 +03:00
Wanjohi
c2f179e8ab 🧹 chore(infra): Wipe out everything 2025-07-18 19:41:51 +03:00
Wanjohi
c516af4168 feat: Add new web frontend 2025-07-17 12:45:55 +03:00
Wanjohi
b668bd48b9 fix: Nuke everything 2025-07-16 19:55:36 +03:00
Kristian Ollikainen
41dca22d9d feat(runner): More runner improvements (#294)
## Description
Whew..

- Steam can now run without namespaces using live-patcher (because
Docker..)
- Improved NVIDIA GPU selection and handling
- Pipeline tests for GPU picking logic
- Optimizations and cleanup all around
- SSH (by default disabled) for easier instance debugging.
- CachyOS' Proton because that works without namespaces (couldn't figure
out how to enable automatically in Steam yet..)
- Package updates and partial removal of futures (libp2p is going to
switch to Tokio in next release hopefully)



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

- **New Features**
- SSH server can now be enabled within the container for remote access
when configured.
- Added persistent live patching for Steam runtime entrypoints to
improve compatibility with namespace-less applications.
- Enhanced GPU selection with multi-GPU support and PCI bus ID matching
for improved hardware compatibility.
- Improved encoder selection by runtime testing of video encoders for
better reliability.
  - Added WebSocket transport support in peer-to-peer networking.
- Added flexible compositor and application launching with configurable
commands and improved socket handling.

- **Bug Fixes**
- Addressed NVIDIA-specific GStreamer issues by setting new environment
variables.
  - Improved error handling and logging for GPU and encoder selection.
- Fixed process monitoring to handle patcher restarts and added cleanup
logic.
- Added GStreamer cache clearing workaround for Wayland socket failures.

- **Improvements**
- Real-time logging of container processes to standard output and error
for easier monitoring.
- Enhanced process management and reduced CPU usage in protocol handling
loops.
- Updated dependency versions for greater stability and feature support.
  - Improved audio capture defaults and expanded audio pipeline support.
- Enhanced video pipeline setup with conditional handling for different
encoder APIs and DMA-BUF support.
- Refined concurrency and lifecycle management in protocol messaging for
increased robustness.
- Consistent namespace usage and updated crate references across the
codebase.
- Enhanced SSH configuration with key management, port customization,
and startup verification.
  - Improved GPU and video encoder integration in pipeline construction.
- Simplified error handling and consolidated write operations in
protocol streams.
- Removed Ludusavi installation from container image and updated package
installations.

- **Other**
- Minor formatting and style changes for better code readability and
maintainability.
- Docker build context now ignores `.idea` directory to streamline
builds.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-07-07 09:06:48 +03:00
Philipp Neumann
191c59d230 📖 docs: Update README.md (#189)
## Description
Update the readme file

## Related Issues
none

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that changes existing
functionality)
- [x] Documentation update
- [ ] Other (please describe):

## Checklist

- [x] I have updated relevant documentation
- [x] My code follows the project's coding style
- [x] My changes generate no new warnings/errors

## Notes for Reviewers
none

## Screenshots/Demo
none

## Additional Context
none

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

- **Documentation**
- Major overhaul of documentation structure and content for improved
clarity, modern presentation, and ease of navigation.
- Updated and expanded guides for installation, setup, architecture, and
command-line interfaces.
- Enhanced FAQ and troubleshooting sections with more comprehensive
answers and technical details.
- Added new guides for self-hosting with reverse proxy examples (Caddy,
Traefik) and developer notes.
- Improved theming, styling, and home page layout for the documentation
site.
  - Simplified main README to a minimalistic project header and tagline.
- Added new documentation files for Nestri Relay introduction and
container CLI parameters.
- Removed outdated or redundant documentation files and components to
streamline content.

- **Chores**
- Updated, reorganized, or removed configuration files for dependencies,
linting, and environment setup.
- Switched to a new documentation theme and updated related project
dependencies.
- Removed Renovate configuration and ESLint config specific to docs app.
- Adjusted TypeScript and package configurations for better
compatibility.

- **Style**
- Improved dark mode support and visual consistency across documentation
and components.
- Introduced new Tailwind CSS theming and animation support for the
documentation site.

- **New Features**
- Added example configuration files for deploying Nestri Relay with
Caddy and Traefik reverse proxies.
- Introduced new Tailwind CSS theming and animation support for the
documentation site.
- Added a new logo component supporting light/dark mode and optional
title display.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Victor Pahuus Petersen <49293748+victorpahuus@users.noreply.github.com>
Co-authored-by: Wanjohi <elviswanjohi47@gmail.com>
Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-07-01 05:34:53 +05:00
Wanjohi
77f47a0306 🐜 fix(infra): Remove db on team member stages 2025-06-29 17:12:56 +03:00
135 changed files with 45235 additions and 12370 deletions

View File

@@ -1,3 +1,4 @@
**/target/ **/target/
**/.git **/.git
**/.env **/.env
**/.idea

View File

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

132
README.md
View File

@@ -1,115 +1,23 @@
<div align="center"> <p align="center">
<div align="center">
<h1>
<a href="https://nestri.io"> <a href="https://nestri.io">
<img src="/apps/www/public/seo/banner.png" alt="Nestri - What will you play next?"> <picture>
<source srcset="packages/web/public/logo.white.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/web/public/logo.black.svg" media="(prefers-color-scheme: light)">
<img src="packages/web/public/logo.black.svg" alt="Nestri logo">
</picture>
</a> </a>
</p>
<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://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> -->
</p>
</h1> <!-- TODO: Add a link to the demo app when it's ready -->
</div> <!-- TODO: Add a link to install for self-hosters -->
<!-- TODO: Add a CTA for hosted option -->
&nbsp; <!-- TODO: Add feature imagery like Lobechat -->
&nbsp;
Nestri is an open-source, self-hosted Geforce Now alternative with Stadia's social features. <strong>Built and shaped by our gaming community.</strong>
<br/>
<br/>
</div>
<div align="center">
[![][github-release-shield]][github-release-link]
[![][discord-shield]][discord-link]
[![][github-license-shield]][github-license-link]
[![][github-stars-shield]][github-stars-link]
**Share the Nestri Repository on Social Media**
[![][share-x-shield]][share-x-link]
[![][share-reddit-shield]][share-reddit-link]
</div>
&nbsp;
&nbsp;
> **Note**
> Nestri is more closer (in feature comparison) to Jellyfin/Plex than Moonlight. Our goal is to develop a comprehensive self-hosted cloud gaming solution for your home server.
## Features
- Save and share your game progress easily with friends
- Simultaneously run multiple games on your GPU using Virtio-GPU Venus and/or Virgl
- Play games using either your integrated GPU or dedicated GPU
- Enjoy titles from your preferred Game Stores - Steam, Epic Games, Amazon Games, GOG.com
- Experience Android gaming
- Organize gaming sessions with friends and family through Nestri Parties
- Stream directly to YouTube and Twitch straight from your setup
- Family sharing capabilities
- Support for Controller, Touchscreen, Keyboard, and Mouse devices
## Possible Use Cases
- Organize game nights or LAN parties with friends online or locally
- For game developers, showcase your proof-of-concept multiplayer games for testing without installation
- Create and manage your custom cloud-gaming platform using our robust API
- Establish a game server for your family to enjoy gaming on the go
## Goals
- Provide a user-friendly setup - fire and forget
- Deliver a simple and elegant interface for managing and playing your game library
- Ensure a high-quality gaming experience out-of-the-box
- Optimize for the best gaming performance right from the start
## Non-Goals
- Become a generic cloud-gaming service
## Built With
- Cloudflare Workers
- Cloudflare Pages
- Supabase
- CrosVM (with Virtio-GPU Venus and Virgl support)
- Docker
- Qwik
- Media-Over-Quic
- AWS Route53
## Known Issues
- CrosVM is still under development and needs to be merged
- Currently, the Intel dGPU, particularly the Arc A780, is the only tested and verified GPU
## Donation
If you appreciate our work and wish to support the development of Nestri, consider making a donation [here](https://polar.sh/nestri/donate). Your contributions will help us improve the platform and enhance your gaming experience. Thank you for your support!
## Demo
Nestri is still in development, but here is some footage from Behind-The-Scenes
<img src="/apps/www/public/seo/code.avif" alt="Nestri - What will you play next?">
[github-release-link]: https://github.com/nestriness/nestri/releases
[github-release-shield]: https://img.shields.io/github/v/release/nestriness/nestri?color=369eff&labelColor=black&logo=github&style=flat-square
[discord-shield]: https://img.shields.io/discord/1080111004698021909?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square
[discord-link]: https://discord.com/invite/Y6etn3qKZ3
[github-license-shield]: https://img.shields.io/github/license/nestriness/nestri?color=white&labelColor=black&style=flat-square
[github-license-link]: https://github.com/nestriness/nestri/blob/main/LICENSE
[github-stars-shield]: https://img.shields.io/github/stars/nestriness/nestri?color=ffcb47&labelColor=black&style=flat-square
[github-stars-link]: https://github.com/nestriness/nestri/network/stargazers
[share-x-shield]: https://img.shields.io/badge/-share%20on%20x-black?labelColor=black&logo=x&logoColor=white&style=flat-square
[share-x-link]: https://twitter.com/intent/tweet?text=Hey%2C%20check%20out%20this%20Github%20repository.%20It%20is%20an%20open-source%20self-hosted%20Geforce%20Now%20alternative.&url=https%3A%2F%2Fgithub.com%2Fnestriness%2Fnestri
[share-reddit-shield]: https://img.shields.io/badge/-share%20on%20reddit-black?labelColor=black&logo=reddit&logoColor=white&style=flat-square
[share-reddit-link]: https://www.reddit.com/submit?title=Hey%2C%20check%20out%20this%20Github%20repository.%20It%20is%20an%20open-source%20self-hosted%20Geforce%20Now%20alternative.&url=https%3A%2F%2Fgithub.com%2Fnestriness%2Fnestri
[image-overview]: assets/banner.png
[website-link]: https://nestri.io
[neko-url]: https://github.com/m1k1o/neko
[image-star]: assets/star-us.png
[moq-github-url]: https://quic.video
[vmaf-cuda-link]: https://developer.nvidia.com/blog/calculating-video-quality-using-nvidia-gpus-and-vmaf-cuda/

View File

@@ -1,14 +0,0 @@
module.exports = {
root: true,
extends: ['@nuxt/eslint-config'],
ignorePatterns: [
'dist',
'node_modules',
'.output',
'.nuxt'
],
rules: {
'vue/max-attributes-per-line': 'off',
'vue/multi-word-component-names': 'off'
}
}

35
apps/docs/.gitignore vendored
View File

@@ -1,12 +1,25 @@
node_modules # Nuxt dev/build outputs
*.iml
.idea
*.log*
.nuxt
.vscode
.DS_Store
coverage
dist
sw.*
.env
.output .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

49
apps/docs/README.md Normal file
View File

@@ -0,0 +1,49 @@
# 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,87 +0,0 @@
# How to Deploy Your Own MoQ Relay on a Server
This guide will walk you through the steps to deploy your own MoQ relay on a server.
## Prerequisites
1. **Server Requirements:**
- Ensure port 443 is open for both TCP and UDP (`:443/udp & :443/tcp`).
- The server should have a minimum of **4GB RAM** and **2 vCPUs**.
- Supports 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) for Docker.
- Git must be installed to clone the necessary repository.
3. **Certificates:**
- You will need private and public certificates. It is recommended to use certificates from a trusted CA rather than self-signed certificates.
## Installation Steps
### Step 1: Clone the Repository
Clone the `kixelated/moq-rs` repository to your local machine:
```bash
git clone https://github.com/kixelated/moq-rs moq
```
### Step 2: Verify Port Availability
Check if port 443 is already in use on your server:
```bash
sudo netstat -tulpn | grep ':443' | grep LISTEN
```
or
```bash
sudo lsof -i -P -n | grep LISTEN | grep 443
```
If you find any processes using port 443, consider terminating them.
### Step 3: Configure Ports
Navigate to the cloned directory and edit the Docker compose file to use port 443:
```bash
cd moq
vim docker-compose.yml
```
Change the ports section from lines 34 to 35 to:
```yaml
ports:
- "443:443"
- "443:443/udp"
```
### Step 4: Prepare Certificates
Copy your generated certificates into the `moq/dev` directory and rename them:
```bash
cp cert.pem moq/dev/localhost.crt
cp key.pem moq/dev/localhost.key
```
### Step 5: Start Docker Instances
Ensure you are in the root directory of the `moq` project, then start the Docker containers:
```bash
docker compose up -d
```
### Step 6: Link Domain to Server IP
Configure your DNS settings to connect your server's IP address to your domain:
```
Record Type: A
Subdomain: relay.fst.so
IP Address: xx.xxx.xx.xxx
```
Congratulations, your MoQ server is now set up! You can verify its functionality by using the [MoQ Checker](https://nestri.pages.dev/moq/checker).

View File

@@ -1,44 +1,79 @@
// https://github.com/nuxt-themes/docus/blob/main/nuxt.schema.ts
export default defineAppConfig({ export default defineAppConfig({
docus: { shadcnDocs: {
title: 'Nestri', site: {
description: 'An open-source, self-hosted Geforce Now alternative', name: 'Nestri Docs',
image: 'https://feat-relay-hetzner.nestri.pages.dev/logo.webp', description: 'Beautifully designed Nuxt Content template built with shadcn-vue. Customizable. Compatible. Open Source.',
socials: {
twitter: 'nestriness',
github: 'nestriness/nestri',
reddit: '/r/nestri',
website: {
label: 'Website',
icon: 'lucide:house',
href: 'https://nestri.io'
}
}, },
github: { theme: {
dir: 'apps/docs/content', customizable: false,
branch: 'main', color: 'orange',
repo: 'nestri', radius: 0.5,
owner: 'nestriness', },
edit: true 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: { aside: {
level: 0, useLevel: true,
collapsed: false, collapse: false,
exclude: []
}, },
main: { main: {
padded: true, breadCrumb: true,
fluid: true showTitle: true,
},
logo: "/nestri-logo.svg",
header: {
logo: true,
showLinkIcon: true,
exclude: [],
fluid: true
}, },
footer: { footer: {
credits: false, 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

@@ -0,0 +1,88 @@
@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,62 +0,0 @@
<script setup lang="ts">
const socials = ['twitter', 'facebook', 'instagram', 'tiktok', 'youtube', 'github', 'medium', 'reddit', 'discord']
const { config } = useDocus()
const icons = computed<any>(() => {
return Object.entries(config.value.socials || {})
.map(([key, value]) => {
if (typeof value === 'object') {
return value
} else if (typeof value === 'string' && value && socials.includes(key)) {
return {
href: /^https?:\/\//.test(value) ? value : `https://${key}.com/${value}`,
icon: `fa-brands:${key}`,
label: value,
rel: 'noopener noreferrer'
}
} else {
return null
}
})
.filter(Boolean)
})
</script>
<template>
<NuxtLink
v-for="icon in icons"
:key="icon.label"
:rel="icon.rel"
:title="icon.label"
:aria-label="icon.label"
:href="icon.href"
target="_blank"
>
<Icon
v-if="icon.icon"
:name="icon.icon"
/>
</NuxtLink>
</template>
<style lang="ts" scoped>
css({
a: {
display: 'flex',
color: '{color.gray.500}',
padding: '{space.4}',
'@dark': {
color: '{color.gray.400}'
},
'&:hover': {
color: '{color.gray.700}',
'@dark': {
color: '{color.gray.200}',
}
},
}
})
</style>

View File

@@ -1,3 +0,0 @@
<template>
<img width="120" src="/img/nestri-logo-sm.svg"/>
</template>

View File

@@ -3,7 +3,7 @@
<div class="py-8"> <div class="py-8">
<h2 class="text-3xl lg:text-4xl font-bold mb-12 text-gray-900"> <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> Contributors made <span class="text-orange-500">Nestri</span>
</h2> </h2>
<div class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-8 gap-4 sm:gap-5 lg:gap-6"> <div class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-8 gap-4 sm:gap-5 lg:gap-6">
@@ -22,7 +22,7 @@
}" }"
> >
<UTooltip class="w-full text-orange-500" :text="contributor.login"> <UTooltip class="w-full text-orange-500" :text="contributor.login">
<NuxtImg <img
:src="contributor.avatar_url" :src="contributor.avatar_url"
provider="ipx" provider="ipx"
densities="x1 x2" densities="x1 x2"
@@ -33,7 +33,7 @@
class="rounded-xl w-full h-full transition lg:hover:scale-110" class="rounded-xl w-full h-full transition lg:hover:scale-110"
/> />
</UTooltip> </UTooltip>
<span class="inline-block rounded-t px-1 bg-gray-950 text-white absolute -bottom-2 right-0 font-medium text-sm"> <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 class="font-light text-xs text-gray-400">#</span>{{ index + 1 }}
</span> </span>

View File

@@ -0,0 +1,15 @@
<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,55 +0,0 @@
---
title: Home
navigation: false
layout: page
main:
fluid: false
---
:ellipsis{right=0px width=75% blur=150px}
::block-hero
---
cta:
- Get started
- /introduction/what-is-nestri
secondary:
- Open on GitHub →
- https://github.com/nestriness/nestri
---
#title
An open-source, self-hosted Geforce Now alternative.
#description
Play your favorite games on the go or with your friends on your own game cloud.
#extra
::list
- **Selfhosted** cloud gaming
- **Open Source** and **Free**
- 1.5k ⭐️ on GitHub
::
<!--#support
::terminal
---
content:
- npx nuxi@latest init -t themes/docus
- cd docs
- npm install
- npm run dev
---
::-->
::
::contributors
::

View File

@@ -1,21 +1,25 @@
# What is Nestri? ---
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. 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 entirely 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. 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 Modules Nestri is composed of the following key components:
To provide a smooth and efficient gaming experience, Nestri is composed of the following key components: #### Nestri Node
### 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).
The Nestri Node 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 requires an NVIDIA graphics card to ensure a high-quality gaming experience.
Since Nestri Node cannot run alongside Xorg (the graphical interface), its recommended to install it on a dedicated machine. This way, your server can focus solely on streaming your games while avoiding conflicts with your local display setup. **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 #### Nestri Relay
The Nestri Relay is responsible for transporting the video stream from your Nestri Node to the device you're gaming on. By default, Nestri connects to the Nestri-hosted Relay, which requires no configuration and is available for all users. This simplifies the setup process, ensuring a smooth streaming experience without the need for advanced networking or SSL certificate management.
For advanced users, it's possible to self-host the relay, but this requires the setup of secure SSL certificates. This option is typically more complex and is recommended only for developers or those familiar with network configuration. 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,10 +1,23 @@
# FAQ ---
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? ## Is Nestri free?
Yes! Nestri offers two options: a free, self-hosted version and a paid, hosted version. 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.
- Hosted Version (Paid): 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! - **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? ## 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. 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.
@@ -16,4 +29,28 @@ Currently, we have one relay deployed in Helsinki, Finland. As we grow, we plan
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. 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? ## 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 runs games on Linux using Proton and the Gstreamer encoding, there will be a bit of additional processing required, so some extra power will be helpful. 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,2 +1,3 @@
icon: ph:star-duotone title: Getting started
icon: lucide:rocket
navigation.redirect: /introduction/what-is-nestri navigation.redirect: /introduction/what-is-nestri

View File

@@ -1,9 +1,7 @@
# What is Nestri Node? ---
title: What is Nestri Node?
Nestri Node is the core component of Nestri's self-hosted cloud-gaming solution, designed for users who want the freedom and flexibility of running their own game-streaming server. Similar to services like NVIDIA GeForce Now, Nestri allows you to play your games remotely via your browser. However, unlike other cloud-gaming platforms, Nestri is fully self-hosted, giving you complete control over your server and gaming experience. 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'
The Nestri Node 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 machines with Linux and requires an NVIDIA, AMD or an Intel graphics card . ---
## ⚠️ Important Note
We recommend not installing Nestri Node on your primary PC if you only intend to use it over a weekend. This is because Nestri Node cannot run simultaneously with Xorg, the display server responsible for managing the graphical user interface (GUI). This means that while Nestri Node is running, you will not be able to use an attached screen. For this reason, Nestri Node is best set up on a dedicated machine that wont be used for other tasks.
**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,53 +1,27 @@
# Prerequisite ---
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. 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. For optimal performance, however, we recommend using Intel or NVIDIA GPUs. Our testing has shown that these GPUs provide the best results, while AMD graphics cards may encounter limitations due to partial support for Arch Linux in AMD's AMF drivers. As a workaround, we utilize the VA-API plugin for GStreamer with AMD cards to ensure functionality. 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. 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 ## Recommended host configuration
::list{type="primary"} ::list{type="primary"}
- **NVIDIA or Intel GPU** (AMD is supported, but not reccomended, due to lack of natively supported API-drivers in CachyOS) - **AMD, NVIDIA or Intel GPU**
- **AVX supported CPU** (If your CPU doesent support AVX, you can use our `noavx` image) - **CPU with AVX2 support**
- **Fedora or Arch** based distributions ( [Debian and Ubuntu is **not** supported](/nestri-node/node-faq#can-i-run-nestri-node-on-debianubuntu) ) - **Fedora or Arch** based distribution
:: ::
## Software Requirements ## Software Requirements
::list{type="primary"} ::list{type="primary"}
- **Nvidia Drivers** - **GPU Drivers** (if not provided by the kernel)
- **[NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#installing-with-apt)** - **Podman or Docker** (Podman is recommended for better compatibility)
- **[Docker](https://linuxiac.com/how-to-install-docker-on-ubuntu-24-04-lts/)**
:: ::
## Disconnect monitor
Since Nestri requires access to your GPU, then you need to unplug you screen from it.
If you want to see the Desktop and have a integrated graphicscard in your CPU, then you can connect your monitor to the motherboard.
### Change the Default Boot Target to Multi-User (Non-GUI Mode)
Ubuntu typically starts in graphical mode (using the graphical.target systemd target). You should change to the non-graphical multi-user.target, which will prevent Xorg from starting.
1. Open a terminal or access your system via SSH.
2. To check your current default target (which should be graphical.target)
```bash
systemctl get-default
```
3. Change the default target to multi-user.target (which corresponds to text mode, without Xorg):
```bash
sudo systemctl set-default multi-user.target
```
4. Reboot the system
5. Verify that Xorg is not running
```bash
nvidia-smi
```

View File

@@ -1,101 +1,72 @@
# Getting Started ---
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"} ::alert{type="danger"}
Nestri is in a **very early-beta phase**, so errors and bugs may occur. Nestri is in a **very early phase**, so errors and bugs may occur.
:: ::
### Step 0: Construct Your Docker Image
Checkout your branch with the latest version of nestri and build the image `<your-nestri-image>` within git root folder:
```bash
docker buildx build -t <your-nestri-image>:latest -f Containerfile.runner .
```
::alert{type="info"} ::alert{type="info"}
You can right now also pull the docker image from DatHorse GitHub Containter Registry with: You can pull the docker image from GitHub Container Registry with:
```bash ```bash [pull image command]
docker pull ghcr.io/datcaptainhorse/nestri-cachyos:latest podman pull ghcr.io/nestrilabs/nestri/runner:nightly
``` ```
:: ::
### Step 1: Navigate to Your Game Directory ### Step 1: Create a home directory for your Nestri Node
First, change your directory to the location of your `.exe` file. For Steam games, this typically means: This will be the directory where Steam, games and other persistent files will be saved.
```bash You may use any directory you like, but for simplicity, we will use `~/nestri` as the home directory in this guide.
cd $HOME/.steam/steam/steamapps ```bash [create home directory command]
ls -la . mkdir -p ~/nestri
sudo chmod 777 ~/nestri
``` ```
### Step 2: Generate a Session ID 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.
Create a unique session ID using the following command: This is important for the Nestri Node to function properly.
```bash ### Step 2: Launch the Nestri Runner
echo "$(head /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | head -c 16)" 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.
This command generates a random 16-character string. Be sure to note this string carefully, as you'll need it for the next step.
### Step 3: Launch the Nestri Server
With your SESSION_ID ready, insert it into the command below, replacing `<your_session_id>` with your actual session ID, also replace `<relay_url>` with your relay URL and `<your-nestri-image>` with your build nestri image or nestri remote image. Then run the command to start the Nestri server:
```bash ```bash [run container (nvidia)]
docker run --rm -it --shm-size=1g --gpus all -e NVIDIA_DRIVER_CAPABILITIES=all --runtime=nvidia -e RELAY_URL='<relay_url>' -e NESTRI_ROOM=<your_session_id> -e RESOLUTION=1920x1080 -e FRAMERATE=60 -e NESTRI_PARAMS='--verbose=true --video-codec=h264 --video-bitrate=4000 --video-bitrate-max=6000'--name nestri -d -v "$(pwd)":/mnt/game/ <your-nestri-image>:latest 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
``` ```
### Step 4: Get Into your container ```bash [run container (amd/intel)]
Get into your container to start your game: 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
```bash
sudo docker exec -it nestri bash
```
### Step 5: Installing a Launcher
For most games that are not DRM free you need a launcher. In this case use the umu launcher and optional mangohud:
```bash
pacman -S --overwrite="*" umu-launcher mangohud
``` ```
### Step 5: Running Your Game ### Step 3: Begin Playing
You have to execute your game now with nestri user. If you have a linux game just execute it with the nestri user Finally, construct the play URL with your room name and relay URL:
```bash `https://nestri.io/play/<room_name>?peerURL=<relay_url>`
su nestri
source /etc/nestri/envs.sh Navigate to this URL in your browser, click on the button to capture your mouse pointer and keyboard, and start playing!
GAMEID=0 PROTONPATH=GE-Proton mangohud umu-run /mnt/game/<your-game.exe>
### 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
``` ```
### Step 6: Begin Playing ### Start the Nestri Container
Finally, construct the play URL with your session ID: If you want to start the Nestri container after stopping it, you can use the following command:
`https://nestri.io/play/<your_session_id>`
Navigate to this URL in your browser, click on the page to capture your mouse pointer, and start playing!
::alert{type="info"}
You can also use other relays/frontends depending on your choosen `<relay_url>`
For testing you can use DatHorse Relay and Frontend:
| **Placeholder** | **URL** |
| ---------------------------- | ---------- |
| `<relay_url>` | `https://relay.dathorse.com/` |
| `<frontend_url>` | `https://nestritest.dathorse.com/play/<your_session_id>` |
::
<!--
Nestri Node is easy to install using the provided installation script. Follow the steps below to get started.
## Installation
1. Download the installation script using `wget`:
```bash
wget https://github.com/nestriness/nestri/nestri-node-install.sh
```bash [start container command]
podman start nestri
``` ```
2. Make the script executable: ### Remove the Nestri Container
```bash To remove the container, you can use the following command:
chmod +x nestri-node-install.sh
```bash [remove container command]
podman rm nestri
``` ```
3. Run the script to start the installation process:
```bash ### Update Nestri Container
./nestri-node-install.sh 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,2 +1 @@
# Troubleshooting # Troubleshooting

View File

@@ -1,22 +1,36 @@
# Container CLI ---
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 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** | | **Parameter** | **Type** | **Default** | **Description** |
| ---------------------------- | ---------- | --------------------- | ---------------------------------------------------------------------------------------------------- | |--------------------------|----------|-------------|-----------------------------------------------------------------------------------|
| `-v, --verbose` | `string` | false | Shows more logs, for issues we recommend turning it on before running nestri-server and sending the logs for debugging (i.e. `nestri-server --verbose=true > logs.txt`) | | `-v, --verbose` | `string` | false | Enable verbose output. Set to `true` for detailed logs. |
| `-d, --debug-feed` | `string` | false | Adds a timer overlay at bottom-right in the video stream, along with spawning an X11 window on host for doing comparisons against | | `-d, --debug` | `string` | false | Enable additional debugging features. Set to `true` for extra debug information. |
| `-u, --relay-url` | `string` | https://relay.fst.so | [MoQ relay](/nestri-relay/what-is-nestri-node) endpoint URL (must begin with `https://` as MoQ __can't work with unsafe connections__) | | `-u, --relay-url` | `string` | | Nestri relay URL. Specify the URL for the Nestri relay server. |
| `-p, --relay-path` | `string` | default generated on start if not set | namespace/path for the stream, identifies the stream (basically stream name), must be unique | | `-r, --resolution` | `string` | 1280x720 | Display/stream resolution in 'WxH' format. Default is 1280x720. |
| **Video** | | | | | `-f, --framerate` | `string` | 60 | Display/stream framerate. Default is 60 FPS. |
| `-r, --resolution ` | `string` | 1280x720 | Sets nestri virtual display + stream resolution using `WIDTHxHEIGHT` format | | `--room` | `string` | | Nestri room name/identifier. Specify the room for your Nestri session. |
| `-f, --framerate` | `integer` | 60 | Framerate for nestri virtual display + stream | | `-g, --gpu-vendor` | `string` | | GPU vendor to use (e.g., NVIDIA, AMD, Intel). |
| `-g, --gpu-vendor` | `string` | | allows selecting specific GPU by vendor name (`nvidia`, `amd` or `intel`) | | `-n, --gpu-name` | `string` | | GPU name to use. Specify the exact GPU model. |
| `-i, --gpu-index` | `string` | | allows selecting a GPU by it's general name, doesn't have to be full name as it's matched partially (i.e. `3060` would get you `RTX 3060` GPU, but it would also let `RTX 3060 Ti` pass) | | `-i, --gpu-index` | `string` | -1 | GPU index to use. Default is -1 (auto-select). |
| `-a, --gpu-card-path` | `string` | | allows specifying GPU by `/dev/dri/cardX` or `/dev/dri/renderX` path, this won't work with the other 3 gpu parameters as it's explicitly setting the GPU | | `--gpu-card-path` | `string` | | Force a specific GPU by `/dev/dri/` card or render path. |
| **Encoder** | | | | | `-c, --video-codec` | `string` | h264 | Preferred video codec. Options: h264, h265, av1. Default is h264. |
| `-c, --encoder-vcodec` | `string` | h264 | Sets the stream video codec (`h264` or `av1`) | | `--video-encoder` | `string` | | Override video encoder (e.g., `nvenc`, `libx264`). |
| `-t, --encoder-type` | `string` | hardware | Sets whether to use GPU encoder (`hardware`), or CPU encoder (`software`, only should be used with debugging or if GPU has no encoding capabilities) | | `--video-rate-control` | `string` | cbr | Rate control method. Options: cqp, vbr, cbr. Default is cbr. |
| `-e, --encoder-name` | `string` | | forces a specific encoder by GStreamer element name (i.e. `vah264enc`) | | `--video-cqp` | `string` | 26 | Constant Quantization Parameter (CQP) quality. Default is 26. |
| `-q, --encoder-cqp` | `string` | 25 | sets the stream quality level, lower means higher quality and much more bitrate used | | `--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,12 +1,14 @@
# Node FAQ ---
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'
---
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.. ## 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 on Debian/Ubuntu? :icon{name="logos:ubuntu" style="opacity:100"} :icon{name="logos:debian" style="opacity:100"}
Unfortunately, it is not possible to run Nestri Node on Debian-based distributions like Ubuntu at this time. After extensive debugging efforts, we have decided to focus on platforms that currently work well, such as Fedora and Arch-based distributions. We may revisit the possibility of supporting Debian in the future, but for now, it is not supported.
## Can I run Nestri Node in a virtualized environment like Proxmox? ## 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. However, we do not recommend this setup as virtualization may introduce additional overhead and latency. For the best performance, we recommend running Nestri Node on bare-metal hardware. 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? ## 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 servers. No, the Nestri Node service does not support Windows-based systems. It can only be deployed on Linux-based systems.

View File

@@ -0,0 +1,36 @@
---
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,2 +1,3 @@
title: 'Nestri Node' title: Nestri Node
icon: heroicons-outline:bookmark-alt navigation.redirect: /nestri-node/what-is-nestri-node
icon: lucide:box

View File

@@ -1,9 +0,0 @@
# What is Nestri Relay?
Nestri Relay is an essential component in the Nestri cloud-gaming ecosystem, responsible for transporting the video gameplay stream from your Nestri Node to the device youre playing on. It is built on the moq-rs protocol, designed for efficient and smooth video transmission, ensuring a low-latency gaming experience.
By default, your Nestri Node will connect to the Nestri-hosted Relay, which we manage and is available for all users. This is the simplest and most straightforward option, requiring no additional configuration on your end.
## ⚠️ Important Note
We recommend not installing Nestri Node on your primary PC if you only intend to use it over a weekend. This is because Nestri Node cannot run simultaneously with Xorg, the display server responsible for managing the graphical user interface (GUI). This means that while Nestri Node is running, you will not be able to use an attached screen. For this reason, Nestri Node is best set up on a dedicated machine that wont be used for other tasks.

View File

@@ -0,0 +1,8 @@
---
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,20 +1,37 @@
## 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 ## Self-hosted Nestri Relay
For those who prefer full control over their infrastructure, it is possible to self-host the Nestri Relay. However, setting this up can be a bit complex, as it requires generating SSL certificates for secure communication between your Nestri Node and your gaming devices. There are three main options: 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 **recommended option** 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. - **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, as these certificates are already trusted by browsers and dont require as much manual setup. - **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 and requires no configuration. 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 ## Prerequisites
1. **Server Requirements:** 1. **Server Requirements:**
- Ensure **port 443** is open for both **TCP and UDP** (`:443/udp & :443/tcp`). - Ensure **port 443** is open for both **TCP and UDP** (`:443/udp & :443/tcp`).
- The server should have at least **4GB RAM** and **2 vCPUs**. - The server should have at least **6-8GB RAM** and **2 vCPUs**.
- Supports both ARM or AMD64 architecture. - Supports both ARM or AMD64 architecture.
2. **Software Requirements:** 2. **Software Requirements:**
@@ -23,3 +40,127 @@ While self-hosting offers more flexibility, most users will find the **Nestri-ho
3. **Certificates:** 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. - 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

@@ -0,0 +1,25 @@
---
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,69 +0,0 @@
## Installation Steps
### Step 1: Clone the Repository
Clone the `kixelated/moq-rs` repository to your local machine:
```bash
git clone https://github.com/kixelated/moq-rs moq
```
### Step 2: Verify Port Availability
Check if port 443 is already in use on your server:
```bash
sudo netstat -tulpn | grep ':443' | grep LISTEN
```
or
```bash
sudo lsof -i -P -n | grep LISTEN | grep 443
```
If you find any processes using port 443, consider terminating them.
### Step 3: Configure Ports
Navigate to the cloned directory and edit the Docker compose file to use port 443:
```bash
cd moq
vim docker-compose.yml
```
Change the ports section from lines 34 to 35 to:
```yaml
ports:
- "443:443"
- "443:443/udp"
```
### Step 4: Prepare Certificates
Copy your generated certificates into the `moq/dev` directory and rename them:
```bash
cp cert.pem moq/dev/localhost.crt
cp key.pem moq/dev/localhost.key
```
### Step 5: Start Docker Instances
Ensure you are in the root directory of the `moq` project, then start the Docker containers:
```bash
docker compose up -d
```
### Step 6: Link Domain to Server IP
Configure your DNS settings to connect your server's IP address to your domain:
```
Record Type: A
Subdomain: relay.fst.so
IP Address: xx.xxx.xx.xxx
```
Congratulations, your MoQ server is now set up! You can verify its functionality by using the [MoQ Checker](https://nestri.pages.dev/moq/checker).

View File

@@ -1,42 +0,0 @@
# ⚠️ Advanced users
## Generating an SSL Certificate for Nestri Relay
This guide is for developers and advanced users who wish to self-host Nestri Relay. We strongly discourage this setup for general users due to its complexity, particularly when it comes to configuring SSL certificates correctly. Using a self-signed certificate or manually generating certificates can lead to issues with browser compatibility and security warnings, making it difficult to ensure a smooth experience.
For most users, we highly recommend using the **Nestri-hosted Relay**, which requires no manual setup and is ready to use out of the box.
---
## Generating an SSL Certificate Using Terraform
If you still wish to proceed with self-hosting, we recommend using Terraform to generate a valid SSL certificate. This method provides a secure, automated way to obtain the necessary certificates for Nestri Relay.
### Usage
1. **Update the `terraform.tfvars`** file with your domain and email.
2. Run the following command to initialize the Terraform working directory:
```bash
terraform init
```
```bash
terraform plan
```
```bash
terraform apply
```
The configuration provides two sensitive outputs:
```bash
certificate_pem: The full certificate chain
private_key_pem: The private key for the certificate
```
These can be then be used in your `moq-relay` as it requires SSL/TLS certificates.
## Note
The generated certificate and key files are saved locally and ignored by git:
```git
.terraform
relay_*
```

View File

@@ -1,4 +0,0 @@
## MOQ Tester
Test your Nestri Relay, with our MOQ tester tool.
:button-link[Try MOQ Test Tool]{size="small" icon="IconStackBlitz" href="https://nestri.pages.dev/moq/checker" blank}

View File

@@ -1,2 +1,3 @@
title: 'Nestri Relay' title: Nestri Relay
icon: heroicons-outline:bookmark-alt navigation.redirect: /nestri-relay/what-is-nestri-relay
icon: lucide:box

View File

@@ -1,3 +0,0 @@
# What is this?
This is the part of the docs dedicated for the team working on Nestri

View File

@@ -1,27 +0,0 @@
# Setup
- Install bun [https://bun.sh/](https://bun.sh/)
- Generate your Cloudflare token from [here](https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22dns%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22memberships%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22user_details%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_kv_storage%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_r2%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_routes%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_scripts%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_tail%22%2C%22type%22%3A%22read%22%7D%5D&name=sst&accountId=*&zoneId=all)
- save it to a `.env` file like this
```
CLOUDFLARE_API_TOKEN=xxx
```
- Copy this to your `~/.aws/config` file
```
[sso-session nestri]
sso_start_url = https://nestri.awsapps.com/start
sso_region = us-east-1
[profile nestri-dev]
sso_session = nestri
sso_account_id = 535002871375
sso_role_name = AdministratorAccess
region = us-east-1
[profile nestri-production]
sso_session = nestri
sso_account_id = 209479283398
sso_role_name = AdministratorAccess
region = us-east-1
```
- You need to login once a day with `bun sso` in root

View File

@@ -1,2 +0,0 @@
title: 'Nestri Internals'
icon: heroicons-outline:bookmark-alt

View File

@@ -0,0 +1,30 @@
---
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,14 +1,6 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
// https://github.com/nuxt-themes/docus
extends: ['@nuxt-themes/docus'],
components: true,
devtools: { enabled: true }, devtools: { enabled: true },
extends: ['shadcn-docs-nuxt'],
modules: [// Remove it if you don't use Plausible analytics compatibilityDate: '2024-07-06',
// https://github.com/nuxt-modules/plausible });
'@nuxtjs/plausible', '@nuxt/ui'],
compatibilityDate: '2024-09-29'
})

14274
apps/docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "docus-starter", "name": "shadcn-docs-nuxt-starter",
"version": "0.1.0",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"nestri.dev": "nuxi dev", "nestri.dev": "nuxi dev",
"build": "nuxi build --preset=cloudflare_pages", "build": "nuxi build --preset=cloudflare_pages",
@@ -9,14 +9,10 @@
"preview": "nuxi preview", "preview": "nuxi preview",
"lint": "eslint ." "lint": "eslint ."
}, },
"devDependencies": { "dependencies": {
"@nuxt-themes/docus": "latest", "nuxt": "^3.15.4",
"@nuxt/devtools": "^2.3.2", "shadcn-docs-nuxt": "^0.8.14",
"@nuxt/eslint-config": "^0.5.6", "vue": "^3.5.13",
"@nuxt/ui": "^2.19.2", "vue-router": "^4.5.0"
"@nuxtjs/plausible": "^1.0.2",
"@types/node": "^20.16.5",
"eslint": "^9.10.0",
"nuxt": "^3.16.1"
} }
} }

BIN
apps/docs/public/README.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1 +0,0 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 167 44" width="167" height="44"><defs><image width="47" height="36" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAkCAMAAAAuPpNdAAAAAXNSR0IB2cksfwAAAEJQTFRF/1gO/1MG/1MG/1UJ/08B/08B/14X/1oQ/1oQAAAA/18Z/1oR/1oR/1kP/1MH/1MH/2ck/2Me/2Me/24v/2sq/2sqUcSXkwAAABZ0Uk5T//+f//+fp6hpACsrG3BwRmdoQUpKLmJsmB4AAAA6SURBVHicY2RgZCABMDIyj6ofUPVsJKrnJEU5UAOp6rkGWfiMqqeuet5Blt4EBln4jKqnrnph0tQDAHjbARfH6mW/AAAAAElFTkSuQmCC"/></defs><style>.a{fill:#0a0a0a}</style><use href="#img1" x="6" y="4"/><path class="a" d="m69.2 34h-5.8v-24h7.9l8.5 16.3h0.1v-16.3h5.8v24h-7.4l-9-16.8h-0.1zm28.4 0.5q-2.8 0-4.6-0.7-1.8-0.8-3-2.2-1.1-1.3-1.6-3.1-0.5-1.7-0.5-3.6 0-2.1 0.5-4 0.6-1.9 1.7-3.3 1.1-1.5 2.8-2.4 1.8-0.8 4.3-0.8 2.5 0 4.2 0.8 1.8 0.9 2.8 2.4 1.1 1.5 1.4 3.5 0.3 2-0.1 4.3l-13.9 0.2v-3.1l9.4-0.2-0.8 1.8q0.3-1.6 0-2.8-0.3-1.1-1-1.7-0.7-0.6-2-0.6-1.4 0-2.1 0.7-0.8 0.7-1.2 1.9-0.3 1.3-0.3 3 0 2.9 1 4.2 1 1.4 3 1.4 0.9 0 1.4-0.2 0.6-0.2 1-0.6 0.3-0.5 0.5-1.1 0.1-0.6 0.1-1.3l5.4 0.2q0.1 1.2-0.3 2.5-0.3 1.3-1.3 2.4-0.9 1.1-2.6 1.8-1.7 0.6-4.2 0.6zm18.7 0q-1.9 0-3.6-0.3-1.6-0.4-2.8-1.3-1.2-0.8-1.9-2.2-0.6-1.4-0.5-3.4l5.1-0.4q0.1 1.2 0.6 2 0.4 0.7 1.3 1.1 0.8 0.4 2 0.4 1.1 0 1.9-0.4 0.9-0.4 0.9-1.3 0-0.5-0.3-0.8-0.3-0.3-1.1-0.5-0.8-0.3-2.3-0.7-1.8-0.5-3.3-0.9-1.5-0.5-2.6-1.2-1-0.6-1.5-1.6-0.6-0.9-0.6-2.4 0-2 1.1-3.3 1-1.4 2.9-2.2 1.9-0.7 4.4-0.7 2.2 0 4.1 0.7 2 0.6 3 2.2 1.2 1.6 0.9 4.1l-5 0.5q0.1-1-0.3-1.8-0.4-0.7-1.2-1.1-0.7-0.4-1.8-0.4-1.3 0-2 0.5-0.6 0.4-0.6 1.1 0 0.5 0.4 0.9 0.4 0.4 1.3 0.7 0.9 0.3 2.3 0.6 1.3 0.2 2.6 0.6 1.3 0.4 2.5 1.1 1.2 0.7 1.9 1.8 0.7 1.1 0.7 2.7 0 1.8-1 3.1-0.9 1.4-2.8 2.1-1.9 0.7-4.7 0.7zm18.1 0q-3.4 0-5.1-1.7-1.6-1.8-1.6-5.4v-8h-2.1v-3.7h0.1q2.2-0.2 3.2-1.4 0.9-1.3 1.1-3.7v-0.1h3.5v4.4h4.4v4.7h-4.4v7.2q0 1.4 0.6 2 0.7 0.6 1.7 0.6 0.5 0 1.1-0.2 0.5-0.1 1-0.4v5.2q-1.1 0.3-2 0.4-0.9 0.1-1.5 0.1zm11.5-0.5h-5.8v-10.4-8.7h5v7.6h0.3q0.2-3.1 0.9-4.8 0.6-1.8 1.6-2.5 0.9-0.8 2.1-0.8 0.7 0 1.4 0.2 0.7 0.2 1.4 0.6l-0.3 6.5q-0.8-0.4-1.6-0.7-0.7-0.2-1.4-0.2-1.2 0-2 0.6-0.8 0.7-1.2 2-0.4 1.2-0.4 3zm14.5 0h-5.8v-19.1h5.8zm-2.9-20.6q-1.7 0-2.6-0.6-0.9-0.8-0.9-2.1 0-1.4 0.9-2.1 0.9-0.7 2.6-0.7 1.7 0 2.6 0.7 0.9 0.7 0.9 2.1 0 1.3-0.9 2-0.9 0.7-2.6 0.7z"/></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1 +0,0 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 1080" width="1080" height="1080"><defs><image width="308" height="234" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAATQAAADqCAMAAAAbHElGAAAAAXNSR0IB2cksfwAAADNQTFRF/5Bf/2wr/3tB/08B/4VQ/2Uh/9K//9K/AAAA/5xw/3tA/5Fg/2wr/5Zo/4NN/9K//9K/u/+9NwAAABF0Uk5T/f////H/HCsAVVWAgLjVOVVGDSqkAAACDElEQVR4nO3cQQrCAAxFwUaKKCK9/217hryNCDMnCG/7IXPMwdI8RFsTLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLZhTtLV5irY2r19f8IdEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAvmbSNYM+EFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBfMRbc2nvkC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0QLRAtEC0YL6GlTUTXiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaIFogWiBaMKdoa3OJtnYDtq8EmnzQ7p8AAAAASUVORK5CYII="/></defs><style>.a{fill:#0a0a0a}</style><use href="#img1" x="8" y="423"/><path class="a" d="m426 624h-38.4v-158.4h52.1l56.4 107.3h0.5v-107.3h38.4v158.4h-49l-59.5-110.9h-0.5zm187.4 3.4q-18 0-30.2-5.1-12-5.2-19.4-14.1-7.5-9.1-10.8-20.7-3.2-11.5-3.2-24.2 0-13.7 3.4-26.2 3.6-12.4 10.8-22 7.4-9.9 19-15.4 11.7-5.8 28-5.8 16.4 0 27.9 5.8 11.7 5.5 18.7 15.6 7 10.1 8.9 23.3 2.1 13.2-0.5 28.5l-91.9 1.5v-21.1l62.1-1.2-5 12.2q1.4-11-0.5-18.5-1.7-7.7-6.5-11.5-4.8-4.1-13.2-4.1-8.8 0-14.1 4.6-5.3 4.5-7.5 12.9-2.1 8.2-2.1 19.5 0 19.4 6.5 28.3 6.4 8.9 19.9 8.9 5.7 0 9.6-1.5 3.8-1.4 6.2-4 2.4-2.9 3.4-6.8 0.9-4 0.7-9.1l35.5 1.9q0.7 8-1.7 16.4-2.1 8.4-8.4 15.6-6.2 7.2-17.5 11.7-11 4.6-28.1 4.6zm124.1 0q-12.7 0-23.7-2.4-10.8-2.7-19-8.2-7.9-5.5-12.2-14.6-4.1-9.4-3.4-22.6l33.8-2.9q0.5 8 3.6 13.2 3.2 5.1 8.7 7.5 5.5 2.4 13.4 2.4 7.5 0 12.7-2.4 5.6-2.7 5.6-8.7 0-3.1-2-5-1.9-2.2-7.2-3.8-5-2-14.8-4.4-12.3-3.1-22.1-6.2-9.9-3.1-16.8-7.4-7-4.4-10.6-10.6-3.6-6.5-3.6-15.8 0-13.2 7-22.4 7.2-9.3 19.7-14.1 12.4-5.1 28.8-5.1 14.6 0 27.3 4.6 12.7 4.6 19.9 14.9 7.5 10.3 5.6 27.3l-33.2 3.2q0.8-7-1.9-11.8-2.6-5-7.7-7.4-5-2.7-12.2-2.7-8.2 0-12.7 3.1-4.3 2.9-4.3 7.5 0 3.6 2.6 6.2 2.9 2.4 8.6 4.3 6 1.7 15.4 3.9 8.2 1.7 17 4.3 8.9 2.6 16.6 7.2 7.7 4.3 12.5 11.5 4.8 7.2 4.8 18.3 0 11.7-6.5 20.6-6.5 8.9-19 13.7-12.4 4.8-30.7 4.8zm119.3-0.5q-22.3 0-33.4-11.3-10.8-11.5-10.8-35.5v-52.6h-13.9v-24.4h0.7q14.9-2 21.2-9.9 6.2-8.1 7.6-24.5v-0.4h22.8v29h29.1v31.2h-29.1v48q0 9.1 4.4 13 4.5 3.8 11 3.8 3.4 0 7-1 3.6-0.9 6.7-2.6v33.8q-7.5 2.2-13.2 2.9-5.8 0.5-10.1 0.5zm76.3-2.9h-38.6v-68.9-57.8h33.6v50.4h1.9q1.4-20.2 5.5-31.7 4.3-11.7 10.6-16.5 6.5-5.1 14.4-5.1 4.3 0 8.9 1.2 4.8 1.2 9.3 3.9l-1.9 43.2q-5.3-3.2-10.3-4.6-5.1-1.7-9.6-1.7-7.7 0-13 4.3-5.3 4.4-8.1 12.8-2.7 8.4-2.7 20.4zm96.3 0h-38.9v-126.8h38.9zm-19.7-135.8q-11.1 0-17.1-4.6-5.7-4.8-5.7-13.6 0-9.2 5.7-13.7 6-4.8 17.1-4.8 11.3 0 17 4.8 6 4.8 6 13.7 0 8.6-6 13.4-5.7 4.8-17 4.8z"/></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1 @@
<?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>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
apps/docs/public/logo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

View File

@@ -1,6 +0,0 @@
{
"extends": ["github>nuxt/renovate-config-nuxt"],
"lockFileMaintenance": {
"enabled": true
}
}

View File

@@ -0,0 +1,88 @@
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,218 +0,0 @@
import { defineTheme } from 'pinceau'
export default defineTheme({
color: {
black: '#0B0A0A',
// Primary is modified lightblue
primary: {
50: '#fff6ec',
100: '#ffebd3',
200: '#ffd4a5',
300: '#ffb56d',
400: '#ff8a32',
500: '#ff680a',
600: '#ff4f01',
700: '#cc3602',
800: '#a12b0b',
900: '#82260c'
},
gray: {
50: '#FBFBFB',
100: '#F6F5F4',
200: '#ECEBE8',
300: '#DBD9D3',
400: '#ADA9A4',
500: '#97948F',
600: '#67635D',
700: '#36332E',
800: '#201E1B',
900: '#121110'
},
red: {
50: '#FFF9F8',
100: '#FFF3F0',
200: '#FFDED7',
300: '#FFA692',
400: '#FF7353',
500: '#FF3B10',
600: '#BB2402',
700: '#701704',
800: '#340A01',
900: '#1C0301'
},
blue: {
50: '#F2FAFF',
100: '#DFF3FF',
200: '#C6EAFF',
300: '#A1DDFF',
400: '#64C7FF',
500: '#1AADFF',
600: '#0069A6',
700: '#014267',
800: '#002235',
900: '#00131D'
},
green: {
50: '#ECFFF7',
100: '#DEFFF1',
200: '#C3FFE6',
300: '#86FBCB',
400: '#3CEEA5',
500: '#0DD885',
600: '#00B467',
700: '#006037',
800: '#002817',
900: '#00190F'
},
yellow: {
50: '#FFFCEE',
100: '#FFF6D3',
200: '#FFF0B1',
300: '#FFE372',
400: '#FFDC4E',
500: '#FBCA05',
600: '#CBA408',
700: '#614E02',
800: '#292100',
900: '#1B1500'
},
shadow: {
initial: '{color.gray.400}',
dark: '{color.gray.800}'
}
},
shadow: {
xs: '0px 1px 2px 0px {color.shadow}',
sm: '0px 1px 3px 0px {color.shadow}, 0px 1px 2px -1px {color.shadow}',
md: '0px 4px 6px -1px {color.shadow}, 0px 2px 4px -2px {color.shadow}',
lg: '0px 10px 15px -3px {color.shadow}, 0px 4px 6px -4px {color.shadow}',
xl: '0px 20px 25px -5px {color.shadow}, 0px 8px 10px -6px {color.shadow}',
'2xl': '0px 25px 50px -12px {color.shadow}',
none: '0px 0px 0px 0px transparent'
},
docus: {
$schema: {
title: 'All the configurable tokens from Docus.',
tags: [
'@studioIcon material-symbols:docs'
]
},
body: {
backgroundColor: {
initial: '{color.white}',
dark: '{color.black}'
},
color: {
initial: '{color.gray.800}',
dark: '{color.gray.200}'
},
fontFamily: '{font.sans}'
},
header: {
height: '64px',
logo: {
height: {
initial: '{space.6}',
sm: '{space.7}'
}
},
title: {
fontSize: '{fontSize.2xl}',
fontWeight: '{fontWeight.bold}',
color: {
static: {
initial: '{color.gray.900}',
dark: '{color.gray.100}',
},
hover: '{color.primary.500}',
}
}
},
footer: { height: { initial: '145px', sm: '100px' }, padding: '{space.4} 0' },
readableLine: '78ch',
loadingBar: {
height: '3px',
gradientColorStop1: '#00dc82',
gradientColorStop2: '#34cdfe',
gradientColorStop3: '#0047e1'
},
search: {
backdropFilter: 'blur(24px)',
input: {
borderRadius: '{radii.2xs}',
borderWidth: '1px',
borderStyle: 'solid',
borderColor: {
initial: '{color.gray.200}',
dark: 'transparent'
},
fontSize: '{fontSize.sm}',
gap: '{space.2}',
padding: '{space.2} {space.4}',
backgroundColor: {
initial: '{color.gray.200}',
dark: '{color.gray.800}'
},
},
results: {
window: {
marginX: {
initial: '0',
sm: '{space.4}'
},
borderRadius: {
initial: 'none',
sm: '{radii.xs}'
},
marginTop: {
initial: '0',
sm: '20vh'
},
maxWidth: '640px',
maxHeight: {
initial: '100%',
sm: '320px'
},
},
selected: {
backgroundColor: {
initial: '{color.gray.300}',
dark: '{color.gray.700}'
},
},
highlight: {
color: 'white',
backgroundColor: '{color.primary.500}'
}
}
}
},
typography: {
color: {
primary: {
50: '{color.primary.50}',
100: '{color.primary.100}',
200: '{color.primary.200}',
300: '{color.primary.300}',
400: '{color.primary.400}',
500: '{color.primary.500}',
600: '{color.primary.600}',
700: '{color.primary.700}',
800: '{color.primary.800}',
900: '{color.primary.900}'
},
secondary: {
50: '{color.gray.50}',
100: '{color.gray.100}',
200: '{color.gray.200}',
300: '{color.gray.300}',
400: '{color.gray.400}',
500: '{color.gray.500}',
600: '{color.gray.600}',
700: '{color.gray.700}',
800: '{color.gray.800}',
900: '{color.gray.900}'
}
}
}
})

View File

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

View File

@@ -67,7 +67,7 @@
"typescript": "5.4.5", "typescript": "5.4.5",
"undici": "*", "undici": "*",
"valibot": "^0.42.1", "valibot": "^0.42.1",
"vite": "6.0.15", "vite": "6.1.6",
"vite-tsconfig-paths": "^4.2.1", "vite-tsconfig-paths": "^4.2.1",
"wrangler": "^3.0.0" "wrangler": "^3.0.0"
}, },

246
bun.lock
View File

@@ -4,7 +4,7 @@
"": { "": {
"name": "nestri", "name": "nestri",
"dependencies": { "dependencies": {
"sst": "^3.11.21", "sst": "^3.17.8",
}, },
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "4.20240821.1", "@cloudflare/workers-types": "4.20240821.1",
@@ -143,6 +143,18 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@bufbuild/protobuf": "^2.2.3", "@bufbuild/protobuf": "^2.2.3",
"@chainsafe/libp2p-noise": "^16.1.3",
"@chainsafe/libp2p-yamux": "^7.0.1",
"@libp2p/identify": "^3.0.32",
"@libp2p/interface": "^2.10.2",
"@libp2p/ping": "^2.0.32",
"@libp2p/websockets": "^9.2.13",
"@multiformats/multiaddr": "^12.4.0",
"it-length-prefixed": "^10.0.1",
"it-pipe": "^3.0.1",
"libp2p": "^2.8.8",
"uint8arraylist": "^2.4.8",
"uint8arrays": "^5.1.0",
}, },
"devDependencies": { "devDependencies": {
"@bufbuild/buf": "^1.50.0", "@bufbuild/buf": "^1.50.0",
@@ -487,6 +499,18 @@
"@cbor-extract/cbor-extract-win32-x64": ["@cbor-extract/cbor-extract-win32-x64@2.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w=="], "@cbor-extract/cbor-extract-win32-x64": ["@cbor-extract/cbor-extract-win32-x64@2.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w=="],
"@chainsafe/as-chacha20poly1305": ["@chainsafe/as-chacha20poly1305@0.1.0", "", {}, "sha512-BpNcL8/lji/GM3+vZ/bgRWqJ1q5kwvTFmGPk7pxm/QQZDbaMI98waOHjEymTjq2JmdD/INdNBFOVSyJofXg7ew=="],
"@chainsafe/as-sha256": ["@chainsafe/as-sha256@1.2.0", "", {}, "sha512-H2BNHQ5C3RS+H0ZvOdovK6GjFAyq5T6LClad8ivwj9Oaiy28uvdsGVS7gNJKuZmg0FGHAI+n7F0Qju6U0QkKDA=="],
"@chainsafe/is-ip": ["@chainsafe/is-ip@2.1.0", "", {}, "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w=="],
"@chainsafe/libp2p-noise": ["@chainsafe/libp2p-noise@16.1.4", "", { "dependencies": { "@chainsafe/as-chacha20poly1305": "^0.1.0", "@chainsafe/as-sha256": "^1.0.0", "@libp2p/crypto": "^5.0.0", "@libp2p/interface": "^2.9.0", "@libp2p/peer-id": "^5.0.0", "@noble/ciphers": "^1.1.3", "@noble/curves": "^1.1.0", "@noble/hashes": "^1.3.1", "it-length-prefixed": "^10.0.1", "it-length-prefixed-stream": "^2.0.1", "it-pair": "^2.0.6", "it-pipe": "^3.0.1", "it-stream-types": "^2.0.1", "protons-runtime": "^5.5.0", "uint8arraylist": "^2.4.3", "uint8arrays": "^5.0.0", "wherearewe": "^2.0.1" } }, "sha512-f4FlyRVndcs4PoioOIZWrFc6wfO/mrAj7H63o0+eA0O2xhcoRkxHh6zna4W+WtScaF/Ua/UULgiNGuKNpLvLlQ=="],
"@chainsafe/libp2p-yamux": ["@chainsafe/libp2p-yamux@7.0.4", "", { "dependencies": { "@libp2p/interface": "^2.0.0", "@libp2p/utils": "^6.0.0", "get-iterator": "^2.0.1", "it-foreach": "^2.0.6", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.1", "race-signal": "^1.1.3", "uint8arraylist": "^2.4.8" } }, "sha512-Qw+EB9ew/9hRCq9V702gkm5xXThFHQ3Bdvh01M+enI1RScriSDWFGod02dwNHUxsYRc743i49sLlHp0edC7hSQ=="],
"@chainsafe/netmask": ["@chainsafe/netmask@2.0.0", "", { "dependencies": { "@chainsafe/is-ip": "^2.0.1" } }, "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg=="],
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="],
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.0.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.8", "workerd": "^1.20250124.0" }, "optionalPeers": ["workerd"] }, "sha512-Ar4HixFYP8e990JPACno3nqe10QsjS3yVWr48z5Vop5LygdnvPa5cfNHxGoQSPavvg5aaGnF0VAWc3JJ1tBKuQ=="], "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.0.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.8", "workerd": "^1.20250124.0" }, "optionalPeers": ["workerd"] }, "sha512-Ar4HixFYP8e990JPACno3nqe10QsjS3yVWr48z5Vop5LygdnvPa5cfNHxGoQSPavvg5aaGnF0VAWc3JJ1tBKuQ=="],
@@ -777,6 +801,34 @@
"@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="], "@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="],
"@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="],
"@libp2p/crypto": ["@libp2p/crypto@5.1.7", "", { "dependencies": { "@libp2p/interface": "^2.10.5", "@noble/curves": "^1.9.1", "@noble/hashes": "^1.8.0", "multiformats": "^13.3.6", "protons-runtime": "^5.5.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.1.0" } }, "sha512-7DO0piidLEKfCuNfS420BlHG0e2tH7W/zugdsPSiC/1Apa/s1B1dBkaIEgfDkGjrRP4S/8Or86Rtq7zXeEu67g=="],
"@libp2p/identify": ["@libp2p/identify@3.0.37", "", { "dependencies": { "@libp2p/crypto": "^5.1.7", "@libp2p/interface": "^2.10.5", "@libp2p/interface-internal": "^2.3.18", "@libp2p/peer-id": "^5.1.8", "@libp2p/peer-record": "^8.0.34", "@libp2p/utils": "^6.7.1", "@multiformats/multiaddr": "^12.4.4", "@multiformats/multiaddr-matcher": "^1.7.2", "it-drain": "^3.0.9", "it-parallel": "^3.0.11", "it-protobuf-stream": "^2.0.2", "main-event": "^1.0.1", "protons-runtime": "^5.5.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.1.0" } }, "sha512-oDdNZaP6a0eH3UIXBee4gOeOT4U4krfOAbqfqe/pM6exQqTyvzv21lOuFvC5EKgOYw63NoNPw1Iwnk36hDBpTg=="],
"@libp2p/interface": ["@libp2p/interface@2.10.5", "", { "dependencies": { "@multiformats/dns": "^1.0.6", "@multiformats/multiaddr": "^12.4.4", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.2", "main-event": "^1.0.1", "multiformats": "^13.3.6", "progress-events": "^1.0.1", "uint8arraylist": "^2.4.8" } }, "sha512-Z52n04Mph/myGdwyExbFi5S/HqrmZ9JOmfLc2v4r2Cik3GRdw98vrGH19PFvvwjLwAjaqsweCtlGaBzAz09YDw=="],
"@libp2p/interface-internal": ["@libp2p/interface-internal@2.3.18", "", { "dependencies": { "@libp2p/interface": "^2.10.5", "@libp2p/peer-collections": "^6.0.34", "@multiformats/multiaddr": "^12.4.4", "progress-events": "^1.0.1" } }, "sha512-tnZ20IFASXLbDc2JxeUPZNIXDuN5Ge7be6BU458WLvmquf93NlSqZkWs6xFdi+0yXUrw7GGTgzIP5v+1LnDUmA=="],
"@libp2p/logger": ["@libp2p/logger@5.1.21", "", { "dependencies": { "@libp2p/interface": "^2.10.5", "@multiformats/multiaddr": "^12.4.4", "interface-datastore": "^8.3.1", "multiformats": "^13.3.6", "weald": "^1.0.4" } }, "sha512-V1TWlZM5BuKkiGQ7En4qOnseVP82JwDIpIfNjceUZz1ArL32A5HXJjLQnJchkZ3VW8PVciJzUos/vP6slhPY6Q=="],
"@libp2p/multistream-select": ["@libp2p/multistream-select@6.0.28", "", { "dependencies": { "@libp2p/interface": "^2.10.5", "it-length-prefixed": "^10.0.1", "it-length-prefixed-stream": "^2.0.2", "it-stream-types": "^2.0.2", "p-defer": "^4.0.1", "race-signal": "^1.1.3", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.1.0" } }, "sha512-ILu65FAX2Hak7x40DXb0gYptF6BmlGGW2kNgGeKIcNeseuvsAkBPO8k0CHwr8MU5mnHamTiweLJh5jD0iVZJ1A=="],
"@libp2p/peer-collections": ["@libp2p/peer-collections@6.0.34", "", { "dependencies": { "@libp2p/interface": "^2.10.5", "@libp2p/peer-id": "^5.1.8", "@libp2p/utils": "^6.7.1", "multiformats": "^13.3.6" } }, "sha512-rw8gDGhou4sF6W6i9ntmRARFePX19Dw9MMVpZHr6Kx9q2kvBJq91IXUzsXP06roexEOu1CUlZwxtUAqOBy+Eww=="],
"@libp2p/peer-id": ["@libp2p/peer-id@5.1.8", "", { "dependencies": { "@libp2p/crypto": "^5.1.7", "@libp2p/interface": "^2.10.5", "multiformats": "^13.3.6", "uint8arrays": "^5.1.0" } }, "sha512-pGaM4BwjnXdGtAtd84L4/wuABpsnFYE+AQ+h3GxNFme0IsTaTVKWd1jBBE5YFeKHBHGUOhF3TlHsdjFfjQA7TA=="],
"@libp2p/peer-record": ["@libp2p/peer-record@8.0.34", "", { "dependencies": { "@libp2p/crypto": "^5.1.7", "@libp2p/interface": "^2.10.5", "@libp2p/peer-id": "^5.1.8", "@libp2p/utils": "^6.7.1", "@multiformats/multiaddr": "^12.4.4", "multiformats": "^13.3.6", "protons-runtime": "^5.5.0", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.1.0" } }, "sha512-GqvRBpvclscoKuF0JUfLyZTv+BwzICBBe50LFiAKio8LijZMBr43b+AcEaSEwFWDwlWmaKU73q8EQLrCb/e67Q=="],
"@libp2p/peer-store": ["@libp2p/peer-store@11.2.6", "", { "dependencies": { "@libp2p/crypto": "^5.1.7", "@libp2p/interface": "^2.10.5", "@libp2p/peer-collections": "^6.0.34", "@libp2p/peer-id": "^5.1.8", "@libp2p/peer-record": "^8.0.34", "@multiformats/multiaddr": "^12.4.4", "interface-datastore": "^8.3.1", "it-all": "^3.0.8", "main-event": "^1.0.1", "mortice": "^3.2.1", "multiformats": "^13.3.6", "protons-runtime": "^5.5.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.1.0" } }, "sha512-3Lc982/7drqlXa51s9l1/DFHD48zzIjMMYajxFM2KbobyStH+lztYnFc3kNGB9sZijULaW1480PvbTMm9WaJ0g=="],
"@libp2p/ping": ["@libp2p/ping@2.0.36", "", { "dependencies": { "@libp2p/crypto": "^5.1.7", "@libp2p/interface": "^2.10.5", "@libp2p/interface-internal": "^2.3.18", "@multiformats/multiaddr": "^12.4.4", "it-byte-stream": "^2.0.2", "main-event": "^1.0.1", "uint8arrays": "^5.1.0" } }, "sha512-susC5yMBZhMDz+jIjX7RvQ7NXNL5L4fkRzPhwu+1eLbwNXTYZtDd7fJ53p40I5Ur/A+DgtCpIk03X2nrHPc5Jg=="],
"@libp2p/utils": ["@libp2p/utils@6.7.1", "", { "dependencies": { "@chainsafe/is-ip": "^2.1.0", "@chainsafe/netmask": "^2.0.0", "@libp2p/crypto": "^5.1.7", "@libp2p/interface": "^2.10.5", "@libp2p/logger": "^5.1.21", "@multiformats/multiaddr": "^12.4.4", "@sindresorhus/fnv1a": "^3.1.0", "any-signal": "^4.1.1", "delay": "^6.0.0", "get-iterator": "^2.0.1", "is-loopback-addr": "^2.0.2", "is-plain-obj": "^4.1.0", "it-foreach": "^2.1.3", "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "it-stream-types": "^2.0.2", "main-event": "^1.0.1", "netmask": "^2.0.2", "p-defer": "^4.0.1", "race-event": "^1.3.0", "race-signal": "^1.1.3", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.1.0" } }, "sha512-x3WImvw4unmx1ZeAedj8AkRe4UImUlkw0ZItYAiKiekElMNUXwv+Yt48dI/LmB38JIof8sng29XvUeCVU3F6OA=="],
"@libp2p/websockets": ["@libp2p/websockets@9.2.17", "", { "dependencies": { "@libp2p/interface": "^2.10.5", "@libp2p/utils": "^6.7.1", "@multiformats/multiaddr": "^12.4.4", "@multiformats/multiaddr-matcher": "^1.7.2", "@multiformats/multiaddr-to-uri": "^11.0.0", "@types/ws": "^8.18.1", "it-ws": "^6.1.5", "main-event": "^1.0.1", "p-defer": "^4.0.1", "p-event": "^6.0.1", "progress-events": "^1.0.1", "race-signal": "^1.1.3", "ws": "^8.18.2" } }, "sha512-PNhLFZA+DyV8xynCphrl4H4a1mVRIe6ZPKqLt9UwWr9Ye0ecjgVcL38uDo0XAZkx+NO3S+YspCx8yquJnDyi2A=="],
"@logdna/tail-file": ["@logdna/tail-file@2.2.0", "", {}, "sha512-XGSsWDweP80Fks16lwkAUIr54ICyBs6PsI4mpfTLQaWgEJRtY9xEV+PeyDpJ+sJEGZxqINlpmAwe/6tS1pP8Ng=="], "@logdna/tail-file": ["@logdna/tail-file@2.2.0", "", {}, "sha512-XGSsWDweP80Fks16lwkAUIr54ICyBs6PsI4mpfTLQaWgEJRtY9xEV+PeyDpJ+sJEGZxqINlpmAwe/6tS1pP8Ng=="],
"@macaron-css/babel": ["@macaron-css/babel@1.5.1", "", { "dependencies": { "@babel/core": "^7.18.2", "@babel/generator": "^7.18.2", "@babel/helper-module-imports": "^7.16.7", "@babel/preset-typescript": "^7.22.5", "@emotion/hash": "^0.8.0", "@types/babel__core": "^7.1.19" } }, "sha512-gN/ju/21avvQBeT3gGX/nLnWRrIcOIYcleKKZRi1hWK1b1lxJy7uMdNMFLf/4QpvlOTsqzu53I5seGatb445qg=="], "@macaron-css/babel": ["@macaron-css/babel@1.5.1", "", { "dependencies": { "@babel/core": "^7.18.2", "@babel/generator": "^7.18.2", "@babel/helper-module-imports": "^7.16.7", "@babel/preset-typescript": "^7.22.5", "@emotion/hash": "^0.8.0", "@types/babel__core": "^7.1.19" } }, "sha512-gN/ju/21avvQBeT3gGX/nLnWRrIcOIYcleKKZRi1hWK1b1lxJy7uMdNMFLf/4QpvlOTsqzu53I5seGatb445qg=="],
@@ -799,6 +851,14 @@
"@modular-forms/solid": ["@modular-forms/solid@0.25.1", "", { "dependencies": { "valibot": "^1.0.0-beta.6" }, "peerDependencies": { "solid-js": "^1.3.1" } }, "sha512-issNZ3xl4tj+1K7KT4dNTQaRq5SmVUXgUPeGTMjtrAzCeTnwM/u6vUxSuTY2bcMw4GzTxreFXLx1xMMhrFkt0A=="], "@modular-forms/solid": ["@modular-forms/solid@0.25.1", "", { "dependencies": { "valibot": "^1.0.0-beta.6" }, "peerDependencies": { "solid-js": "^1.3.1" } }, "sha512-issNZ3xl4tj+1K7KT4dNTQaRq5SmVUXgUPeGTMjtrAzCeTnwM/u6vUxSuTY2bcMw4GzTxreFXLx1xMMhrFkt0A=="],
"@multiformats/dns": ["@multiformats/dns@1.0.6", "", { "dependencies": { "@types/dns-packet": "^5.6.5", "buffer": "^6.0.3", "dns-packet": "^5.6.1", "hashlru": "^2.3.0", "p-queue": "^8.0.1", "progress-events": "^1.0.0", "uint8arrays": "^5.0.2" } }, "sha512-nt/5UqjMPtyvkG9BQYdJ4GfLK3nMqGpFZOzf4hAmIa0sJh2LlS9YKXZ4FgwBDsaHvzZqR/rUFIywIc7pkHNNuw=="],
"@multiformats/multiaddr": ["@multiformats/multiaddr@12.5.1", "", { "dependencies": { "@chainsafe/is-ip": "^2.0.1", "@chainsafe/netmask": "^2.0.0", "@multiformats/dns": "^1.0.3", "abort-error": "^1.0.1", "multiformats": "^13.0.0", "uint8-varint": "^2.0.1", "uint8arrays": "^5.0.0" } }, "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ=="],
"@multiformats/multiaddr-matcher": ["@multiformats/multiaddr-matcher@1.7.2", "", { "dependencies": { "@chainsafe/is-ip": "^2.0.1", "@multiformats/multiaddr": "^12.0.0", "multiformats": "^13.0.0" } }, "sha512-BJzHOBAAxGZKw+FY/MzeIKGKERAW/1XOrpj61wgzZVvR/iksyGTQhliyTgmuakpBJPSsCxlrk3eLemVhZuJIFQ=="],
"@multiformats/multiaddr-to-uri": ["@multiformats/multiaddr-to-uri@11.0.2", "", { "dependencies": { "@multiformats/multiaddr": "^12.3.0" } }, "sha512-SiLFD54zeOJ0qMgo9xv1Tl9O5YktDKAVDP4q4hL16mSq4O4sfFNagNADz8eAofxd6TfQUzGQ3TkRRG9IY2uHRg=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.7", "", { "dependencies": { "@emnapi/core": "^1.3.1", "@emnapi/runtime": "^1.3.1", "@tybys/wasm-util": "^0.9.0" } }, "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.7", "", { "dependencies": { "@emnapi/core": "^1.3.1", "@emnapi/runtime": "^1.3.1", "@tybys/wasm-util": "^0.9.0" } }, "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw=="],
"@nestri/core": ["@nestri/core@workspace:packages/core"], "@nestri/core": ["@nestri/core@workspace:packages/core"],
@@ -1371,6 +1431,8 @@
"@sinclair/typebox": ["@sinclair/typebox@0.34.28", "", {}, "sha512-e2B9vmvaa5ym5hWgCHw5CstP54au6AOLXrhZErLsOyyRzuWJtXl/8TszKtc5x8rw/b+oY7HKS9m9iRI53RK0WQ=="], "@sinclair/typebox": ["@sinclair/typebox@0.34.28", "", {}, "sha512-e2B9vmvaa5ym5hWgCHw5CstP54au6AOLXrhZErLsOyyRzuWJtXl/8TszKtc5x8rw/b+oY7HKS9m9iRI53RK0WQ=="],
"@sindresorhus/fnv1a": ["@sindresorhus/fnv1a@3.1.0", "", {}, "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w=="],
"@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="],
@@ -1553,7 +1615,7 @@
"@types/basic-auth": ["@types/basic-auth@1.1.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-dKcUeixGuZn8pBjcUrf1N7x5K6lWuKuwHHitM2IZ4vwZUDWEhhNtwCWiba8jTA9zn0GQQ+fTFkWpKx8pOU/enw=="], "@types/basic-auth": ["@types/basic-auth@1.1.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-dKcUeixGuZn8pBjcUrf1N7x5K6lWuKuwHHitM2IZ4vwZUDWEhhNtwCWiba8jTA9zn0GQQ+fTFkWpKx8pOU/enw=="],
"@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="], "@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
"@types/bunyan": ["@types/bunyan@1.8.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ=="], "@types/bunyan": ["@types/bunyan@1.8.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ=="],
@@ -1565,6 +1627,8 @@
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
"@types/dns-packet": ["@types/dns-packet@5.6.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-qXOC7XLOEe43ehtWJCMnQXvgcIpv6rPmQ1jXT98Ad8A3TB1Ue50jsCbSSSyuazScEuZ/Q026vHbrOTVkmwA+7Q=="],
"@types/doctrine": ["@types/doctrine@0.0.9", "", {}, "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA=="], "@types/doctrine": ["@types/doctrine@0.0.9", "", {}, "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA=="],
"@types/dom-mediacapture-transform": ["@types/dom-mediacapture-transform@0.1.11", "", { "dependencies": { "@types/dom-webcodecs": "*" } }, "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ=="], "@types/dom-mediacapture-transform": ["@types/dom-mediacapture-transform@0.1.11", "", { "dependencies": { "@types/dom-webcodecs": "*" } }, "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ=="],
@@ -1659,25 +1723,25 @@
"@types/xml2js": ["@types/xml2js@0.4.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="], "@types/xml2js": ["@types/xml2js@0.4.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.33.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.33.1", "@typescript-eslint/type-utils": "8.33.1", "@typescript-eslint/utils": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.33.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.35.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/type-utils": "8.35.0", "@typescript-eslint/utils": "8.35.0", "@typescript-eslint/visitor-keys": "8.35.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.35.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.33.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.33.1", "@typescript-eslint/types": "8.33.1", "@typescript-eslint/typescript-estree": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.35.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/types": "8.35.0", "@typescript-eslint/typescript-estree": "8.35.0", "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.33.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.33.1", "@typescript-eslint/types": "^8.33.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw=="], "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.35.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.35.0", "@typescript-eslint/types": "^8.35.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.33.1", "", { "dependencies": { "@typescript-eslint/types": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1" } }, "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.35.0", "", { "dependencies": { "@typescript-eslint/types": "8.35.0", "@typescript-eslint/visitor-keys": "8.35.0" } }, "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.33.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g=="], "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.35.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.33.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.33.1", "@typescript-eslint/utils": "8.33.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww=="], "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.35.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.35.0", "@typescript-eslint/utils": "8.35.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.33.1", "", {}, "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg=="], "@typescript-eslint/types": ["@typescript-eslint/types@8.35.0", "", {}, "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.33.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.33.1", "@typescript-eslint/tsconfig-utils": "8.33.1", "@typescript-eslint/types": "8.33.1", "@typescript-eslint/visitor-keys": "8.33.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA=="], "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.35.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.35.0", "@typescript-eslint/tsconfig-utils": "8.35.0", "@typescript-eslint/types": "8.35.0", "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.33.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.33.1", "@typescript-eslint/types": "8.33.1", "@typescript-eslint/typescript-estree": "8.33.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ=="], "@typescript-eslint/utils": ["@typescript-eslint/utils@8.35.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/types": "8.35.0", "@typescript-eslint/typescript-estree": "8.35.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.33.1", "", { "dependencies": { "@typescript-eslint/types": "8.33.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ=="], "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.35.0", "", { "dependencies": { "@typescript-eslint/types": "8.35.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g=="],
"@typescript/lib-dom": ["@types/web@0.0.115", "", {}, "sha512-IBtUgtxnITC7WTCg4tv6kCnSP0T+fM+3PzQPIzLzJY1DDlhBFKM/9+uMURw14YweWPDiFNIZ94Gc1bJtwow97g=="], "@typescript/lib-dom": ["@types/web@0.0.115", "", {}, "sha512-IBtUgtxnITC7WTCg4tv6kCnSP0T+fM+3PzQPIzLzJY1DDlhBFKM/9+uMURw14YweWPDiFNIZ94Gc1bJtwow97g=="],
@@ -1783,6 +1847,8 @@
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"abort-error": ["abort-error@1.0.1", "", {}, "sha512-fxqCblJiIPdSXIUrxI0PL+eJG49QdP9SQ70qtB65MVAoMr2rASlOyAbJFOylfB467F/f+5BCLJJq58RYi7mGfg=="],
"abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="], "abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
@@ -1817,6 +1883,8 @@
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
"any-signal": ["any-signal@4.1.1", "", {}, "sha512-iADenERppdC+A2YKbOXXB2WUeABLaM6qnpZ70kZbPZ1cZMMJ7eF+3CaYm+/PhBizgkzlvssC7QuHS30oOiQYWA=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="],
@@ -2169,6 +2237,8 @@
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
"datastore-core": ["datastore-core@10.0.4", "", { "dependencies": { "@libp2p/logger": "^5.1.18", "interface-datastore": "^8.0.0", "interface-store": "^6.0.0", "it-drain": "^3.0.9", "it-filter": "^3.1.3", "it-map": "^3.1.3", "it-merge": "^3.0.11", "it-pipe": "^3.0.1", "it-sort": "^3.0.8", "it-take": "^3.0.8" } }, "sha512-IctgCO0GA7GHG7aRm3JRruibCsfvN4EXNnNIlLCZMKIv0TPkdAL5UFV3/xTYFYrrZ1jRNrXZNZRvfcVf/R+rAw=="],
"date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
"db0": ["db0@0.3.1", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-3RogPLE2LLq6t4YiFCREyl572aBjkfMvfwPyN51df00TbPbryL3XqBYuJ/j6mgPssPK8AKfYdLxizaO5UG10sA=="], "db0": ["db0@0.3.1", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-3RogPLE2LLq6t4YiFCREyl572aBjkfMvfwPyN51df00TbPbryL3XqBYuJ/j6mgPssPK8AKfYdLxizaO5UG10sA=="],
@@ -2211,6 +2281,8 @@
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
"delay": ["delay@6.0.0", "", {}, "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="],
@@ -2245,6 +2317,8 @@
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
"dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="],
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
"docus-starter": ["docus-starter@workspace:apps/docs"], "docus-starter": ["docus-starter@workspace:apps/docs"],
@@ -2443,6 +2517,8 @@
"eval": ["eval@0.1.8", "", { "dependencies": { "@types/node": "*", "require-like": ">= 0.1.1" } }, "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw=="], "eval": ["eval@0.1.8", "", { "dependencies": { "@types/node": "*", "require-like": ">= 0.1.1" } }, "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw=="],
"event-iterator": ["event-iterator@2.0.0", "", {}, "sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ=="],
"event-source-plus": ["event-source-plus@0.1.8", "", { "dependencies": { "ofetch": "^1.4.1" } }, "sha512-Zzj5lk73Qjh4Q4ax7kyHoKyEcZrw5FtK2wBGXSTCRX+BXw/4I/mM8b+fQ+OnEt3F79FqKavOyz5E5xUrnkzhoQ=="], "event-source-plus": ["event-source-plus@0.1.8", "", { "dependencies": { "ofetch": "^1.4.1" } }, "sha512-Zzj5lk73Qjh4Q4ax7kyHoKyEcZrw5FtK2wBGXSTCRX+BXw/4I/mM8b+fQ+OnEt3F79FqKavOyz5E5xUrnkzhoQ=="],
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
@@ -2595,6 +2671,8 @@
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-iterator": ["get-iterator@2.0.1", "", {}, "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
"get-port-please": ["get-port-please@3.1.2", "", {}, "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ=="], "get-port-please": ["get-port-please@3.1.2", "", {}, "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ=="],
@@ -2671,6 +2749,8 @@
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hashlru": ["hashlru@2.3.0", "", {}, "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
@@ -2775,6 +2855,10 @@
"input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="], "input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="],
"interface-datastore": ["interface-datastore@8.3.2", "", { "dependencies": { "interface-store": "^6.0.0", "uint8arrays": "^5.1.0" } }, "sha512-R3NLts7pRbJKc3qFdQf+u40hK8XWc0w4Qkx3OFEstC80VoaDUABY/dXA2EJPhtNC+bsrf41Ehvqb6+pnIclyRA=="],
"interface-store": ["interface-store@6.0.3", "", {}, "sha512-+WvfEZnFUhRwFxgz+QCQi7UC6o9AM0EHM9bpIe2Nhqb100NHCsTvNAn4eJgvgV2/tmLo1MP9nGxQKEcZTAueLA=="],
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
"invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="],
@@ -2821,6 +2905,8 @@
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
"is-electron": ["is-electron@2.2.2", "", {}, "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
@@ -2839,6 +2925,8 @@
"is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="],
"is-loopback-addr": ["is-loopback-addr@2.0.2", "", {}, "sha512-26POf2KRCno/KTNL5Q0b/9TYnL00xEsSaLfiFRmjM7m7Lw7ZMmFybzzuX4CcsLAluZGd+niLUiMRxEooVE3aqg=="],
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
"is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="], "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
@@ -2899,6 +2987,50 @@
"isstream": ["isstream@0.1.2", "", {}, "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="], "isstream": ["isstream@0.1.2", "", {}, "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="],
"it-all": ["it-all@3.0.9", "", {}, "sha512-fz1oJJ36ciGnu2LntAlE6SA97bFZpW7Rnt0uEc1yazzR2nKokZLr8lIRtgnpex4NsmaBcvHF+Z9krljWFy/mmg=="],
"it-byte-stream": ["it-byte-stream@2.0.3", "", { "dependencies": { "abort-error": "^1.0.1", "it-queueless-pushable": "^2.0.0", "it-stream-types": "^2.0.2", "race-signal": "^1.1.3", "uint8arraylist": "^2.4.8" } }, "sha512-h7FFcn4DWiWsJw1dCJhuPdiY8cGi1z8g4aLAfFspTaJbwQxvEMlEBFG/f8lIVGwM8YK26ClM4/9lxLVhF33b8g=="],
"it-drain": ["it-drain@3.0.10", "", {}, "sha512-0w/bXzudlyKIyD1+rl0xUKTI7k4cshcS43LTlBiGFxI8K1eyLydNPxGcsVLsFVtKh1/ieS8AnVWt6KwmozxyEA=="],
"it-filter": ["it-filter@3.1.4", "", { "dependencies": { "it-peekable": "^3.0.0" } }, "sha512-80kWEKgiFEa4fEYD3mwf2uygo1dTQ5Y5midKtL89iXyjinruA/sNXl6iFkTcdNedydjvIsFhWLiqRPQP4fAwWQ=="],
"it-foreach": ["it-foreach@2.1.4", "", { "dependencies": { "it-peekable": "^3.0.0" } }, "sha512-gFntBbNLpVK9uDmaHusugICD8/Pp+OCqbF5q1Z8K+B8WaG20YgMePWbMxI1I25+JmNWWr3hk0ecKyiI9pOLgeA=="],
"it-length-prefixed": ["it-length-prefixed@10.0.1", "", { "dependencies": { "it-reader": "^6.0.1", "it-stream-types": "^2.0.1", "uint8-varint": "^2.0.1", "uint8arraylist": "^2.0.0", "uint8arrays": "^5.0.1" } }, "sha512-BhyluvGps26u9a7eQIpOI1YN7mFgi8lFwmiPi07whewbBARKAG9LE09Odc8s1Wtbt2MB6rNUrl7j9vvfXTJwdQ=="],
"it-length-prefixed-stream": ["it-length-prefixed-stream@2.0.3", "", { "dependencies": { "abort-error": "^1.0.1", "it-byte-stream": "^2.0.0", "it-stream-types": "^2.0.2", "uint8-varint": "^2.0.4", "uint8arraylist": "^2.4.8" } }, "sha512-Ns3jNFy2mcFnV59llCYitJnFHapg8wIcOsWkEaAwOkG9v4HBCk24nze/zGDQjiJdDTyFXTT5GOY3M/uaksot3w=="],
"it-map": ["it-map@3.1.4", "", { "dependencies": { "it-peekable": "^3.0.0" } }, "sha512-QB9PYQdE9fUfpVFYfSxBIyvKynUCgblb143c+ktTK6ZuKSKkp7iH58uYFzagqcJ5HcqIfn1xbfaralHWam+3fg=="],
"it-merge": ["it-merge@3.0.12", "", { "dependencies": { "it-queueless-pushable": "^2.0.0" } }, "sha512-nnnFSUxKlkZVZD7c0jYw6rDxCcAQYcMsFj27thf7KkDhpj0EA0g9KHPxbFzHuDoc6US2EPS/MtplkNj8sbCx4Q=="],
"it-pair": ["it-pair@2.0.6", "", { "dependencies": { "it-stream-types": "^2.0.1", "p-defer": "^4.0.0" } }, "sha512-5M0t5RAcYEQYNG5BV7d7cqbdwbCAp5yLdzvkxsZmkuZsLbTdZzah6MQySYfaAQjNDCq6PUnDt0hqBZ4NwMfW6g=="],
"it-parallel": ["it-parallel@3.0.13", "", { "dependencies": { "p-defer": "^4.0.1" } }, "sha512-85PPJ/O8q97Vj9wmDTSBBXEkattwfQGruXitIzrh0RLPso6RHfiVqkuTqBNufYYtB1x6PSkh0cwvjmMIkFEPHA=="],
"it-peekable": ["it-peekable@3.0.8", "", {}, "sha512-7IDBQKSp/dtBxXV3Fj0v3qM1jftJ9y9XrWLRIuU1X6RdKqWiN60syNwP0fiDxZD97b8SYM58dD3uklIk1TTQAw=="],
"it-pipe": ["it-pipe@3.0.1", "", { "dependencies": { "it-merge": "^3.0.0", "it-pushable": "^3.1.2", "it-stream-types": "^2.0.1" } }, "sha512-sIoNrQl1qSRg2seYSBH/3QxWhJFn9PKYvOf/bHdtCBF0bnghey44VyASsWzn5dAx0DCDDABq1hZIuzKmtBZmKA=="],
"it-protobuf-stream": ["it-protobuf-stream@2.0.3", "", { "dependencies": { "abort-error": "^1.0.1", "it-length-prefixed-stream": "^2.0.0", "it-stream-types": "^2.0.2", "uint8arraylist": "^2.4.8" } }, "sha512-Dus9qyylOSnC7l75/3qs6j3Fe9MCM2K5luXi9o175DYijFRne5FPucdOGIYdwaDBDQ4Oy34dNCuFobOpcusvEQ=="],
"it-pushable": ["it-pushable@3.2.3", "", { "dependencies": { "p-defer": "^4.0.0" } }, "sha512-gzYnXYK8Y5t5b/BnJUr7glfQLO4U5vyb05gPx/TyTw+4Bv1zM9gFk4YsOrnulWefMewlphCjKkakFvj1y99Tcg=="],
"it-queue": ["it-queue@1.1.0", "", { "dependencies": { "abort-error": "^1.0.1", "it-pushable": "^3.2.3", "main-event": "^1.0.0", "race-event": "^1.3.0", "race-signal": "^1.1.3" } }, "sha512-aK9unJRIaJc9qiv53LByhF7/I2AuD7Ro4oLfLieVLL9QXNvRx++ANMpv8yCp2UO0KAtBuf70GOxSYb6ElFVRpQ=="],
"it-queueless-pushable": ["it-queueless-pushable@2.0.2", "", { "dependencies": { "abort-error": "^1.0.1", "p-defer": "^4.0.1", "race-signal": "^1.1.3" } }, "sha512-2BqIt7XvDdgEgudLAdJkdseAwbVSBc0yAd8yPVHrll4eBuJPWIj9+8C3OIxzEKwhswLtd3bi+yLrzgw9gCyxMA=="],
"it-reader": ["it-reader@6.0.4", "", { "dependencies": { "it-stream-types": "^2.0.1", "uint8arraylist": "^2.0.0" } }, "sha512-XCWifEcNFFjjBHtor4Sfaj8rcpt+FkY0L6WdhD578SCDhV4VUm7fCkF3dv5a+fTcfQqvN9BsxBTvWbYO6iCjTg=="],
"it-sort": ["it-sort@3.0.9", "", { "dependencies": { "it-all": "^3.0.0" } }, "sha512-jsM6alGaPiQbcAJdzMsuMh00uJcI+kD9TBoScB8TR75zUFOmHvhSsPi+Dmh2zfVkcoca+14EbfeIZZXTUGH63w=="],
"it-stream-types": ["it-stream-types@2.0.2", "", {}, "sha512-Rz/DEZ6Byn/r9+/SBCuJhpPATDF9D+dz5pbgSUyBsCDtza6wtNATrz/jz1gDyNanC3XdLboriHnOC925bZRBww=="],
"it-take": ["it-take@3.0.9", "", {}, "sha512-XMeUbnjOcgrhFXPUqa7H0VIjYSV/BvyxxjCp76QHVAFDJw2LmR1SHxUFiqyGeobgzJr7P2ZwSRRJQGn4D2BVlA=="],
"it-ws": ["it-ws@6.1.5", "", { "dependencies": { "@types/ws": "^8.2.2", "event-iterator": "^2.0.0", "it-stream-types": "^2.0.1", "uint8arrays": "^5.0.0", "ws": "^8.4.0" } }, "sha512-uWjMtpy5HqhSd/LlrlP3fhYrr7rUfJFFMABv0F5d6n13Q+0glhZthwUKpEAVhDrXY95Tb1RB5lLqqef+QbVNaw=="],
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"javascript-stringify": ["javascript-stringify@2.1.0", "", {}, "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg=="], "javascript-stringify": ["javascript-stringify@2.1.0", "", {}, "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg=="],
@@ -2995,6 +3127,8 @@
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"libp2p": ["libp2p@2.8.12", "", { "dependencies": { "@chainsafe/is-ip": "^2.1.0", "@chainsafe/netmask": "^2.0.0", "@libp2p/crypto": "^5.1.7", "@libp2p/interface": "^2.10.5", "@libp2p/interface-internal": "^2.3.18", "@libp2p/logger": "^5.1.21", "@libp2p/multistream-select": "^6.0.28", "@libp2p/peer-collections": "^6.0.34", "@libp2p/peer-id": "^5.1.8", "@libp2p/peer-store": "^11.2.6", "@libp2p/utils": "^6.7.1", "@multiformats/dns": "^1.0.6", "@multiformats/multiaddr": "^12.4.4", "@multiformats/multiaddr-matcher": "^1.7.2", "any-signal": "^4.1.1", "datastore-core": "^10.0.2", "interface-datastore": "^8.3.1", "it-byte-stream": "^2.0.2", "it-merge": "^3.0.11", "it-parallel": "^3.0.11", "main-event": "^1.0.1", "multiformats": "^13.3.6", "p-defer": "^4.0.1", "p-retry": "^6.2.1", "progress-events": "^1.0.1", "race-event": "^1.3.0", "race-signal": "^1.1.3", "uint8arrays": "^5.1.0" } }, "sha512-wWknf2YsfHwnNK0XyFxCkVVNrckCrY+lwCdp4bpHScb5ALcWmzpgWP8+h/b66sEJ3IUJjhgjMkN1W9sS53KTPQ=="],
"light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="], "light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
@@ -3075,6 +3209,8 @@
"magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="],
"main-event": ["main-event@1.0.1", "", {}, "sha512-NWtdGrAca/69fm6DIVd8T9rtfDII4Q8NQbIbsKQq2VzS9eqOGYs8uaNQjcuaCq/d9H/o625aOTJX2Qoxzqw0Pw=="],
"make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="], "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
"make-fetch-happen": ["make-fetch-happen@13.0.1", "", { "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", "http-cache-semantics": "^4.1.1", "is-lambda": "^1.0.1", "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "proc-log": "^4.2.0", "promise-retry": "^2.0.1", "ssri": "^10.0.0" } }, "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA=="], "make-fetch-happen": ["make-fetch-happen@13.0.1", "", { "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", "http-cache-semantics": "^4.1.1", "is-lambda": "^1.0.1", "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "proc-log": "^4.2.0", "promise-retry": "^2.0.1", "ssri": "^10.0.0" } }, "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA=="],
@@ -3267,6 +3403,8 @@
"module-details-from-path": ["module-details-from-path@1.0.3", "", {}, "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A=="], "module-details-from-path": ["module-details-from-path@1.0.3", "", {}, "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A=="],
"mortice": ["mortice@3.3.1", "", { "dependencies": { "abort-error": "^1.0.0", "it-queue": "^1.1.0", "main-event": "^1.0.0" } }, "sha512-t3oESfijIPGsmsdLEKjF+grHfrbnKSXflJtgb1wY14cjxZpS6GnhHRXTxxzCAoCCnq1YYfpEPwY3gjiCPhOufQ=="],
"motion": ["motion@12.6.2", "", { "dependencies": { "framer-motion": "^12.6.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-8OBjjuC59WuWHKmPzVWT5M0t5kDxtkfMfHF1M7Iey6F/nvd0AI15YlPnpGlcagW/eOfkdWDO90U/K5LF/k55Yw=="], "motion": ["motion@12.6.2", "", { "dependencies": { "framer-motion": "^12.6.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-8OBjjuC59WuWHKmPzVWT5M0t5kDxtkfMfHF1M7Iey6F/nvd0AI15YlPnpGlcagW/eOfkdWDO90U/K5LF/k55Yw=="],
"motion-dom": ["motion-dom@11.18.1", "", { "dependencies": { "motion-utils": "^11.18.1" } }, "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw=="], "motion-dom": ["motion-dom@11.18.1", "", { "dependencies": { "motion-utils": "^11.18.1" } }, "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw=="],
@@ -3285,6 +3423,8 @@
"muggle-string": ["muggle-string@0.2.2", "", {}, "sha512-YVE1mIJ4VpUMqZObFndk9CJu6DBJR/GB13p3tXuNbwD4XExaI5EOuRl6BHeIDxIqXZVxSfAC+y6U1Z/IxCfKUg=="], "muggle-string": ["muggle-string@0.2.2", "", {}, "sha512-YVE1mIJ4VpUMqZObFndk9CJu6DBJR/GB13p3tXuNbwD4XExaI5EOuRl6BHeIDxIqXZVxSfAC+y6U1Z/IxCfKUg=="],
"multiformats": ["multiformats@13.3.7", "", {}, "sha512-meL9DERHj+fFVWoOX9fXqfcYcSpUfSYJPcFvDPKrxitICbwAoWR+Ut4j5NO9zAT917HUHLQmqzQbAsGNHlDcxQ=="],
"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
@@ -3299,6 +3439,8 @@
"negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
"nitropack": ["nitropack@2.11.8", "", { "dependencies": { "@cloudflare/kv-asset-handler": "^0.4.0", "@netlify/functions": "^3.0.2", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-terser": "^0.4.4", "@vercel/nft": "^0.29.2", "archiver": "^7.0.1", "c12": "^3.0.2", "chokidar": "^4.0.3", "citty": "^0.1.6", "compatx": "^0.1.8", "confbox": "^0.2.1", "consola": "^3.4.2", "cookie-es": "^2.0.0", "croner": "^9.0.0", "crossws": "^0.3.4", "db0": "^0.3.1", "defu": "^6.1.4", "destr": "^2.0.3", "dot-prop": "^9.0.0", "esbuild": "^0.25.1", "escape-string-regexp": "^5.0.0", "etag": "^1.8.1", "exsolve": "^1.0.4", "globby": "^14.1.0", "gzip-size": "^7.0.0", "h3": "^1.15.1", "hookable": "^5.5.3", "httpxy": "^0.1.7", "ioredis": "^5.6.0", "jiti": "^2.4.2", "klona": "^2.0.6", "knitwork": "^1.2.0", "listhen": "^1.9.0", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mime": "^4.0.6", "mlly": "^1.7.4", "node-fetch-native": "^1.6.6", "node-mock-http": "^1.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.1.0", "pretty-bytes": "^6.1.1", "radix3": "^1.1.2", "rollup": "^4.36.0", "rollup-plugin-visualizer": "^5.14.0", "scule": "^1.3.0", "semver": "^7.7.1", "serve-placeholder": "^2.0.2", "serve-static": "^1.16.2", "source-map": "^0.7.4", "std-env": "^3.8.1", "ufo": "^1.5.4", "ultrahtml": "^1.5.3", "uncrypto": "^0.1.3", "unctx": "^2.4.1", "unenv": "^2.0.0-rc.15", "unimport": "^4.1.2", "unplugin-utils": "^0.2.4", "unstorage": "^1.15.0", "untyped": "^2.0.0", "unwasm": "^0.3.9", "youch": "^4.1.0-beta.6", "youch-core": "^0.3.2" }, "peerDependencies": { "xml2js": "^0.6.2" }, "optionalPeers": ["xml2js"], "bin": { "nitro": "dist/cli/index.mjs", "nitropack": "dist/cli/index.mjs" } }, "sha512-ummTu4R8Lhd1nO3nWrW7eeiHA2ey3ntbWFKkYakm4rcbvT6meWp+oykyrYBNFQKhobQl9CydmUWlCyztYXFPJw=="], "nitropack": ["nitropack@2.11.8", "", { "dependencies": { "@cloudflare/kv-asset-handler": "^0.4.0", "@netlify/functions": "^3.0.2", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-terser": "^0.4.4", "@vercel/nft": "^0.29.2", "archiver": "^7.0.1", "c12": "^3.0.2", "chokidar": "^4.0.3", "citty": "^0.1.6", "compatx": "^0.1.8", "confbox": "^0.2.1", "consola": "^3.4.2", "cookie-es": "^2.0.0", "croner": "^9.0.0", "crossws": "^0.3.4", "db0": "^0.3.1", "defu": "^6.1.4", "destr": "^2.0.3", "dot-prop": "^9.0.0", "esbuild": "^0.25.1", "escape-string-regexp": "^5.0.0", "etag": "^1.8.1", "exsolve": "^1.0.4", "globby": "^14.1.0", "gzip-size": "^7.0.0", "h3": "^1.15.1", "hookable": "^5.5.3", "httpxy": "^0.1.7", "ioredis": "^5.6.0", "jiti": "^2.4.2", "klona": "^2.0.6", "knitwork": "^1.2.0", "listhen": "^1.9.0", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mime": "^4.0.6", "mlly": "^1.7.4", "node-fetch-native": "^1.6.6", "node-mock-http": "^1.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.1.0", "pretty-bytes": "^6.1.1", "radix3": "^1.1.2", "rollup": "^4.36.0", "rollup-plugin-visualizer": "^5.14.0", "scule": "^1.3.0", "semver": "^7.7.1", "serve-placeholder": "^2.0.2", "serve-static": "^1.16.2", "source-map": "^0.7.4", "std-env": "^3.8.1", "ufo": "^1.5.4", "ultrahtml": "^1.5.3", "uncrypto": "^0.1.3", "unctx": "^2.4.1", "unenv": "^2.0.0-rc.15", "unimport": "^4.1.2", "unplugin-utils": "^0.2.4", "unstorage": "^1.15.0", "untyped": "^2.0.0", "unwasm": "^0.3.9", "youch": "^4.1.0-beta.6", "youch-core": "^0.3.2" }, "peerDependencies": { "xml2js": "^0.6.2" }, "optionalPeers": ["xml2js"], "bin": { "nitro": "dist/cli/index.mjs", "nitropack": "dist/cli/index.mjs" } }, "sha512-ummTu4R8Lhd1nO3nWrW7eeiHA2ey3ntbWFKkYakm4rcbvT6meWp+oykyrYBNFQKhobQl9CydmUWlCyztYXFPJw=="],
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
@@ -3429,14 +3571,22 @@
"p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
"p-defer": ["p-defer@4.0.1", "", {}, "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A=="],
"p-event": ["p-event@6.0.1", "", { "dependencies": { "p-timeout": "^6.1.2" } }, "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w=="],
"p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="],
"p-queue": ["p-queue@8.1.0", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw=="],
"p-retry": ["p-retry@6.2.1", "", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="], "p-retry": ["p-retry@6.2.1", "", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="],
"p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="],
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
@@ -3663,6 +3813,8 @@
"proggy": ["proggy@2.0.0", "", {}, "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A=="], "proggy": ["proggy@2.0.0", "", {}, "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A=="],
"progress-events": ["progress-events@1.0.1", "", {}, "sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw=="],
"promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="], "promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="],
"promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="], "promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="],
@@ -3681,6 +3833,8 @@
"protocols": ["protocols@2.0.2", "", {}, "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ=="], "protocols": ["protocols@2.0.2", "", {}, "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ=="],
"protons-runtime": ["protons-runtime@5.6.0", "", { "dependencies": { "uint8-varint": "^2.0.2", "uint8arraylist": "^2.4.3", "uint8arrays": "^5.0.1" } }, "sha512-/Kde+sB9DsMFrddJT/UZWe6XqvL7SL5dbag/DBCElFKhkwDj7XKt53S+mzLyaDP5OqS0wXjV5SA572uWDaT0Hg=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
@@ -3711,6 +3865,10 @@
"quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="],
"race-event": ["race-event@1.3.0", "", {}, "sha512-kaLm7axfOnahIqD3jQ4l1e471FIFcEGebXEnhxyLscuUzV8C94xVHtWEqDDXxll7+yu/6lW0w1Ff4HbtvHvOHg=="],
"race-signal": ["race-signal@1.1.3", "", {}, "sha512-Mt2NznMgepLfORijhQMncE26IhkmjEphig+/1fKC0OtaKwys/gpvpmswSjoN01SS+VO951mj0L4VIDXdXsjnfA=="],
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
@@ -4027,17 +4185,23 @@
"ssri": ["ssri@10.0.6", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ=="], "ssri": ["ssri@10.0.6", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ=="],
"sst": ["sst@3.11.21", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.11.21", "sst-darwin-x64": "3.11.21", "sst-linux-arm64": "3.11.21", "sst-linux-x64": "3.11.21", "sst-linux-x86": "3.11.21" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-q0zeVplSv4mvfWaruru83f2y9LcHQlpFjF7+KhsmSs5VheKMbIGExa35dHl00bYxOsGSbECx/W3S6fXMtC18rg=="], "sst": ["sst@3.17.8", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.8", "sst-darwin-x64": "3.17.8", "sst-linux-arm64": "3.17.8", "sst-linux-x64": "3.17.8", "sst-linux-x86": "3.17.8", "sst-win32-arm64": "3.17.8", "sst-win32-x64": "3.17.8", "sst-win32-x86": "3.17.8" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-P/a9/ZsjtQRrTBerBMO1ODaVa5HVTmNLrQNJiYvu2Bgd0ov+vefQeHv6oima8HLlPwpDIPS2gxJk8BZrTZMfCA=="],
"sst-darwin-arm64": ["sst-darwin-arm64@3.11.21", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aHXl27gEt7uyYnfGlby0eEjA+07iTKIJL+F6ElWCz7iyQ3gYG8ncFxjIFlsRKnz2kvl8EPze0/fwy/sj053qLA=="], "sst-darwin-arm64": ["sst-darwin-arm64@3.17.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-50P6YRMnZVItZUfB0+NzqMww2mmm4vB3zhTVtWUtGoXeiw78g1AEnVlmS28gYXPHM1P987jTvR7EON9u9ig/Dg=="],
"sst-darwin-x64": ["sst-darwin-x64@3.11.21", "", { "os": "darwin", "cpu": "x64" }, "sha512-2mh4zgLtr9o48+gQOmHcdpvu/vkXUQXqVNLLpVw267asvGswAfADyzPG68cHzZ7EBYg80H5DnAgdkAonps5q+A=="], "sst-darwin-x64": ["sst-darwin-x64@3.17.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-P0pnMHCmpkpcsxkWpilmeoD79LkbkoIcv6H0aeM9ArT/71/JBhvqH+HjMHSJCzni/9uR6er+nH5F+qol0UO6Bw=="],
"sst-linux-arm64": ["sst-linux-arm64@3.11.21", "", { "os": "linux", "cpu": "arm64" }, "sha512-DlyseKrnINW5sGN6EUCOsh927p+NDVV1+6JTqkdcaxsK5nkdZ8h44plcFAvx1PXLSmGkqSwWt1bf2Yu2VEE0Ig=="], "sst-linux-arm64": ["sst-linux-arm64@3.17.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-vun54YA/UzprCu9p8BC4rMwFU5Cj9xrHAHYLYUp/yq4H0pfmBIiQM62nsfIKizRThe/TkBFy60EEi9myf6raYA=="],
"sst-linux-x64": ["sst-linux-x64@3.11.21", "", { "os": "linux", "cpu": "x64" }, "sha512-LXOcnYRkFOFByY/T8PkOltkLLrM8OS2SXb4bS9AprDIjful6hO2wYsqR/29LvuZEubgU7pco2OMHmjjlYCkSGg=="], "sst-linux-x64": ["sst-linux-x64@3.17.8", "", { "os": "linux", "cpu": "x64" }, "sha512-HqByCaLE2gEJbM20P1QRd+GqDMAiieuU53FaZA1F+AGxQi+kR82NWjrPqFcMj4dMYg8w/TWXuV+G5+PwoUmpDw=="],
"sst-linux-x86": ["sst-linux-x86@3.11.21", "", { "os": "linux", "cpu": "none" }, "sha512-DRdSdyETA6EWU8uGC2gtmBkh4edwC/IGYONAwqhKAe/290ZvD/MDjQo7o0Yyu9QirowfTKTAw4SPijjCOL7t1g=="], "sst-linux-x86": ["sst-linux-x86@3.17.8", "", { "os": "linux", "cpu": "none" }, "sha512-bCd6QM3MejfSmdvg8I/k+aUJQIZEQJg023qmN78fv00vwlAtfECvY7tjT9E2m3LDp33pXrcRYbFOQzPu+tWFfA=="],
"sst-win32-arm64": ["sst-win32-arm64@3.17.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-pilx0n8gm4aHJae/vNiqIwZkWF3tdwWzD/ON7hkytw+CVSZ0FXtyFW/yO/+2u3Yw0Kj0lSWPnUqYgm/eHPLwQA=="],
"sst-win32-x64": ["sst-win32-x64@3.17.8", "", { "os": "win32", "cpu": "x64" }, "sha512-Jb0FVRyiOtESudF1V8ucW65PuHrx/iOHUamIO0JnbujWNHZBTRPB2QHN1dbewgkueYDaCmyS8lvuIImLwYJnzQ=="],
"sst-win32-x86": ["sst-win32-x86@3.17.8", "", { "os": "win32", "cpu": "none" }, "sha512-oVmFa/PoElQmfnGJlB0w6rPXiYuldiagO6AbrLMT/6oAnWerLQ8Uhv9tJWfMh3xtPLImQLTjxDo1v0AIzEv9QA=="],
"stable-hash": ["stable-hash@0.0.4", "", {}, "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g=="], "stable-hash": ["stable-hash@0.0.4", "", {}, "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g=="],
@@ -4229,6 +4393,12 @@
"ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="], "ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="],
"uint8-varint": ["uint8-varint@2.0.4", "", { "dependencies": { "uint8arraylist": "^2.0.0", "uint8arrays": "^5.0.0" } }, "sha512-FwpTa7ZGA/f/EssWAb5/YV6pHgVF1fViKdW8cWaEarjB8t7NyofSWBdOTyFPaGuUG4gx3v1O3PQ8etsiOs3lcw=="],
"uint8arraylist": ["uint8arraylist@2.4.8", "", { "dependencies": { "uint8arrays": "^5.0.1" } }, "sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ=="],
"uint8arrays": ["uint8arrays@5.1.0", "", { "dependencies": { "multiformats": "^13.0.0" } }, "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww=="],
"ulid": ["ulid@2.3.0", "", { "bin": { "ulid": "./bin/cli.js" } }, "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw=="], "ulid": ["ulid@2.3.0", "", { "bin": { "ulid": "./bin/cli.js" } }, "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw=="],
"ultrahtml": ["ultrahtml@1.5.3", "", {}, "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg=="], "ultrahtml": ["ultrahtml@1.5.3", "", {}, "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg=="],
@@ -4387,6 +4557,8 @@
"walk-up-path": ["walk-up-path@3.0.1", "", {}, "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA=="], "walk-up-path": ["walk-up-path@3.0.1", "", {}, "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA=="],
"weald": ["weald@1.0.4", "", { "dependencies": { "ms": "^3.0.0-canary.1", "supports-color": "^9.4.0" } }, "sha512-+kYTuHonJBwmFhP1Z4YQK/dGi3jAnJGCYhyODFpHK73rbxnp9lnZQj7a2m+WVgn8fXr5bJaxUpF6l8qZpPeNWQ=="],
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
@@ -4403,6 +4575,8 @@
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"wherearewe": ["wherearewe@2.0.1", "", { "dependencies": { "is-electron": "^2.2.0" } }, "sha512-XUguZbDxCA2wBn2LoFtcEhXL6AXo+hVjGonwhSTTTU9SzbWG8Xu3onNIpzf9j/mYUcJQ0f+m37SzG77G851uFw=="],
"which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
@@ -4901,6 +5075,12 @@
"@jridgewell/source-map/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], "@jridgewell/source-map/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@libp2p/crypto/@noble/curves": ["@noble/curves@1.9.2", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g=="],
"@libp2p/websockets/@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"@libp2p/websockets/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"@macaron-css/babel/@emotion/hash": ["@emotion/hash@0.8.0", "", {}, "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="], "@macaron-css/babel/@emotion/hash": ["@emotion/hash@0.8.0", "", {}, "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="],
"@macaron-css/integration/esbuild": ["esbuild@0.14.54", "", { "optionalDependencies": { "@esbuild/linux-loong64": "0.14.54", "esbuild-android-64": "0.14.54", "esbuild-android-arm64": "0.14.54", "esbuild-darwin-64": "0.14.54", "esbuild-darwin-arm64": "0.14.54", "esbuild-freebsd-64": "0.14.54", "esbuild-freebsd-arm64": "0.14.54", "esbuild-linux-32": "0.14.54", "esbuild-linux-64": "0.14.54", "esbuild-linux-arm": "0.14.54", "esbuild-linux-arm64": "0.14.54", "esbuild-linux-mips64le": "0.14.54", "esbuild-linux-ppc64le": "0.14.54", "esbuild-linux-riscv64": "0.14.54", "esbuild-linux-s390x": "0.14.54", "esbuild-netbsd-64": "0.14.54", "esbuild-openbsd-64": "0.14.54", "esbuild-sunos-64": "0.14.54", "esbuild-windows-32": "0.14.54", "esbuild-windows-64": "0.14.54", "esbuild-windows-arm64": "0.14.54" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA=="], "@macaron-css/integration/esbuild": ["esbuild@0.14.54", "", { "optionalDependencies": { "@esbuild/linux-loong64": "0.14.54", "esbuild-android-64": "0.14.54", "esbuild-android-arm64": "0.14.54", "esbuild-darwin-64": "0.14.54", "esbuild-darwin-arm64": "0.14.54", "esbuild-freebsd-64": "0.14.54", "esbuild-freebsd-arm64": "0.14.54", "esbuild-linux-32": "0.14.54", "esbuild-linux-64": "0.14.54", "esbuild-linux-arm": "0.14.54", "esbuild-linux-arm64": "0.14.54", "esbuild-linux-mips64le": "0.14.54", "esbuild-linux-ppc64le": "0.14.54", "esbuild-linux-riscv64": "0.14.54", "esbuild-linux-s390x": "0.14.54", "esbuild-netbsd-64": "0.14.54", "esbuild-openbsd-64": "0.14.54", "esbuild-sunos-64": "0.14.54", "esbuild-windows-32": "0.14.54", "esbuild-windows-64": "0.14.54", "esbuild-windows-arm64": "0.14.54" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA=="],
@@ -4919,10 +5099,14 @@
"@modular-forms/solid/valibot": ["valibot@1.0.0-rc.3", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-LT0REa7Iqx4QGcaHLiTiTkcmJqJ9QdpOy89HALFFBJgejTS64GQFRIbDF7e4f6pauQbo/myfKGmWXCLhMeM6+g=="], "@modular-forms/solid/valibot": ["valibot@1.0.0-rc.3", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-LT0REa7Iqx4QGcaHLiTiTkcmJqJ9QdpOy89HALFFBJgejTS64GQFRIbDF7e4f6pauQbo/myfKGmWXCLhMeM6+g=="],
"@multiformats/dns/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="], "@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="],
"@nestri/sdk/@types/node": ["@types/node@18.19.80", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ=="], "@nestri/sdk/@types/node": ["@types/node@18.19.80", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ=="],
"@nestri/ui/prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"@nestri/web/@polar-sh/sdk": ["@polar-sh/sdk@0.21.3", "", { "dependencies": { "standardwebhooks": "^1.0.0" }, "peerDependencies": { "zod": ">= 3" } }, "sha512-ZdsZmO3T+cwRDpRKdbnXRKofrUFF4fibs9deD697BGbqoeBeRqR2KlMb8WkgMhG5qw6Q4voo6YLZ9AOxUgG5FQ=="], "@nestri/web/@polar-sh/sdk": ["@polar-sh/sdk@0.21.3", "", { "dependencies": { "standardwebhooks": "^1.0.0" }, "peerDependencies": { "zod": ">= 3" } }, "sha512-ZdsZmO3T+cwRDpRKdbnXRKofrUFF4fibs9deD697BGbqoeBeRqR2KlMb8WkgMhG5qw6Q4voo6YLZ9AOxUgG5FQ=="],
"@nestri/web/@types/eslint": ["@types/eslint@8.56.10", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ=="], "@nestri/web/@types/eslint": ["@types/eslint@8.56.10", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ=="],
@@ -5433,7 +5617,7 @@
"@types/basic-auth/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], "@types/basic-auth/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
"@types/bun/bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="], "@types/bun/bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
"@types/bunyan/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], "@types/bunyan/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
@@ -5441,6 +5625,8 @@
"@types/connect/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], "@types/connect/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
"@types/dns-packet/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
"@types/keyv/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], "@types/keyv/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
"@types/memcached/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], "@types/memcached/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
@@ -5475,7 +5661,7 @@
"@typescript-eslint/utils/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], "@typescript-eslint/utils/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"@valibot/to-json-schema/valibot": ["valibot@1.0.0-rc.3", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-LT0REa7Iqx4QGcaHLiTiTkcmJqJ9QdpOy89HALFFBJgejTS64GQFRIbDF7e4f6pauQbo/myfKGmWXCLhMeM6+g=="], "@valibot/to-json-schema/valibot": ["valibot@1.0.0-rc.3", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-LT0REa7Iqx4QGcaHLiTiTkcmJqJ9QdpOy89HALFFBJgejTS64GQFRIbDF7e4f6pauQbo/myfKGmWXCLhMeM6+g=="],
@@ -5717,6 +5903,10 @@
"is-installed-globally/is-path-inside": ["is-path-inside@4.0.0", "", {}, "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA=="], "is-installed-globally/is-path-inside": ["is-path-inside@4.0.0", "", {}, "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA=="],
"it-ws/@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"it-ws/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"koa/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], "koa/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"koa/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], "koa/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
@@ -6107,6 +6297,10 @@
"vite-plugin-checker/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], "vite-plugin-checker/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"weald/ms": ["ms@3.0.0-canary.1", "", {}, "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g=="],
"weald/supports-color": ["supports-color@9.4.0", "", {}, "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw=="],
"which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="], "wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="],
@@ -6437,6 +6631,8 @@
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"@libp2p/websockets/@types/ws/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
"@macaron-css/integration/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.14.54", "", { "os": "linux", "cpu": "none" }, "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw=="], "@macaron-css/integration/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.14.54", "", { "os": "linux", "cpu": "none" }, "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw=="],
"@mapbox/node-pre-gyp/nopt/abbrev": ["abbrev@3.0.0", "", {}, "sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA=="], "@mapbox/node-pre-gyp/nopt/abbrev": ["abbrev@3.0.0", "", {}, "sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA=="],
@@ -6451,6 +6647,8 @@
"@modelcontextprotocol/sdk/eventsource/eventsource-parser": ["eventsource-parser@3.0.0", "", {}, "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA=="], "@modelcontextprotocol/sdk/eventsource/eventsource-parser": ["eventsource-parser@3.0.0", "", {}, "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA=="],
"@multiformats/dns/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"@nestri/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], "@nestri/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@nestri/web/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], "@nestri/web/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
@@ -6767,6 +6965,8 @@
"@types/connect/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], "@types/connect/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/dns-packet/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/keyv/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], "@types/keyv/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@types/memcached/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], "@types/memcached/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
@@ -6953,6 +7153,8 @@
"impound/unplugin/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], "impound/unplugin/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"it-ws/@types/ws/@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
"koa-send/http-errors/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="], "koa-send/http-errors/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="],
"koa-send/http-errors/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="], "koa-send/http-errors/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="],
@@ -7699,6 +7901,8 @@
"@grpc/proto-loader/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "@grpc/proto-loader/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"@libp2p/websockets/@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"@mapbox/node-pre-gyp/tar/minizlib/rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], "@mapbox/node-pre-gyp/tar/minizlib/rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
"@nestri/web/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@7.16.1", "", {}, "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ=="], "@nestri/web/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@7.16.1", "", {}, "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ=="],
@@ -7975,6 +8179,8 @@
"eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "eslint-plugin-qwik/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"it-ws/@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], "pkg-dir/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="],
"read-pkg-up/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "read-pkg-up/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],

9
containers/Caddyfile Normal file
View File

@@ -0,0 +1,9 @@
relay.example.com {
@ws {
header Connection Upgrade
header Upgrade websocket
}
tls you@example.com
reverse_proxy @ws relay:8088
reverse_proxy relay:8088
}

View File

@@ -0,0 +1,33 @@
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

View File

@@ -0,0 +1,52 @@
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

View File

@@ -86,7 +86,7 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
# Clone repository # Clone repository
RUN git clone -b dev-dmabuf https://github.com/DatCaptainHorse/gst-wayland-display.git RUN git clone --depth 1 -b "dev-dmabuf" https://github.com/DatCaptainHorse/gst-wayland-display.git
#-------------------------------------------------------------------- #--------------------------------------------------------------------
FROM gst-wayland-deps AS gst-wayland-planner FROM gst-wayland-deps AS gst-wayland-planner
@@ -135,11 +135,12 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
vulkan-intel lib32-vulkan-intel vpl-gpu-rt \ vulkan-intel lib32-vulkan-intel vpl-gpu-rt \
vulkan-radeon lib32-vulkan-radeon \ vulkan-radeon lib32-vulkan-radeon \
mesa \ mesa \
steam steam-native-runtime gtk3 lib32-gtk3 \ steam steam-native-runtime proton-cachyos gtk3 lib32-gtk3 \
sudo xorg-xwayland seatd libinput gamescope mangohud \ sudo xorg-xwayland seatd libinput gamescope mangohud wlr-randr \
libssh2 curl wget \ libssh2 curl wget \
pipewire pipewire-pulse pipewire-alsa wireplumber \ pipewire pipewire-pulse pipewire-alsa wireplumber \
noto-fonts-cjk supervisor jq chwd lshw pacman-contrib && \ noto-fonts-cjk supervisor jq chwd lshw pacman-contrib \
openssh && \
# GStreamer stack # GStreamer stack
pacman -Sy --needed --noconfirm \ pacman -Sy --needed --noconfirm \
gstreamer gst-plugins-base gst-plugins-good \ gstreamer gst-plugins-base gst-plugins-good \
@@ -153,14 +154,6 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
paccache -rk1 && \ paccache -rk1 && \
rm -rf /usr/share/{info,man,doc}/* rm -rf /usr/share/{info,man,doc}/*
### Application Installation ###
ARG LUDUSAVI_VERSION="0.28.0"
RUN curl -fsSL -o ludusavi.tar.gz \
"https://github.com/mtkennerly/ludusavi/releases/download/v${LUDUSAVI_VERSION}/ludusavi-v${LUDUSAVI_VERSION}-linux.tar.gz" && \
tar -xzvf ludusavi.tar.gz && \
mv ludusavi /usr/bin/ && \
rm ludusavi.tar.gz
### User Configuration ### ### User Configuration ###
ENV USER="nestri" \ ENV USER="nestri" \
UID=1000 \ UID=1000 \

View File

@@ -1,120 +1 @@
import { bus } from "./bus";
import { vpc } from "./vpc";
import { auth } from "./auth";
import { domain } from "./dns";
import { secret } from "./secret";
import { postgres } from "./postgres";
const urls = new sst.Linkable("Urls", {
properties: {
api: `https://api.${domain}`,
auth: `https://auth.${domain}`,
site: $dev ? "http://localhost:3000" : `https://console.${domain}`,
}
})
const apiFn = new sst.aws.Function("ApiFn", {
vpc,
handler: "packages/functions/src/api/index.handler",
streaming: !$dev,
link: [
bus,
urls,
auth,
postgres,
secret.SteamApiKey,
secret.PolarSecret,
secret.PolarWebhookSecret,
secret.NestriFamilyMonthly,
secret.NestriFamilyYearly,
secret.NestriFreeMonthly,
secret.NestriProMonthly,
secret.NestriProYearly,
],
url: true,
});
const provider = new aws.Provider("UsEast1", { region: "us-east-1" });
const webAcl = new aws.wafv2.WebAcl(
"ApiWaf",
{
scope: "CLOUDFRONT",
defaultAction: {
allow: {},
},
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: "api-rate-limit-metric",
sampledRequestsEnabled: true,
},
rules: [
{
name: "rate-limit-rule",
priority: 1,
action: {
block: {
customResponse: {
responseCode: 429,
customResponseBodyKey: "rate-limit-response",
},
},
},
statement: {
rateBasedStatement: {
limit: 2 * 60, // 2 rps per authorization header
evaluationWindowSec: 60,
aggregateKeyType: "CUSTOM_KEYS",
customKeys: [
{
header: {
name: "Authorization",
textTransformations: [{ priority: 0, type: "NONE" }],
},
},
],
},
},
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: "rate-limit-rule-metric",
sampledRequestsEnabled: true,
},
},
],
customResponseBodies: [
{
key: "rate-limit-response",
content: JSON.stringify({
type: "rate_limit",
code: "too_many_requests",
message: "Rate limit exceeded. Please try again later.",
}),
contentType: "APPLICATION_JSON",
},
],
},
{ provider },
);
export const api = new sst.aws.Router("Api", {
routes: {
"/*": apiFn.url,
},
domain: {
name: "api." + domain,
dns: sst.cloudflare.dns(),
},
transform: {
cdn(args) {
if (!args.transform) {
args.transform = {
distribution: {},
};
}
args.transform!.distribution = {
webAclId: webAcl.arn,
};
},
},
});

View File

@@ -1,32 +0,0 @@
import { bus } from "./bus";
import { vpc } from "./vpc";
import { domain } from "./dns";
import { secret } from "./secret";
import { postgres } from "./postgres";
export const auth = new sst.aws.Auth("Auth", {
authorizer: {
vpc,
link: [
bus,
postgres,
secret.PolarSecret,
secret.GithubClientID,
secret.DiscordClientID,
secret.GithubClientSecret,
secret.DiscordClientSecret,
],
permissions: [
{
actions: ["ses:SendEmail"],
resources: ["*"],
},
],
handler: "packages/functions/src/auth/index.handler",
},
domain: {
name: "auth." + domain,
dns: sst.cloudflare.dns(),
},
forceUpgrade: "v2",
});

View File

@@ -1,70 +0,0 @@
import { vpc } from "./vpc";
import { secret } from "./secret";
import { storage } from "./storage";
import { postgres } from "./postgres";
export const dlq = new sst.aws.Queue("Dlq");
export const retryQueue = new sst.aws.Queue("RetryQueue");
export const bus = new sst.aws.Bus("Bus");
export const eventSub = bus.subscribe("Event", {
vpc,
handler: "packages/functions/src/events/index.handler",
link: [
// email,
bus,
storage,
postgres,
retryQueue,
secret.PolarSecret,
secret.SteamApiKey
],
environment: {
RETRIES: "2",
},
memory: "3002 MB",// For faster processing of large(r) images
timeout: "10 minutes",
});
new aws.lambda.FunctionEventInvokeConfig("EventConfig", {
functionName: $resolve([eventSub.nodes.function.name]).apply(
([name]) => name,
),
maximumRetryAttempts: 1,
destinationConfig: {
onFailure: {
destination: retryQueue.arn,
},
},
});
retryQueue.subscribe({
vpc,
handler: "packages/functions/src/queues/retry.handler",
timeout: "30 seconds",
environment: {
RETRIER_QUEUE_URL: retryQueue.url,
},
link: [
dlq,
retryQueue,
eventSub.nodes.function,
],
permissions: [
{
actions: ["lambda:GetFunction", "lambda:InvokeFunction"],
resources: [
$interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:${aws.getCallerIdentityOutput().accountId}:function:*`,
],
},
],
transform: {
function: {
deadLetterConfig: {
targetArn: dlq.arn,
},
},
},
});

View File

@@ -1,18 +0,0 @@
import { domain } from "./dns";
import { storage } from "./storage";
export const cdn = new sst.aws.Router("CDNRouter", {
routes: {
"/*": {
bucket: storage,
rewrite: {
regex: "^/([a-zA-Z0-9_-]+)$",
to: "/public/$1"
},
},
},
domain: {
name: "cdn." + domain,
dns: sst.cloudflare.dns()
}
});

View File

@@ -1,6 +0,0 @@
import { vpc } from "./vpc";
export const cluster = new sst.aws.Cluster("Cluster", {
vpc,
forceUpgrade: "v2"
});

View File

@@ -1,9 +1,5 @@
export const domain = export const domain = (() => {
{ if ($app.stage === "production") return "nestri.io"
production: "nestri.io", if ($app.stage === "dev") return "nestri.io"
dev: "dev.nestri.io", return `${$app.stage}.dev.nestri.io`
}[$app.stage] || $app.stage + ".dev.nestri.io"; })()
export const zone = cloudflare.getZoneOutput({
name: "nestri.io",
});

View File

@@ -1,6 +0,0 @@
import { domain } from "./dns";
export const email = new sst.aws.Email("Email",{
sender: domain,
dns: sst.cloudflare.dns(),
})

View File

@@ -1,64 +0,0 @@
import { vpc } from "./vpc";
export const postgres = new sst.aws.Aurora("Database", {
vpc,
engine: "postgres",
scaling: {
min: "0 ACU",
max: "1 ACU",
},
transform: {
clusterParameterGroup: {
parameters: [
{
name: "rds.logical_replication",
value: "1",
applyMethod: "pending-reboot",
},
{
name: "max_slot_wal_keep_size",
value: "10240",
applyMethod: "pending-reboot",
},
{
name: "rds.force_ssl",
value: "0",
applyMethod: "pending-reboot",
},
{
name: "max_connections",
value: "1000",
applyMethod: "pending-reboot",
},
],
},
},
});
new sst.x.DevCommand("Studio", {
link: [postgres],
dev: {
command: "bun db:dev studio",
directory: "packages/core",
autostart: true,
},
});
// const migrator = new sst.aws.Function("DatabaseMigrator", {
// handler: "packages/functions/src/migrator.handler",
// link: [postgres],
// copyFiles: [
// {
// from: "packages/core/migrations",
// to: "./migrations",
// },
// ],
// });
// if (!$dev) {
// new aws.lambda.Invocation("DatabaseMigratorInvocation", {
// input: Date.now().toString(),
// functionName: migrator.name,
// });
// }

View File

@@ -1,9 +0,0 @@
// import { auth } from "./auth";
import { postgres } from "./postgres";
export const device = new sst.aws.Realtime("Realtime", {
authorizer: {
link: [ postgres],
handler: "packages/functions/src/realtime/authorizer.handler"
}
})

View File

@@ -1,18 +0,0 @@
export const secret = {
PolarSecret: new sst.Secret("PolarSecret", process.env.POLAR_API_KEY),
SteamApiKey: new sst.Secret("SteamApiKey"),
GithubClientID: new sst.Secret("GithubClientID"),
DiscordClientID: new sst.Secret("DiscordClientID"),
PolarWebhookSecret: new sst.Secret("PolarWebhookSecret"),
GithubClientSecret: new sst.Secret("GithubClientSecret"),
DiscordClientSecret: new sst.Secret("DiscordClientSecret"),
// Pricing
NestriFreeMonthly: new sst.Secret("NestriFreeMonthly"),
NestriProMonthly: new sst.Secret("NestriProMonthly"),
NestriProYearly: new sst.Secret("NestriProYearly"),
NestriFamilyMonthly: new sst.Secret("NestriFamilyMonthly"),
NestriFamilyYearly: new sst.Secret("NestriFamilyYearly"),
};
export const allSecrets = Object.values(secret);

View File

@@ -1,2 +0,0 @@
export const isPermanentStage =
$app.stage === "production" || $app.stage === "dev";

View File

@@ -1,5 +0,0 @@
export const storage = new sst.aws.Bucket("Storage", {
access: "cloudfront"
});
export const zeroStorage = new sst.aws.Bucket("ZeroStorage");

View File

@@ -1,11 +0,0 @@
import { isPermanentStage } from "./stage";
export const vpc = isPermanentStage
? new sst.aws.Vpc("VPC", {
az: 2,
// For lambdas to work in this VPC
nat: "ec2",
// For SST tunnel to work
bastion: true,
})
: sst.aws.Vpc.get("VPC", "vpc-0beb1cdc21a725748");

View File

@@ -1,25 +1,9 @@
// This is the website part where people play and connect
import { api } from "./api";
import { cdn } from "./cdn";
import { auth } from "./auth";
import { zero } from "./zero";
import { domain } from "./dns"; import { domain } from "./dns";
new sst.aws.StaticSite("Web", { new sst.cloudflare.x.Astro("Web", {
path: "packages/www", domain,
build: { path: "packages/web",
output: "./dist",
command: "bun run build",
},
domain: {
dns: sst.cloudflare.dns(),
name: "console." + domain
},
environment: { environment: {
VITE_API_URL: api.url, SST_STAGE: $app.stage,
VITE_CDN_URL: cdn.url,
VITE_STAGE: $app.stage,
VITE_AUTH_URL: auth.url,
VITE_ZERO_URL: zero.url,
}, },
}) })

View File

@@ -1,186 +0,0 @@
import { auth } from "./auth";
import { domain } from "./dns";
import { readFileSync } from "fs";
import { cluster } from "./cluster";
import { postgres } from "./postgres";
import { zeroStorage } from "./storage";
const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}:${postgres.port}/${postgres.database}`;
const tag = $dev
? `latest`
: JSON.parse(
readFileSync("./node_modules/@rocicorp/zero/package.json").toString(),
).version.replace("+", "-");
const zeroEnv = {
FORCE: "1",
NO_COLOR: "1",
ZERO_LOG_LEVEL: "info",
ZERO_LITESTREAM_LOG_LEVEL: "info",
ZERO_UPSTREAM_DB: connectionString,
ZERO_IMAGE_URL: `rocicorp/zero:${tag}`,
ZERO_CVR_DB: connectionString,
ZERO_CHANGE_DB: connectionString,
ZERO_REPLICA_FILE: "/tmp/nestri.db",
ZERO_LITESTREAM_RESTORE_PARALLELISM: "64",
ZERO_APP_ID: $app.stage,
ZERO_AUTH_JWKS_URL: $interpolate`${auth.url}/.well-known/jwks.json`,
ZERO_INITIAL_SYNC_ROW_BATCH_SIZE: "30000",
NODE_OPTIONS: "--max-old-space-size=8192",
...($dev
? {
}
: {
ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${zeroStorage.name}/zero/0`,
}),
};
// Replication Manager Service
const replicationManager = !$dev
? new sst.aws.Service(`ZeroReplication`, {
cluster,
wait: true,
cpu: "0.5 vCPU",
memory: "1 GB",
capacity: "spot",
architecture: "arm64",
image: zeroEnv.ZERO_IMAGE_URL,
link: [zeroStorage, postgres],
health: {
command: ["CMD-SHELL", "curl -f http://localhost:4849/ || exit 1"],
interval: "5 seconds",
retries: 3,
startPeriod: "300 seconds",
},
environment: {
...zeroEnv,
ZERO_CHANGE_MAX_CONNS: "3",
ZERO_NUM_SYNC_WORKERS: "0",
},
logging: {
retention: "1 month",
},
loadBalancer: {
public: false,
ports: [
{
listen: "80/http",
forward: "4849/http",
},
],
},
transform: {
loadBalancer: {
idleTimeout: 3600,
},
service: {
healthCheckGracePeriodSeconds: 900,
},
},
}) : undefined;
// Permissions deployment
// const permissions = new sst.aws.Function(
// "ZeroPermissions",
// {
// vpc,
// link: [postgres],
// handler: "packages/functions/src/zero.handler",
// // environment: { ["ZERO_UPSTREAM_DB"]: connectionString },
// copyFiles: [{
// from: "packages/zero/permissions.sql",
// to: "./.permissions.sql"
// }],
// }
// );
// if (replicationManager) {
// new aws.lambda.Invocation(
// "ZeroPermissionsInvocation",
// {
// input: Date.now().toString(),
// functionName: permissions.name,
// },
// { dependsOn: replicationManager }
// );
// // new command.local.Command(
// // "ZeroPermission",
// // {
// // dir: process.cwd() + "/packages/zero",
// // environment: {
// // ZERO_UPSTREAM_DB: connectionString,
// // },
// // create: "bun run zero-deploy-permissions",
// // triggers: [Date.now()],
// // },
// // {
// // dependsOn: [replicationManager],
// // },
// // );
// }
export const zero = new sst.aws.Service("Zero", {
cluster,
image: zeroEnv.ZERO_IMAGE_URL,
link: [zeroStorage, postgres],
architecture: "arm64",
cpu: "0.5 vCPU",
memory: "1 GB",
capacity: "spot",
environment: {
...zeroEnv,
...($dev
? {
ZERO_NUM_SYNC_WORKERS: "1",
}
: {
ZERO_CHANGE_STREAMER_URI: replicationManager.url.apply((val) =>
val.replace("http://", "ws://"),
),
ZERO_UPSTREAM_MAX_CONNS: "15",
ZERO_CVR_MAX_CONNS: "160",
}),
},
health: {
retries: 3,
command: ["CMD-SHELL", "curl -f http://localhost:4848/ || exit 1"],
interval: "5 seconds",
startPeriod: "300 seconds",
},
loadBalancer: {
domain: {
name: "zero." + domain,
dns: sst.cloudflare.dns()
},
rules: [
{ listen: "443/https", forward: "4848/http" },
{ listen: "80/http", forward: "4848/http" },
],
},
scaling: {
min: 1,
max: 4,
},
logging: {
retention: "1 month",
},
transform: {
service: {
healthCheckGracePeriodSeconds: 900,
},
// taskDefinition: {
// ephemeralStorage: {
// sizeInGib: 200,
// },
// },
loadBalancer: {
idleTimeout: 3600,
},
},
dev: {
command: "bun dev",
directory: "packages/zero",
url: "http://localhost:4848",
},
});

34660
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -11,20 +11,18 @@
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
"packageManager": "bun@1.2.4", "packageManager": "bun@1.2.18",
"private": true, "private": true,
"scripts": { "scripts": {
"format": "prettier --write \"**/*.{ts,tsx,md}\"", "format": "prettier --write \"**/*.{ts,tsx,md}\"",
"sso": "aws sso login --sso-session=nestri --no-browser --use-device-code" "sso": "aws sso login --sso-session=nestri --no-browser --use-device-code"
}, },
"overrides": { "overrides": {
"@openauthjs/openauth": "0.4.3", "@openauthjs/openauth": "0.4.3"
"steam-session": "1.9.3"
}, },
"patchedDependencies": { "patchedDependencies": {
"@macaron-css/solid@1.5.3": "patches/@macaron-css%2Fsolid@1.5.3.patch", "@macaron-css/solid@1.5.3": "patches/@macaron-css%2Fsolid@1.5.3.patch",
"drizzle-orm@0.36.1": "patches/drizzle-orm@0.36.1.patch", "drizzle-orm@0.36.1": "patches/drizzle-orm@0.36.1.patch"
"steam-session@1.9.3": "patches/steam-session@1.9.3.patch"
}, },
"trustedDependencies": [ "trustedDependencies": [
"core-js-pure", "core-js-pure",
@@ -37,6 +35,6 @@
"packages/*" "packages/*"
], ],
"dependencies": { "dependencies": {
"sst": "^3.11.21" "sst": "^3.17.8"
} }
} }

View File

@@ -48,7 +48,7 @@ export const handler = bus.subscriber(
await s3.send( await s3.send(
new HeadObjectCommand({ new HeadObjectCommand({
Bucket: Resource.Storage.name, Bucket: Resource.Storage.name,
Key: `public/${image.hash}`, Key: `images/${image.hash}`,
}) })
); );
@@ -57,7 +57,7 @@ export const handler = bus.subscriber(
await s3.send( await s3.send(
new PutObjectCommand({ new PutObjectCommand({
Bucket: Resource.Storage.name, Bucket: Resource.Storage.name,
Key: `public/${image.hash}`, Key: `images/${image.hash}`,
Body: image.buffer, Body: image.buffer,
...(image.format && { ContentType: `image/${image.format}` }), ...(image.format && { ContentType: `image/${image.format}` }),
Metadata: { Metadata: {
@@ -91,7 +91,7 @@ export const handler = bus.subscriber(
await s3.send( await s3.send(
new HeadObjectCommand({ new HeadObjectCommand({
Bucket: Resource.Storage.name, Bucket: Resource.Storage.name,
Key: `public/${image.hash}`, Key: `images/${image.hash}`,
}) })
); );
@@ -100,7 +100,7 @@ export const handler = bus.subscriber(
await s3.send( await s3.send(
new PutObjectCommand({ new PutObjectCommand({
Bucket: Resource.Storage.name, Bucket: Resource.Storage.name,
Key: `public/${image.hash}`, Key: `images/${image.hash}`,
Body: image.buffer, Body: image.buffer,
...(image.format && { ContentType: `image/${image.format}` }), ...(image.format && { ContentType: `image/${image.format}` }),
Metadata: { Metadata: {
@@ -136,7 +136,7 @@ export const handler = bus.subscriber(
await s3.send( await s3.send(
new HeadObjectCommand({ new HeadObjectCommand({
Bucket: Resource.Storage.name, Bucket: Resource.Storage.name,
Key: `public/${image.hash}`, Key: `images/${image.hash}`,
}) })
); );
@@ -145,7 +145,7 @@ export const handler = bus.subscriber(
await s3.send( await s3.send(
new PutObjectCommand({ new PutObjectCommand({
Bucket: Resource.Storage.name, Bucket: Resource.Storage.name,
Key: `public/${image.hash}`, Key: `images/${image.hash}`,
Body: image.buffer, Body: image.buffer,
...(image.format && { ContentType: `image/${image.format}` }), ...(image.format && { ContentType: `image/${image.format}` }),
Metadata: { Metadata: {

View File

@@ -0,0 +1,10 @@
#!/bin/bash
while true; do
case "$1" in
--*) shift ;;
*) break ;;
esac
done
exec "$@"

View File

@@ -13,7 +13,7 @@ log() {
# Ensures user directory ownership # Ensures user directory ownership
chown_user_directory() { chown_user_directory() {
local user_group="${USER}:${GID}" local user_group="${USER}:${GID}"
if ! chown -R -h --no-preserve-root "$user_group" "${HOME}" 2>/dev/null; then if ! chown -h --no-preserve-root "$user_group" "${HOME}" 2>/dev/null; then
echo "Error: Failed to change ownership of ${HOME} to ${user_group}" >&2 echo "Error: Failed to change ownership of ${HOME} to ${user_group}" >&2
return 1 return 1
fi fi
@@ -36,6 +36,12 @@ wait_for_socket() {
return 1 return 1
} }
# Prepares environment for namespace-less applications (like Steam)
setup_namespaceless() {
rm -f /run/systemd/container || true
mkdir -p /run/pressure-vessel || true
}
# Ensures cache directory exists # Ensures cache directory exists
setup_cache() { setup_cache() {
log "Setting up NVIDIA driver cache directory at $CACHE_DIR..." log "Setting up NVIDIA driver cache directory at $CACHE_DIR..."
@@ -103,8 +109,10 @@ get_nvidia_installer() {
install_nvidia_driver() { install_nvidia_driver() {
local filename="$1" local filename="$1"
log "Installing NVIDIA driver components from $filename..." log "Installing NVIDIA driver components from $filename..."
sudo ./"$filename" \ bash ./"$filename" \
--silent \ --silent \
--skip-depmod \
--skip-module-unload \
--no-kernel-module \ --no-kernel-module \
--install-compat32-libs \ --install-compat32-libs \
--no-nouveau-check \ --no-nouveau-check \
@@ -116,18 +124,102 @@ install_nvidia_driver() {
log "Error: NVIDIA driver installation failed." log "Error: NVIDIA driver installation failed."
return 1 return 1
} }
# Install CUDA package
log "Checking if CUDA is already installed"
if ! pacman -Q cuda &>/dev/null; then
log "Installing CUDA package"
pacman -S --noconfirm cuda --assume-installed opencl-nvidia
else
log "CUDA package is already installed, skipping"
fi
log "NVIDIA driver installation completed." log "NVIDIA driver installation completed."
return 0 return 0
} }
function log_gpu_info { log_gpu_info() {
if ! declare -p vendor_devices &>/dev/null; then
log "Warning: vendor_devices array is not defined"
return
fi
log "Detected GPUs:" log "Detected GPUs:"
for vendor in "${!vendor_devices[@]}"; do for vendor in "${!vendor_devices[@]}"; do
log "> $vendor: ${vendor_devices[$vendor]}" log "> $vendor: ${vendor_devices[$vendor]}"
done done
} }
configure_ssh() {
# Return early if SSH not enabled
if [ -z "${SSH_ENABLE_PORT+x}" ] || [ "${SSH_ENABLE_PORT:-0}" -eq 0 ]; then
return 0
fi
# Check if we have required key
if [ -z "${SSH_ALLOWED_KEY+x}" ] || [ -z "${SSH_ALLOWED_KEY}" ]; then
return 0
fi
log "Configuring SSH server on port ${SSH_ENABLE_PORT} with public key authentication"
# Ensure SSH host keys exist
ssh-keygen -A 2>/dev/null || {
log "Error: Failed to generate SSH host keys"
return 1
}
# Create .ssh directory and authorized_keys file for nestri user
mkdir -p /home/nestri/.ssh
echo "${SSH_ALLOWED_KEY}" > /home/nestri/.ssh/authorized_keys
chmod 700 /home/nestri/.ssh
chmod 600 /home/nestri/.ssh/authorized_keys
chown -R nestri:nestri /home/nestri/.ssh
# Update SSHD config
sed -i -E "s/^#?Port .*/Port ${SSH_ENABLE_PORT}/" /etc/ssh/sshd_config || {
log "Error: Failed to update SSH port configuration"
return 1
}
# Configure secure SSH settings
{
echo "PasswordAuthentication no"
echo "PermitRootLogin no"
echo "ChallengeResponseAuthentication no"
echo "UsePAM no"
echo "PubkeyAuthentication yes"
} | while read -r line; do
grep -qF "$line" /etc/ssh/sshd_config || echo "$line" >> /etc/ssh/sshd_config
done
# Start SSH server
log "Starting SSH server on port ${SSH_ENABLE_PORT}"
/usr/sbin/sshd -D -p "${SSH_ENABLE_PORT}" &
SSH_PID=$!
# Verify the process started
if ! ps -p $SSH_PID > /dev/null 2>&1; then
log "Error: SSH server failed to start"
return 1
fi
log "SSH server started with PID ${SSH_PID}"
return 0
}
main() { main() {
# Configure SSH
if [ -n "${SSH_ENABLE_PORT+x}" ] && [ "${SSH_ENABLE_PORT:-0}" -ne 0 ] && \
[ -n "${SSH_ALLOWED_KEY+x}" ] && [ -n "${SSH_ALLOWED_KEY}" ]; then
if ! configure_ssh; then
log "Error: SSH configuration failed with given variables - exiting"
exit 1
fi
else
log "SSH not configured (missing SSH_ENABLE_PORT or SSH_ALLOWED_KEY)"
fi
# Wait for required sockets # Wait for required sockets
wait_for_socket "/run/dbus/system_bus_socket" "DBus" || exit 1 wait_for_socket "/run/dbus/system_bus_socket" "DBus" || exit 1
wait_for_socket "/run/user/${UID}/pipewire-0" "PipeWire" || exit 1 wait_for_socket "/run/user/${UID}/pipewire-0" "PipeWire" || exit 1
@@ -215,6 +307,10 @@ main() {
log "Ensuring user directory permissions..." log "Ensuring user directory permissions..."
chown_user_directory || exit 1 chown_user_directory || exit 1
# Setup namespaceless env
log "Applying namespace-less configuration"
setup_namespaceless
# Switch to nestri user # Switch to nestri user
log "Switching to nestri user for application startup..." log "Switching to nestri user for application startup..."
if [[ ! -x /etc/nestri/entrypoint_nestri.sh ]]; then if [[ ! -x /etc/nestri/entrypoint_nestri.sh ]]; then

View File

@@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
set -euo pipefail
log() { log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
@@ -50,6 +49,70 @@ kill_if_running() {
fi fi
} }
# Starts up Steam namespace-less live-patcher
start_steam_namespaceless_patcher() {
kill_if_running "${PATCHER_PID:-}" "steam-patcher"
local entrypoints=(
"${HOME}/.local/share/Steam/steamrt64/steam-runtime-steamrt/_v2-entry-point"
"${HOME}/.local/share/Steam/steamapps/common/SteamLinuxRuntime_soldier/_v2-entry-point"
"${HOME}/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point"
# < Add more entrypoints here if needed >
)
local custom_entrypoint="/etc/nestri/_v2-entry-point"
local temp_entrypoint="/tmp/_v2-entry-point.padded"
if [[ ! -f "$custom_entrypoint" ]]; then
log "Error: Custom _v2-entry-point not found at $custom_entrypoint"
exit 1
fi
log "Starting Steam _v2-entry-point patcher..."
(
while true; do
for i in "${!entrypoints[@]}"; do
local steam_entrypoint="${entrypoints[$i]}"
if [[ -f "$steam_entrypoint" ]]; then
# Get original file size
local original_size
original_size=$(stat -c %s "$steam_entrypoint" 2>/dev/null)
if [[ -z "$original_size" ]] || [[ "$original_size" -eq 0 ]]; then
log "Warning: Could not determine size of $steam_entrypoint, retrying..."
continue
fi
# Copy custom entrypoint to temp location
cp "$custom_entrypoint" "$temp_entrypoint" 2>/dev/null || {
log "Warning: Failed to copy custom entrypoint to $temp_entrypoint"
continue
}
# Pad the temporary file to match original size
if (( $(stat -c %s "$temp_entrypoint") < original_size )); then
truncate -s "$original_size" "$temp_entrypoint" 2>/dev/null || {
log "Warning: Failed to pad $temp_entrypoint to $original_size bytes"
continue
}
fi
# Copy padded file to Steam's entrypoint, if contents differ
if ! cmp -s "$temp_entrypoint" "$steam_entrypoint"; then
cp "$temp_entrypoint" "$steam_entrypoint" 2>/dev/null || {
log "Warning: Failed to patch $steam_entrypoint"
}
fi
fi
done
# Sleep for 1s
sleep 1
done
) &
PATCHER_PID=$!
log "Steam _v2-entry-point patcher started (PID: $PATCHER_PID)"
}
# Starts nestri-server # Starts nestri-server
start_nestri_server() { start_nestri_server() {
kill_if_running "${NESTRI_PID:-}" "nestri-server" kill_if_running "${NESTRI_PID:-}" "nestri-server"
@@ -71,33 +134,93 @@ start_nestri_server() {
done done
log "Error: Wayland display 'wayland-1' not available." log "Error: Wayland display 'wayland-1' not available."
# Workaround for gstreamer being bit slow at times
log "Clearing gstreamer cache.."
rm -rf "${HOME}/.cache/gstreamer-1.0" 2>/dev/null || true
increment_retry "nestri-server" increment_retry "nestri-server"
restart_chain restart_chain
} }
# Starts compositor (gamescope) with Steam # Starts compositor with optional application
start_compositor() { start_compositor() {
kill_if_running "${COMPOSITOR_PID:-}" "compositor" kill_if_running "${COMPOSITOR_PID:-}" "compositor"
kill_if_running "${APP_PID:-}" "application"
log "Starting compositor with Steam..." # Set default values only if variables are unset (not empty)
rm -rf /tmp/.X11-unix && mkdir -p /tmp/.X11-unix && chown nestri:nestri /tmp/.X11-unix if [[ -z "${NESTRI_LAUNCH_CMD+x}" ]]; then
WAYLAND_DISPLAY=wayland-1 gamescope --backend wayland -g -f -e --rt --mangoapp -W "${WIDTH}" -H "${HEIGHT}" -- steam-native -tenfoot -cef-force-gpu & NESTRI_LAUNCH_CMD="steam-native -tenfoot -cef-force-gpu"
fi
if [[ -z "${NESTRI_LAUNCH_COMPOSITOR+x}" ]]; then
NESTRI_LAUNCH_COMPOSITOR="gamescope --backend wayland --force-grab-cursor -g -f --rt --mangoapp -W ${WIDTH} -H ${HEIGHT} -r ${FRAMERATE:-60}"
fi
# Start Steam patcher only if Steam command is present
if [[ -n "${NESTRI_LAUNCH_CMD}" ]] && [[ "$NESTRI_LAUNCH_CMD" == *"steam"* ]]; then
start_steam_namespaceless_patcher
fi
# Launch compositor if configured
if [[ -n "${NESTRI_LAUNCH_COMPOSITOR}" ]]; then
local compositor_cmd="$NESTRI_LAUNCH_COMPOSITOR"
local is_gamescope=false
# Check if this is a gamescope command
if [[ "$compositor_cmd" == *"gamescope"* ]]; then
is_gamescope=true
# Append application command for gamescope if needed
if [[ -n "$NESTRI_LAUNCH_CMD" ]] && [[ "$compositor_cmd" != *" -- "* ]]; then
# If steam in launch command, enable gamescope integration via -e
if [[ "$NESTRI_LAUNCH_CMD" == *"steam"* ]]; then
compositor_cmd+=" -e"
fi
compositor_cmd+=" -- $NESTRI_LAUNCH_CMD"
fi
fi
log "Starting compositor: $compositor_cmd"
WAYLAND_DISPLAY=wayland-1 /bin/bash -c "$compositor_cmd" &
COMPOSITOR_PID=$! COMPOSITOR_PID=$!
log "Waiting for compositor to initialize..." # Wait for appropriate socket based on compositor type
if $is_gamescope; then
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/gamescope-0" COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/gamescope-0"
log "Waiting for gamescope socket..."
else
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/wayland-0"
log "Waiting for wayland-0 socket..."
fi
for ((i=1; i<=15; i++)); do for ((i=1; i<=15; i++)); do
if [[ -e "$COMPOSITOR_SOCKET" ]]; then if [[ -e "$COMPOSITOR_SOCKET" ]]; then
log "Compositor initialized, gamescope-0 ready." log "Compositor socket ready ($COMPOSITOR_SOCKET)."
sleep 2 # Patch resolution with wlr-randr for non-gamescope compositors
if ! $is_gamescope; then
local OUTPUT_NAME
OUTPUT_NAME=$(WAYLAND_DISPLAY=wayland-0 wlr-randr --json | jq -r '.[] | select(.enabled == true) | .name' | head -n 1)
if [ -z "$OUTPUT_NAME" ]; then
log "Warning: No enabled outputs detected. Skipping wlr-randr resolution patch."
return
fi
WAYLAND_DISPLAY=wayland-0 wlr-randr --output "$OUTPUT_NAME" --custom-mode "$WIDTH"x"$HEIGHT"
log "Patched resolution with wlr-randr."
fi
return return
fi fi
sleep 1 sleep 1
done done
log "Warning: Compositor socket not found after 15 seconds ($COMPOSITOR_SOCKET)."
log "Error: Compositor did not initialize." else
increment_retry "compositor" # Launch standalone application if no compositor
start_compositor if [[ -n "${NESTRI_LAUNCH_CMD}" ]]; then
log "Starting application: $NESTRI_LAUNCH_CMD"
WAYLAND_DISPLAY=wayland-1 /bin/bash -c "$NESTRI_LAUNCH_CMD" &
APP_PID=$!
else
log "No compositor or application configured."
fi
fi
} }
# Increments retry counter # Increments retry counter
@@ -113,21 +236,24 @@ increment_retry() {
# Restarts the chain # Restarts the chain
restart_chain() { restart_chain() {
log "Restarting nestri-server and compositor..." log "Restarting nestri-server and compositor..."
RETRY_COUNT=0
start_nestri_server start_nestri_server
} }
# Cleans up processes # Cleans up processes
cleanup() { cleanup() {
local exit_code=$?
log "Terminating processes..." log "Terminating processes..."
kill_if_running "${NESTRI_PID:-}" "nestri-server" kill_if_running "${NESTRI_PID:-}" "nestri-server"
kill_if_running "${COMPOSITOR_PID:-}" "compositor" kill_if_running "${COMPOSITOR_PID:-}" "compositor"
exit 0 kill_if_running "${APP_PID:-}" "application"
kill_if_running "${PATCHER_PID:-}" "steam-patcher"
rm -f "/tmp/_v2-entry-point.padded" 2>/dev/null
exit $exit_code
} }
# Monitor processes for unexpected exits # Monitor processes for unexpected exits
main_loop() { main_loop() {
trap cleanup SIGINT SIGTERM trap cleanup SIGINT SIGTERM EXIT
while true; do while true; do
sleep 1 sleep 1
@@ -141,6 +267,16 @@ main_loop() {
log "compositor died." log "compositor died."
increment_retry "compositor" increment_retry "compositor"
start_compositor start_compositor
# Check application
elif [[ -n "${APP_PID:-}" ]] && ! kill -0 "${APP_PID}" 2>/dev/null; then
log "application died."
increment_retry "application"
start_compositor
# Check patcher
elif [[ -n "${PATCHER_PID:-}" ]] && ! kill -0 "${PATCHER_PID}" 2>/dev/null; then
log "steam-patcher died."
increment_retry "steam-patcher"
start_steam_namespaceless_patcher
fi fi
done done
} }

View File

@@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
set -euo pipefail
export XDG_RUNTIME_DIR=/run/user/${UID}/ export XDG_RUNTIME_DIR=/run/user/${UID}/
export XDG_SESSION_TYPE=x11 export XDG_SESSION_TYPE=x11
@@ -11,3 +10,7 @@ export PROTON_NO_FSYNC=1
# Sleeker Mangohud preset :) # Sleeker Mangohud preset :)
export MANGOHUD_CONFIG=preset=2 export MANGOHUD_CONFIG=preset=2
# Make gstreamer GL elements work without display output (NVIDIA issue..)
export GST_GL_API=gles2
export GST_GL_WINDOW=surfaceless

View File

@@ -54,3 +54,6 @@ autorestart=false
autostart=true autostart=true
startretries=0 startretries=0
priority=10 priority=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true

File diff suppressed because it is too large Load Diff

View File

@@ -8,25 +8,26 @@ name = "nestri-server"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main", features = ["v1_26"] } gstreamer = { version = "0.23", features = ["v1_26"] }
gst-webrtc = { package = "gstreamer-webrtc", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main", features = ["v1_26"] } gstreamer-webrtc = { version = "0.23", features = ["v1_26"] }
gstrswebrtc = { package = "gst-plugin-webrtc", git = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs", branch = "main" } gst-plugin-webrtc = { version = "0.13", features = ["v1_22"] }
serde = {version = "1.0", features = ["derive"] } serde = {version = "1.0", features = ["derive"] }
tokio = { version = "1.44", features = ["full"] } tokio = { version = "1.45", features = ["full"] }
clap = { version = "4.5", features = ["env"] } tokio-stream = { version = "0.1", features = ["full"] }
clap = { version = "4.5", features = ["env", "derive"] }
serde_json = "1.0" serde_json = "1.0"
webrtc = "0.13" webrtc = "0.13"
regex = "1.11" regex = "1.11"
rand = "0.9" rand = "0.9"
rustls = { version = "0.23", features = ["ring"] } rustls = { version = "0.23", features = ["ring"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = { version = "0.3", features = ["env-filter"] }
chrono = "0.4" chrono = "0.4"
futures-util = "0.3" prost = "0.14"
prost = "0.13" prost-types = "0.14"
prost-types = "0.13"
parking_lot = "0.12" parking_lot = "0.12"
atomic_refcell = "0.1" atomic_refcell = "0.1"
byteorder = "1.5" byteorder = "1.5"
libp2p = { version = "0.55", features = ["identify", "dns", "tcp", "noise", "ping", "tokio", "serde", "yamux", "macros"] } libp2p = { version = "0.55", features = ["identify", "dns", "tcp", "noise", "ping", "tokio", "serde", "yamux", "macros", "websocket", "autonat"] }
libp2p-stream = "0.3.0-alpha" libp2p-stream = { version = "0.3.0-alpha" }
dashmap = "6.1"

View File

@@ -1,7 +1,7 @@
use crate::args::encoding_args::AudioCaptureMethod; use crate::args::encoding_args::AudioCaptureMethod;
use crate::enc_helper::{AudioCodec, EncoderType, VideoCodec}; use crate::enc_helper::{AudioCodec, EncoderType, VideoCodec};
use clap::{Arg, Command, value_parser};
use clap::builder::{BoolishValueParser, NonEmptyStringValueParser}; use clap::builder::{BoolishValueParser, NonEmptyStringValueParser};
use clap::{Arg, Command, value_parser};
pub mod app_args; pub mod app_args;
pub mod device_args; pub mod device_args;
@@ -89,7 +89,7 @@ impl Args {
.env("GPU_INDEX") .env("GPU_INDEX")
.help("GPU to use by index") .help("GPU to use by index")
.value_parser(value_parser!(i32).range(-1..)) .value_parser(value_parser!(i32).range(-1..))
.default_value("-1") .default_value("-1"),
) )
.arg( .arg(
Arg::new("gpu-card-path") Arg::new("gpu-card-path")
@@ -160,7 +160,7 @@ impl Args {
.env("AUDIO_CAPTURE_METHOD") .env("AUDIO_CAPTURE_METHOD")
.help("Audio capture method") .help("Audio capture method")
.value_parser(value_parser!(AudioCaptureMethod)) .value_parser(value_parser!(AudioCaptureMethod))
.default_value("pipewire"), .default_value("pulseaudio"),
) )
.arg( .arg(
Arg::new("audio-codec") Arg::new("audio-codec")

View File

@@ -55,7 +55,11 @@ impl AppArgs {
tracing::info!("AppArgs:"); tracing::info!("AppArgs:");
tracing::info!("> verbose: {}", self.verbose); tracing::info!("> verbose: {}", self.verbose);
tracing::info!("> debug: {}", self.debug); tracing::info!("> debug: {}", self.debug);
tracing::info!("> resolution: '{}x{}'", self.resolution.0, self.resolution.1); tracing::info!(
"> resolution: '{}x{}'",
self.resolution.0,
self.resolution.1
);
tracing::info!("> framerate: {}", self.framerate); tracing::info!("> framerate: {}", self.framerate);
tracing::info!("> relay_url: '{}'", self.relay_url); tracing::info!("> relay_url: '{}'", self.relay_url);
tracing::info!("> room: '{}'", self.room); tracing::info!("> room: '{}'", self.room);

View File

@@ -19,10 +19,7 @@ impl DeviceArgs {
.get_one::<String>("gpu-name") .get_one::<String>("gpu-name")
.unwrap_or(&"".to_string()) .unwrap_or(&"".to_string())
.clone(), .clone(),
gpu_index: matches gpu_index: matches.get_one::<i32>("gpu-index").unwrap_or(&-1).clone(),
.get_one::<i32>("gpu-index")
.unwrap_or(&-1)
.clone(),
gpu_card_path: matches gpu_card_path: matches
.get_one::<String>("gpu-card-path") .get_one::<String>("gpu-card-path")
.unwrap_or(&"".to_string()) .unwrap_or(&"".to_string())

View File

@@ -120,20 +120,11 @@ impl VideoEncodingOptions {
.unwrap(), .unwrap(),
}), }),
RateControlMethod::CBR => RateControl::CBR(RateControlCBR { RateControlMethod::CBR => RateControl::CBR(RateControlCBR {
target_bitrate: matches target_bitrate: matches.get_one::<u32>("video-bitrate").unwrap().clone(),
.get_one::<u32>("video-bitrate")
.unwrap()
.clone(),
}), }),
RateControlMethod::VBR => RateControl::VBR(RateControlVBR { RateControlMethod::VBR => RateControl::VBR(RateControlVBR {
target_bitrate: matches target_bitrate: matches.get_one::<u32>("video-bitrate").unwrap().clone(),
.get_one::<u32>("video-bitrate") max_bitrate: matches.get_one::<u32>("video-bitrate-max").unwrap().clone(),
.unwrap()
.clone(),
max_bitrate: matches
.get_one::<u32>("video-bitrate-max")
.unwrap()
.clone(),
}), }),
}, },
}, },
@@ -209,20 +200,11 @@ impl AudioEncodingOptions {
.unwrap_or(&RateControlMethod::CBR) .unwrap_or(&RateControlMethod::CBR)
{ {
RateControlMethod::CBR => RateControl::CBR(RateControlCBR { RateControlMethod::CBR => RateControl::CBR(RateControlCBR {
target_bitrate: matches target_bitrate: matches.get_one::<u32>("audio-bitrate").unwrap().clone(),
.get_one::<u32>("audio-bitrate")
.unwrap()
.clone(),
}), }),
RateControlMethod::VBR => RateControl::VBR(RateControlVBR { RateControlMethod::VBR => RateControl::VBR(RateControlVBR {
target_bitrate: matches target_bitrate: matches.get_one::<u32>("audio-bitrate").unwrap().clone(),
.get_one::<u32>("audio-bitrate") max_bitrate: matches.get_one::<u32>("audio-bitrate-max").unwrap().clone(),
.unwrap()
.clone(),
max_bitrate: matches
.get_one::<u32>("audio-bitrate-max")
.unwrap()
.clone(),
}), }),
wot => panic!("Invalid rate control method for audio: {}", wot.as_str()), wot => panic!("Invalid rate control method for audio: {}", wot.as_str()),
}, },

View File

@@ -1,7 +1,7 @@
use crate::args::encoding_args::RateControl; use crate::args::encoding_args::RateControl;
use crate::gpu::{self, GPUInfo, get_gpu_by_card_path, get_gpus_by_vendor}; use crate::gpu::{GPUInfo, get_gpu_by_card_path, get_gpus_by_vendor, get_nvidia_gpu_by_cuda_id};
use clap::ValueEnum; use clap::ValueEnum;
use gst::prelude::*; use gstreamer::prelude::*;
use std::error::Error; use std::error::Error;
use std::str::FromStr; use std::str::FromStr;
@@ -107,7 +107,7 @@ impl EncoderType {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct VideoEncoderInfo { pub struct VideoEncoderInfo {
pub name: String, pub name: String,
pub codec: VideoCodec, pub codec: VideoCodec,
@@ -146,9 +146,9 @@ impl VideoEncoderInfo {
self.parameters.push((key.into(), value.into())); self.parameters.push((key.into(), value.into()));
} }
pub fn apply_parameters(&self, element: &gst::Element, verbose: bool) { pub fn apply_parameters(&self, element: &gstreamer::Element, verbose: bool) {
for (key, value) in &self.parameters { for (key, value) in &self.parameters {
if element.has_property(key) { if element.has_property(key, None) {
if verbose { if verbose {
tracing::debug!("Setting property {} to {}", key, value); tracing::debug!("Setting property {} to {}", key, value);
} }
@@ -191,7 +191,7 @@ where
F: FnMut(&str) -> Option<(String, String)>, F: FnMut(&str) -> Option<(String, String)>,
{ {
let mut encoder_optz = encoder.clone(); let mut encoder_optz = encoder.clone();
let element = match gst::ElementFactory::make(&encoder_optz.name).build() { let element = match gstreamer::ElementFactory::make(&encoder_optz.name).build() {
Ok(e) => e, Ok(e) => e,
Err(_) => return encoder_optz, // Return original if element creation fails Err(_) => return encoder_optz, // Return original if element creation fails
}; };
@@ -329,16 +329,15 @@ pub fn encoder_low_latency_params(
encoder_optz encoder_optz
} }
pub fn get_compatible_encoders() -> Vec<VideoEncoderInfo> { pub fn get_compatible_encoders(gpus: &Vec<GPUInfo>) -> Vec<VideoEncoderInfo> {
let mut encoders = Vec::new(); let mut encoders = Vec::new();
let registry = gst::Registry::get(); let registry = gstreamer::Registry::get();
let gpus = gpu::get_gpus();
for plugin in registry.plugins() { for plugin in registry.plugins() {
for feature in registry.features_by_plugin(plugin.plugin_name().as_str()) { for feature in registry.features_by_plugin(plugin.plugin_name().as_str()) {
let encoder_name = feature.name(); let encoder_name = feature.name();
let factory = match gst::ElementFactory::find(encoder_name.as_str()) { let factory = match gstreamer::ElementFactory::find(encoder_name.as_str()) {
Some(f) => f, Some(f) => f,
None => continue, None => continue,
}; };
@@ -376,9 +375,9 @@ pub fn get_compatible_encoders() -> Vec<VideoEncoderInfo> {
match api { match api {
EncoderAPI::QSV | EncoderAPI::VAAPI => { EncoderAPI::QSV | EncoderAPI::VAAPI => {
// Safe property access with panic protection, gstreamer-rs is fun // Safe property access with panic protection, gstreamer-rs is fun
let path = if element.has_property("device-path") { let path = if element.has_property("device-path", None) {
Some(element.property::<String>("device-path")) Some(element.property::<String>("device-path"))
} else if element.has_property("device") { } else if element.has_property("device", None) {
Some(element.property::<String>("device")) Some(element.property::<String>("device"))
} else { } else {
None None
@@ -386,13 +385,11 @@ pub fn get_compatible_encoders() -> Vec<VideoEncoderInfo> {
path.and_then(|p| get_gpu_by_card_path(&gpus, &p)) path.and_then(|p| get_gpu_by_card_path(&gpus, &p))
} }
EncoderAPI::NVENC if element.has_property("cuda-device-id") => { EncoderAPI::NVENC if element.has_property("cuda-device-id", None) => {
let cuda_id = element.property::<u32>("cuda-device-id"); let cuda_id = element.property::<u32>("cuda-device-id");
get_gpus_by_vendor(&gpus, "nvidia") get_nvidia_gpu_by_cuda_id(&gpus, cuda_id as usize)
.get(cuda_id as usize)
.cloned()
} }
EncoderAPI::AMF if element.has_property("device") => { EncoderAPI::AMF if element.has_property("device", None) => {
let device_id = element.property::<u32>("device"); let device_id = element.property::<u32>("device");
get_gpus_by_vendor(&gpus, "amd") get_gpus_by_vendor(&gpus, "amd")
.get(device_id as usize) .get(device_id as usize)
@@ -540,3 +537,140 @@ pub fn get_best_compatible_encoder(
Err("No compatible encoder found".into()) Err("No compatible encoder found".into())
} }
} }
/// Returns the best compatible encoder that also passes test_encoder
pub fn get_best_working_encoder(
encoders: &Vec<VideoEncoderInfo>,
codec: &Codec,
encoder_type: &EncoderType,
dma_buf: bool,
) -> Result<VideoEncoderInfo, Box<dyn Error>> {
let mut candidates = get_encoders_by_videocodec(
encoders,
match codec {
Codec::Video(c) => c,
Codec::Audio(_) => {
return Err("Audio codec not supported for video encoder selection".into());
}
},
);
candidates = get_encoders_by_type(&candidates, encoder_type);
let mut tried = Vec::new();
while !candidates.is_empty() {
let best = get_best_compatible_encoder(&candidates, codec, encoder_type)?;
tracing::info!("Testing encoder: {}", best.name,);
if test_encoder(&best, dma_buf).is_ok() {
return Ok(best);
} else {
// Remove this encoder and try next best
candidates.retain(|e| e != &best);
tried.push(best.name.clone());
}
}
Err(format!("No working encoder found (tried: {:?})", tried).into())
}
/// Test if a pipeline with the given encoder can be created and set to Playing
pub fn test_encoder(encoder: &VideoEncoderInfo, dma_buf: bool) -> Result<(), Box<dyn Error>> {
let src = gstreamer::ElementFactory::make("waylanddisplaysrc").build()?;
if let Some(gpu_info) = &encoder.gpu_info {
src.set_property_from_str("render-node", gpu_info.render_path());
}
let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
let caps = gstreamer::Caps::from_str(&format!(
"{},width=1280,height=720,framerate=30/1{}",
if dma_buf {
"video/x-raw(memory:DMABuf)"
} else {
"video/x-raw"
},
if dma_buf { "" } else { ",format=RGBx" }
))?;
caps_filter.set_property("caps", &caps);
let enc = gstreamer::ElementFactory::make(&encoder.name).build()?;
let sink = gstreamer::ElementFactory::make("fakesink").build()?;
// Apply encoder parameters
encoder.apply_parameters(&enc, false);
// Create pipeline and link elements
let pipeline = gstreamer::Pipeline::new();
if dma_buf && encoder.encoder_api == EncoderAPI::NVENC {
// GL upload element
let glupload = gstreamer::ElementFactory::make("glupload").build()?;
// GL color convert element
let glconvert = gstreamer::ElementFactory::make("glcolorconvert").build()?;
// GL color convert caps
let gl_caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
let gl_caps = gstreamer::Caps::from_str("video/x-raw(memory:GLMemory),format=NV12")?;
gl_caps_filter.set_property("caps", &gl_caps);
// CUDA upload element
let cudaupload = gstreamer::ElementFactory::make("cudaupload").build()?;
pipeline.add_many(&[
&src,
&caps_filter,
&glupload,
&glconvert,
&gl_caps_filter,
&cudaupload,
&enc,
&sink,
])?;
gstreamer::Element::link_many(&[
&src,
&caps_filter,
&glupload,
&glconvert,
&gl_caps_filter,
&cudaupload,
&enc,
&sink,
])?;
} else {
let vapostproc = gstreamer::ElementFactory::make("vapostproc").build()?;
// VA caps filter
let va_caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
let va_caps = gstreamer::Caps::from_str("video/x-raw(memory:VAMemory),format=NV12")?;
va_caps_filter.set_property("caps", &va_caps);
pipeline.add_many(&[
&src,
&caps_filter,
&vapostproc,
&va_caps_filter,
&enc,
&sink,
])?;
gstreamer::Element::link_many(&[
&src,
&caps_filter,
&vapostproc,
&va_caps_filter,
&enc,
&sink,
])?;
}
let bus = pipeline.bus().ok_or("Pipeline has no bus")?;
let _ = pipeline.set_state(gstreamer::State::Playing);
for msg in bus.iter_timed(gstreamer::ClockTime::from_seconds(2)) {
match msg.view() {
gstreamer::MessageView::Error(err) => {
let err_msg = format!("Pipeline error: {}", err.error());
tracing::error!("Pipeline error, encoder test failed: {}", err_msg);
let _ = pipeline.set_state(gstreamer::State::Null);
return Err(err_msg.into());
}
gstreamer::MessageView::Eos(_) => {
tracing::info!("Pipeline EOS received");
let _ = pipeline.set_state(gstreamer::State::Null);
return Err("Pipeline EOS received, encoder test failed".into());
}
_ => {}
}
}
let _ = pipeline.set_state(gstreamer::State::Null);
Ok(())
}

View File

@@ -3,7 +3,7 @@ use std::fs;
use std::process::Command; use std::process::Command;
use std::str; use std::str;
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum GPUVendor { pub enum GPUVendor {
UNKNOWN, UNKNOWN,
INTEL, INTEL,
@@ -11,12 +11,13 @@ pub enum GPUVendor {
AMD, AMD,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct GPUInfo { pub struct GPUInfo {
vendor: GPUVendor, vendor: GPUVendor,
card_path: String, card_path: String,
render_path: String, render_path: String,
device_name: String, device_name: String,
pci_bus_id: String,
} }
impl GPUInfo { impl GPUInfo {
@@ -44,6 +45,10 @@ impl GPUInfo {
pub fn device_name(&self) -> &str { pub fn device_name(&self) -> &str {
&self.device_name &self.device_name
} }
pub fn pci_bus_id(&self) -> &str {
&self.pci_bus_id
}
} }
fn get_gpu_vendor(vendor_id: &str) -> GPUVendor { fn get_gpu_vendor(vendor_id: &str) -> GPUVendor {
@@ -71,14 +76,17 @@ pub fn get_gpus() -> Vec<GPUInfo> {
.filter(|(class_id, _, _, _)| matches!(class_id.as_str(), "0300" | "0302" | "0380")) .filter(|(class_id, _, _, _)| matches!(class_id.as_str(), "0300" | "0302" | "0380"))
.filter_map(|(_, vendor_id, device_name, pci_addr)| { .filter_map(|(_, vendor_id, device_name, pci_addr)| {
get_dri_device_path(&pci_addr) get_dri_device_path(&pci_addr)
.map(|(card, render)| (vendor_id, card, render, device_name)) .map(|(card, render)| (vendor_id, card, render, device_name, pci_addr))
}) })
.map(|(vid, card_path, render_path, device_name)| GPUInfo { .map(
|(vid, card_path, render_path, device_name, pci_bus_id)| GPUInfo {
vendor: get_gpu_vendor(&vid), vendor: get_gpu_vendor(&vid),
card_path, card_path,
render_path, render_path,
device_name, device_name,
}) pci_bus_id,
},
)
.collect() .collect()
} }
@@ -137,7 +145,6 @@ fn get_dri_device_path(pci_addr: &str) -> Option<(String, String)> {
None None
} }
// Helper functions remain similar with improved readability:
pub fn get_gpus_by_vendor(gpus: &[GPUInfo], vendor: &str) -> Vec<GPUInfo> { pub fn get_gpus_by_vendor(gpus: &[GPUInfo], vendor: &str) -> Vec<GPUInfo> {
let target = vendor.to_lowercase(); let target = vendor.to_lowercase();
gpus.iter() gpus.iter()
@@ -162,10 +169,42 @@ pub fn get_gpu_by_card_path(gpus: &[GPUInfo], path: &str) -> Option<GPUInfo> {
.cloned() .cloned()
} }
pub fn get_gpu_by_index(gpus: &[GPUInfo], index: i32) -> Option<GPUInfo> { pub fn get_nvidia_gpu_by_cuda_id(gpus: &[GPUInfo], cuda_device_id: usize) -> Option<GPUInfo> {
if index < 0 || index as usize >= gpus.len() { // Check if nvidia-smi is available
None if Command::new("nvidia-smi").arg("--help").output().is_err() {
tracing::warn!("nvidia-smi is not available");
return None;
}
// Run nvidia-smi to get information about the CUDA device
let output = Command::new("nvidia-smi")
.args([
"--query-gpu=pci.bus_id",
"--format=csv,noheader",
"-i",
&cuda_device_id.to_string(),
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
// Parse the output to get the PCI bus ID
let pci_bus_id = str::from_utf8(&output.stdout).ok()?.trim().to_uppercase(); // nvidia-smi returns uppercase PCI IDs
// Convert from 00000000:05:00.0 to 05:00.0 if needed
let pci_bus_id = if pci_bus_id.starts_with("00000000:") {
pci_bus_id[9..].to_string() // Skip the domain part
} else if pci_bus_id.starts_with("0000:") {
pci_bus_id[5..].to_string() // Alternate check for older nvidia-smi versions
} else { } else {
Some(gpus[index as usize].clone()) pci_bus_id
} };
// Find the GPU with the matching PCI bus ID
gpus.iter()
.find(|gpu| gpu.vendor == GPUVendor::NVIDIA && gpu.pci_bus_id.to_uppercase() == pci_bus_id)
.cloned()
} }

View File

@@ -8,24 +8,24 @@ mod p2p;
mod proto; mod proto;
use crate::args::encoding_args; use crate::args::encoding_args;
use crate::enc_helper::EncoderType; use crate::enc_helper::{EncoderAPI, EncoderType};
use crate::gpu::GPUVendor; use crate::gpu::{GPUInfo, GPUVendor};
use crate::nestrisink::NestriSignaller; use crate::nestrisink::NestriSignaller;
use crate::p2p::p2p::NestriP2P; use crate::p2p::p2p::NestriP2P;
use futures_util::StreamExt; use gstreamer::prelude::*;
use gst::prelude::*;
use gstrswebrtc::signaller::Signallable; use gstrswebrtc::signaller::Signallable;
use gstrswebrtc::webrtcsink::BaseWebRTCSink; use gstrswebrtc::webrtcsink::BaseWebRTCSink;
use std::error::Error; use std::error::Error;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use tokio_stream::StreamExt;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::filter::LevelFilter;
// Handles gathering GPU information and selecting the most suitable GPU // Handles gathering GPU information and selecting the most suitable GPU
fn handle_gpus(args: &args::Args) -> Result<gpu::GPUInfo, Box<dyn Error>> { fn handle_gpus(args: &args::Args) -> Result<Vec<gpu::GPUInfo>, Box<dyn Error>> {
tracing::info!("Gathering GPU information.."); tracing::info!("Gathering GPU information..");
let gpus = gpu::get_gpus(); let mut gpus = gpu::get_gpus();
if gpus.is_empty() { if gpus.is_empty() {
return Err("No GPUs found".into()); return Err("No GPUs found".into());
} }
@@ -40,10 +40,11 @@ fn handle_gpus(args: &args::Args) -> Result<gpu::GPUInfo, Box<dyn Error>> {
); );
} }
// Based on available arguments, pick a GPU // Additional GPU filtering
let gpu;
if !args.device.gpu_card_path.is_empty() { if !args.device.gpu_card_path.is_empty() {
gpu = gpu::get_gpu_by_card_path(&gpus, &args.device.gpu_card_path); if let Some(gpu) = gpu::get_gpu_by_card_path(&gpus, &args.device.gpu_card_path) {
return Ok(Vec::from([gpu]));
}
} else { } else {
// Run all filters that are not empty // Run all filters that are not empty
let mut filtered_gpus = gpus.clone(); let mut filtered_gpus = gpus.clone();
@@ -55,35 +56,43 @@ fn handle_gpus(args: &args::Args) -> Result<gpu::GPUInfo, Box<dyn Error>> {
} }
if args.device.gpu_index > -1 { if args.device.gpu_index > -1 {
// get single GPU by index // get single GPU by index
gpu = gpu::get_gpu_by_index(&filtered_gpus, args.device.gpu_index).or_else(|| { let gpu_index = args.device.gpu_index as usize;
tracing::warn!("GPU index {} is out of range", args.device.gpu_index); if gpu_index >= filtered_gpus.len() {
None
});
} else {
// get first GPU
gpu = filtered_gpus
.into_iter()
.find(|g| *g.vendor() != GPUVendor::UNKNOWN);
}
}
if gpu.is_none() {
return Err(format!( return Err(format!(
"No GPU found with the specified parameters: vendor='{}', name='{}', index='{}', card_path='{}'", "GPU index {} is out of bounds for available GPUs (0-{})",
gpu_index,
filtered_gpus.len() - 1
)
.into());
}
gpus = Vec::from([filtered_gpus[gpu_index].clone()]);
} else {
// Filter out unknown vendor GPUs
gpus = filtered_gpus
.into_iter()
.filter(|gpu| *gpu.vendor() != GPUVendor::UNKNOWN)
.collect();
}
}
if gpus.is_empty() {
return Err(format!(
"No GPU(s) found with the specified parameters: vendor='{}', name='{}', index='{}', card_path='{}'",
args.device.gpu_vendor, args.device.gpu_vendor,
args.device.gpu_name, args.device.gpu_name,
args.device.gpu_index, args.device.gpu_index,
args.device.gpu_card_path args.device.gpu_card_path
).into()); ).into());
} }
let gpu = gpu.unwrap(); Ok(gpus)
tracing::info!("Selected GPU: '{}'", gpu.device_name());
Ok(gpu)
} }
// Handles picking video encoder // Handles picking video encoder
fn handle_encoder_video(args: &args::Args) -> Result<enc_helper::VideoEncoderInfo, Box<dyn Error>> { fn handle_encoder_video(
args: &args::Args,
gpus: &Vec<GPUInfo>,
) -> Result<enc_helper::VideoEncoderInfo, Box<dyn Error>> {
tracing::info!("Getting compatible video encoders.."); tracing::info!("Getting compatible video encoders..");
let video_encoders = enc_helper::get_compatible_encoders(); let video_encoders = enc_helper::get_compatible_encoders(gpus);
if video_encoders.is_empty() { if video_encoders.is_empty() {
return Err("No compatible video encoders found".into()); return Err("No compatible video encoders found".into());
} }
@@ -107,10 +116,11 @@ fn handle_encoder_video(args: &args::Args) -> Result<enc_helper::VideoEncoderInf
video_encoder = video_encoder =
enc_helper::get_encoder_by_name(&video_encoders, &args.encoding.video.encoder)?; enc_helper::get_encoder_by_name(&video_encoders, &args.encoding.video.encoder)?;
} else { } else {
video_encoder = enc_helper::get_best_compatible_encoder( video_encoder = enc_helper::get_best_working_encoder(
&video_encoders, &video_encoders,
&args.encoding.video.codec, &args.encoding.video.codec,
&args.encoding.video.encoder_type, &args.encoding.video.encoder_type,
args.app.dma_buf,
)?; )?;
} }
tracing::info!("Selected video encoder: '{}'", video_encoder.name); tracing::info!("Selected video encoder: '{}'", video_encoder.name);
@@ -191,17 +201,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
let nestri_p2p = Arc::new(NestriP2P::new().await?); let nestri_p2p = Arc::new(NestriP2P::new().await?);
let p2p_conn = nestri_p2p.connect(relay_url).await?; let p2p_conn = nestri_p2p.connect(relay_url).await?;
gst::init()?; gstreamer::init()?;
gstrswebrtc::plugin_register_static()?; let _ = gstrswebrtc::plugin_register_static(); // Might be already registered, so we'll pass..
// Handle GPU selection
let gpu = match handle_gpus(&args) {
Ok(gpu) => gpu,
Err(e) => {
tracing::error!("Failed to find a suitable GPU: {}", e);
return Err(e);
}
};
if args.app.dma_buf { if args.app.dma_buf {
if args.encoding.video.encoder_type != EncoderType::HARDWARE { if args.encoding.video.encoder_type != EncoderType::HARDWARE {
@@ -214,8 +215,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
} }
} }
// Handle GPU selection
let gpus = match handle_gpus(&args) {
Ok(gpu) => gpu,
Err(e) => {
tracing::error!("Failed to find a suitable GPU: {}", e);
return Err(e);
}
};
// Handle video encoder selection // Handle video encoder selection
let mut video_encoder_info = match handle_encoder_video(&args) { let mut video_encoder_info = match handle_encoder_video(&args, &gpus) {
Ok(encoder) => encoder, Ok(encoder) => encoder,
Err(e) => { Err(e) => {
tracing::error!("Failed to find a suitable video encoder: {}", e); tracing::error!("Failed to find a suitable video encoder: {}", e);
@@ -231,33 +241,35 @@ async fn main() -> Result<(), Box<dyn Error>> {
/*** PIPELINE CREATION ***/ /*** PIPELINE CREATION ***/
// Create the pipeline // Create the pipeline
let pipeline = Arc::new(gst::Pipeline::new()); let pipeline = Arc::new(gstreamer::Pipeline::new());
/* Audio */ /* Audio */
// Audio Source Element // Audio Source Element
let audio_source = match args.encoding.audio.capture_method { let audio_source = match args.encoding.audio.capture_method {
encoding_args::AudioCaptureMethod::PULSEAUDIO => { encoding_args::AudioCaptureMethod::PULSEAUDIO => {
gst::ElementFactory::make("pulsesrc").build()? gstreamer::ElementFactory::make("pulsesrc").build()?
} }
encoding_args::AudioCaptureMethod::PIPEWIRE => { encoding_args::AudioCaptureMethod::PIPEWIRE => {
gst::ElementFactory::make("pipewiresrc").build()? gstreamer::ElementFactory::make("pipewiresrc").build()?
}
encoding_args::AudioCaptureMethod::ALSA => {
gstreamer::ElementFactory::make("alsasrc").build()?
} }
encoding_args::AudioCaptureMethod::ALSA => gst::ElementFactory::make("alsasrc").build()?,
}; };
// Audio Converter Element // Audio Converter Element
let audio_converter = gst::ElementFactory::make("audioconvert").build()?; let audio_converter = gstreamer::ElementFactory::make("audioconvert").build()?;
// Audio Rate Element // Audio Rate Element
let audio_rate = gst::ElementFactory::make("audiorate").build()?; let audio_rate = gstreamer::ElementFactory::make("audiorate").build()?;
// Required to fix gstreamer opus issue, where quality sounds off (due to wrong sample rate) // Required to fix gstreamer opus issue, where quality sounds off (due to wrong sample rate)
let audio_capsfilter = gst::ElementFactory::make("capsfilter").build()?; let audio_capsfilter = gstreamer::ElementFactory::make("capsfilter").build()?;
let audio_caps = gst::Caps::from_str("audio/x-raw,rate=48000,channels=2").unwrap(); let audio_caps = gstreamer::Caps::from_str("audio/x-raw,rate=48000,channels=2").unwrap();
audio_capsfilter.set_property("caps", &audio_caps); audio_capsfilter.set_property("caps", &audio_caps);
// Audio Encoder Element // Audio Encoder Element
let audio_encoder = gst::ElementFactory::make(audio_encoder.as_str()).build()?; let audio_encoder = gstreamer::ElementFactory::make(audio_encoder.as_str()).build()?;
audio_encoder.set_property( audio_encoder.set_property(
"bitrate", "bitrate",
&match &args.encoding.audio.rate_control { &match &args.encoding.audio.rate_control {
@@ -267,18 +279,27 @@ async fn main() -> Result<(), Box<dyn Error>> {
}, },
); );
// If has "frame-size" (opus), set to 10 for lower latency (below 10 seems to be too low?) // If has "frame-size" (opus), set to 10 for lower latency (below 10 seems to be too low?)
if audio_encoder.has_property("frame-size") { if audio_encoder.has_property("frame-size", None) {
audio_encoder.set_property_from_str("frame-size", "10"); audio_encoder.set_property_from_str("frame-size", "10");
} }
// Audio parse Element
let mut audio_parser = None;
if audio_encoder.name() == "opusenc" {
// Opus encoder requires a parser
audio_parser = Some(gstreamer::ElementFactory::make("opusparse").build()?);
}
/* Video */ /* Video */
// Video Source Element // Video Source Element
let video_source = Arc::new(gst::ElementFactory::make("waylanddisplaysrc").build()?); let video_source = Arc::new(gstreamer::ElementFactory::make("waylanddisplaysrc").build()?);
video_source.set_property_from_str("render-node", gpu.render_path()); if let Some(gpu_info) = &video_encoder_info.gpu_info {
video_source.set_property_from_str("render-node", gpu_info.render_path());
}
// Caps Filter Element (resolution, fps) // Caps Filter Element (resolution, fps)
let caps_filter = gst::ElementFactory::make("capsfilter").build()?; let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
let caps = gst::Caps::from_str(&format!( let caps = gstreamer::Caps::from_str(&format!(
"{},width={},height={},framerate={}/1{}", "{},width={},height={},framerate={}/1{}",
if args.app.dma_buf { if args.app.dma_buf {
"video/x-raw(memory:DMABuf)" "video/x-raw(memory:DMABuf)"
@@ -292,38 +313,71 @@ async fn main() -> Result<(), Box<dyn Error>> {
))?; ))?;
caps_filter.set_property("caps", &caps); caps_filter.set_property("caps", &caps);
// GL Upload element // GL and CUDA elements (NVIDIA only..)
let glupload = gst::ElementFactory::make("glupload").build()?; let mut glupload = None;
let mut glconvert = None;
let mut gl_caps_filter = None;
let mut cudaupload = None;
if args.app.dma_buf && video_encoder_info.encoder_api == EncoderAPI::NVENC {
// GL upload element
glupload = Some(gstreamer::ElementFactory::make("glupload").build()?);
// GL color convert element // GL color convert element
let glcolorconvert = gst::ElementFactory::make("glcolorconvert").build()?; glconvert = Some(gstreamer::ElementFactory::make("glcolorconvert").build()?);
// GL color convert caps
let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
let gl_caps = gstreamer::Caps::from_str("video/x-raw(memory:GLMemory),format=NV12")?;
caps_filter.set_property("caps", &gl_caps);
gl_caps_filter = Some(caps_filter);
// CUDA upload element
cudaupload = Some(gstreamer::ElementFactory::make("cudaupload").build()?);
}
// GL upload caps filter // vapostproc for VA compatible encoders
let gl_caps_filter = gst::ElementFactory::make("capsfilter").build()?; let mut vapostproc = None;
let gl_caps = gst::Caps::from_str("video/x-raw(memory:GLMemory),format=NV12")?; let mut va_caps_filter = None;
gl_caps_filter.set_property("caps", &gl_caps); if video_encoder_info.encoder_api == EncoderAPI::VAAPI
|| video_encoder_info.encoder_api == EncoderAPI::QSV
// GL download element (needed only for DMA-BUF outside NVIDIA GPUs) {
let gl_download = gst::ElementFactory::make("gldownload").build()?; vapostproc = Some(gstreamer::ElementFactory::make("vapostproc").build()?);
// VA caps filter
let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
let va_caps = gstreamer::Caps::from_str("video/x-raw(memory:VAMemory),format=NV12")?;
caps_filter.set_property("caps", &va_caps);
va_caps_filter = Some(caps_filter);
}
// Video Converter Element // Video Converter Element
let video_converter = gst::ElementFactory::make("videoconvert").build()?; let mut video_converter = None;
if !args.app.dma_buf {
video_converter = Some(gstreamer::ElementFactory::make("videoconvert").build()?);
}
// Video Encoder Element // Video Encoder Element
let video_encoder = gst::ElementFactory::make(video_encoder_info.name.as_str()).build()?; let video_encoder =
gstreamer::ElementFactory::make(video_encoder_info.name.as_str()).build()?;
video_encoder_info.apply_parameters(&video_encoder, args.app.verbose); video_encoder_info.apply_parameters(&video_encoder, args.app.verbose);
// Video parser Element, required for GStreamer 1.26 as it broke some things.. // Video parser Element
let video_parser; let video_parser;
if video_encoder_info.codec == enc_helper::VideoCodec::H264 { match video_encoder_info.codec {
enc_helper::VideoCodec::H264 => {
video_parser = Some( video_parser = Some(
gst::ElementFactory::make("h264parse") gstreamer::ElementFactory::make("h264parse")
.property("config-interval", -1i32) .property("config-interval", -1i32)
.build()?, .build()?,
); );
} else { }
enc_helper::VideoCodec::H265 => {
video_parser = Some(
gstreamer::ElementFactory::make("h265parse")
.property("config-interval", -1i32)
.build()?,
);
}
_ => {
video_parser = None; video_parser = None;
} }
}
/* Output */ /* Output */
// WebRTC sink Element // WebRTC sink Element
@@ -335,24 +389,24 @@ async fn main() -> Result<(), Box<dyn Error>> {
webrtcsink.set_property("do-retransmission", false); webrtcsink.set_property("do-retransmission", false);
/* Queues */ /* Queues */
let video_queue = gst::ElementFactory::make("queue2") let video_queue = gstreamer::ElementFactory::make("queue2")
.property("max-size-buffers", 3u32) .property("max-size-buffers", 3u32)
.property("max-size-time", 0u64) .property("max-size-time", 0u64)
.property("max-size-bytes", 0u32) .property("max-size-bytes", 0u32)
.build()?; .build()?;
let audio_queue = gst::ElementFactory::make("queue2") let audio_queue = gstreamer::ElementFactory::make("queue2")
.property("max-size-buffers", 3u32) .property("max-size-buffers", 3u32)
.property("max-size-time", 0u64) .property("max-size-time", 0u64)
.property("max-size-bytes", 0u32) .property("max-size-bytes", 0u32)
.build()?; .build()?;
/* Clock Sync */ /* Clock Sync */
let video_clocksync = gst::ElementFactory::make("clocksync") let video_clocksync = gstreamer::ElementFactory::make("clocksync")
.property("sync-to-first", true) .property("sync-to-first", true)
.build()?; .build()?;
let audio_clocksync = gst::ElementFactory::make("clocksync") let audio_clocksync = gstreamer::ElementFactory::make("clocksync")
.property("sync-to-first", true) .property("sync-to-first", true)
.build()?; .build()?;
@@ -360,7 +414,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
pipeline.add_many(&[ pipeline.add_many(&[
webrtcsink.upcast_ref(), webrtcsink.upcast_ref(),
&video_encoder, &video_encoder,
&video_converter,
&caps_filter, &caps_filter,
&video_queue, &video_queue,
&video_clocksync, &video_clocksync,
@@ -374,21 +427,35 @@ async fn main() -> Result<(), Box<dyn Error>> {
&audio_source, &audio_source,
])?; ])?;
if let Some(video_converter) = &video_converter {
pipeline.add(video_converter)?;
}
if let Some(parser) = &audio_parser {
pipeline.add(parser)?;
}
if let Some(parser) = &video_parser { if let Some(parser) = &video_parser {
pipeline.add(parser)?; pipeline.add(parser)?;
} }
// If DMA-BUF is enabled, add glupload, color conversion and caps filter // If DMA-BUF..
if args.app.dma_buf { if args.app.dma_buf {
if *gpu.vendor() == GPUVendor::NVIDIA { // VA-API / QSV pipeline
pipeline.add_many(&[&glupload, &glcolorconvert, &gl_caps_filter])?; if let (Some(vapostproc), Some(va_caps_filter)) = (&vapostproc, &va_caps_filter) {
pipeline.add_many(&[vapostproc, va_caps_filter])?;
} else { } else {
pipeline.add_many(&[&glupload, &glcolorconvert, &gl_caps_filter, &gl_download])?; // NVENC pipeline
if let (Some(glupload), Some(glconvert), Some(gl_caps_filter), Some(cudaupload)) =
(&glupload, &glconvert, &gl_caps_filter, &cudaupload)
{
pipeline.add_many(&[glupload, glconvert, gl_caps_filter, cudaupload])?;
}
} }
} }
// Link main audio branch // Link main audio branch
gst::Element::link_many(&[ gstreamer::Element::link_many(&[
&audio_source, &audio_source,
&audio_converter, &audio_converter,
&audio_rate, &audio_rate,
@@ -396,51 +463,62 @@ async fn main() -> Result<(), Box<dyn Error>> {
&audio_queue, &audio_queue,
&audio_clocksync, &audio_clocksync,
&audio_encoder, &audio_encoder,
webrtcsink.upcast_ref(),
])?; ])?;
// With DMA-BUF, also link glupload and it's caps // Link audio parser to audio encoder if present, otherwise just webrtcsink
if let Some(parser) = &audio_parser {
gstreamer::Element::link_many(&[&audio_encoder, parser, webrtcsink.upcast_ref()])?;
} else {
gstreamer::Element::link_many(&[&audio_encoder, webrtcsink.upcast_ref()])?;
}
// With DMA-BUF..
if args.app.dma_buf { if args.app.dma_buf {
if *gpu.vendor() == GPUVendor::NVIDIA { // VA-API / QSV pipeline
gst::Element::link_many(&[ if let (Some(vapostproc), Some(va_caps_filter)) = (&vapostproc, &va_caps_filter) {
gstreamer::Element::link_many(&[
&video_source, &video_source,
&caps_filter, &caps_filter,
&video_queue, &video_queue,
&video_clocksync, &video_clocksync,
&glupload, &vapostproc,
&glcolorconvert, &va_caps_filter,
&gl_caps_filter,
&video_encoder, &video_encoder,
])?; ])?;
} else { } else {
gst::Element::link_many(&[ // NVENC pipeline
if let (Some(glupload), Some(glconvert), Some(gl_caps_filter), Some(cudaupload)) =
(&glupload, &glconvert, &gl_caps_filter, &cudaupload)
{
gstreamer::Element::link_many(&[
&video_source, &video_source,
&caps_filter, &caps_filter,
&video_queue, &video_queue,
&video_clocksync, &video_clocksync,
&glupload, &glupload,
&glcolorconvert, &glconvert,
&gl_caps_filter, &gl_caps_filter,
&gl_download, &cudaupload,
&video_encoder, &video_encoder,
])?; ])?;
} }
}
} else { } else {
gst::Element::link_many(&[ gstreamer::Element::link_many(&[
&video_source, &video_source,
&caps_filter, &caps_filter,
&video_queue, &video_queue,
&video_clocksync, &video_clocksync,
&video_converter, &video_converter.unwrap(),
&video_encoder, &video_encoder,
])?; ])?;
} }
// Link video parser if present with webrtcsink, otherwise just link webrtc sink // Link video parser if present with webrtcsink, otherwise just link webrtc sink
if let Some(parser) = &video_parser { if let Some(parser) = &video_parser {
gst::Element::link_many(&[&video_encoder, parser, webrtcsink.upcast_ref()])?; gstreamer::Element::link_many(&[&video_encoder, parser, webrtcsink.upcast_ref()])?;
} else { } else {
gst::Element::link_many(&[&video_encoder, webrtcsink.upcast_ref()])?; gstreamer::Element::link_many(&[&video_encoder, webrtcsink.upcast_ref()])?;
} }
// Set QOS // Set QOS
@@ -468,14 +546,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
} }
} }
// Clean up
tracing::info!("Exiting gracefully..");
Ok(()) Ok(())
} }
async fn run_pipeline(pipeline: Arc<gst::Pipeline>) -> Result<(), Box<dyn Error>> { async fn run_pipeline(pipeline: Arc<gstreamer::Pipeline>) -> Result<(), Box<dyn Error>> {
let bus = { pipeline.bus().ok_or("Pipeline has no bus")? }; let bus = { pipeline.bus().ok_or("Pipeline has no bus")? };
{ {
if let Err(e) = pipeline.set_state(gst::State::Playing) { if let Err(e) = pipeline.set_state(gstreamer::State::Playing) {
tracing::error!("Failed to start pipeline: {}", e); tracing::error!("Failed to start pipeline: {}", e);
return Err("Failed to start pipeline".into()); return Err("Failed to start pipeline".into());
} }
@@ -495,24 +576,24 @@ async fn run_pipeline(pipeline: Arc<gst::Pipeline>) -> Result<(), Box<dyn Error>
} }
{ {
pipeline.set_state(gst::State::Null)?; pipeline.set_state(gstreamer::State::Null)?;
} }
Ok(()) Ok(())
} }
async fn listen_for_gst_messages(bus: gst::Bus) -> Result<(), Box<dyn Error>> { async fn listen_for_gst_messages(bus: gstreamer::Bus) -> Result<(), Box<dyn Error>> {
let bus_stream = bus.stream(); let bus_stream = bus.stream();
tokio::pin!(bus_stream); tokio::pin!(bus_stream);
while let Some(msg) = bus_stream.next().await { while let Some(msg) = bus_stream.next().await {
match msg.view() { match msg.view() {
gst::MessageView::Eos(_) => { gstreamer::MessageView::Eos(_) => {
tracing::info!("Received EOS"); tracing::info!("Received EOS");
break; break;
} }
gst::MessageView::Error(err) => { gstreamer::MessageView::Error(err) => {
let err_msg = format!( let err_msg = format!(
"Error from {:?}: {:?}", "Error from {:?}: {:?}",
err.src().map(|s| s.path_string()), err.src().map(|s| s.path_string()),

View File

@@ -7,9 +7,9 @@ use crate::proto::proto::proto_input::InputType::{
use crate::proto::proto::{ProtoInput, ProtoMessageInput}; use crate::proto::proto::{ProtoInput, ProtoMessageInput};
use atomic_refcell::AtomicRefCell; use atomic_refcell::AtomicRefCell;
use glib::subclass::prelude::*; use glib::subclass::prelude::*;
use gst::glib; use gstreamer::glib;
use gst::prelude::*; use gstreamer::prelude::*;
use gst_webrtc::{WebRTCSDPType, WebRTCSessionDescription, gst_sdp}; use gstreamer_webrtc::{gst_sdp, WebRTCSDPType, WebRTCSessionDescription};
use gstrswebrtc::signaller::{Signallable, SignallableImpl}; use gstrswebrtc::signaller::{Signallable, SignallableImpl};
use parking_lot::RwLock as PLRwLock; use parking_lot::RwLock as PLRwLock;
use prost::Message; use prost::Message;
@@ -20,8 +20,8 @@ use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
pub struct Signaller { pub struct Signaller {
stream_room: PLRwLock<Option<String>>, stream_room: PLRwLock<Option<String>>,
stream_protocol: PLRwLock<Option<Arc<NestriStreamProtocol>>>, stream_protocol: PLRwLock<Option<Arc<NestriStreamProtocol>>>,
wayland_src: PLRwLock<Option<Arc<gst::Element>>>, wayland_src: PLRwLock<Option<Arc<gstreamer::Element>>>,
data_channel: AtomicRefCell<Option<gst_webrtc::WebRTCDataChannel>>, data_channel: AtomicRefCell<Option<gstreamer_webrtc::WebRTCDataChannel>>,
} }
impl Default for Signaller { impl Default for Signaller {
fn default() -> Self { fn default() -> Self {
@@ -51,19 +51,19 @@ impl Signaller {
self.stream_protocol.read().clone() self.stream_protocol.read().clone()
} }
pub fn set_wayland_src(&self, wayland_src: Arc<gst::Element>) { pub fn set_wayland_src(&self, wayland_src: Arc<gstreamer::Element>) {
*self.wayland_src.write() = Some(wayland_src); *self.wayland_src.write() = Some(wayland_src);
} }
pub fn get_wayland_src(&self) -> Option<Arc<gst::Element>> { pub fn get_wayland_src(&self) -> Option<Arc<gstreamer::Element>> {
self.wayland_src.read().clone() self.wayland_src.read().clone()
} }
pub fn set_data_channel(&self, data_channel: gst_webrtc::WebRTCDataChannel) { pub fn set_data_channel(&self, data_channel: gstreamer_webrtc::WebRTCDataChannel) {
match self.data_channel.try_borrow_mut() { match self.data_channel.try_borrow_mut() {
Ok(mut dc) => *dc = Some(data_channel), Ok(mut dc) => *dc = Some(data_channel),
Err(_) => gst::warning!( Err(_) => gstreamer::warning!(
gst::CAT_DEFAULT, gstreamer::CAT_DEFAULT,
"Failed to set data channel - already borrowed" "Failed to set data channel - already borrowed"
), ),
} }
@@ -72,7 +72,7 @@ impl Signaller {
/// Helper method to clean things up /// Helper method to clean things up
fn register_callbacks(&self) { fn register_callbacks(&self) {
let Some(stream_protocol) = self.get_stream_protocol() else { let Some(stream_protocol) = self.get_stream_protocol() else {
gst::error!(gst::CAT_DEFAULT, "Stream protocol not set"); gstreamer::error!(gstreamer::CAT_DEFAULT, "Stream protocol not set");
return; return;
}; };
{ {
@@ -87,7 +87,7 @@ impl Signaller {
&[&"unique-session-id", &answer], &[&"unique-session-id", &answer],
); );
} else { } else {
gst::error!(gst::CAT_DEFAULT, "Failed to decode SDP message"); gstreamer::error!(gstreamer::CAT_DEFAULT, "Failed to decode SDP message");
} }
}); });
} }
@@ -108,7 +108,7 @@ impl Signaller {
], ],
); );
} else { } else {
gst::error!(gst::CAT_DEFAULT, "Failed to decode ICE message"); gstreamer::error!(gstreamer::CAT_DEFAULT, "Failed to decode ICE message");
} }
}); });
} }
@@ -118,13 +118,16 @@ impl Signaller {
if let Ok(answer) = serde_json::from_slice::<MessageRaw>(&data) { if let Ok(answer) = serde_json::from_slice::<MessageRaw>(&data) {
// Decode room name string // Decode room name string
if let Some(room_name) = answer.data.as_str() { if let Some(room_name) = answer.data.as_str() {
gst::info!( gstreamer::info!(
gst::CAT_DEFAULT, gstreamer::CAT_DEFAULT,
"Received OK answer for room: {}", "Received OK answer for room: {}",
room_name room_name
); );
} else { } else {
gst::error!(gst::CAT_DEFAULT, "Failed to decode room name from answer"); gstreamer::error!(
gstreamer::CAT_DEFAULT,
"Failed to decode room name from answer"
);
} }
// Send our SDP offer // Send our SDP offer
@@ -137,7 +140,7 @@ impl Signaller {
], ],
); );
} else { } else {
gst::error!(gst::CAT_DEFAULT, "Failed to decode answer"); gstreamer::error!(gstreamer::CAT_DEFAULT, "Failed to decode answer");
} }
}); });
} }
@@ -147,17 +150,18 @@ impl Signaller {
self_obj.connect_closure( self_obj.connect_closure(
"webrtcbin-ready", "webrtcbin-ready",
false, false,
glib::closure!(move |signaller: &super::NestriSignaller, glib::closure!(
move |signaller: &super::NestriSignaller,
_consumer_identifier: &str, _consumer_identifier: &str,
webrtcbin: &gst::Element| { webrtcbin: &gstreamer::Element| {
gst::info!(gst::CAT_DEFAULT, "Adding data channels"); gstreamer::info!(gstreamer::CAT_DEFAULT, "Adding data channels");
// Create data channels on webrtcbin // Create data channels on webrtcbin
let data_channel = Some( let data_channel = Some(
webrtcbin.emit_by_name::<gst_webrtc::WebRTCDataChannel>( webrtcbin.emit_by_name::<gstreamer_webrtc::WebRTCDataChannel>(
"create-data-channel", "create-data-channel",
&[ &[
&"nestri-data-channel", &"nestri-data-channel",
&gst::Structure::builder("config") &gstreamer::Structure::builder("config")
.field("ordered", &true) .field("ordered", &true)
.field("max-retransmits", &2u32) .field("max-retransmits", &2u32)
.field("priority", "high") .field("priority", "high")
@@ -167,24 +171,31 @@ impl Signaller {
), ),
); );
if let Some(data_channel) = data_channel { if let Some(data_channel) = data_channel {
gst::info!(gst::CAT_DEFAULT, "Data channel created"); gstreamer::info!(gstreamer::CAT_DEFAULT, "Data channel created");
if let Some(wayland_src) = signaller.imp().get_wayland_src() { if let Some(wayland_src) = signaller.imp().get_wayland_src() {
setup_data_channel(&data_channel, &*wayland_src); setup_data_channel(&data_channel, &*wayland_src);
signaller.imp().set_data_channel(data_channel); signaller.imp().set_data_channel(data_channel);
} else { } else {
gst::error!(gst::CAT_DEFAULT, "Wayland display source not set"); gstreamer::error!(
gstreamer::CAT_DEFAULT,
"Wayland display source not set"
);
} }
} else { } else {
gst::error!(gst::CAT_DEFAULT, "Failed to create data channel"); gstreamer::error!(
gstreamer::CAT_DEFAULT,
"Failed to create data channel"
);
} }
}), }
),
); );
} }
} }
} }
impl SignallableImpl for Signaller { impl SignallableImpl for Signaller {
fn start(&self) { fn start(&self) {
gst::info!(gst::CAT_DEFAULT, "Signaller started"); gstreamer::info!(gstreamer::CAT_DEFAULT, "Signaller started");
// Register message callbacks // Register message callbacks
self.register_callbacks(); self.register_callbacks();
@@ -193,7 +204,7 @@ impl SignallableImpl for Signaller {
// TODO: Re-implement reconnection handling // TODO: Re-implement reconnection handling
let Some(stream_room) = self.stream_room.read().clone() else { let Some(stream_room) = self.stream_room.read().clone() else {
gst::error!(gst::CAT_DEFAULT, "Stream room not set"); gstreamer::error!(gstreamer::CAT_DEFAULT, "Stream room not set");
return; return;
}; };
@@ -206,7 +217,7 @@ impl SignallableImpl for Signaller {
}; };
let Some(stream_protocol) = self.get_stream_protocol() else { let Some(stream_protocol) = self.get_stream_protocol() else {
gst::error!(gst::CAT_DEFAULT, "Stream protocol not set"); gstreamer::error!(gstreamer::CAT_DEFAULT, "Stream protocol not set");
return; return;
}; };
@@ -216,7 +227,7 @@ impl SignallableImpl for Signaller {
} }
fn stop(&self) { fn stop(&self) {
gst::info!(gst::CAT_DEFAULT, "Signaller stopped"); gstreamer::info!(gstreamer::CAT_DEFAULT, "Signaller stopped");
} }
fn send_sdp(&self, _session_id: &str, sdp: &WebRTCSessionDescription) { fn send_sdp(&self, _session_id: &str, sdp: &WebRTCSessionDescription) {
@@ -229,7 +240,7 @@ impl SignallableImpl for Signaller {
}; };
let Some(stream_protocol) = self.get_stream_protocol() else { let Some(stream_protocol) = self.get_stream_protocol() else {
gst::error!(gst::CAT_DEFAULT, "Stream protocol not set"); gstreamer::error!(gstreamer::CAT_DEFAULT, "Stream protocol not set");
return; return;
}; };
@@ -260,7 +271,7 @@ impl SignallableImpl for Signaller {
}; };
let Some(stream_protocol) = self.get_stream_protocol() else { let Some(stream_protocol) = self.get_stream_protocol() else {
gst::error!(gst::CAT_DEFAULT, "Stream protocol not set"); gstreamer::error!(gstreamer::CAT_DEFAULT, "Stream protocol not set");
return; return;
}; };
@@ -270,7 +281,7 @@ impl SignallableImpl for Signaller {
} }
fn end_session(&self, session_id: &str) { fn end_session(&self, session_id: &str) {
gst::info!(gst::CAT_DEFAULT, "Ending session: {}", session_id); gstreamer::info!(gstreamer::CAT_DEFAULT, "Ending session: {}", session_id);
} }
} }
#[glib::object_subclass] #[glib::object_subclass]
@@ -303,7 +314,10 @@ impl ObjectImpl for Signaller {
} }
} }
fn setup_data_channel(data_channel: &gst_webrtc::WebRTCDataChannel, wayland_src: &gst::Element) { fn setup_data_channel(
data_channel: &gstreamer_webrtc::WebRTCDataChannel,
wayland_src: &gstreamer::Element,
) {
let wayland_src = wayland_src.clone(); let wayland_src = wayland_src.clone();
data_channel.connect_on_message_data(move |_data_channel, data| { data_channel.connect_on_message_data(move |_data_channel, data| {
@@ -328,64 +342,64 @@ fn setup_data_channel(data_channel: &gst_webrtc::WebRTCDataChannel, wayland_src:
}); });
} }
fn handle_input_message(input_msg: ProtoInput) -> Option<gst::Event> { fn handle_input_message(input_msg: ProtoInput) -> Option<gstreamer::Event> {
if let Some(input_type) = input_msg.input_type { if let Some(input_type) = input_msg.input_type {
match input_type { match input_type {
MouseMove(data) => { MouseMove(data) => {
let structure = gst::Structure::builder("MouseMoveRelative") let structure = gstreamer::Structure::builder("MouseMoveRelative")
.field("pointer_x", data.x as f64) .field("pointer_x", data.x as f64)
.field("pointer_y", data.y as f64) .field("pointer_y", data.y as f64)
.build(); .build();
Some(gst::event::CustomUpstream::new(structure)) Some(gstreamer::event::CustomUpstream::new(structure))
} }
MouseMoveAbs(data) => { MouseMoveAbs(data) => {
let structure = gst::Structure::builder("MouseMoveAbsolute") let structure = gstreamer::Structure::builder("MouseMoveAbsolute")
.field("pointer_x", data.x as f64) .field("pointer_x", data.x as f64)
.field("pointer_y", data.y as f64) .field("pointer_y", data.y as f64)
.build(); .build();
Some(gst::event::CustomUpstream::new(structure)) Some(gstreamer::event::CustomUpstream::new(structure))
} }
KeyDown(data) => { KeyDown(data) => {
let structure = gst::Structure::builder("KeyboardKey") let structure = gstreamer::Structure::builder("KeyboardKey")
.field("key", data.key as u32) .field("key", data.key as u32)
.field("pressed", true) .field("pressed", true)
.build(); .build();
Some(gst::event::CustomUpstream::new(structure)) Some(gstreamer::event::CustomUpstream::new(structure))
} }
KeyUp(data) => { KeyUp(data) => {
let structure = gst::Structure::builder("KeyboardKey") let structure = gstreamer::Structure::builder("KeyboardKey")
.field("key", data.key as u32) .field("key", data.key as u32)
.field("pressed", false) .field("pressed", false)
.build(); .build();
Some(gst::event::CustomUpstream::new(structure)) Some(gstreamer::event::CustomUpstream::new(structure))
} }
MouseWheel(data) => { MouseWheel(data) => {
let structure = gst::Structure::builder("MouseAxis") let structure = gstreamer::Structure::builder("MouseAxis")
.field("x", data.x as f64) .field("x", data.x as f64)
.field("y", data.y as f64) .field("y", data.y as f64)
.build(); .build();
Some(gst::event::CustomUpstream::new(structure)) Some(gstreamer::event::CustomUpstream::new(structure))
} }
MouseKeyDown(data) => { MouseKeyDown(data) => {
let structure = gst::Structure::builder("MouseButton") let structure = gstreamer::Structure::builder("MouseButton")
.field("button", data.key as u32) .field("button", data.key as u32)
.field("pressed", true) .field("pressed", true)
.build(); .build();
Some(gst::event::CustomUpstream::new(structure)) Some(gstreamer::event::CustomUpstream::new(structure))
} }
MouseKeyUp(data) => { MouseKeyUp(data) => {
let structure = gst::Structure::builder("MouseButton") let structure = gstreamer::Structure::builder("MouseButton")
.field("button", data.key as u32) .field("button", data.key as u32)
.field("pressed", false) .field("pressed", false)
.build(); .build();
Some(gst::event::CustomUpstream::new(structure)) Some(gstreamer::event::CustomUpstream::new(structure))
} }
} }
} else { } else {

View File

@@ -1,6 +1,6 @@
use crate::p2p::p2p::NestriConnection; use crate::p2p::p2p::NestriConnection;
use gst::glib; use gstreamer::glib;
use gst::subclass::prelude::*; use gstreamer::subclass::prelude::*;
use gstrswebrtc::signaller::Signallable; use gstrswebrtc::signaller::Signallable;
use std::sync::Arc; use std::sync::Arc;
@@ -14,7 +14,7 @@ impl NestriSignaller {
pub async fn new( pub async fn new(
room: String, room: String,
nestri_conn: NestriConnection, nestri_conn: NestriConnection,
wayland_src: Arc<gst::Element>, wayland_src: Arc<gstreamer::Element>,
) -> Result<Self, Box<dyn std::error::Error>> { ) -> Result<Self, Box<dyn std::error::Error>> {
let obj: Self = glib::Object::new(); let obj: Self = glib::Object::new();
obj.imp().set_stream_room(room); obj.imp().set_stream_room(room);

View File

@@ -1,4 +1,4 @@
use futures_util::StreamExt; use libp2p::futures::StreamExt;
use libp2p::multiaddr::Protocol; use libp2p::multiaddr::Protocol;
use libp2p::{ use libp2p::{
Multiaddr, PeerId, Swarm, identify, noise, ping, Multiaddr, PeerId, Swarm, identify, noise, ping,
@@ -20,6 +20,7 @@ struct NestriBehaviour {
identify: identify::Behaviour, identify: identify::Behaviour,
ping: ping::Behaviour, ping: ping::Behaviour,
stream: libp2p_stream::Behaviour, stream: libp2p_stream::Behaviour,
autonatv2: libp2p::autonat::v2::client::Behaviour,
} }
pub struct NestriP2P { pub struct NestriP2P {
@@ -36,6 +37,8 @@ impl NestriP2P {
yamux::Config::default, yamux::Config::default,
)? )?
.with_dns()? .with_dns()?
.with_websocket(noise::Config::new, yamux::Config::default)
.await?
.with_behaviour(|key| { .with_behaviour(|key| {
let identify_behaviour = identify::Behaviour::new(identify::Config::new( let identify_behaviour = identify::Behaviour::new(identify::Config::new(
"/ipfs/id/1.0.0".to_string(), "/ipfs/id/1.0.0".to_string(),
@@ -43,11 +46,13 @@ impl NestriP2P {
)); ));
let ping_behaviour = ping::Behaviour::default(); let ping_behaviour = ping::Behaviour::default();
let stream_behaviour = libp2p_stream::Behaviour::default(); let stream_behaviour = libp2p_stream::Behaviour::default();
let autonatv2_behaviour = libp2p::autonat::v2::client::Behaviour::default();
Ok(NestriBehaviour { Ok(NestriBehaviour {
identify: identify_behaviour, identify: identify_behaviour,
ping: ping_behaviour, ping: ping_behaviour,
stream: stream_behaviour, stream: stream_behaviour,
autonatv2: autonatv2_behaviour,
}) })
})? })?
.build(), .build(),

View File

@@ -1,9 +1,10 @@
use crate::p2p::p2p::NestriConnection; use crate::p2p::p2p::NestriConnection;
use crate::p2p::p2p_safestream::SafeStream; use crate::p2p::p2p_safestream::SafeStream;
use dashmap::DashMap;
use libp2p::StreamProtocol; use libp2p::StreamProtocol;
use std::collections::HashMap; use std::sync::Arc;
use std::sync::{Arc, RwLock};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::time::{self, Duration};
// Cloneable callback type // Cloneable callback type
pub type CallbackInner = dyn Fn(Vec<u8>) + Send + Sync + 'static; pub type CallbackInner = dyn Fn(Vec<u8>) + Send + Sync + 'static;
@@ -33,9 +34,11 @@ impl From<Box<CallbackInner>> for Callback {
/// NestriStreamProtocol manages the stream protocol for Nestri connections. /// NestriStreamProtocol manages the stream protocol for Nestri connections.
pub struct NestriStreamProtocol { pub struct NestriStreamProtocol {
tx: mpsc::Sender<Vec<u8>>, tx: Option<mpsc::Sender<Vec<u8>>>,
safe_stream: Arc<SafeStream>, safe_stream: Arc<SafeStream>,
callbacks: Arc<RwLock<HashMap<String, Callback>>>, callbacks: Arc<DashMap<String, Callback>>,
read_handle: Option<tokio::task::JoinHandle<()>>,
write_handle: Option<tokio::task::JoinHandle<()>>,
} }
impl NestriStreamProtocol { impl NestriStreamProtocol {
const NESTRI_PROTOCOL_STREAM_PUSH: StreamProtocol = const NESTRI_PROTOCOL_STREAM_PUSH: StreamProtocol =
@@ -56,21 +59,35 @@ impl NestriStreamProtocol {
} }
}; };
let (tx, rx) = mpsc::channel(1000); let mut sp = NestriStreamProtocol {
tx: None,
let sp = NestriStreamProtocol {
tx,
safe_stream: Arc::new(SafeStream::new(push_stream)), safe_stream: Arc::new(SafeStream::new(push_stream)),
callbacks: Arc::new(RwLock::new(HashMap::new())), callbacks: Arc::new(DashMap::new()),
read_handle: None,
write_handle: None,
}; };
// Spawn the loops // Use restart method to initialize the read and write loops
sp.spawn_read_loop(); sp.restart()?;
sp.spawn_write_loop(rx);
Ok(sp) Ok(sp)
} }
pub fn restart(&mut self) -> Result<(), Box<dyn std::error::Error>> {
// Return if tx and handles are already initialized
if self.tx.is_some() && self.read_handle.is_some() && self.write_handle.is_some() {
tracing::warn!("NestriStreamProtocol is already running, restart skipped");
return Ok(());
}
let (tx, rx) = mpsc::channel(1000);
self.tx = Some(tx);
self.read_handle = Some(self.spawn_read_loop());
self.write_handle = Some(self.spawn_write_loop(rx));
Ok(())
}
fn spawn_read_loop(&self) -> tokio::task::JoinHandle<()> { fn spawn_read_loop(&self) -> tokio::task::JoinHandle<()> {
let safe_stream = self.safe_stream.clone(); let safe_stream = self.safe_stream.clone();
let callbacks = self.callbacks.clone(); let callbacks = self.callbacks.clone();
@@ -89,14 +106,22 @@ impl NestriStreamProtocol {
match serde_json::from_slice::<crate::messages::MessageBase>(&data) { match serde_json::from_slice::<crate::messages::MessageBase>(&data) {
Ok(base_message) => { Ok(base_message) => {
let response_type = base_message.payload_type; let response_type = base_message.payload_type;
let callback = {
let callbacks_lock = callbacks.read().unwrap();
callbacks_lock.get(&response_type).cloned()
};
if let Some(callback) = callback { // With DashMap, we don't need explicit locking
// Call the registered callback with the raw data // we just get the callback directly if it exists
callback.call(data); if let Some(callback) = callbacks.get(&response_type) {
// Execute the callback
if let Err(e) =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
callback.call(data.clone())
}))
{
tracing::error!(
"Callback for response type '{}' panicked: {:?}",
response_type,
e
);
}
} else { } else {
tracing::warn!( tracing::warn!(
"No callback registered for response type: {}", "No callback registered for response type: {}",
@@ -108,6 +133,9 @@ impl NestriStreamProtocol {
tracing::error!("Failed to decode message: {}", e); tracing::error!("Failed to decode message: {}", e);
} }
} }
// Add a small sleep to reduce CPU usage
time::sleep(Duration::from_micros(100)).await;
} }
}) })
} }
@@ -117,15 +145,21 @@ impl NestriStreamProtocol {
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {
// Wait for a message from the channel // Wait for a message from the channel
if let Some(tx_data) = rx.recv().await { match rx.recv().await {
Some(tx_data) => {
if let Err(e) = safe_stream.send_raw(&tx_data).await { if let Err(e) = safe_stream.send_raw(&tx_data).await {
tracing::error!("Error sending data: {:?}", e); tracing::error!("Error sending data: {:?}", e);
} }
} else { }
None => {
tracing::info!("Receiver closed, exiting write loop"); tracing::info!("Receiver closed, exiting write loop");
break; break;
} }
} }
// Add a small sleep to reduce CPU usage
time::sleep(Duration::from_micros(100)).await;
}
}) })
} }
@@ -134,16 +168,25 @@ impl NestriStreamProtocol {
message: &M, message: &M,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let json_data = serde_json::to_vec(message)?; let json_data = serde_json::to_vec(message)?;
self.tx.try_send(json_data)?; let Some(tx) = &self.tx else {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::NotConnected,
if self.read_handle.is_none() && self.write_handle.is_none() {
"NestriStreamProtocol has been shutdown"
} else {
"NestriStreamProtocol is not properly initialized"
},
)));
};
tx.try_send(json_data)?;
Ok(()) Ok(())
} }
/// Register a callback for a specific response type
pub fn register_callback<F>(&self, response_type: &str, callback: F) pub fn register_callback<F>(&self, response_type: &str, callback: F)
where where
F: Fn(Vec<u8>) + Send + Sync + 'static, F: Fn(Vec<u8>) + Send + Sync + 'static,
{ {
let mut callbacks_lock = self.callbacks.write().unwrap(); self.callbacks
callbacks_lock.insert(response_type.to_string(), Callback::new(callback)); .insert(response_type.to_string(), Callback::new(callback));
} }
} }

View File

@@ -1,6 +1,6 @@
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
use futures_util::io::{ReadHalf, WriteHalf}; use libp2p::futures::io::{ReadHalf, WriteHalf};
use futures_util::{AsyncReadExt, AsyncWriteExt}; use libp2p::futures::{AsyncReadExt, AsyncWriteExt};
use prost::Message; use prost::Message;
use serde::Serialize; use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
@@ -63,21 +63,15 @@ impl SafeStream {
async fn send_with_length_prefix(&self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> { async fn send_with_length_prefix(&self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
if data.len() > MAX_SIZE { if data.len() > MAX_SIZE {
return Err(Box::new(std::io::Error::new( return Err("Data exceeds maximum size".into());
std::io::ErrorKind::InvalidData,
"Data exceeds maximum size",
)));
} }
let mut buffer = Vec::with_capacity(4 + data.len());
buffer.extend_from_slice(&(data.len() as u32).to_be_bytes()); // Length prefix
buffer.extend_from_slice(data); // Payload
let mut stream_write = self.stream_write.lock().await; let mut stream_write = self.stream_write.lock().await;
stream_write.write_all(&buffer).await?; // Single write
// Write the 4-byte length prefix
let mut length_prefix = [0u8; 4];
BigEndian::write_u32(&mut length_prefix, data.len() as u32);
stream_write.write_all(&length_prefix).await?;
// Write the actual data
stream_write.write_all(data).await?;
stream_write.flush().await?; stream_write.flush().await?;
Ok(()) Ok(())
} }
@@ -85,20 +79,16 @@ impl SafeStream {
async fn receive_with_length_prefix(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> { async fn receive_with_length_prefix(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut stream_read = self.stream_read.lock().await; let mut stream_read = self.stream_read.lock().await;
// Read the 4-byte length prefix // Read length prefix + data in one syscall
let mut length_prefix = [0u8; 4]; let mut length_prefix = [0u8; 4];
stream_read.read_exact(&mut length_prefix).await?; stream_read.read_exact(&mut length_prefix).await?;
let length = BigEndian::read_u32(&length_prefix) as usize; let length = BigEndian::read_u32(&length_prefix) as usize;
if length > MAX_SIZE { if length > MAX_SIZE {
return Err(Box::new(std::io::Error::new( return Err("Data exceeds maximum size".into());
std::io::ErrorKind::InvalidData,
"Data exceeds maximum size",
)));
} }
// Read the actual data let mut buffer = vec![0u8; length];
let mut buffer = vec![0; length];
stream_read.read_exact(&mut buffer).await?; stream_read.read_exact(&mut buffer).await?;
Ok(buffer) Ok(buffer)
} }

View File

@@ -0,0 +1 @@
export default new Map();

View File

@@ -0,0 +1 @@
export default new Map();

View File

@@ -0,0 +1 @@
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.11.2","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://dev.nestri.io\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"index.js\",\"redirects\":false,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":\"0.0.0.0\",\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/noop\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":false},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false},\"legacy\":{\"collections\":false},\"session\":{\"driver\":\"fs-lite\",\"options\":{\"base\":\"/home/wanjohi/nestri/packages/web/.astro/integrations/_astrojs_cloudflare/sessions\"}}}"]

View File

@@ -0,0 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1752741260450
}
}

1
packages/web/.astro/types.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="astro/client" />

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