Compare commits
1 Commits
feat/blog
...
feat/cloud
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4512385b1 |
@@ -1,2 +0,0 @@
|
|||||||
CLOUDFLARE_API_TOKEN=
|
|
||||||
NEON_API_KEY=
|
|
||||||
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,2 +1,28 @@
|
|||||||
## Description
|
## Description
|
||||||
<!-- Briefly describe the purpose and scope of your changes -->
|
<!-- Briefly describe the purpose and scope of your changes -->
|
||||||
|
|
||||||
|
## Related Issues
|
||||||
|
<!-- List any related issues (e.g., "Closes #123", "Fixes #456") -->
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
|
||||||
|
- [ ] Bug fix (non-breaking change)
|
||||||
|
- [ ] New feature (non-breaking change)
|
||||||
|
- [ ] Breaking change (fix or feature that changes existing functionality)
|
||||||
|
- [ ] Documentation update
|
||||||
|
- [ ] Other (please describe):
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] I have updated relevant documentation
|
||||||
|
- [ ] My code follows the project's coding style
|
||||||
|
- [ ] My changes generate no new warnings/errors
|
||||||
|
|
||||||
|
## Notes for Reviewers
|
||||||
|
<!-- Point out areas you'd like reviewers to focus on, questions you have, or decisions that need discussion -->
|
||||||
|
|
||||||
|
## Screenshots/Demo
|
||||||
|
<!-- If applicable, add screenshots or a GIF demo of your changes (especially for UI changes) -->
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
<!-- Add any other context about the pull request here -->
|
||||||
40
.github/workflows/docs.yml
vendored
@@ -1,40 +0,0 @@
|
|||||||
name: Build docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "apps/docs/**"
|
|
||||||
- ".github/workflows/docs.yml"
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- "apps/docs/**"
|
|
||||||
- ".github/workflows/docs.yml"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy-docs:
|
|
||||||
name: Build and deploy docs
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: "apps/docs"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
- name: Install dependencies
|
|
||||||
run: bun install
|
|
||||||
- name: Build Project Artifacts
|
|
||||||
run: bun run build
|
|
||||||
- name: Deploy Project Artifacts to Cloudflare
|
|
||||||
uses: cloudflare/wrangler-action@v3
|
|
||||||
with:
|
|
||||||
packageManager: bun
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
wranglerVersion: "3.93.0"
|
|
||||||
workingDirectory: "apps/docs"
|
|
||||||
command: pages deploy ./dist --project-name=${{ vars.CF_DOCS_PAGES_PROJECT_NAME }} --commit-dirty=true
|
|
||||||
env:
|
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
8
.github/workflows/runner.yml
vendored
@@ -7,7 +7,6 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "containers/runner.Containerfile"
|
- "containers/runner.Containerfile"
|
||||||
- "packages/scripts/**"
|
- "packages/scripts/**"
|
||||||
- "packages/server/**"
|
|
||||||
- ".github/workflows/runner.yml"
|
- ".github/workflows/runner.yml"
|
||||||
schedule:
|
schedule:
|
||||||
- cron: 7 0 * * 1,3,6 # Regularly to keep that build cache warm
|
- cron: 7 0 * * 1,3,6 # Regularly to keep that build cache warm
|
||||||
@@ -17,7 +16,6 @@ on:
|
|||||||
- "containers/runner.Containerfile"
|
- "containers/runner.Containerfile"
|
||||||
- ".github/workflows/runner.yml"
|
- ".github/workflows/runner.yml"
|
||||||
- "packages/scripts/**"
|
- "packages/scripts/**"
|
||||||
- "packages/server/**"
|
|
||||||
tags:
|
tags:
|
||||||
- v*.*.*
|
- v*.*.*
|
||||||
release:
|
release:
|
||||||
@@ -27,7 +25,6 @@ env:
|
|||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: nestrilabs/nestri
|
IMAGE_NAME: nestrilabs/nestri
|
||||||
BASE_TAG_PREFIX: runner
|
BASE_TAG_PREFIX: runner
|
||||||
BASE_IMAGE: docker.io/cachyos/cachyos:latest
|
|
||||||
|
|
||||||
# This makes our release ci quit prematurely
|
# This makes our release ci quit prematurely
|
||||||
# concurrency:
|
# concurrency:
|
||||||
@@ -56,7 +53,7 @@ jobs:
|
|||||||
swap-size-gb: 20
|
swap-size-gb: 20
|
||||||
-
|
-
|
||||||
name: Build Docker image
|
name: Build Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
file: containers/runner.Containerfile
|
file: containers/runner.Containerfile
|
||||||
context: ./
|
context: ./
|
||||||
@@ -108,7 +105,7 @@ jobs:
|
|||||||
swap-size-gb: 20
|
swap-size-gb: 20
|
||||||
-
|
-
|
||||||
name: Build Docker image
|
name: Build Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
file: containers/runner.Containerfile
|
file: containers/runner.Containerfile
|
||||||
context: ./
|
context: ./
|
||||||
@@ -117,4 +114,3 @@ jobs:
|
|||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha,mode=max
|
cache-from: type=gha,mode=max
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
pull: ${{ github.event_name == 'schedule' }} # Pull base image for scheduled builds
|
|
||||||
|
|||||||
40
.github/workflows/www.yml
vendored
@@ -1,40 +0,0 @@
|
|||||||
name: Build www
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "apps/www/**"
|
|
||||||
- ".github/workflows/www.yml"
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- "apps/www/**"
|
|
||||||
- ".github/workflows/www.yml"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy-www:
|
|
||||||
name: Build and deploy www
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: "apps/www"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
- name: Install dependencies
|
|
||||||
run: bun install
|
|
||||||
- name: Build Project Artifacts
|
|
||||||
run: bun run build
|
|
||||||
- name: Deploy Project Artifacts to Cloudflare
|
|
||||||
uses: cloudflare/wrangler-action@v3
|
|
||||||
with:
|
|
||||||
packageManager: bun
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
wranglerVersion: "3.93.0"
|
|
||||||
workingDirectory: "apps/www"
|
|
||||||
command: pages deploy ./dist --project-name=${{ vars.CF_WWW_PAGES_PROJECT_NAME }} --commit-dirty=true
|
|
||||||
env:
|
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
1
.gitignore
vendored
@@ -8,7 +8,6 @@ node_modules
|
|||||||
# Local env files
|
# Local env files
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.sst
|
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
|||||||
4
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["dbaeumer.vscode-eslint", "unifiedjs.vscode-mdx"],
|
||||||
|
"unwantedRecommendations": []
|
||||||
|
}
|
||||||
24
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "chrome",
|
||||||
|
"url": "http://localhost:5173",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"name": "dev.debug",
|
||||||
|
"request": "launch",
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"program": "${workspaceFolder}/node_modules/vite/bin/vite.js",
|
||||||
|
"args": ["--mode", "ssr", "--force"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
36
.vscode/qwik-city.code-snippets
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"onRequest": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qonRequest",
|
||||||
|
"description": "onRequest function for a route index",
|
||||||
|
"body": [
|
||||||
|
"export const onRequest: RequestHandler = (request) => {",
|
||||||
|
" $0",
|
||||||
|
"};",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"loader$": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qloader$",
|
||||||
|
"description": "loader$()",
|
||||||
|
"body": ["export const $1 = routeLoader$(() => {", " $0", "});"],
|
||||||
|
},
|
||||||
|
"action$": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qaction$",
|
||||||
|
"description": "action$()",
|
||||||
|
"body": ["export const $1 = routeAction$((data) => {", " $0", "});"],
|
||||||
|
},
|
||||||
|
"Full Page": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qpage",
|
||||||
|
"description": "Simple page component",
|
||||||
|
"body": [
|
||||||
|
"import { component$ } from '@builder.io/qwik';",
|
||||||
|
"",
|
||||||
|
"export default component$(() => {",
|
||||||
|
" $0",
|
||||||
|
"});",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
78
.vscode/qwik.code-snippets
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"Qwik component (simple)": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qcomponent$",
|
||||||
|
"description": "Simple Qwik component",
|
||||||
|
"body": [
|
||||||
|
"export const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = component$(() => {",
|
||||||
|
" return <${2:div}>$4</$2>",
|
||||||
|
"});",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Qwik component (props)": {
|
||||||
|
"scope": "typescriptreact",
|
||||||
|
"prefix": "qcomponent$ + props",
|
||||||
|
"description": "Qwik component w/ props",
|
||||||
|
"body": [
|
||||||
|
"export interface ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}Props {",
|
||||||
|
" $2",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"export const $1 = component$<$1Props>((props) => {",
|
||||||
|
" const ${2:count} = useSignal(0);",
|
||||||
|
" return (",
|
||||||
|
" <${3:div} on${4:Click}$={(ev) => {$5}}>",
|
||||||
|
" $6",
|
||||||
|
" </${3}>",
|
||||||
|
" );",
|
||||||
|
"});",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Qwik signal": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseSignal",
|
||||||
|
"description": "useSignal() declaration",
|
||||||
|
"body": ["const ${1:foo} = useSignal($2);", "$0"],
|
||||||
|
},
|
||||||
|
"Qwik store": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseStore",
|
||||||
|
"description": "useStore() declaration",
|
||||||
|
"body": ["const ${1:state} = useStore({", " $2", "});", "$0"],
|
||||||
|
},
|
||||||
|
"$ hook": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "q$",
|
||||||
|
"description": "$() function hook",
|
||||||
|
"body": ["$(() => {", " $0", "});", ""],
|
||||||
|
},
|
||||||
|
"useVisibleTask": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseVisibleTask",
|
||||||
|
"description": "useVisibleTask$() function hook",
|
||||||
|
"body": ["useVisibleTask$(({ track }) => {", " $0", "});", ""],
|
||||||
|
},
|
||||||
|
"useTask": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseTask$",
|
||||||
|
"description": "useTask$() function hook",
|
||||||
|
"body": [
|
||||||
|
"useTask$(({ track }) => {",
|
||||||
|
" track(() => $1);",
|
||||||
|
" $0",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"useResource": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseResource$",
|
||||||
|
"description": "useResource$() declaration",
|
||||||
|
"body": [
|
||||||
|
"const $1 = useResource$(({ track, cleanup }) => {",
|
||||||
|
" $0",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
10
.vscode/settings.json
vendored
@@ -1,3 +1,11 @@
|
|||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
"material-icon-theme.activeIconPack": "qwik",
|
||||||
|
"emmet.includeLanguages": {
|
||||||
|
"typescriptreact": "html"
|
||||||
|
},
|
||||||
|
"emmet.preferences": {
|
||||||
|
// to ensure closing tags are used (e.g. <img/> not just <img> like in HTML)
|
||||||
|
// https://github.com/microsoft/vscode/commit/083bf9020407ea5a91199eb1f0b373859df8d600#diff-88456bc9b7caa2f8126aea0107b4671db0f094961aaf39a7c689f890e23aaaba
|
||||||
|
"output.selfClosingStyle": "xhtml"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
24
apps/blog/.gitignore
vendored
@@ -1,24 +0,0 @@
|
|||||||
# build output
|
|
||||||
dist/
|
|
||||||
# generated types
|
|
||||||
.astro/
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# logs
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
|
|
||||||
|
|
||||||
# environment variables
|
|
||||||
.env
|
|
||||||
.env.production
|
|
||||||
|
|
||||||
# macOS-specific files
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# jetbrains setting folder
|
|
||||||
.idea/
|
|
||||||
4
apps/blog/.vscode/extensions.json
vendored
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"],
|
|
||||||
"unwantedRecommendations": []
|
|
||||||
}
|
|
||||||
11
apps/blog/.vscode/launch.json
vendored
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"command": "./node_modules/.bin/astro dev",
|
|
||||||
"name": "Development server",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Astro Starter Kit: Blog
|
|
||||||
|
|
||||||
```sh
|
|
||||||
bun create astro@latest -- --template blog
|
|
||||||
```
|
|
||||||
|
|
||||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/blog)
|
|
||||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/blog)
|
|
||||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/blog/devcontainer.json)
|
|
||||||
|
|
||||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Features:
|
|
||||||
|
|
||||||
- ✅ Minimal styling (make it your own!)
|
|
||||||
- ✅ 100/100 Lighthouse performance
|
|
||||||
- ✅ SEO-friendly with canonical URLs and OpenGraph data
|
|
||||||
- ✅ Sitemap support
|
|
||||||
- ✅ RSS Feed support
|
|
||||||
- ✅ Markdown & MDX support
|
|
||||||
|
|
||||||
## 🚀 Project Structure
|
|
||||||
|
|
||||||
Inside of your Astro project, you'll see the following folders and files:
|
|
||||||
|
|
||||||
```text
|
|
||||||
├── public/
|
|
||||||
├── src/
|
|
||||||
│ ├── components/
|
|
||||||
│ ├── content/
|
|
||||||
│ ├── layouts/
|
|
||||||
│ └── pages/
|
|
||||||
├── astro.config.mjs
|
|
||||||
├── README.md
|
|
||||||
├── package.json
|
|
||||||
└── tsconfig.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
|
||||||
|
|
||||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
|
||||||
|
|
||||||
The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more.
|
|
||||||
|
|
||||||
Any static assets, like images, can be placed in the `public/` directory.
|
|
||||||
|
|
||||||
## 🧞 Commands
|
|
||||||
|
|
||||||
All commands are run from the root of the project, from a terminal:
|
|
||||||
|
|
||||||
| Command | Action |
|
|
||||||
| :------------------------ | :----------------------------------------------- |
|
|
||||||
| `bun install` | Installs dependencies |
|
|
||||||
| `bun dev` | Starts local dev server at `localhost:4321` |
|
|
||||||
| `bun build` | Build your production site to `./dist/` |
|
|
||||||
| `bun preview` | Preview your build locally, before deploying |
|
|
||||||
| `bun astro ...` | Run CLI commands like `astro add`, `astro check` |
|
|
||||||
| `bun astro -- --help` | Get help using the Astro CLI |
|
|
||||||
|
|
||||||
## 👀 Want to learn more?
|
|
||||||
|
|
||||||
Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
|
||||||
|
|
||||||
## Credit
|
|
||||||
|
|
||||||
This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/).
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import mdx from '@astrojs/mdx';
|
|
||||||
import sitemap from '@astrojs/sitemap';
|
|
||||||
|
|
||||||
import solidJs from '@astrojs/solid-js';
|
|
||||||
|
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
export default defineConfig({
|
|
||||||
site: 'https://example.com',
|
|
||||||
integrations: [mdx(), sitemap(), solidJs()],
|
|
||||||
|
|
||||||
vite: {
|
|
||||||
plugins: [tailwindcss()],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
1022
apps/blog/bun.lock
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "module",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "astro dev",
|
|
||||||
"build": "astro build",
|
|
||||||
"preview": "astro preview",
|
|
||||||
"astro": "astro"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/mdx": "^4.2.6",
|
|
||||||
"@astrojs/rss": "^4.0.11",
|
|
||||||
"@astrojs/sitemap": "^3.4.0",
|
|
||||||
"@astrojs/solid-js": "^5.0.10",
|
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
|
||||||
"astro": "^5.7.13",
|
|
||||||
"solid-js": "^1.9.7",
|
|
||||||
"tailwindcss": "^4.1.7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 21 KiB |
@@ -1,9 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
|
||||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
|
||||||
<style>
|
|
||||||
path { fill: #000; }
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
path { fill: #FFF; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 157 KiB |
@@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
// Import the global.css file here so that it is included on
|
|
||||||
// all pages through the use of the <BaseHead /> component.
|
|
||||||
import '../styles/global.css';
|
|
||||||
import { SITE_TITLE } from '../consts';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
image?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
|
||||||
|
|
||||||
const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Global Metadata -->
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
|
||||||
<link
|
|
||||||
rel="alternate"
|
|
||||||
type="application/rss+xml"
|
|
||||||
title={SITE_TITLE}
|
|
||||||
href={new URL('rss.xml', Astro.site)}
|
|
||||||
/>
|
|
||||||
<meta name="generator" content={Astro.generator} />
|
|
||||||
|
|
||||||
<!-- Font preloads -->
|
|
||||||
<link rel="preload" href="/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin />
|
|
||||||
<link rel="preload" href="/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin />
|
|
||||||
|
|
||||||
<!-- Canonical URL -->
|
|
||||||
<link rel="canonical" href={canonicalURL} />
|
|
||||||
|
|
||||||
<!-- Primary Meta Tags -->
|
|
||||||
<title>{title}</title>
|
|
||||||
<meta name="title" content={title} />
|
|
||||||
<meta name="description" content={description} />
|
|
||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:url" content={Astro.url} />
|
|
||||||
<meta property="og:title" content={title} />
|
|
||||||
<meta property="og:description" content={description} />
|
|
||||||
<meta property="og:image" content={new URL(image, Astro.url)} />
|
|
||||||
|
|
||||||
<!-- Twitter -->
|
|
||||||
<meta property="twitter:card" content="summary_large_image" />
|
|
||||||
<meta property="twitter:url" content={Astro.url} />
|
|
||||||
<meta property="twitter:title" content={title} />
|
|
||||||
<meta property="twitter:description" content={description} />
|
|
||||||
<meta property="twitter:image" content={new URL(image, Astro.url)} />
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
---
|
|
||||||
import "../styles/global.css"
|
|
||||||
const today = new Date();
|
|
||||||
---
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
|
||||||
<span class="hover:text-primary-500 transition-colors duration-200">
|
|
||||||
<a rel="noreferrer" href="https://nestri.io/terms" >Terms of Service</a></span>
|
|
||||||
<span class="text-gray-400 dark:text-gray-600">•</span>
|
|
||||||
<span class="hover:text-primary-500 transition-colors duration-200">
|
|
||||||
<a href="https://nestri.io/privacy">Privacy Policy</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6 w-full justify-center flex items-center space-x-4">
|
|
||||||
<a href="https://discord.gg/6um5K6jrYj" target="_blank">
|
|
||||||
<span class="sr-only">Join our Discord Server</span>
|
|
||||||
<svg width="59" height="44" viewBox="0 0 59 44" aria-hidden="true" astro-icon="social/discord" style="height:28px">
|
|
||||||
<path d="M37.1937 0C36.6265 1.0071 36.1172 2.04893 35.6541 3.11392C31.2553 2.45409 26.7754 2.45409 22.365 3.11392C21.9136 2.04893 21.3926 1.0071 20.8254 0C16.6928 0.70613 12.6644 1.94475 8.84436 3.69271C1.27372 14.9098 -0.775214 25.8374 0.243466 36.6146C4.67704 39.8906 9.6431 42.391 14.9333 43.9884C16.1256 42.391 17.179 40.6893 18.0819 38.9182C16.3687 38.2815 14.7133 37.4828 13.1274 36.5567C13.5442 36.2557 13.9493 35.9432 14.3429 35.6422C23.6384 40.0179 34.4039 40.0179 43.711 35.6422C44.1046 35.9663 44.5097 36.2789 44.9264 36.5567C43.3405 37.4943 41.6852 38.2815 39.9604 38.9298C40.8633 40.7009 41.9167 42.4025 43.109 44C48.3992 42.4025 53.3653 39.9137 57.7988 36.6377C59.0027 24.1358 55.7383 13.3007 49.1748 3.70429C45.3663 1.95633 41.3379 0.717706 37.2053 0.0231518L37.1937 0ZM19.3784 29.9816C16.5192 29.9816 14.1461 27.3886 14.1461 24.1821C14.1461 20.9755 16.4266 18.371 19.3669 18.371C22.3071 18.371 24.6455 20.9871 24.5992 24.1821C24.5529 27.377 22.2956 29.9816 19.3784 29.9816ZM38.6639 29.9816C35.7931 29.9816 33.4431 27.3886 33.4431 24.1821C33.4431 20.9755 35.7236 18.371 38.6639 18.371C41.6042 18.371 43.9309 20.9871 43.8846 24.1821C43.8383 27.377 41.581 29.9816 38.6639 29.9816Z" fill="white"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/nestrilabs/nestri/" target="_blank">
|
|
||||||
<span class="sr-only">Go to Nestri's GitHub repo</span>
|
|
||||||
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/github"
|
|
||||||
><path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
<style>
|
|
||||||
footer {
|
|
||||||
padding: 2em 1em 6em 1em;
|
|
||||||
background: linear-gradient(var(--gray-gradient)) no-repeat;
|
|
||||||
color: rgb(var(--gray));
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.social-links {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 1em;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
.social-links a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: rgb(var(--gray));
|
|
||||||
}
|
|
||||||
.social-links a:hover {
|
|
||||||
color: rgb(var(--gray-dark));
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
interface Props {
|
|
||||||
date: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { date } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<time datetime={date.toISOString()}>
|
|
||||||
{
|
|
||||||
date.toLocaleDateString('en-us', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</time>
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
---
|
|
||||||
import HeaderLink from './HeaderLink.astro';
|
|
||||||
import { SITE_TITLE } from '../consts';
|
|
||||||
import "../styles/global.css";
|
|
||||||
---
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<nav>
|
|
||||||
<h2><a href="/">{SITE_TITLE}</a></h2>
|
|
||||||
<div class="internal-links">
|
|
||||||
<HeaderLink href="https://nestri.io/">Nestri Home</HeaderLink>
|
|
||||||
<HeaderLink href="/blog">Blog</HeaderLink>
|
|
||||||
<HeaderLink href="https://nestri.io/about">About us</HeaderLink>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<style>
|
|
||||||
header {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 1em;
|
|
||||||
border-bottom: solid;
|
|
||||||
box-: 0 2px 8px rgba(var(--black), 5%);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 a,
|
|
||||||
h2 a.active {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
nav {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
nav a {
|
|
||||||
padding: 1em 0.5em;
|
|
||||||
color: var(--black);
|
|
||||||
border-bottom: 4px solid transparent;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
nav a.active {
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom-color: var(--accent);
|
|
||||||
}
|
|
||||||
.social-links,
|
|
||||||
.social-links a {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
.social-links {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
import type { HTMLAttributes } from 'astro/types';
|
|
||||||
|
|
||||||
type Props = HTMLAttributes<'a'>;
|
|
||||||
|
|
||||||
const { href, class: className, ...props } = Astro.props;
|
|
||||||
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, '');
|
|
||||||
const subpath = pathname.match(/[^\/]+/g);
|
|
||||||
const isActive = href === pathname || href === '/' + (subpath?.[0] || '');
|
|
||||||
---
|
|
||||||
|
|
||||||
<a href={href} class:list={[className, { active: isActive }]} {...props}>
|
|
||||||
<slot />
|
|
||||||
</a>
|
|
||||||
<style>
|
|
||||||
a {
|
|
||||||
display: inline-block;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
a.active {
|
|
||||||
font-weight: bolder;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// Place any global data in this file.
|
|
||||||
// You can import this data from anywhere in your site by using the `import` keyword.
|
|
||||||
|
|
||||||
export const SITE_TITLE = 'Nestri Blog';
|
|
||||||
export const SITE_DESCRIPTION = 'Welcome to Nestri\'s Blog - This Blog is about the current status of and about intresting facts about Nestri';
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { glob } from 'astro/loaders';
|
|
||||||
import { defineCollection, z } from 'astro:content';
|
|
||||||
|
|
||||||
const blog = defineCollection({
|
|
||||||
// Load Markdown and MDX files in the `src/content/blog/` directory.
|
|
||||||
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
|
|
||||||
// Type-check frontmatter using a schema
|
|
||||||
schema: z.object({
|
|
||||||
title: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
// Transform string to Date object
|
|
||||||
pubDate: z.coerce.date(),
|
|
||||||
updatedDate: z.coerce.date().optional(),
|
|
||||||
heroImage: z.string().optional(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const collections = { blog };
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'First post'
|
|
||||||
description: 'Lorem ipsum dolor sit amet'
|
|
||||||
pubDate: 'Jul 08 2022'
|
|
||||||
heroImage: '/blog-placeholder-3.jpg'
|
|
||||||
---
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
|
|
||||||
|
|
||||||
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
|
|
||||||
|
|
||||||
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
|
|
||||||
|
|
||||||
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
|
|
||||||
|
|
||||||
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'Technical Deep Dive into Latency'
|
|
||||||
description: "Why It's High and How to Reduce It"
|
|
||||||
pubDate: 'May 18 2025'
|
|
||||||
heroImage: '/pexels-brett-sayles-2881224.jpg'
|
|
||||||
---
|
|
||||||
|
|
||||||
### Why It's High and How to Reduce It
|
|
||||||
|
|
||||||
First, let's start with the basics of the Internet.
|
|
||||||
|
|
||||||
The Internet connects clients and servers. Webpages primarily use the Application Layer protocol HTTP(S) to communicate with servers. HTTP is widely adopted for various applications, including mobile apps and other services requiring server communication.
|
|
||||||
|
|
||||||
There are also other client protocols like WebRTC (Web Real-Time Communication), which mainly powers streaming services needing a back channel. Nestri utilizes WebRTC, and we'll delve deeper into that later.
|
|
||||||
|
|
||||||
Imagine using a client protocol like WebRTC to send messages. Common formats for these messages include XML, HTML, or JSON.
|
|
||||||
|
|
||||||
While HTML contains significant duplicate symbols (e.g., `<a href="example.com">Some Link</a> <a href="example.com/subpage">Some nested Link</a>`), the modern web employs techniques to reduce its size. For instance, using modern zipping algorithms like gzip, this data can be compressed, resulting in a smaller size for transmission over the HTTP protocol.
|
|
||||||
|
|
||||||
In computer science, the more dense the information in a message (achieved through compression, for example), the higher its message entropy. Therefore, sending messages with high entropy is beneficial as it allows for the transfer of more information in a smaller package. Pure HTTP has relatively low entropy, similar to XML. JSON offers higher entropy, which can be further increased by removing whitespace and shortening attribute names. However, in modern client-server applications, JSON is often compressed.
|
|
||||||
|
|
||||||
So, we compress JSON traffic for efficiency. Have you ever compressed a large file? Modern systems make this process incredibly fast! But this requires computing power on both the client and server sides, which directly influences latency.
|
|
||||||
|
|
||||||
"Well, if I have a fiber connection, I don't need to worry about that..."
|
|
||||||
|
|
||||||
While a fiber connection offers significant bandwidth, this statement is somewhat misleading.
|
|
||||||
|
|
||||||
Latency also depends on your local network. A modern and stable Wi-Fi connection might seem sufficient, but the physical layer of the internet also contributes to latency. Wireless protocols, in particular, operate on a shared medium – the air. This medium is utilized by various devices, commonly on frequencies around 2.4 or 5 GHz. This spectrum is divided among all these devices. Mechanisms like scheduling and signal modulation are used to manage this shared resource. In essence, to avoid a deeper dive into wireless communication, a wired connection is generally superior to a wireless connection due to the absence of a shared physical medium.
|
|
||||||
|
|
||||||
Okay, but what about Ethernet or fiber cables? Aren't we sharing those as well, with multiple applications or other internet users?
|
|
||||||
|
|
||||||
Yes, this also impacts latency. If many users in your local area are utilizing the same uplinks to a backbone (a high-speed part of the internet), you'll have to share that bandwidth. While fiber optic cables have substantial capacity due to advanced modulation techniques, consider the journey these data packets undertake across the internet.
|
|
||||||
|
|
||||||
Sometimes, if a data center is located nearby, your connection will involve fewer routers (fewer hops) between you and the server. Fewer hops generally translate to lower latency. Each router needs to queue your messages and determine the next destination. Modern routing protocols facilitate this process. However, even routers have to process messages in their queues. Thus, higher message entropy means fewer or smaller packets need to be sent.
|
|
||||||
|
|
||||||
What happens when your messages are too large for transmission? They are split into multiple parts and sent using protocols like TCP. TCP ensures reliable packet exchange by retransmitting any packets that are likely lost during internet transit. Packet loss can occur if a router's queue overflows, forcing it to drop packets, potentially prioritizing other traffic. This retransmission significantly increases latency as a packet might need to be sent multiple times.
|
|
||||||
|
|
||||||
UDP offers a different approach: it sends all packets without the overhead of retransmission. In this case, the application protocol is responsible for handling any lost packets. Fortunately, there's an application protocol that manages this quite effectively: WebRTC.
|
|
||||||
|
|
||||||
WebRTC is an open-source project providing APIs for real-time communication of audio, video, and generic data between peers via a browser. It leverages protocols like ICE, STUN, and TURN to handle NAT traversal and establish peer-to-peer connections, enabling low-latency media streaming and data exchange directly within web applications.
|
|
||||||
|
|
||||||
Sending raw video streams over WebRTC is inefficient; they require compression using modern codecs. A GPU is the optimal choice for this task because it has dedicated hardware (hardware encoder) to accelerate video encoding, significantly speeding up the process compared to software encoding on a CPU. Therefore, your GPU also plays a crucial role in reducing latency during video encoding and decoding.
|
|
||||||
|
|
||||||
So, why is all this relevant to Nestri?
|
|
||||||
|
|
||||||
We aim to deliver a cutting-edge, low-latency cloud gaming experience. Here's what we've implemented to combat bad latency:
|
|
||||||
|
|
||||||
**1. Reducing Mouse and Keyboard Latency**
|
|
||||||
1. Reduce package size by using the Protobuf protocol instead of JSON.
|
|
||||||
2. Avoid wasting compute power by not compressing these already optimized messages.
|
|
||||||
3. Minimize message flooding by bundling multiple mouse events into fewer messages through aggregation.
|
|
||||||
4. Implement all of this within WebRTC for a super lightweight communication over UDP.
|
|
||||||
|
|
||||||
**2. Reducing Video Latency**
|
|
||||||
1. Utilize cutting-edge encoder-decoders on a GPU instead of a CPU.
|
|
||||||
|
|
||||||
**3. Reducing Network Latency in the Backbone**
|
|
||||||
1. Bring servers closer to users to reduce the hop count.
|
|
||||||
|
|
||||||
Here's a glimpse of the results of these improvements, comparing the experience before and after implementation:
|
|
||||||
|
|
||||||
](https://fs.dathorse.com/w/ad2bee7e322b942491044fcffcccc899)
|
|
||||||
**Latency Test and comparison to the old Nestri**
|
|
||||||
|
|
||||||
Did you enjoy this blog post? Join our Discord and share your thoughts!
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'Markdown Style Guide'
|
|
||||||
description: 'Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.'
|
|
||||||
pubDate: 'Jun 19 2024'
|
|
||||||
heroImage: '/blog-placeholder-1.jpg'
|
|
||||||
---
|
|
||||||
|
|
||||||
Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.
|
|
||||||
|
|
||||||
## Headings
|
|
||||||
|
|
||||||
The following HTML `<h1>`—`<h6>` elements represent six levels of section headings. `<h1>` is the highest section level while `<h6>` is the lowest.
|
|
||||||
|
|
||||||
# H1
|
|
||||||
|
|
||||||
## H2
|
|
||||||
|
|
||||||
### H3
|
|
||||||
|
|
||||||
#### H4
|
|
||||||
|
|
||||||
##### H5
|
|
||||||
|
|
||||||
###### H6
|
|
||||||
|
|
||||||
## Paragraph
|
|
||||||
|
|
||||||
Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.
|
|
||||||
|
|
||||||
Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.
|
|
||||||
|
|
||||||
## Images
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||

|
|
||||||
```
|
|
||||||
|
|
||||||
### Output
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Blockquotes
|
|
||||||
|
|
||||||
The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations.
|
|
||||||
|
|
||||||
### Blockquote without attribution
|
|
||||||
|
|
||||||
#### Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
|
|
||||||
> **Note** that you can use _Markdown syntax_ within a blockquote.
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
|
|
||||||
> **Note** that you can use _Markdown syntax_ within a blockquote.
|
|
||||||
|
|
||||||
### Blockquote with attribution
|
|
||||||
|
|
||||||
#### Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
> Don't communicate by sharing memory, share memory by communicating.<br>
|
|
||||||
> — <cite>Rob Pike[^1]</cite>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
> Don't communicate by sharing memory, share memory by communicating.<br>
|
|
||||||
> — <cite>Rob Pike[^1]</cite>
|
|
||||||
|
|
||||||
[^1]: The above quote is excerpted from Rob Pike's [talk](https://www.youtube.com/watch?v=PAAkCSZUG1c) during Gopherfest, November 18, 2015.
|
|
||||||
|
|
||||||
## Tables
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
| Italics | Bold | Code |
|
|
||||||
| --------- | -------- | ------ |
|
|
||||||
| _italics_ | **bold** | `code` |
|
|
||||||
```
|
|
||||||
|
|
||||||
### Output
|
|
||||||
|
|
||||||
| Italics | Bold | Code |
|
|
||||||
| --------- | -------- | ------ |
|
|
||||||
| _italics_ | **bold** | `code` |
|
|
||||||
|
|
||||||
## Code Blocks
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
we can use 3 backticks ``` in new line and write snippet and close with 3 backticks on new line and to highlight language specific syntax, write one word of language name after first 3 backticks, for eg. html, javascript, css, markdown, typescript, txt, bash
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
```html
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Example HTML5 Document</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>Test</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
### Output
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Example HTML5 Document</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>Test</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
## List Types
|
|
||||||
|
|
||||||
### Ordered List
|
|
||||||
|
|
||||||
#### Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
1. First item
|
|
||||||
2. Second item
|
|
||||||
3. Third item
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
1. First item
|
|
||||||
2. Second item
|
|
||||||
3. Third item
|
|
||||||
|
|
||||||
### Unordered List
|
|
||||||
|
|
||||||
#### Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
- List item
|
|
||||||
- Another item
|
|
||||||
- And another item
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
- List item
|
|
||||||
- Another item
|
|
||||||
- And another item
|
|
||||||
|
|
||||||
### Nested list
|
|
||||||
|
|
||||||
#### Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
- Fruit
|
|
||||||
- Apple
|
|
||||||
- Orange
|
|
||||||
- Banana
|
|
||||||
- Dairy
|
|
||||||
- Milk
|
|
||||||
- Cheese
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
- Fruit
|
|
||||||
- Apple
|
|
||||||
- Orange
|
|
||||||
- Banana
|
|
||||||
- Dairy
|
|
||||||
- Milk
|
|
||||||
- Cheese
|
|
||||||
|
|
||||||
## Other Elements — abbr, sub, sup, kbd, mark
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.
|
|
||||||
|
|
||||||
H<sub>2</sub>O
|
|
||||||
|
|
||||||
X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>
|
|
||||||
|
|
||||||
Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.
|
|
||||||
|
|
||||||
Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Output
|
|
||||||
|
|
||||||
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.
|
|
||||||
|
|
||||||
H<sub>2</sub>O
|
|
||||||
|
|
||||||
X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>
|
|
||||||
|
|
||||||
Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.
|
|
||||||
|
|
||||||
Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'Second post'
|
|
||||||
description: 'Lorem ipsum dolor sit amet'
|
|
||||||
pubDate: 'Jul 15 2022'
|
|
||||||
heroImage: '/blog-placeholder-4.jpg'
|
|
||||||
---
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
|
|
||||||
|
|
||||||
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
|
|
||||||
|
|
||||||
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
|
|
||||||
|
|
||||||
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
|
|
||||||
|
|
||||||
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'Third post'
|
|
||||||
description: 'Lorem ipsum dolor sit amet'
|
|
||||||
pubDate: 'Jul 22 2022'
|
|
||||||
heroImage: '/blog-placeholder-2.jpg'
|
|
||||||
---
|
|
||||||
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
|
|
||||||
|
|
||||||
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
|
|
||||||
|
|
||||||
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
|
|
||||||
|
|
||||||
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
|
|
||||||
|
|
||||||
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'Using MDX'
|
|
||||||
description: 'Lorem ipsum dolor sit amet'
|
|
||||||
pubDate: 'Jun 01 2024'
|
|
||||||
heroImage: '/blog-placeholder-5.jpg'
|
|
||||||
---
|
|
||||||
|
|
||||||
This theme comes with the [@astrojs/mdx](https://docs.astro.build/en/guides/integrations-guide/mdx/) integration installed and configured in your `astro.config.mjs` config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file.
|
|
||||||
|
|
||||||
## Why MDX?
|
|
||||||
|
|
||||||
MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax. This unlocks the ability to [mix JavaScript and UI Components into your Markdown content](https://docs.astro.build/en/guides/markdown-content/#mdx-features) for things like interactive charts or alerts.
|
|
||||||
|
|
||||||
If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
Here is how you import and use a UI component inside of MDX.
|
|
||||||
When you open this page in the browser, you should see the clickable button below.
|
|
||||||
|
|
||||||
import HeaderLink from '../../components/HeaderLink.astro';
|
|
||||||
|
|
||||||
<HeaderLink href="#" onclick="alert('clicked!')">
|
|
||||||
Embedded component in MDX
|
|
||||||
</HeaderLink>
|
|
||||||
|
|
||||||
## More Links
|
|
||||||
|
|
||||||
- [MDX Syntax Documentation](https://mdxjs.com/docs/what-is-mdx)
|
|
||||||
- [Astro Usage Documentation](https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages)
|
|
||||||
- **Note:** [Client Directives](https://docs.astro.build/en/reference/directives-reference/#client-directives) are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default.
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
---
|
|
||||||
import type { CollectionEntry } from 'astro:content';
|
|
||||||
import BaseHead from '../components/BaseHead.astro';
|
|
||||||
import Header from '../components/Header.astro';
|
|
||||||
import Footer from '../components/Footer.astro';
|
|
||||||
import FormattedDate from '../components/FormattedDate.astro';
|
|
||||||
import "../styles/global.css"
|
|
||||||
|
|
||||||
type Props = CollectionEntry<'blog'>['data'];
|
|
||||||
|
|
||||||
const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<BaseHead title={title} description={description} />
|
|
||||||
<style>
|
|
||||||
main {
|
|
||||||
width: calc(100% - 2em);
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.hero-image {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.hero-image img {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
.prose {
|
|
||||||
width: 720px;
|
|
||||||
max-width: calc(100% - 2em);
|
|
||||||
margin: auto;
|
|
||||||
padding: 1em;
|
|
||||||
color: rgb(var(--gray-dark));
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
padding: 1em 0;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.title h1 {
|
|
||||||
margin: 0 0 0.5em 0;
|
|
||||||
}
|
|
||||||
.date {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
color: rgb(var(--gray));
|
|
||||||
}
|
|
||||||
.last-updated-on {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<Header />
|
|
||||||
<main>
|
|
||||||
<article>
|
|
||||||
|
|
||||||
<div class="grid gap-8 items-start justify-center">
|
|
||||||
<div class="relative group">
|
|
||||||
<div class="absolute -inset-0.5 bg-radial-gradient opacity-40 group-hover:opacity-80 transition duration-1000 group-hover:duration-200 animate-tilt" />
|
|
||||||
<div class="relative bg-black rounded-lg leading-none flex items-center divide-x divide-gray-600">
|
|
||||||
{heroImage && <img width={1020} height={510} src={heroImage} alt="" />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="prose">
|
|
||||||
<div class="title">
|
|
||||||
<div class="date">
|
|
||||||
<FormattedDate date={pubDate} />
|
|
||||||
{
|
|
||||||
updatedDate && (
|
|
||||||
<div class="last-updated-on">
|
|
||||||
Last updated on <FormattedDate date={updatedDate} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<h1>{title}</h1>
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</main>
|
|
||||||
<Footer />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
---
|
|
||||||
import Layout from '../layouts/BlogPost.astro';
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout
|
|
||||||
title="About Me"
|
|
||||||
description="Lorem ipsum dolor sit amet"
|
|
||||||
pubDate={new Date('August 08 2021')}
|
|
||||||
heroImage="/blog-placeholder-about.jpg"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
|
||||||
labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo
|
|
||||||
viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam
|
|
||||||
adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus
|
|
||||||
et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus
|
|
||||||
vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque
|
|
||||||
sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non
|
|
||||||
tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non
|
|
||||||
blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna
|
|
||||||
porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis
|
|
||||||
massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc.
|
|
||||||
Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis
|
|
||||||
bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra
|
|
||||||
massa massa ultricies mi.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl
|
|
||||||
suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet
|
|
||||||
nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae
|
|
||||||
turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem
|
|
||||||
dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat
|
|
||||||
semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus
|
|
||||||
vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum
|
|
||||||
facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam
|
|
||||||
vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla
|
|
||||||
urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper
|
|
||||||
viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc
|
|
||||||
scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur
|
|
||||||
gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus
|
|
||||||
pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim
|
|
||||||
blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id
|
|
||||||
cursus metus aliquam eleifend mi.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta
|
|
||||||
nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam
|
|
||||||
tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci
|
|
||||||
ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar
|
|
||||||
proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.
|
|
||||||
</p>
|
|
||||||
</Layout>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
import { type CollectionEntry, getCollection } from 'astro:content';
|
|
||||||
import BlogPost from '../../layouts/BlogPost.astro';
|
|
||||||
import { render } from 'astro:content';
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
|
||||||
const posts = await getCollection('blog');
|
|
||||||
return posts.map((post) => ({
|
|
||||||
params: { slug: post.id },
|
|
||||||
props: post,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
type Props = CollectionEntry<'blog'>;
|
|
||||||
|
|
||||||
const post = Astro.props;
|
|
||||||
const { Content } = await render(post);
|
|
||||||
---
|
|
||||||
|
|
||||||
<BlogPost {...post.data}>
|
|
||||||
<Content />
|
|
||||||
</BlogPost>
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
---
|
|
||||||
import BaseHead from '../../components/BaseHead.astro';
|
|
||||||
import Header from '../../components/Header.astro';
|
|
||||||
import Footer from '../../components/Footer.astro';
|
|
||||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
|
|
||||||
import { getCollection } from 'astro:content';
|
|
||||||
import FormattedDate from '../../components/FormattedDate.astro';
|
|
||||||
import "../../styles/global.css"
|
|
||||||
|
|
||||||
const posts = (await getCollection('blog')).sort(
|
|
||||||
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
|
|
||||||
);
|
|
||||||
---
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
|
|
||||||
<style>
|
|
||||||
main {
|
|
||||||
width: 960px;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 2rem;
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
ul li {
|
|
||||||
width: calc(50% - 1rem);
|
|
||||||
}
|
|
||||||
ul li * {
|
|
||||||
text-decoration: none;
|
|
||||||
transition: 0.2s ease;
|
|
||||||
}
|
|
||||||
ul li:first-child {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
ul li:first-child img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
ul li:first-child .title {
|
|
||||||
font-size: 2.369rem;
|
|
||||||
}
|
|
||||||
ul li img {
|
|
||||||
|
|
||||||
}
|
|
||||||
ul li a {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
margin: 0;
|
|
||||||
color: #d9d9d9;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.date {
|
|
||||||
margin: 0;
|
|
||||||
color: #c0c0c0;
|
|
||||||
}
|
|
||||||
ul li a:hover h4,
|
|
||||||
ul li a:hover .date {
|
|
||||||
color: #f2f2f2;
|
|
||||||
}
|
|
||||||
ul a:hover img {
|
|
||||||
box-shadow: var(--box-shadow);
|
|
||||||
}
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
ul {
|
|
||||||
gap: 0.5em;
|
|
||||||
}
|
|
||||||
ul li {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
ul li:first-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
ul li:first-child .title {
|
|
||||||
font-size: 1.563em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<Header />
|
|
||||||
<main>
|
|
||||||
<section>
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
posts.map((post) => (
|
|
||||||
<li>
|
|
||||||
<a href={`/blog/${post.id}/`}>
|
|
||||||
|
|
||||||
<div class="grid gap-8 items-start justify-center">
|
|
||||||
<div class="relative group">
|
|
||||||
<div class="absolute -inset-0.5 bg-radial-gradient opacity-0 group-hover:opacity-80 transition duration-1000 group-hover:duration-200 animate-tilt" />
|
|
||||||
<div class="relative bg-black rounded-lg leading-none flex items-center divide-x divide-gray-600">
|
|
||||||
<img width={720} height={360} src={post.data.heroImage} alt="" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h4 class="title py-4">{post.data.title}</h4>
|
|
||||||
<p class="date">
|
|
||||||
<FormattedDate date={post.data.pubDate} />
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
<Footer />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
import BaseHead from '../components/BaseHead.astro';
|
|
||||||
import Header from '../components/Header.astro';
|
|
||||||
import Footer from '../components/Footer.astro';
|
|
||||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
|
|
||||||
---
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<Header />
|
|
||||||
<main>
|
|
||||||
<h1>🧑🚀 Hello, Astronaut!</h1>
|
|
||||||
<p>
|
|
||||||
Welcome to the official <a href="https://astro.build/">Astro</a> blog starter template. This
|
|
||||||
template serves as a lightweight, minimally-styled starting point for anyone looking to build
|
|
||||||
a personal website, blog, or portfolio with Astro.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This template comes with a few integrations already configured in your
|
|
||||||
<code>astro.config.mjs</code> file. You can customize your setup with
|
|
||||||
<a href="https://astro.build/integrations">Astro Integrations</a> to add tools like Tailwind,
|
|
||||||
React, or Vue to your project.
|
|
||||||
</p>
|
|
||||||
<p>Here are a few ideas on how to get started with the template:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Edit this page in <code>src/pages/index.astro</code></li>
|
|
||||||
<li>Edit the site header items in <code>src/components/Header.astro</code></li>
|
|
||||||
<li>Add your name to the footer in <code>src/components/Footer.astro</code></li>
|
|
||||||
<li>Check out the included blog posts in <code>src/content/blog/</code></li>
|
|
||||||
<li>Customize the blog post page layout in <code>src/layouts/BlogPost.astro</code></li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
Have fun! If you get stuck, remember to
|
|
||||||
<a href="https://docs.astro.build/">read the docs</a>
|
|
||||||
or <a href="https://astro.build/chat">join us on Discord</a> to ask questions.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Looking for a blog template with a bit more personality? Check out
|
|
||||||
<a href="https://github.com/Charca/astro-blog-template">astro-blog-template</a>
|
|
||||||
by <a href="https://twitter.com/Charca">Maxi Ferreira</a>.
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
<Footer />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import rss from '@astrojs/rss';
|
|
||||||
import { getCollection } from 'astro:content';
|
|
||||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
|
|
||||||
|
|
||||||
export async function GET(context) {
|
|
||||||
const posts = await getCollection('blog');
|
|
||||||
return rss({
|
|
||||||
title: SITE_TITLE,
|
|
||||||
description: SITE_DESCRIPTION,
|
|
||||||
site: context.site,
|
|
||||||
items: posts.map((post) => ({
|
|
||||||
...post.data,
|
|
||||||
link: `/blog/${post.id}/`,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
/*
|
|
||||||
The CSS in this style tag is based off of Bear Blog's default CSS.
|
|
||||||
https://github.com/HermanMartinus/bearblog/blob/297026a877bc2ab2b3bdfbd6b9f7961c350917dd/templates/styles/blog/default.css
|
|
||||||
License MIT: https://github.com/HermanMartinus/bearblog/blob/master/LICENSE.md
|
|
||||||
*/
|
|
||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
|
|
||||||
:root {
|
|
||||||
/*--accent: rgb(255, 79, 1);*/
|
|
||||||
/*--accent-dark: #fafafa;*/
|
|
||||||
/*--black: 15, 18, 25;*/
|
|
||||||
/*--gray: 96, 1, 159;*/
|
|
||||||
/*--gray-light: 82, 82, 82;*/
|
|
||||||
--gray-dark: 250, 250, 250;
|
|
||||||
--gray-gradient: rgba(var(--gray-light), 50%), #fff;
|
|
||||||
--box-shadow:
|
|
||||||
0 2px 6px rgba(var(--gray), 25%), 0 8px 24px rgba(var(--gray), 33%),
|
|
||||||
0 16px 32px rgba(var(--gray), 33%);
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Atkinson';
|
|
||||||
src: url('/fonts/atkinson-regular.woff') format('woff');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Atkinson';
|
|
||||||
src: url('/fonts/atkinson-bold.woff') format('woff');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Atkinson', sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
text-align: left;
|
|
||||||
background: linear-gradient(var(--gray-gradient)) no-repeat;
|
|
||||||
background-color: #171717;
|
|
||||||
background-size: 100% 600px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
color: rgb(var(--gray-dark));
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
width: 720px;
|
|
||||||
max-width: calc(100% - 2em);
|
|
||||||
margin: auto;
|
|
||||||
padding: 3em 1em;
|
|
||||||
}
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
color: rgb(var(--black));
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3.052em;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 2.441em;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 1.953em;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-size: 1.563em;
|
|
||||||
}
|
|
||||||
h5 {
|
|
||||||
font-size: 1.25em;
|
|
||||||
}
|
|
||||||
strong,
|
|
||||||
b {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
.prose p {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
padding: 2px 5px;
|
|
||||||
background-color: rgb(var(--gray-light));
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
padding: 1.5em;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
pre > code {
|
|
||||||
all: unset;
|
|
||||||
}
|
|
||||||
blockquote {
|
|
||||||
border-left: 4px solid var(--accent);
|
|
||||||
padding: 0 0 0 20px;
|
|
||||||
margin: 0px;
|
|
||||||
font-size: 1.333em;
|
|
||||||
}
|
|
||||||
hr {
|
|
||||||
border: none;
|
|
||||||
border-top: 1px solid rgb(var(--gray-light));
|
|
||||||
}
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
body {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sr-only {
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
position: absolute !important;
|
|
||||||
height: 1px;
|
|
||||||
width: 1px;
|
|
||||||
overflow: hidden;
|
|
||||||
/* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
|
|
||||||
clip: rect(1px 1px 1px 1px);
|
|
||||||
/* maybe deprecated but we need to support legacy browsers */
|
|
||||||
clip: rect(1px, 1px, 1px, 1px);
|
|
||||||
/* modern browsers, clip-path works inwards from each corner */
|
|
||||||
clip-path: inset(50%);
|
|
||||||
/* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-radial-gradient {
|
|
||||||
filter: blur(32px);
|
|
||||||
background-image: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
rgb(239, 118, 70),
|
|
||||||
rgb(251, 91, 88),
|
|
||||||
rgb(255, 61, 116),
|
|
||||||
rgb(249, 33, 149),
|
|
||||||
rgb(227, 34, 188),
|
|
||||||
rgb(181, 94, 230),
|
|
||||||
rgb(118, 128, 252),
|
|
||||||
rgb(0, 150, 255),
|
|
||||||
rgb(0, 183, 255),
|
|
||||||
rgb(0, 208, 242),
|
|
||||||
rgb(0, 227, 184),
|
|
||||||
rgb(70, 239, 111)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "astro/tsconfigs/strict",
|
|
||||||
"include": [
|
|
||||||
".astro/types.d.ts",
|
|
||||||
"**/*"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"jsxImportSource": "solid-js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# What is this?
|
|
||||||
|
|
||||||
This is the part of the docs dedicated for the team working on Nestri
|
|
||||||
@@ -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
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
title: 'Nestri Internals'
|
|
||||||
icon: heroicons-outline:bookmark-alt
|
|
||||||
3829
apps/docs/package-lock.json
generated
@@ -4,19 +4,19 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"nestri.dev": "nuxi dev",
|
"nestri.dev": "nuxi dev",
|
||||||
"build": "nuxi build --preset=cloudflare_pages",
|
"build": "nuxi build",
|
||||||
"generate": "nuxi generate",
|
"generate": "nuxi generate",
|
||||||
"preview": "nuxi preview",
|
"preview": "nuxi preview",
|
||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt-themes/docus": "latest",
|
"@nuxt-themes/docus": "latest",
|
||||||
"@nuxt/devtools": "^2.3.2",
|
"@nuxt/devtools": "^1.4.1",
|
||||||
"@nuxt/eslint-config": "^0.5.6",
|
"@nuxt/eslint-config": "^0.5.6",
|
||||||
"@nuxt/ui": "^2.19.2",
|
"@nuxt/ui": "^2.19.2",
|
||||||
"@nuxtjs/plausible": "^1.0.2",
|
"@nuxtjs/plausible": "^1.0.2",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.5",
|
||||||
"eslint": "^9.10.0",
|
"eslint": "^9.10.0",
|
||||||
"nuxt": "^3.16.1"
|
"nuxt": "^3.15.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,16 +35,14 @@
|
|||||||
"@builder.io/qwik": "^1.8.0",
|
"@builder.io/qwik": "^1.8.0",
|
||||||
"@builder.io/qwik-city": "^1.8.0",
|
"@builder.io/qwik-city": "^1.8.0",
|
||||||
"@builder.io/qwik-react": "0.5.0",
|
"@builder.io/qwik-react": "0.5.0",
|
||||||
"@fontsource-variable/bricolage-grotesque": "^5.0.1",
|
"@fontsource-variable/bricolage-grotesque": "^5.1.1",
|
||||||
"@fontsource/geist-mono": "^5.1.0",
|
|
||||||
"@fontsource/geist-sans": "^5.1.0",
|
|
||||||
"@fontsource-variable/mona-sans": "^5.0.1",
|
"@fontsource-variable/mona-sans": "^5.0.1",
|
||||||
"@modular-forms/qwik": "^0.29.0",
|
"@modular-forms/qwik": "^0.29.0",
|
||||||
"@nestri/input": "*",
|
"@nestri/input": "*",
|
||||||
"@nestri/libmoq": "*",
|
"@nestri/libmoq": "*",
|
||||||
"@nestri/sdk": "0.1.0-alpha.14",
|
"@nestri/sdk": "0.1.0-alpha.14",
|
||||||
"@nestri/ui": "*",
|
"@nestri/ui": "*",
|
||||||
"@openauthjs/openauth": "*",
|
"@openauthjs/openauth": "^0.2.6",
|
||||||
"@polar-sh/checkout": "^0.1.8",
|
"@polar-sh/checkout": "^0.1.8",
|
||||||
"@polar-sh/sdk": "^0.21.1",
|
"@polar-sh/sdk": "^0.21.1",
|
||||||
"@qwik-ui/headless": "^0.6.4",
|
"@qwik-ui/headless": "^0.6.4",
|
||||||
@@ -63,11 +61,10 @@
|
|||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"semver": "^7.7.1",
|
|
||||||
"typescript": "5.4.5",
|
"typescript": "5.4.5",
|
||||||
"undici": "*",
|
"undici": "*",
|
||||||
"valibot": "^0.42.1",
|
"valibot": "^0.42.1",
|
||||||
"vite": "6.0.15",
|
"vite": "5.4.12",
|
||||||
"vite-tsconfig-paths": "^4.2.1",
|
"vite-tsconfig-paths": "^4.2.1",
|
||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export default component$(() => {
|
|||||||
<div class="w-screen relative">
|
<div class="w-screen relative">
|
||||||
<HeroSection client:load>
|
<HeroSection client:load>
|
||||||
<div class="sm:w-full flex gap-3 justify-center pt-4 sm:flex-row flex-col w-auto items-center">
|
<div class="sm:w-full flex gap-3 justify-center pt-4 sm:flex-row flex-col w-auto items-center">
|
||||||
<Link href="https://discord.gg/6um5K6jrYj" prefetch={false} class="flex ring-2 ring-primary-500 font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
<Link href="/auth/login" prefetch={false} class="flex ring-2 ring-primary-500 font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||||
Join our Discord
|
Get early access
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/links/github" prefetch={false} class="sm:flex text-sm sm:text-base hidden font-bricolage items-center gap-2 rounded-full font-semibold text-gray-900/70 dark:text-gray-100/70 bg-white dark:bg-black px-5 py-4 ring-2 ring-gray-300 dark:ring-gray-700 transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
<Link href="/links/github" prefetch={false} class="sm:flex text-sm sm:text-base hidden font-bricolage items-center gap-2 rounded-full font-semibold text-gray-900/70 dark:text-gray-100/70 bg-white dark:bg-black px-5 py-4 ring-2 ring-gray-300 dark:ring-gray-700 transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="h-5 w-5 fill-content3-light"><path fill-rule="evenodd" d="M4.25 5.5a.75.75 0 00-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 00.75-.75v-4a.75.75 0 011.5 0v4A2.25 2.25 0 0112.75 17h-8.5A2.25 2.25 0 012 14.75v-8.5A2.25 2.25 0 014.25 4h5a.75.75 0 010 1.5h-5z" clip-rule="evenodd"></path><path fill-rule="evenodd" d="M6.194 12.753a.75.75 0 001.06.053L16.5 4.44v2.81a.75.75 0 001.5 0v-4.5a.75.75 0 00-.75-.75h-4.5a.75.75 0 000 1.5h2.553l-9.056 8.194a.75.75 0 00-.053 1.06z" clip-rule="evenodd"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="h-5 w-5 fill-content3-light"><path fill-rule="evenodd" d="M4.25 5.5a.75.75 0 00-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 00.75-.75v-4a.75.75 0 011.5 0v4A2.25 2.25 0 0112.75 17h-8.5A2.25 2.25 0 012 14.75v-8.5A2.25 2.25 0 014.25 4h5a.75.75 0 010 1.5h-5z" clip-rule="evenodd"></path><path fill-rule="evenodd" d="M6.194 12.753a.75.75 0 001.06.053L16.5 4.44v2.81a.75.75 0 001.5 0v-4.5a.75.75 0 00-.75-.75h-4.5a.75.75 0 000 1.5h2.553l-9.056 8.194a.75.75 0 00-.053 1.06z" clip-rule="evenodd"></path></svg>
|
||||||
@@ -570,8 +570,8 @@ export default component$(() => {
|
|||||||
</section>
|
</section>
|
||||||
<Footer client:load>
|
<Footer client:load>
|
||||||
<div class="w-full flex justify-center flex-col items-center gap-3">
|
<div class="w-full flex justify-center flex-col items-center gap-3">
|
||||||
<Link href="https://discord.gg/6um5K6jrYj" prefetch={false} class="ring-2 ring-primary-500 flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
<Link href="/auth/login" prefetch={false} class="ring-2 ring-primary-500 flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||||
Join our Discord
|
Get early access
|
||||||
</Link>
|
</Link>
|
||||||
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
||||||
<span class="hover:text-primary-500 transition-colors duration-200">
|
<span class="hover:text-primary-500 transition-colors duration-200">
|
||||||
|
|||||||
@@ -521,8 +521,8 @@ export default component$(() => {
|
|||||||
</MotionComponent>
|
</MotionComponent>
|
||||||
<Footer client:load>
|
<Footer client:load>
|
||||||
<div class="w-full flex justify-center flex-col items-center gap-3">
|
<div class="w-full flex justify-center flex-col items-center gap-3">
|
||||||
<Link href="https://discord.gg/6um5K6jrYj" prefetch={false} class="flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
<Link href="/auth/login" prefetch={false} class="flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||||
Join our Discord
|
Get early access
|
||||||
</Link>
|
</Link>
|
||||||
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
||||||
<span class="hover:text-primary-500 transition-colors duration-200">
|
<span class="hover:text-primary-500 transition-colors duration-200">
|
||||||
|
|||||||
84
cloud/ubuntu-cloud-init.yaml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#cloud-config
|
||||||
|
# Cloud-init script to install NVIDIA 570 drivers on Ubuntu
|
||||||
|
# Tested with TensorDock Ubuntu 22.04 instances
|
||||||
|
|
||||||
|
# Install required packages
|
||||||
|
packages:
|
||||||
|
- build-essential
|
||||||
|
- linux-headers-generic
|
||||||
|
- dkms
|
||||||
|
|
||||||
|
# Our scripts
|
||||||
|
write_files:
|
||||||
|
- path: /root/prevent_driver_update.sh
|
||||||
|
encoding: b64
|
||||||
|
permissions: '0755'
|
||||||
|
content: ZHBrZy1xdWVyeSAtVyAtLXNob3dmb3JtYXQ9JyR7UGFja2FnZX0gJHtTdGF0dXN9XG4nIHwgZ3JlcCAtdiBkZWluc3RhbGwgfCBhd2sgJ3sgcHJpbnQgJDEgfScgfCBncmVwIC1FICdudmlkaWEuKi1bMC05XSskJyB8IHhhcmdzIC1yIC1MIDEgc3VkbyBhcHQtbWFyayBob2xk
|
||||||
|
# Blacklist nouveau just in case
|
||||||
|
- path: /etc/modprobe.d/blacklist-nouveau.conf
|
||||||
|
content: |
|
||||||
|
blacklist nouveau
|
||||||
|
# Enable modesetting for NVIDIA drivers
|
||||||
|
- path: /etc/modprobe.d/nvidia.conf
|
||||||
|
content: |
|
||||||
|
options nvidia-drm modeset=1
|
||||||
|
# Main setup script
|
||||||
|
- path: /root/setup_nvidia.sh
|
||||||
|
permissions: '0755'
|
||||||
|
content: |
|
||||||
|
#!/bin/bash
|
||||||
|
echo "Starting Nestri NVIDIA driver setup..."
|
||||||
|
|
||||||
|
echo "Purging old NVIDIA packages..."
|
||||||
|
apt remove --purge -y '*nvidia*'
|
||||||
|
apt autoremove -y
|
||||||
|
|
||||||
|
echo "Unloading conflicting kernel modules..."
|
||||||
|
modprobe -r nouveau 2>/dev/null || true
|
||||||
|
modprobe -r nvidia_drm 2>/dev/null || true
|
||||||
|
modprobe -r nvidia_modeset 2>/dev/null || true
|
||||||
|
modprobe -r nvidia 2>/dev/null || true
|
||||||
|
|
||||||
|
# Update initramfs to apply blacklist
|
||||||
|
update-initramfs -u
|
||||||
|
|
||||||
|
echo "Installing NVIDIA 570 driver..."
|
||||||
|
wget https://us.download.nvidia.com/XFree86/Linux-x86_64/570.86.16/NVIDIA-Linux-x86_64-570.86.16.run -O /root/NVIDIA-Linux-x86_64-570.86.16.run
|
||||||
|
chmod +x /root/NVIDIA-Linux-x86_64-570.86.16.run
|
||||||
|
# Install without building kernel module immediately, then build with DKMS
|
||||||
|
/root/NVIDIA-Linux-x86_64-570.86.16.run --silent --dkms
|
||||||
|
# Clean up
|
||||||
|
rm /root/NVIDIA-Linux-x86_64-570.86.16.run
|
||||||
|
|
||||||
|
# Prevent auto-update from nuking driver
|
||||||
|
echo "Making the new driver held version..."
|
||||||
|
bash /root/prevent_driver_update.sh
|
||||||
|
|
||||||
|
echo "Loading new NVIDIA modules..."
|
||||||
|
modprobe nvidia
|
||||||
|
modprobe nvidia_modeset
|
||||||
|
modprobe nvidia_drm
|
||||||
|
|
||||||
|
# Re-install container toolkit
|
||||||
|
echo "Re-installing NVIDIA container toolkit..."
|
||||||
|
apt install -y nvidia-container-toolkit
|
||||||
|
|
||||||
|
echo "Configuring NVIDIA container toolkit..."
|
||||||
|
nvidia-ctk runtime configure --runtime=docker
|
||||||
|
# Restart Docker only if necessary
|
||||||
|
if ! nvidia-smi > /dev/null 2>&1; then
|
||||||
|
echo "Restarting Docker to apply GPU changes..."
|
||||||
|
systemctl restart docker
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Setting up permissions..."
|
||||||
|
chmod 777 /dev/dri/* 2>/dev/null || true
|
||||||
|
|
||||||
|
cd /home/user/ && mkdir -p nestri && chown user:user nestri
|
||||||
|
docker run --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" --name=nestri -d --shm-size=6g --runtime=nvidia --gpus=all -e RELAY_URL='https://relay.dathorse.com' -e NESTRI_ROOM=cloudinit123 -e RESOLUTION=1920x1080 -e FRAMERATE=60 -e GST_DEBUG=3 -e NESTRI_PARAMS='--verbose=true --video-codec=h264 --video-bitrate=6000 --video-bitrate-max=8000' -v /home/user/nestri:/home/nestri ghcr.io/datcaptainhorse/nestri-cachyos:latest-v3
|
||||||
|
|
||||||
|
echo "Nestri NVIDIA driver setup complete!"
|
||||||
|
|
||||||
|
# Run setup script on first launch
|
||||||
|
runcmd:
|
||||||
|
- /root/setup_nvidia.sh
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
FROM docker.io/golang:1.24-bookworm AS go-build
|
|
||||||
WORKDIR /builder
|
|
||||||
COPY packages/maitred/ /builder/
|
|
||||||
RUN go build
|
|
||||||
|
|
||||||
FROM docker.io/golang:1.24-bookworm
|
|
||||||
COPY --from=go-build /builder/maitred /maitred/maitred
|
|
||||||
WORKDIR /maitred
|
|
||||||
|
|
||||||
RUN apt update && apt install -y --no-install-recommends pciutils
|
|
||||||
|
|
||||||
ENTRYPOINT ["/maitred/maitred"]
|
|
||||||
@@ -1,31 +1,20 @@
|
|||||||
FROM docker.io/golang:1.24-alpine AS go-build
|
FROM docker.io/golang:1.23-alpine AS go-build
|
||||||
WORKDIR /builder
|
WORKDIR /builder
|
||||||
COPY packages/relay/ /builder/
|
COPY packages/relay/ /builder/
|
||||||
RUN go build
|
RUN go build
|
||||||
|
|
||||||
FROM docker.io/golang:1.24-alpine
|
FROM docker.io/golang:1.23-alpine
|
||||||
COPY --from=go-build /builder/relay /relay/relay
|
COPY --from=go-build /builder/relay /relay/relay
|
||||||
WORKDIR /relay
|
WORKDIR /relay
|
||||||
|
|
||||||
# TODO: Switch running layer to just alpine (doesn't need golang dev stack)
|
|
||||||
|
|
||||||
# ENV flags
|
# ENV flags
|
||||||
ENV VERBOSE=false
|
ENV VERBOSE=false
|
||||||
ENV DEBUG=false
|
|
||||||
ENV ENDPOINT_PORT=8088
|
ENV ENDPOINT_PORT=8088
|
||||||
ENV MESH_PORT=8089
|
|
||||||
ENV WEBRTC_UDP_START=10000
|
ENV WEBRTC_UDP_START=10000
|
||||||
ENV WEBRTC_UDP_END=20000
|
ENV WEBRTC_UDP_END=20000
|
||||||
ENV STUN_SERVER="stun.l.google.com:19302"
|
ENV STUN_SERVER="stun.l.google.com:19302"
|
||||||
ENV WEBRTC_UDP_MUX=8088
|
|
||||||
ENV WEBRTC_NAT_IPS=""
|
|
||||||
ENV AUTO_ADD_LOCAL_IP=true
|
|
||||||
ENV TLS_CERT=""
|
|
||||||
ENV TLS_KEY=""
|
|
||||||
|
|
||||||
EXPOSE $ENDPOINT_PORT
|
EXPOSE $ENDPOINT_PORT
|
||||||
EXPOSE $MESH_PORT
|
|
||||||
EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp
|
EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp
|
||||||
EXPOSE $WEBRTC_UDP_MUX/udp
|
|
||||||
|
|
||||||
ENTRYPOINT ["/relay/relay"]
|
ENTRYPOINT ["/relay/relay"]
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
# Container build arguments #
|
# Container build arguments #
|
||||||
ARG BASE_IMAGE=docker.io/cachyos/cachyos:latest
|
ARG BASE_IMAGE=docker.io/cachyos/cachyos:latest
|
||||||
|
|
||||||
#******************************************************************************
|
|
||||||
# Base Stage - Updates system packages
|
|
||||||
#******************************************************************************
|
|
||||||
FROM ${BASE_IMAGE} AS base
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
|
||||||
pacman --noconfirm -Syu
|
|
||||||
|
|
||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
# Base Builder Stage - Prepares core build environment
|
# Base Builder Stage - Prepares core build environment
|
||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
FROM base AS base-builder
|
FROM ${BASE_IMAGE} AS base-builder
|
||||||
|
|
||||||
# Environment setup for Rust and Cargo
|
# Environment setup for Rust and Cargo
|
||||||
ENV CARGO_HOME=/usr/local/cargo \
|
ENV CARGO_HOME=/usr/local/cargo \
|
||||||
@@ -22,12 +14,9 @@ ENV CARGO_HOME=/usr/local/cargo \
|
|||||||
|
|
||||||
# Install build essentials and caching tools
|
# Install build essentials and caching tools
|
||||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||||
pacman -Sy --noconfirm mold rustup && \
|
pacman -Sy --noconfirm mold rust && \
|
||||||
mkdir -p "${ARTIFACTS}"
|
mkdir -p "${ARTIFACTS}"
|
||||||
|
|
||||||
# Install latest Rust using rustup
|
|
||||||
RUN rustup default stable
|
|
||||||
|
|
||||||
# Install cargo-chef with proper caching
|
# Install cargo-chef with proper caching
|
||||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||||
cargo install -j $(nproc) cargo-chef cargo-c --locked
|
cargo install -j $(nproc) cargo-chef cargo-c --locked
|
||||||
@@ -39,8 +28,7 @@ FROM base-builder AS nestri-server-deps
|
|||||||
WORKDIR /builder
|
WORKDIR /builder
|
||||||
|
|
||||||
# Install build dependencies
|
# Install build dependencies
|
||||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
RUN pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
||||||
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
|
||||||
gstreamer gst-plugins-base gst-plugins-good gst-plugin-rswebrtc
|
gstreamer gst-plugins-base gst-plugins-good gst-plugin-rswebrtc
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
@@ -85,8 +73,8 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
|||||||
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
||||||
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
|
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
|
||||||
|
|
||||||
# Clone repository
|
# Clone repository with proper directory structure
|
||||||
RUN git clone -b dev-dmabuf https://github.com/DatCaptainHorse/gst-wayland-display.git
|
RUN git clone -b dev-dmabuf https://github.com/games-on-whales/gst-wayland-display.git
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
FROM gst-wayland-deps AS gst-wayland-planner
|
FROM gst-wayland-deps AS gst-wayland-planner
|
||||||
@@ -119,7 +107,7 @@ RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
|||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
# Final Runtime Stage
|
# Final Runtime Stage
|
||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
FROM base AS runtime
|
FROM ${BASE_IMAGE} AS runtime
|
||||||
|
|
||||||
### System Configuration ###
|
### System Configuration ###
|
||||||
RUN sed -i \
|
RUN sed -i \
|
||||||
@@ -129,33 +117,27 @@ RUN sed -i \
|
|||||||
dirmngr </dev/null > /dev/null 2>&1
|
dirmngr </dev/null > /dev/null 2>&1
|
||||||
|
|
||||||
### Package Installation ###
|
### Package Installation ###
|
||||||
# Core system components
|
RUN pacman --noconfirm -Sy && \
|
||||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
# Core system components
|
||||||
pacman -Sy --needed --noconfirm \
|
pacman -S --needed --noconfirm \
|
||||||
vulkan-intel lib32-vulkan-intel vpl-gpu-rt \
|
archlinux-keyring vulkan-intel lib32-vulkan-intel mesa \
|
||||||
vulkan-radeon lib32-vulkan-radeon \
|
steam steam-native-runtime \
|
||||||
mesa \
|
sudo xorg-xwayland labwc wlr-randr mangohud \
|
||||||
steam steam-native-runtime gtk3 lib32-gtk3 \
|
|
||||||
sudo xorg-xwayland seatd libinput gamescope mangohud \
|
|
||||||
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 && \
|
||||||
# GStreamer stack
|
# GStreamer stack
|
||||||
pacman -Sy --needed --noconfirm \
|
pacman -S --needed --noconfirm \
|
||||||
gstreamer gst-plugins-base gst-plugins-good \
|
gstreamer gst-plugins-base gst-plugins-good \
|
||||||
gst-plugins-bad gst-plugin-pipewire \
|
gst-plugins-bad gst-plugin-pipewire \
|
||||||
gst-plugin-webrtchttp gst-plugin-rswebrtc gst-plugin-rsrtp \
|
gst-plugin-rswebrtc gst-plugin-rsrtp && \
|
||||||
gst-plugin-va gst-plugin-qsv && \
|
|
||||||
# lib32 GStreamer stack to fix some games with videos
|
|
||||||
pacman -Sy --needed --noconfirm \
|
|
||||||
lib32-gstreamer lib32-gst-plugins-base lib32-gst-plugins-good && \
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
paccache -rk1 && \
|
paccache -rk1 && \
|
||||||
rm -rf /usr/share/{info,man,doc}/*
|
rm -rf /usr/share/{info,man,doc}/*
|
||||||
|
|
||||||
### Application Installation ###
|
### Application Installation ###
|
||||||
ARG LUDUSAVI_VERSION="0.28.0"
|
ARG LUDUSAVI_VERSION="0.28.0"
|
||||||
RUN curl -fsSL -o ludusavi.tar.gz \
|
RUN pacman -Sy --noconfirm --needed curl && \
|
||||||
|
curl -fsSL -o ludusavi.tar.gz \
|
||||||
"https://github.com/mtkennerly/ludusavi/releases/download/v${LUDUSAVI_VERSION}/ludusavi-v${LUDUSAVI_VERSION}-linux.tar.gz" && \
|
"https://github.com/mtkennerly/ludusavi/releases/download/v${LUDUSAVI_VERSION}/ludusavi-v${LUDUSAVI_VERSION}-linux.tar.gz" && \
|
||||||
tar -xzvf ludusavi.tar.gz && \
|
tar -xzvf ludusavi.tar.gz && \
|
||||||
mv ludusavi /usr/bin/ && \
|
mv ludusavi /usr/bin/ && \
|
||||||
@@ -190,30 +172,6 @@ RUN mkdir -p /run/dbus && \
|
|||||||
-e '/wants = \[/{s/hooks\.node\.suspend\s*//; s/,\s*\]/]/}' \
|
-e '/wants = \[/{s/hooks\.node\.suspend\s*//; s/,\s*\]/]/}' \
|
||||||
/usr/share/wireplumber/wireplumber.conf
|
/usr/share/wireplumber/wireplumber.conf
|
||||||
|
|
||||||
### PipeWire Latency Optimizations (1-5ms instead of 20ms) ###
|
|
||||||
RUN mkdir -p /etc/pipewire/pipewire.conf.d && \
|
|
||||||
echo "[audio]\
|
|
||||||
\n default.clock.rate = 48000\
|
|
||||||
\n default.clock.quantum = 128\
|
|
||||||
\n default.clock.min-quantum = 128\
|
|
||||||
\n default.clock.max-quantum = 256" > /etc/pipewire/pipewire.conf.d/low-latency.conf && \
|
|
||||||
mkdir -p /etc/wireplumber/main.lua.d && \
|
|
||||||
echo 'table.insert(default_nodes.rules, {\
|
|
||||||
\n matches = { { { "node.name", "matches", ".*" } } },\
|
|
||||||
\n apply_properties = {\
|
|
||||||
\n ["audio.format"] = "S16LE",\
|
|
||||||
\n ["audio.rate"] = 48000,\
|
|
||||||
\n ["audio.channels"] = 2,\
|
|
||||||
\n ["api.alsa.period-size"] = 128,\
|
|
||||||
\n ["api.alsa.headroom"] = 0,\
|
|
||||||
\n ["session.suspend-timeout-seconds"] = 0\
|
|
||||||
\n }\
|
|
||||||
\n})' > /etc/wireplumber/main.lua.d/50-low-latency.lua && \
|
|
||||||
echo "default-fragments = 2\
|
|
||||||
\ndefault-fragment-size-msec = 2" >> /etc/pulse/daemon.conf && \
|
|
||||||
echo "load-module module-loopback latency_msec=1" >> /etc/pipewire/pipewire.conf.d/loopback.conf
|
|
||||||
|
|
||||||
|
|
||||||
### Artifacts and Verification ###
|
### Artifacts and Verification ###
|
||||||
COPY --from=nestri-server-cached-builder /artifacts/nestri-server /usr/bin/
|
COPY --from=nestri-server-cached-builder /artifacts/nestri-server /usr/bin/
|
||||||
COPY --from=gst-wayland-cached-builder /artifacts/lib/ /usr/lib/
|
COPY --from=gst-wayland-cached-builder /artifacts/lib/ /usr/lib/
|
||||||
|
|||||||
1
docker-compose.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#FIXME: A simple docker-compose file for running the MoQ relay and the cachyos server
|
||||||
138
infra/api.ts
@@ -1,96 +1,54 @@
|
|||||||
import { bus } from "./bus";
|
import { authFingerprintKey } from "./auth";
|
||||||
import { auth } from "./auth";
|
|
||||||
import { domain } from "./dns";
|
import { domain } from "./dns";
|
||||||
import { cluster } from "./cluster";
|
import { secret } from "./secrets"
|
||||||
import { postgres } from "./postgres";
|
// import { party } from "./party"
|
||||||
import { LibraryQueue } from "./steam";
|
import { gpuTaskDefinition, ecsCluster } from "./cluster";
|
||||||
import { secret, steamEncryptionKey } from "./secret";
|
|
||||||
|
|
||||||
export const apiService = new sst.aws.Service("Api", {
|
export const urls = new sst.Linkable("Urls", {
|
||||||
cluster,
|
properties: {
|
||||||
cpu: $app.stage === "production" ? "2 vCPU" : undefined,
|
api: "https://api." + domain,
|
||||||
memory: $app.stage === "production" ? "4 GB" : undefined,
|
auth: "https://auth." + domain,
|
||||||
link: [
|
|
||||||
bus,
|
|
||||||
auth,
|
|
||||||
postgres,
|
|
||||||
LibraryQueue,
|
|
||||||
steamEncryptionKey,
|
|
||||||
secret.PolarSecret,
|
|
||||||
secret.PolarWebhookSecret,
|
|
||||||
secret.NestriFamilyMonthly,
|
|
||||||
secret.NestriFamilyYearly,
|
|
||||||
secret.NestriFreeMonthly,
|
|
||||||
secret.NestriProMonthly,
|
|
||||||
secret.NestriProYearly,
|
|
||||||
],
|
|
||||||
command: ["bun", "run", "./src/api/index.ts"],
|
|
||||||
image: {
|
|
||||||
dockerfile: "packages/functions/Containerfile",
|
|
||||||
},
|
},
|
||||||
loadBalancer: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
listen: "80/http",
|
|
||||||
forward: "3001/http",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
dev: {
|
|
||||||
url: "http://localhost:3001",
|
|
||||||
command: "bun dev:api",
|
|
||||||
directory: "packages/functions",
|
|
||||||
},
|
|
||||||
scaling:
|
|
||||||
$app.stage === "production"
|
|
||||||
? {
|
|
||||||
min: 2,
|
|
||||||
max: 10,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
// For persisting actor state
|
|
||||||
transform: {
|
|
||||||
taskDefinition: (args) => {
|
|
||||||
const volumes = $output(args.volumes).apply(v => {
|
|
||||||
const next = [...v, {
|
|
||||||
name: "shared-tmp",
|
|
||||||
dockerVolumeConfiguration: {
|
|
||||||
scope: "shared",
|
|
||||||
driver: "local"
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
return next;
|
|
||||||
})
|
|
||||||
|
|
||||||
// "containerDefinitions" is a JSON string, parse first
|
|
||||||
let containers = $jsonParse(args.containerDefinitions);
|
|
||||||
|
|
||||||
containers = containers.apply((containerDefinitions) => {
|
|
||||||
containerDefinitions[0].mountPoints = [
|
|
||||||
...(containerDefinitions[0].mountPoints ?? []),
|
|
||||||
{
|
|
||||||
sourceVolume: "shared-tmp",
|
|
||||||
containerPath: "/tmp"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
return containerDefinitions;
|
|
||||||
});
|
|
||||||
|
|
||||||
args.volumes = volumes
|
|
||||||
args.containerDefinitions = $jsonStringify(containers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const kv = new sst.cloudflare.Kv("CloudflareAuthKV")
|
||||||
|
|
||||||
export const api = !$dev ? new sst.aws.Router("ApiRoute", {
|
export const auth = new sst.cloudflare.Worker("Auth", {
|
||||||
routes: {
|
link: [
|
||||||
// I think api.url should work all the same
|
kv,
|
||||||
"/*": apiService.nodes.loadBalancer.dnsName,
|
urls,
|
||||||
},
|
authFingerprintKey,
|
||||||
domain: {
|
secret.InstantAdminToken,
|
||||||
name: "api." + domain,
|
secret.InstantAppId,
|
||||||
dns: sst.cloudflare.dns(),
|
secret.LoopsApiKey,
|
||||||
},
|
secret.GithubClientID,
|
||||||
}) : apiService
|
secret.GithubClientSecret,
|
||||||
|
secret.DiscordClientID,
|
||||||
|
secret.DiscordClientSecret,
|
||||||
|
],
|
||||||
|
handler: "./packages/functions/src/auth.ts",
|
||||||
|
url: true,
|
||||||
|
domain: "auth." + domain
|
||||||
|
});
|
||||||
|
|
||||||
|
export const api = new sst.cloudflare.Worker("Api", {
|
||||||
|
link: [
|
||||||
|
urls,
|
||||||
|
ecsCluster,
|
||||||
|
gpuTaskDefinition,
|
||||||
|
authFingerprintKey,
|
||||||
|
secret.LoopsApiKey,
|
||||||
|
secret.InstantAppId,
|
||||||
|
secret.AwsAccessKey,
|
||||||
|
secret.AwsSecretKey,
|
||||||
|
secret.InstantAdminToken,
|
||||||
|
],
|
||||||
|
url: true,
|
||||||
|
handler: "./packages/functions/src/api/index.ts",
|
||||||
|
domain: "api." + domain
|
||||||
|
})
|
||||||
|
|
||||||
|
export const outputs = {
|
||||||
|
auth: auth.url,
|
||||||
|
api: api.url
|
||||||
|
}
|
||||||
103
infra/auth.ts
@@ -1,99 +1,12 @@
|
|||||||
import { bus } from "./bus";
|
export const authFingerprintKey = new random.RandomString(
|
||||||
import { domain } from "./dns";
|
"AuthFingerprintKey",
|
||||||
import { cluster } from "./cluster";
|
|
||||||
import { postgres } from "./postgres";
|
|
||||||
import { secret, steamEncryptionKey } from "./secret";
|
|
||||||
|
|
||||||
export const authService = new sst.aws.Service("Auth", {
|
|
||||||
cluster,
|
|
||||||
cpu: $app.stage === "production" ? "1 vCPU" : undefined,
|
|
||||||
memory: $app.stage === "production" ? "2 GB" : undefined,
|
|
||||||
command: ["bun", "run", "./src/auth/index.ts"],
|
|
||||||
link: [
|
|
||||||
bus,
|
|
||||||
postgres,
|
|
||||||
secret.PolarSecret,
|
|
||||||
steamEncryptionKey,
|
|
||||||
secret.GithubClientID,
|
|
||||||
secret.DiscordClientID,
|
|
||||||
secret.GithubClientSecret,
|
|
||||||
secret.DiscordClientSecret,
|
|
||||||
],
|
|
||||||
image: {
|
|
||||||
dockerfile: "packages/functions/Containerfile",
|
|
||||||
},
|
|
||||||
environment: {
|
|
||||||
NO_COLOR: "1",
|
|
||||||
STORAGE: "/tmp/persist.json"
|
|
||||||
},
|
|
||||||
loadBalancer: {
|
|
||||||
rules: [
|
|
||||||
{
|
{
|
||||||
listen: "80/http",
|
length: 32,
|
||||||
forward: "3002/http",
|
|
||||||
},
|
},
|
||||||
],
|
);
|
||||||
},
|
|
||||||
permissions: [
|
|
||||||
{
|
|
||||||
actions: ["ses:SendEmail"],
|
|
||||||
resources: ["*"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
dev: {
|
|
||||||
command: "bun dev:auth",
|
|
||||||
directory: "packages/functions",
|
|
||||||
url: "http://localhost:3002",
|
|
||||||
},
|
|
||||||
scaling:
|
|
||||||
$app.stage === "production"
|
|
||||||
? {
|
|
||||||
min: 2,
|
|
||||||
max: 10,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
//For temporarily persisting the persist.json
|
|
||||||
transform: {
|
|
||||||
taskDefinition: (args) => {
|
|
||||||
const volumes = $output(args.volumes).apply(v => {
|
|
||||||
const next = [...v, {
|
|
||||||
name: "shared-tmp",
|
|
||||||
dockerVolumeConfiguration: {
|
|
||||||
scope: "shared",
|
|
||||||
driver: "local"
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
return next;
|
sst.Linkable.wrap(random.RandomString, (resource) => ({
|
||||||
})
|
properties: {
|
||||||
|
value: resource.result,
|
||||||
// "containerDefinitions" is a JSON string, parse first
|
|
||||||
let containers = $jsonParse(args.containerDefinitions);
|
|
||||||
|
|
||||||
containers = containers.apply((containerDefinitions) => {
|
|
||||||
containerDefinitions[0].mountPoints = [
|
|
||||||
...(containerDefinitions[0].mountPoints ?? []),
|
|
||||||
{
|
|
||||||
sourceVolume: "shared-tmp",
|
|
||||||
containerPath: "/tmp"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
return containerDefinitions;
|
|
||||||
});
|
|
||||||
|
|
||||||
args.volumes = volumes
|
|
||||||
args.containerDefinitions = $jsonStringify(containers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const auth = !$dev ? new sst.aws.Router("AuthRoute", {
|
|
||||||
routes: {
|
|
||||||
// I think auth.url should work all the same
|
|
||||||
"/*": authService.nodes.loadBalancer.dnsName,
|
|
||||||
},
|
},
|
||||||
domain: {
|
}));
|
||||||
name: "auth." + domain,
|
|
||||||
dns: sst.cloudflare.dns(),
|
|
||||||
},
|
|
||||||
}) : authService
|
|
||||||
26
infra/bus.ts
@@ -1,26 +0,0 @@
|
|||||||
import { vpc } from "./vpc";
|
|
||||||
import { storage } from "./storage";
|
|
||||||
// import { email } from "./email";
|
|
||||||
import { postgres } from "./postgres";
|
|
||||||
import { steamEncryptionKey } from "./secret";
|
|
||||||
|
|
||||||
export const bus = new sst.aws.Bus("Bus");
|
|
||||||
|
|
||||||
bus.subscribe("Event", {
|
|
||||||
vpc,
|
|
||||||
handler: "packages/functions/src/events/index.handler",
|
|
||||||
link: [
|
|
||||||
// email,
|
|
||||||
postgres,
|
|
||||||
storage,
|
|
||||||
steamEncryptionKey
|
|
||||||
],
|
|
||||||
timeout: "10 minutes",
|
|
||||||
memory: "3002 MB",// For faster processing of large(r) images
|
|
||||||
permissions: [
|
|
||||||
{
|
|
||||||
actions: ["ses:SendEmail"],
|
|
||||||
resources: ["*"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
157
infra/cluster.ts
@@ -1,6 +1,155 @@
|
|||||||
import { vpc } from "./vpc";
|
import { sshKey } from "./ssh";
|
||||||
|
import { authFingerprintKey } from "./auth";
|
||||||
|
|
||||||
export const cluster = new sst.aws.Cluster("Cluster", {
|
export const ecsCluster = new aws.ecs.Cluster("NestriGPUCluster", {
|
||||||
vpc,
|
name: "NestriGPUCluster",
|
||||||
forceUpgrade: "v2"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ecsInstanceRole = new aws.iam.Role("NestriGPUInstanceRole", {
|
||||||
|
name: "GPUAssumeRoleProd",
|
||||||
|
assumeRolePolicy: JSON.stringify({
|
||||||
|
Version: "2012-10-17",
|
||||||
|
Statement: [{
|
||||||
|
Action: "sts:AssumeRole",
|
||||||
|
Principal: {
|
||||||
|
Service: "ec2.amazonaws.com",
|
||||||
|
},
|
||||||
|
Effect: "Allow",
|
||||||
|
Sid: "",
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
new aws.iam.RolePolicyAttachment("NestriGPUInstancePolicyAttachment", {
|
||||||
|
role: ecsInstanceRole.name,
|
||||||
|
policyArn: "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role",
|
||||||
|
});
|
||||||
|
|
||||||
|
const ecsInstanceProfile = new aws.iam.InstanceProfile("NestriGPUInstanceProfile", {
|
||||||
|
role: ecsInstanceRole.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
// const server = new aws.ec2.Instance("NestriGPU", {
|
||||||
|
// instanceType: aws.ec2.InstanceType.G4dn_XLarge,
|
||||||
|
// ami: "ami-046a6af96ef510bb6",//Fedora cloud
|
||||||
|
// keyName: sshKey.keyName,
|
||||||
|
// instanceMarketOptions: {
|
||||||
|
// marketType: "spot",
|
||||||
|
// spotOptions: {
|
||||||
|
// maxPrice: "0.2",
|
||||||
|
// spotInstanceType: "persistent",
|
||||||
|
// instanceInterruptionBehavior: "stop"
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// iamInstanceProfile: ecsInstanceProfile,
|
||||||
|
// });
|
||||||
|
|
||||||
|
const logGroup = new aws.cloudwatch.LogGroup("NestriGPULogGroup", {
|
||||||
|
name: "/ecs/nestri-gpu-prod",
|
||||||
|
retentionInDays: 7,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a Task Definition for the ECS service to test it
|
||||||
|
export const gpuTaskDefinition = new aws.ecs.TaskDefinition("NestriGPUTask", {
|
||||||
|
family: "NestriGPUTaskProd",
|
||||||
|
requiresCompatibilities: ["EC2"],
|
||||||
|
volumes: [
|
||||||
|
{
|
||||||
|
name: "host",
|
||||||
|
hostPath: "/mnt/"
|
||||||
|
// efsVolumeConfiguration: {
|
||||||
|
// fileSystemId: storage.id,
|
||||||
|
// authorizationConfig: { accessPointId: storage.accessPoint },
|
||||||
|
// transitEncryption: "ENABLED",
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
containerDefinitions: authFingerprintKey.result.apply(v => JSON.stringify([{
|
||||||
|
"essential": true,
|
||||||
|
"name": "nestri",
|
||||||
|
"memory": 1024,
|
||||||
|
"cpu": 200,
|
||||||
|
"gpu": 1,
|
||||||
|
"image": "ghcr.io/nestrilabs/nestri/runner:nightly",
|
||||||
|
"environment": [
|
||||||
|
{
|
||||||
|
"name": "RESOLUTION",
|
||||||
|
"value": "1920x1080"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AUTH_FINGERPRINT",
|
||||||
|
"value": v
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FRAMERATE",
|
||||||
|
"value": "60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NESTRI_ROOM",
|
||||||
|
"value": "aws-testing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RELAY_URL",
|
||||||
|
"value": "https://relay.dathorse.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NESTRI_PARAMS",
|
||||||
|
"value": "--verbose=true --video-codec=h264 --video-bitrate=4000 --video-bitrate-max=6000 --gpu-card-path=/dev/dri/card0"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"mountPoints": [{ "containerPath": "/home/nestri", "sourceVolume": "host" }],
|
||||||
|
"disableNetworking": false,
|
||||||
|
"linuxParameter": {
|
||||||
|
"sharedMemorySize": 5120
|
||||||
|
},
|
||||||
|
"logConfiguration": {
|
||||||
|
"logDriver": "awslogs",
|
||||||
|
"options": {
|
||||||
|
"awslogs-group": "/ecs/nestri-gpu-prod",
|
||||||
|
"awslogs-region": "us-east-1",
|
||||||
|
"awslogs-stream-prefix": "nestri-gpu-task"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]))
|
||||||
|
});
|
||||||
|
|
||||||
|
sst.Linkable.wrap(aws.ecs.TaskDefinition, (resource) => ({
|
||||||
|
properties: {
|
||||||
|
value: resource.arn,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
sst.Linkable.wrap(aws.ecs.Cluster, (resource) => ({
|
||||||
|
properties: {
|
||||||
|
value: resource.arn,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// userData: $interpolate`#!/bin/bash
|
||||||
|
// sudo rm /etc/sysconfig/docker
|
||||||
|
// echo DAEMON_MAXFILES=1048576 | sudo tee -a /etc/sysconfig/docker
|
||||||
|
// echo DAEMON_PIDFILE_TIMEOUT=10 | sud o tee -a /etc/sysconfig/docker
|
||||||
|
// echo OPTIONS="--default-ulimit nofile=32768:65536" | sudo tee -a /etc/sysconfig/docker
|
||||||
|
// sudo tee "/etc/docker/daemon.json" > /dev/null <<EOF
|
||||||
|
// {
|
||||||
|
// "default-runtime": "nvidia",
|
||||||
|
// "runtimes": {
|
||||||
|
// "nvidia": {
|
||||||
|
// "path": "/usr/bin/nvidia-container-runtime",
|
||||||
|
// "runtimeArgs": []
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// EOF
|
||||||
|
// sudo systemctl restart docker
|
||||||
|
// echo ECS_CLUSTER='${ecsCluster.name}' | sudo tee -a /etc/ecs/ecs.config
|
||||||
|
// echo ECS_ENABLE_GPU_SUPPORT=true | sudo tee -a /etc/ecs/ecs.config
|
||||||
|
// echo ECS_CONTAINER_STOP_TIMEOUT=3h | sudo tee -a /etc/ecs/ecs.config
|
||||||
|
// echo ECS_ENABLE_SPOT_INSTANCE_DRAINING=true | sudo tee -a /etc/ecs/ecs.config
|
||||||
|
// `,
|
||||||
|
|
||||||
|
// This is used for requesting a container to be deployed on AWS
|
||||||
|
// const queue = new sst.aws.Queue("PartyQueue", { fifo: true });
|
||||||
|
|
||||||
|
// queue.subscribe({ handler: "packages/functions/src/party/subscriber.handler", permissions:{}, link:[taskF]})
|
||||||
|
// const authRes = $interpolate`${authFingerprintKey.result}`
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { domain } from "./dns";
|
|
||||||
|
|
||||||
export const email = new sst.aws.Email("Email",{
|
|
||||||
sender: domain,
|
|
||||||
dns: sst.cloudflare.dns(),
|
|
||||||
})
|
|
||||||
33
infra/party.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// This is for the websocket/MQTT endpoint that helps the API communicate with the container
|
||||||
|
// [API] <-> party <-websocket-> container
|
||||||
|
// The container is it's own this, and can listen to Websocket connections to start or stop a Steam Game
|
||||||
|
|
||||||
|
// import { authFingerprintKey } from "./auth";
|
||||||
|
// import { ecsCluster, gpuTaskDefinition } from "./cluster";
|
||||||
|
|
||||||
|
// export const party = new sst.aws.Realtime("Party", {
|
||||||
|
// authorizer: "packages/functions/src/party/authorizer.handler"
|
||||||
|
// });
|
||||||
|
|
||||||
|
// export const partyFn = new sst.aws.Function("NestriPartyFn", {
|
||||||
|
// handler: "packages/functions/src/party/create.handler",
|
||||||
|
// // link: [queue],
|
||||||
|
// link: [authFingerprintKey],
|
||||||
|
// environment: {
|
||||||
|
// TASK_DEFINITION: gpuTaskDefinition.arn,
|
||||||
|
// // AUTH_FINGERPRINT: authFingerprintKey.result,
|
||||||
|
// ECS_CLUSTER: ecsCluster.arn,
|
||||||
|
// },
|
||||||
|
// permissions: [
|
||||||
|
// {
|
||||||
|
// effect: "allow",
|
||||||
|
// actions: ["ecs:RunTask"],
|
||||||
|
// resources: [gpuTaskDefinition.arn]
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// url: true,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// export const outputs = {
|
||||||
|
// partyFunction: partyFn.url
|
||||||
|
// }
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import { vpc } from "./vpc";
|
|
||||||
import { isPermanentStage } from "./stage";
|
|
||||||
import { steamEncryptionKey } from "./secret";
|
|
||||||
|
|
||||||
// TODO: Add a dev db to use, this will help with running zero locally... and testing it
|
|
||||||
export const postgres = new sst.aws.Aurora("Database", {
|
|
||||||
vpc,
|
|
||||||
engine: "postgres",
|
|
||||||
scaling: isPermanentStage
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
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, steamEncryptionKey],
|
|
||||||
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,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { auth } from "./auth";
|
|
||||||
import { postgres } from "./postgres";
|
|
||||||
|
|
||||||
export const device = new sst.aws.Realtime("Realtime", {
|
|
||||||
authorizer: {
|
|
||||||
link: [auth, postgres],
|
|
||||||
handler: "packages/functions/src/realtime/authorizer.handler"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
179
infra/relay.ts
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
// const vpc = new sst.aws.Vpc("NestriRelayVpc", { az: 2 })
|
||||||
|
// import { subnet1, subnet2, securityGroup } from "./vpc"
|
||||||
|
|
||||||
|
// const taskExecutionRole = new aws.iam.Role('NestriRelayExecutionRole', {
|
||||||
|
// assumeRolePolicy: JSON.stringify({
|
||||||
|
// Version: '2012-10-17',
|
||||||
|
// Statement: [
|
||||||
|
// {
|
||||||
|
// Effect: 'Allow',
|
||||||
|
// Principal: {
|
||||||
|
// Service: 'ecs-tasks.amazonaws.com',
|
||||||
|
// },
|
||||||
|
// Action: 'sts:AssumeRole',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// }),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const taskRole = new aws.iam.Role('NestriRelayTaskRole', {
|
||||||
|
// assumeRolePolicy: JSON.stringify({
|
||||||
|
// Version: '2012-10-17',
|
||||||
|
// Statement: [
|
||||||
|
// {
|
||||||
|
// Effect: 'Allow',
|
||||||
|
// Principal: {
|
||||||
|
// Service: 'ecs-tasks.amazonaws.com',
|
||||||
|
// },
|
||||||
|
// Action: 'sts:AssumeRole',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// }),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.cloudwatch.LogGroup('NestriRelayLogGroup', {
|
||||||
|
// name: '/ecs/nestri-relay',
|
||||||
|
// retentionInDays: 7,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.iam.RolePolicyAttachment('NestriRelayExecutionRoleAttachment', {
|
||||||
|
// policyArn: 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
|
||||||
|
// role: taskRole,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const logPolicy = new aws.iam.Policy('NestriRelayLogPolicy', {
|
||||||
|
// policy: JSON.stringify({
|
||||||
|
// Version: '2012-10-17',
|
||||||
|
// Statement: [
|
||||||
|
// {
|
||||||
|
// Effect: 'Allow',
|
||||||
|
// Action: ['logs:CreateLogStream', 'logs:PutLogEvents'],
|
||||||
|
// Resource: 'arn:aws:logs:*:*:*',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// }),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.iam.RolePolicyAttachment('NestriRelayTaskRoleAttachment', {
|
||||||
|
// policyArn: logPolicy.arn,
|
||||||
|
// role: taskExecutionRole,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const taskDefinition = new aws.ecs.TaskDefinition("NestriRelayTask", {
|
||||||
|
// family: "NestriRelay",
|
||||||
|
// cpu: "1024",
|
||||||
|
// memory: "2048",
|
||||||
|
// networkMode: "awsvpc",
|
||||||
|
// taskRoleArn: taskRole.arn,
|
||||||
|
// requiresCompatibilities: ["FARGATE"],
|
||||||
|
// executionRoleArn: taskExecutionRole.arn,
|
||||||
|
// containerDefinitions: JSON.stringify([{
|
||||||
|
// name: "nestri-relay",
|
||||||
|
// essential: true,
|
||||||
|
// memory: 2048,
|
||||||
|
// image: "ghcr.io/nestrilabs/nestri/relay:nightly",
|
||||||
|
// portMappings: [
|
||||||
|
// // HTTP port
|
||||||
|
// {
|
||||||
|
// protocol: "tcp",
|
||||||
|
// hostPort: 80,
|
||||||
|
// containerPort: 80,
|
||||||
|
// },
|
||||||
|
// // UDP port range (1,000 ports)
|
||||||
|
// {
|
||||||
|
// containerPortRange: "10000-11000",
|
||||||
|
// protocol: "udp",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// "environment": [
|
||||||
|
// {
|
||||||
|
// name: "ENDPOINT_PORT",
|
||||||
|
// value: "80"
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// logConfiguration: {
|
||||||
|
// logDriver: 'awslogs',
|
||||||
|
// options: {
|
||||||
|
// 'awslogs-group': '/ecs/nestri-relay',
|
||||||
|
// 'awslogs-region': 'us-east-1',
|
||||||
|
// 'awslogs-stream-prefix': 'ecs',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }]),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const relayCluster = new aws.ecs.Cluster('NestriRelay');
|
||||||
|
|
||||||
|
// new aws.ecs.Service('NestriRelayService', {
|
||||||
|
// name: 'NestriRelayService',
|
||||||
|
// cluster: relayCluster.arn,
|
||||||
|
// desiredCount: 1,
|
||||||
|
// launchType: 'FARGATE',
|
||||||
|
// taskDefinition: taskDefinition.arn,
|
||||||
|
// deploymentCircuitBreaker: {
|
||||||
|
// enable: true,
|
||||||
|
// rollback: true,
|
||||||
|
// },
|
||||||
|
// enableExecuteCommand: true,
|
||||||
|
// networkConfiguration: {
|
||||||
|
// assignPublicIp: true,
|
||||||
|
// subnets: [subnet1.id, subnet2.id],
|
||||||
|
// securityGroups: [securityGroup.id],
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
//FIXME: I cannot create Global Accelerators (Something to do with Quotas - Yet my account is fine)
|
||||||
|
// const usWest2 = new aws.Provider("GlobalAccelerator", { region: aws.Region.USWest2 })
|
||||||
|
|
||||||
|
// const accelerator = new aws.globalaccelerator.Accelerator('Accelerator', {
|
||||||
|
// name: 'NestriRelayAccelerator',
|
||||||
|
// enabled: true,
|
||||||
|
// ipAddressType: 'IPV4',
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// const httpListener = new aws.globalaccelerator.Listener('TcpListener', {
|
||||||
|
// acceleratorArn: accelerator.id,
|
||||||
|
// clientAffinity: 'SOURCE_IP',
|
||||||
|
// protocol: 'TCP',
|
||||||
|
// portRanges: [{
|
||||||
|
// fromPort: 80,
|
||||||
|
// toPort: 80,
|
||||||
|
// }],
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// const udpListener = new aws.globalaccelerator.Listener('UdpListener', {
|
||||||
|
// acceleratorArn: accelerator.id,
|
||||||
|
// clientAffinity: 'SOURCE_IP',
|
||||||
|
// protocol: 'UDP',
|
||||||
|
// portRanges: [{
|
||||||
|
// fromPort: 10000,
|
||||||
|
// toPort: 11000,
|
||||||
|
// }],
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// new aws.globalaccelerator.EndpointGroup('TcpRelay', {
|
||||||
|
// listenerArn: httpListener.id,
|
||||||
|
// // healthCheckPath: '/',
|
||||||
|
// endpointGroupRegion: aws.Region.USEast1,
|
||||||
|
// endpointConfigurations: [{
|
||||||
|
// clientIpPreservationEnabled: true,
|
||||||
|
// endpointId: subnet1.id, //vpc.publicSubnets[0].apply(i => i),
|
||||||
|
// weight: 100,
|
||||||
|
// }],
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// new aws.globalaccelerator.EndpointGroup('UdpRelay', {
|
||||||
|
// listenerArn: udpListener.id,
|
||||||
|
// // healthCheckPort: 80,
|
||||||
|
// // healthCheckPath: "/",
|
||||||
|
// endpointGroupRegion: aws.Region.USEast1,
|
||||||
|
// endpointConfigurations: [{
|
||||||
|
// clientIpPreservationEnabled: true,
|
||||||
|
// endpointId: subnet1.id,//vpc.publicSubnets[0].apply(i => i),
|
||||||
|
// weight: 100,
|
||||||
|
// }],
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// export const outputs = {
|
||||||
|
// relay: accelerator.dnsName
|
||||||
|
// }
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
export const secret = {
|
|
||||||
PolarSecret: new sst.Secret("PolarSecret", process.env.POLAR_API_KEY),
|
|
||||||
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);
|
|
||||||
|
|
||||||
sst.Linkable.wrap(random.RandomString, (resource) => ({
|
|
||||||
properties: {
|
|
||||||
value: resource.result,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const steamEncryptionKey = new random.RandomString(
|
|
||||||
"SteamEncryptionKey",
|
|
||||||
{
|
|
||||||
length: 32,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
13
infra/secrets.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export const secret = {
|
||||||
|
LoopsApiKey: new sst.Secret("LoopsApiKey"),
|
||||||
|
InstantAppId: new sst.Secret("InstantAppId"),
|
||||||
|
AwsSecretKey: new sst.Secret("AwsSecretKey"),
|
||||||
|
AwsAccessKey: new sst.Secret("AwsAccessKey"),
|
||||||
|
GithubClientID: new sst.Secret("GithubClientID"),
|
||||||
|
DiscordClientID: new sst.Secret("DiscordClientID"),
|
||||||
|
GithubClientSecret: new sst.Secret("GithubClientSecret"),
|
||||||
|
InstantAdminToken: new sst.Secret("InstantAdminToken"),
|
||||||
|
DiscordClientSecret: new sst.Secret("DiscordClientSecret"),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const allSecrets = Object.values(secret);
|
||||||
19
infra/ssh.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { resolve } from "path";
|
||||||
|
import { writeFileSync } from "fs";
|
||||||
|
|
||||||
|
export const privateKey = new tls.PrivateKey("NestriGPUPrivateKey", {
|
||||||
|
algorithm: "RSA",
|
||||||
|
rsaBits: 4096,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Just in case you want to SSH
|
||||||
|
export const sshKey = new aws.ec2.KeyPair("NestriGPUKey", {
|
||||||
|
keyName: "NestriGPUKeyProd",
|
||||||
|
publicKey: privateKey.publicKeyOpenssh
|
||||||
|
})
|
||||||
|
|
||||||
|
export const keyPath = privateKey.privateKeyOpenssh.apply((key) => {
|
||||||
|
const path = "key_ssh";
|
||||||
|
writeFileSync(path, key, { mode: 0o600 });
|
||||||
|
return resolve(path);
|
||||||
|
});
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { vpc } from "./vpc";
|
|
||||||
import { postgres } from "./postgres";
|
|
||||||
import { steamEncryptionKey } from "./secret";
|
|
||||||
|
|
||||||
export const LibraryQueue = new sst.aws.Queue("LibraryQueue", {
|
|
||||||
fifo: true,
|
|
||||||
visibilityTimeout: "10 minutes",
|
|
||||||
});
|
|
||||||
|
|
||||||
LibraryQueue.subscribe({
|
|
||||||
vpc,
|
|
||||||
timeout: "10 minutes",
|
|
||||||
memory: "3002 MB",
|
|
||||||
handler: "packages/functions/src/queues/library.handler",
|
|
||||||
link: [
|
|
||||||
postgres,
|
|
||||||
steamEncryptionKey
|
|
||||||
],
|
|
||||||
});
|
|
||||||
@@ -1 +1,4 @@
|
|||||||
export const storage = new sst.aws.Bucket("Storage");
|
// export const vpc = new sst.aws.Vpc("Vpc")
|
||||||
|
|
||||||
|
// export const storage = new sst.aws.Efs("GameStorage",{ vpc })
|
||||||
|
// //
|
||||||
112
infra/vpc.ts
@@ -1,11 +1,103 @@
|
|||||||
import { isPermanentStage } from "./stage";
|
// export const vpc = new aws.ec2.Vpc('NestriVpc', {
|
||||||
|
// cidrBlock: '172.16.0.0/16',
|
||||||
|
// });
|
||||||
|
|
||||||
export const vpc = isPermanentStage
|
// export const subnet1 = new aws.ec2.Subnet('NestriSubnet1', {
|
||||||
? new sst.aws.Vpc("VPC", {
|
// vpcId: vpc.id,
|
||||||
az: 2,
|
// cidrBlock: '172.16.1.0/24',
|
||||||
// For lambdas to work in this VPC
|
// // cidrBlock: '110.0.12.0/22',
|
||||||
nat: "ec2",
|
// availabilityZone: 'us-east-1a',
|
||||||
// For SST tunnel to work
|
// });
|
||||||
bastion: true,
|
|
||||||
})
|
// export const subnet2 = new aws.ec2.Subnet('NestriSubnet2', {
|
||||||
: sst.aws.Vpc.get("VPC", "vpc-0beb1cdc21a725748");
|
// vpcId: vpc.id,
|
||||||
|
// cidrBlock: '172.16.2.0/24',
|
||||||
|
// // cidrBlock: '10.0.20.0/22',
|
||||||
|
// availabilityZone: 'us-east-1b',
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const internetGateway = new aws.ec2.InternetGateway('NestriInternetGateway', {
|
||||||
|
// vpcId: vpc.id,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const routeTable = new aws.ec2.RouteTable('NestriRouteTable', {
|
||||||
|
// vpcId: vpc.id,
|
||||||
|
// routes: [
|
||||||
|
// {
|
||||||
|
// cidrBlock: '0.0.0.0/0',
|
||||||
|
// gatewayId: internetGateway.id,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.ec2.RouteTableAssociation('NestriSubnet1RouteTable', {
|
||||||
|
// subnetId: subnet1.id,
|
||||||
|
// routeTableId: routeTable.id,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.ec2.RouteTableAssociation('NestriSubnet2RouteTable', {
|
||||||
|
// subnetId: subnet2.id,
|
||||||
|
// routeTableId: routeTable.id,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // const vpc = new sst.aws.Vpc("NestriRelayVpc")
|
||||||
|
|
||||||
|
// export const securityGroup = new aws.ec2.SecurityGroup("NestriSecurityGroup", {
|
||||||
|
// vpcId: vpc.id,
|
||||||
|
// description: "Managed thru SST",
|
||||||
|
// ingress: [
|
||||||
|
// {
|
||||||
|
// protocol: "tcp",
|
||||||
|
// fromPort: 80,
|
||||||
|
// toPort: 80,
|
||||||
|
// cidrBlocks: ["0.0.0.0/0"],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// protocol: "udp",
|
||||||
|
// fromPort: 10000,
|
||||||
|
// toPort: 20000,
|
||||||
|
// cidrBlocks: ["0.0.0.0/0"],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// egress: [
|
||||||
|
// {
|
||||||
|
// protocol: "-1",
|
||||||
|
// cidrBlocks: ["0.0.0.0/0"],
|
||||||
|
// fromPort: 0,
|
||||||
|
// toPort: 0
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const loadBalancer = new aws.lb.LoadBalancer('NestriVpcLoadBalancer', {
|
||||||
|
// name: 'NestriVpcLoadBalancer',
|
||||||
|
// internal: false,
|
||||||
|
// securityGroups: [securityGroup.id],
|
||||||
|
// subnets: vpc.publicSubnets
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const targetGroup = new aws.lb.TargetGroup('NestriVpcTargetGroup', {
|
||||||
|
// name: 'NestriVpcTargetGroup',
|
||||||
|
// port: 80,
|
||||||
|
// protocol: 'HTTP',
|
||||||
|
// targetType: 'ip',
|
||||||
|
// vpcId: vpc.id,
|
||||||
|
// healthCheck: {
|
||||||
|
// path: '/',
|
||||||
|
// protocol: 'HTTP',
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.lb.Listener('NestriVpcLoadBalancerListener', {
|
||||||
|
// loadBalancerArn: loadBalancer.arn,
|
||||||
|
// port: 80,
|
||||||
|
// protocol: 'HTTP',
|
||||||
|
// defaultActions: [
|
||||||
|
// {
|
||||||
|
// type: 'forward',
|
||||||
|
// targetGroupArn: targetGroup.arn,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // export const subnets = [subnet1, subnet2]
|
||||||
|
|||||||
23
infra/www.ts
@@ -1,23 +0,0 @@
|
|||||||
// This is the website part where people play and connect
|
|
||||||
import { api } from "./api";
|
|
||||||
import { auth } from "./auth";
|
|
||||||
import { zero } from "./zero";
|
|
||||||
import { domain } from "./dns";
|
|
||||||
|
|
||||||
new sst.aws.StaticSite("Web", {
|
|
||||||
path: "packages/www",
|
|
||||||
build: {
|
|
||||||
output: "./dist",
|
|
||||||
command: "bun run build",
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
dns: sst.cloudflare.dns(),
|
|
||||||
name: "console." + domain
|
|
||||||
},
|
|
||||||
environment: {
|
|
||||||
VITE_API_URL: api.url,
|
|
||||||
VITE_STAGE: $app.stage,
|
|
||||||
VITE_AUTH_URL: auth.url,
|
|
||||||
VITE_ZERO_URL: zero.url,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
196
infra/zero.ts
@@ -1,196 +0,0 @@
|
|||||||
import { vpc } from "./vpc";
|
|
||||||
import { auth } from "./auth";
|
|
||||||
import { domain } from "./dns";
|
|
||||||
import { readFileSync } from "fs";
|
|
||||||
import { cluster } from "./cluster";
|
|
||||||
import { storage } from "./storage";
|
|
||||||
import { postgres } from "./postgres";
|
|
||||||
|
|
||||||
// const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}/${postgres.database}`
|
|
||||||
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`,
|
|
||||||
NODE_OPTIONS: "--max-old-space-size=8192",
|
|
||||||
...($dev
|
|
||||||
? {
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${storage.name}/zero/0`,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Replication Manager Service
|
|
||||||
const replicationManager = !$dev
|
|
||||||
? new sst.aws.Service(`ZeroReplication`, {
|
|
||||||
cluster,
|
|
||||||
wait: true,
|
|
||||||
...($app.stage === "production"
|
|
||||||
? {
|
|
||||||
cpu: "2 vCPU",
|
|
||||||
memory: "4 GB",
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
architecture: "arm64",
|
|
||||||
image: zeroEnv.ZERO_IMAGE_URL,
|
|
||||||
link: [storage, 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: [storage, postgres],
|
|
||||||
architecture: "arm64",
|
|
||||||
...($app.stage === "production"
|
|
||||||
? {
|
|
||||||
cpu: "2 vCPU",
|
|
||||||
memory: "4 GB",
|
|
||||||
capacity: "spot"
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
41
package.json
@@ -1,43 +1,34 @@
|
|||||||
{
|
{
|
||||||
"name": "nestri",
|
"name": "nestri",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo build",
|
||||||
|
"dev": "turbo dev",
|
||||||
|
"sst": "sst dev",
|
||||||
|
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||||
|
"lint": "turbo lint"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "4.20240821.1",
|
"@cloudflare/workers-types": "4.20240821.1",
|
||||||
"@pulumi/pulumi": "^3.134.0",
|
"@pulumi/pulumi": "^3.134.0",
|
||||||
"@types/aws-lambda": "8.10.147",
|
"@types/aws-lambda": "8.10.145",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"packageManager": "bun@1.2.4",
|
"packageManager": "bun@1.1.18",
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
|
||||||
"sso": "aws sso login --sso-session=nestri --no-browser --use-device-code"
|
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"@openauthjs/openauth": "0.4.3",
|
|
||||||
"@rocicorp/zero": "0.20.2025050901",
|
|
||||||
"steam-session": "1.9.3"
|
|
||||||
},
|
|
||||||
"patchedDependencies": {
|
|
||||||
"@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",
|
|
||||||
"steam-session@1.9.3": "patches/steam-session@1.9.3.patch"
|
|
||||||
},
|
|
||||||
"trustedDependencies": [
|
|
||||||
"core-js-pure",
|
|
||||||
"esbuild",
|
|
||||||
"protobufjs",
|
|
||||||
"@rocicorp/zero-sqlite3",
|
|
||||||
"workerd"
|
|
||||||
],
|
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
|
"trustedDependencies": [
|
||||||
|
"core-js-pure",
|
||||||
|
"esbuild",
|
||||||
|
"workerd"
|
||||||
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sst": "^3.11.21"
|
"sst": "3.6.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Resource } from "sst";
|
|
||||||
import { defineConfig } from "drizzle-kit";
|
|
||||||
|
|
||||||
const connection = {
|
|
||||||
user: Resource.Database.username,
|
|
||||||
password: Resource.Database.password,
|
|
||||||
host: Resource.Database.host,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
verbose: true,
|
|
||||||
strict: true,
|
|
||||||
out: "./migrations",
|
|
||||||
dialect: "postgresql",
|
|
||||||
dbCredentials: {
|
|
||||||
url: `postgres://${connection.user}:${connection.password}@${connection.host}/nestri`,
|
|
||||||
},
|
|
||||||
schema: "./src/**/*.sql.ts",
|
|
||||||
});
|
|
||||||
30
packages/core/instant.perms.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Docs: https://www.instantdb.com/docs/permissions
|
||||||
|
|
||||||
|
import type { InstantRules } from "@instantdb/core";
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
/**
|
||||||
|
* Welcome to Instant's permission system!
|
||||||
|
* Right now your rules are empty. To start filling them in, check out the docs:
|
||||||
|
* https://www.instantdb.com/docs/permissions
|
||||||
|
*
|
||||||
|
* Here's an example to give you a feel:
|
||||||
|
* posts: {
|
||||||
|
* allow: {
|
||||||
|
* view: "true",
|
||||||
|
* create: "isOwner",
|
||||||
|
* update: "isOwner",
|
||||||
|
* delete: "isOwner",
|
||||||
|
* },
|
||||||
|
* bind: ["isOwner", "auth.id != null && auth.id == data.ownerId"],
|
||||||
|
* },
|
||||||
|
*/
|
||||||
|
// $default: {
|
||||||
|
// allow: {
|
||||||
|
// $default: "isOwner"
|
||||||
|
// },
|
||||||
|
// bind: ["isOwner", "auth.id != null && auth.id == data.ownerID"],
|
||||||
|
// }
|
||||||
|
} satisfies InstantRules;
|
||||||
|
|
||||||
|
export default rules;
|
||||||
123
packages/core/instant.schema.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { i } from "@instantdb/core";
|
||||||
|
|
||||||
|
const _schema = i.schema({
|
||||||
|
entities: {
|
||||||
|
$users: i.entity({
|
||||||
|
email: i.string().unique().indexed(),
|
||||||
|
}),
|
||||||
|
// machines: i.entity({
|
||||||
|
// hostname: i.string(),
|
||||||
|
// fingerprint: i.string().unique().indexed(),
|
||||||
|
// deletedAt: i.date().optional().indexed(),
|
||||||
|
// createdAt: i.date()
|
||||||
|
// }),
|
||||||
|
tasks: i.entity({
|
||||||
|
type: i.string(),
|
||||||
|
lastStatus: i.string(),
|
||||||
|
healthStatus: i.string(),
|
||||||
|
startedAt: i.string(),
|
||||||
|
lastUpdated: i.date(),
|
||||||
|
stoppedAt: i.string().optional(),
|
||||||
|
taskID: i.string().unique().indexed()
|
||||||
|
}),
|
||||||
|
instances: i.entity({
|
||||||
|
hostname: i.string(),
|
||||||
|
lastActive: i.date().optional(),
|
||||||
|
createdAt: i.date()
|
||||||
|
}),
|
||||||
|
profiles: i.entity({
|
||||||
|
avatarUrl: i.string().optional(),
|
||||||
|
username: i.string().indexed(),
|
||||||
|
status: i.string().indexed(),
|
||||||
|
updatedAt: i.date().indexed(),
|
||||||
|
createdAt: i.date(),
|
||||||
|
discriminator: i.string().indexed()
|
||||||
|
}),
|
||||||
|
teams: i.entity({
|
||||||
|
name: i.string(),
|
||||||
|
slug: i.string().unique().indexed(),
|
||||||
|
deletedAt: i.date().optional(),//.indexed(),
|
||||||
|
updatedAt: i.date(),
|
||||||
|
createdAt: i.date(),
|
||||||
|
}),
|
||||||
|
// games: i.entity({
|
||||||
|
// name: i.string(),
|
||||||
|
// steamID: i.number().unique().indexed(),
|
||||||
|
// }),
|
||||||
|
sessions: i.entity({
|
||||||
|
startedAt: i.date(),
|
||||||
|
endedAt: i.date().optional().indexed(),
|
||||||
|
public: i.boolean().indexed(),
|
||||||
|
}),
|
||||||
|
subscriptions: i.entity({
|
||||||
|
checkoutID: i.string(),
|
||||||
|
canceledAt: i.date(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
UserSubscriptions: {
|
||||||
|
forward: { on: "subscriptions", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "subscriptions" }
|
||||||
|
},
|
||||||
|
UserProfiles: {
|
||||||
|
forward: { on: "profiles", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "one", label: "profile" }
|
||||||
|
},
|
||||||
|
UserTasks: {
|
||||||
|
forward: { on: "tasks", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "tasks" }
|
||||||
|
},
|
||||||
|
TaskSessions: {
|
||||||
|
forward: { on: "tasks", has: "many", label: "sessions" },
|
||||||
|
reverse: { on: "sessions", has: "one", label: "task" }
|
||||||
|
},
|
||||||
|
UserSession: {
|
||||||
|
forward: { on: "sessions", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "sessions" }
|
||||||
|
},
|
||||||
|
TeamsOwned: {
|
||||||
|
forward: { on: "teams", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "teamsOwned" },
|
||||||
|
},
|
||||||
|
TeamsJoined: {
|
||||||
|
forward: { on: "teams", has: "many", label: "members" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "teamsJoined" },
|
||||||
|
},
|
||||||
|
// UserMachines: {
|
||||||
|
// forward: { on: "machines", has: "one", label: "owner" },
|
||||||
|
// reverse: { on: "$users", has: "many", label: "machines" }
|
||||||
|
// },
|
||||||
|
// UserGames: {
|
||||||
|
// forward: { on: "games", has: "many", label: "owners" },
|
||||||
|
// reverse: { on: "$users", has: "many", label: "games" }
|
||||||
|
// },
|
||||||
|
// TeamInstances: {
|
||||||
|
// forward: { on: "instances", has: "many", label: "owners" },
|
||||||
|
// reverse: { on: "teams", has: "many", label: "instances" }
|
||||||
|
// },
|
||||||
|
// MachineSessions: {
|
||||||
|
// forward: { on: "machines", has: "many", label: "sessions" },
|
||||||
|
// reverse: { on: "sessions", has: "one", label: "machine" }
|
||||||
|
// },
|
||||||
|
// GamesMachines: {
|
||||||
|
// forward: { on: "machines", has: "many", label: "games" },
|
||||||
|
// reverse: { on: "games", has: "many", label: "machines" }
|
||||||
|
// },
|
||||||
|
// GameSessions: {
|
||||||
|
// forward: { on: "games", has: "many", label: "sessions" },
|
||||||
|
// reverse: { on: "sessions", has: "one", label: "game" }
|
||||||
|
// },
|
||||||
|
// UserSessions: {
|
||||||
|
// forward: { on: "sessions", has: "one", label: "owner" },
|
||||||
|
// reverse: { on: "$users", has: "many", label: "sessions" }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// This helps Typescript display nicer intellisense
|
||||||
|
type _AppSchema = typeof _schema;
|
||||||
|
interface AppSchema extends _AppSchema { }
|
||||||
|
const schema: AppSchema = _schema;
|
||||||
|
|
||||||
|
export type { AppSchema };
|
||||||
|
export default schema;
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
CREATE TABLE "member" (
|
|
||||||
"id" char(30) NOT NULL,
|
|
||||||
"team_id" char(30) NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"time_seen" timestamp with time zone,
|
|
||||||
"email" varchar(255) NOT NULL,
|
|
||||||
CONSTRAINT "member_team_id_id_pk" PRIMARY KEY("team_id","id")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "team" (
|
|
||||||
"id" char(30) PRIMARY KEY NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"name" varchar(255) NOT NULL,
|
|
||||||
"slug" varchar(255) NOT NULL,
|
|
||||||
"plan_type" text NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "user" (
|
|
||||||
"id" char(30) PRIMARY KEY NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"avatar_url" text,
|
|
||||||
"name" varchar(255) NOT NULL,
|
|
||||||
"discriminator" integer NOT NULL,
|
|
||||||
"email" varchar(255) NOT NULL,
|
|
||||||
"polar_customer_id" varchar(255),
|
|
||||||
"flags" json DEFAULT '{}'::json,
|
|
||||||
CONSTRAINT "user_polar_customer_id_unique" UNIQUE("polar_customer_id")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE INDEX "email_global" ON "member" USING btree ("email");--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "member_email" ON "member" USING btree ("team_id","email");--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "team_slug" ON "team" USING btree ("slug");--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "user_email" ON "user" USING btree ("email");
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
DROP INDEX "team_slug";--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "slug" ON "team" USING btree ("slug");
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
CREATE TABLE "steam" (
|
|
||||||
"id" char(30) NOT NULL,
|
|
||||||
"user_id" char(30) NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"avatar_url" text NOT NULL,
|
|
||||||
"access_token" text NOT NULL,
|
|
||||||
"email" varchar(255) NOT NULL,
|
|
||||||
"country" varchar(255) NOT NULL,
|
|
||||||
"username" varchar(255) NOT NULL,
|
|
||||||
"persona_name" varchar(255) NOT NULL,
|
|
||||||
CONSTRAINT "steam_user_id_id_pk" PRIMARY KEY("user_id","id")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE INDEX "global_steam_email" ON "steam" USING btree ("email");--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "steam_email" ON "steam" USING btree ("user_id","email");
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
CREATE TABLE "machine" (
|
|
||||||
"id" char(30) PRIMARY KEY NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"country" text NOT NULL,
|
|
||||||
"timezone" text NOT NULL,
|
|
||||||
"location" "point" NOT NULL,
|
|
||||||
"fingerprint" varchar(32) NOT NULL,
|
|
||||||
"country_code" varchar(2) NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "machine_fingerprint" ON "machine" USING btree ("fingerprint");
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
CREATE TABLE "machine" (
|
|
||||||
"id" char(30) PRIMARY KEY NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"country" text NOT NULL,
|
|
||||||
"timezone" text NOT NULL,
|
|
||||||
"location" "point" NOT NULL,
|
|
||||||
"fingerprint" varchar(32) NOT NULL,
|
|
||||||
"country_code" varchar(2) NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" RENAME COLUMN "country" TO "country_code";--> statement-breakpoint
|
|
||||||
DROP INDEX "global_steam_email";--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" ADD COLUMN "time_seen" timestamp with time zone;--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" ADD COLUMN "steam_id" integer NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" ADD COLUMN "last_game" json NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" ADD COLUMN "steam_email" varchar(255) NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" ADD COLUMN "limitation" json NOT NULL;--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "machine_fingerprint" ON "machine" USING btree ("fingerprint");--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" DROP COLUMN "access_token";--> statement-breakpoint
|
|
||||||
ALTER TABLE "user" DROP COLUMN "flags";
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
ALTER TABLE "steam" RENAME COLUMN "time_seen" TO "last_seen";--> statement-breakpoint
|
|
||||||
DROP INDEX "steam_email";--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" DROP CONSTRAINT "steam_user_id_id_pk";--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" ADD PRIMARY KEY ("id");--> statement-breakpoint
|
|
||||||
ALTER TABLE "machine" ADD CONSTRAINT "machine_user_id_id_pk" PRIMARY KEY("user_id","id");--> statement-breakpoint
|
|
||||||
ALTER TABLE "machine" ADD COLUMN "user_id" char(30);--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" ADD CONSTRAINT "steam_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam" DROP COLUMN "email";
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "machine" DROP CONSTRAINT "machine_user_id_id_pk";--> statement-breakpoint
|
|
||||||
ALTER TABLE "machine" DROP COLUMN "user_id";
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "member" ADD COLUMN "role" text NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "team" DROP COLUMN "plan_type";
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
CREATE TABLE "subscription" (
|
|
||||||
"id" char(30) NOT NULL,
|
|
||||||
"user_id" char(30) NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"team_id" char(30) NOT NULL,
|
|
||||||
"standing" text NOT NULL,
|
|
||||||
"plan_type" text NOT NULL,
|
|
||||||
"tokens" integer NOT NULL,
|
|
||||||
"product_id" varchar(255),
|
|
||||||
"subscription_id" varchar(255)
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "subscription" ADD CONSTRAINT "subscription_team_id_team_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."team"("id") ON DELETE cascade ON UPDATE no action;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE "subscription" ADD CONSTRAINT "subscription_id_team_id_pk" PRIMARY KEY("id","team_id");--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "subscription_id" ON "subscription" USING btree ("id");--> statement-breakpoint
|
|
||||||
CREATE INDEX "subscription_user_id" ON "subscription" USING btree ("user_id");
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
CREATE UNIQUE INDEX "steam_id" ON "steam" USING btree ("steam_id");--> statement-breakpoint
|
|
||||||
CREATE INDEX "steam_user_id" ON "steam" USING btree ("user_id");
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
CREATE TYPE "public"."member_role" AS ENUM('child', 'adult');--> statement-breakpoint
|
|
||||||
CREATE TYPE "public"."steam_status" AS ENUM('online', 'offline', 'dnd', 'playing');--> statement-breakpoint
|
|
||||||
CREATE TABLE "steam_account_credentials" (
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"steam_id" varchar(255) PRIMARY KEY NOT NULL,
|
|
||||||
"refresh_token" text NOT NULL,
|
|
||||||
"expiry" timestamp with time zone NOT NULL,
|
|
||||||
"username" varchar(255) NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "friends_list" (
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"steam_id" varchar(255) NOT NULL,
|
|
||||||
"friend_steam_id" varchar(255) NOT NULL,
|
|
||||||
CONSTRAINT "friends_list_steam_id_friend_steam_id_pk" PRIMARY KEY("steam_id","friend_steam_id")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "members" (
|
|
||||||
"id" char(30) NOT NULL,
|
|
||||||
"team_id" char(30) NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"user_id" char(30),
|
|
||||||
"steam_id" varchar(255) NOT NULL,
|
|
||||||
"role" "member_role" NOT NULL,
|
|
||||||
CONSTRAINT "members_id_team_id_pk" PRIMARY KEY("id","team_id")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "steam_accounts" (
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"steam_id" varchar(255) PRIMARY KEY NOT NULL,
|
|
||||||
"user_id" char(30),
|
|
||||||
"status" "steam_status" NOT NULL,
|
|
||||||
"last_synced_at" timestamp with time zone NOT NULL,
|
|
||||||
"real_name" varchar(255),
|
|
||||||
"member_since" timestamp with time zone NOT NULL,
|
|
||||||
"name" varchar(255) NOT NULL,
|
|
||||||
"profile_url" varchar(255),
|
|
||||||
"username" varchar(255) NOT NULL,
|
|
||||||
"avatar_hash" varchar(255) NOT NULL,
|
|
||||||
"limitations" json NOT NULL,
|
|
||||||
CONSTRAINT "idx_steam_username" UNIQUE("username")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "teams" (
|
|
||||||
"id" char(30) PRIMARY KEY NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"name" varchar(255) NOT NULL,
|
|
||||||
"owner_id" char(30) NOT NULL,
|
|
||||||
"invite_code" varchar(10) NOT NULL,
|
|
||||||
"slug" varchar(255) NOT NULL,
|
|
||||||
"max_members" bigint NOT NULL,
|
|
||||||
CONSTRAINT "idx_team_invite_code" UNIQUE("invite_code")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "users" (
|
|
||||||
"id" char(30) PRIMARY KEY NOT NULL,
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"email" varchar(255) NOT NULL,
|
|
||||||
"avatar_url" text,
|
|
||||||
"last_login" timestamp with time zone NOT NULL,
|
|
||||||
"name" varchar(255) NOT NULL,
|
|
||||||
"polar_customer_id" varchar(255),
|
|
||||||
CONSTRAINT "idx_user_email" UNIQUE("email")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
DROP TABLE "machine" CASCADE;--> statement-breakpoint
|
|
||||||
DROP TABLE "member" CASCADE;--> statement-breakpoint
|
|
||||||
DROP TABLE "steam" CASCADE;--> statement-breakpoint
|
|
||||||
DROP TABLE "subscription" CASCADE;--> statement-breakpoint
|
|
||||||
DROP TABLE "team" CASCADE;--> statement-breakpoint
|
|
||||||
DROP TABLE "user" CASCADE;--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam_account_credentials" ADD CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_friend_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("friend_steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "members" ADD CONSTRAINT "members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "members" ADD CONSTRAINT "members_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE restrict;--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam_accounts" ADD CONSTRAINT "steam_accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "teams" ADD CONSTRAINT "teams_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "teams" ADD CONSTRAINT "teams_slug_steam_accounts_username_fk" FOREIGN KEY ("slug") REFERENCES "public"."steam_accounts"("username") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "idx_member_steam_id" ON "members" USING btree ("team_id","steam_id");--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "idx_member_user_id" ON "members" USING btree ("team_id","user_id") WHERE "members"."user_id" is not null;--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "idx_team_slug" ON "teams" USING btree ("slug");
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
CREATE TYPE "public"."compatibility" AS ENUM('high', 'mid', 'low', 'unknown');--> statement-breakpoint
|
|
||||||
CREATE TYPE "public"."category_type" AS ENUM('tag', 'genre', 'publisher', 'developer');--> statement-breakpoint
|
|
||||||
CREATE TYPE "public"."image_type" AS ENUM('heroArt', 'icon', 'logo', 'superHeroArt', 'poster', 'boxArt', 'screenshot', 'background');--> statement-breakpoint
|
|
||||||
CREATE TABLE "base_games" (
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
|
||||||
"slug" varchar(255) NOT NULL,
|
|
||||||
"name" text NOT NULL,
|
|
||||||
"release_date" timestamp with time zone NOT NULL,
|
|
||||||
"size" json NOT NULL,
|
|
||||||
"description" text NOT NULL,
|
|
||||||
"primary_genre" text NOT NULL,
|
|
||||||
"controller_support" text,
|
|
||||||
"compatibility" "compatibility" DEFAULT 'unknown' NOT NULL,
|
|
||||||
"score" numeric(2, 1) NOT NULL,
|
|
||||||
CONSTRAINT "idx_base_games_slug" UNIQUE("slug")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "categories" (
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"slug" varchar(255) NOT NULL,
|
|
||||||
"type" "category_type" NOT NULL,
|
|
||||||
"name" text NOT NULL,
|
|
||||||
CONSTRAINT "categories_slug_type_pk" PRIMARY KEY("slug","type")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "games" (
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"base_game_id" varchar(255) NOT NULL,
|
|
||||||
"category_slug" varchar(255) NOT NULL,
|
|
||||||
"type" "category_type" NOT NULL,
|
|
||||||
CONSTRAINT "games_base_game_id_category_slug_type_pk" PRIMARY KEY("base_game_id","category_slug","type")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "images" (
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"type" "image_type" NOT NULL,
|
|
||||||
"image_hash" varchar(255) NOT NULL,
|
|
||||||
"base_game_id" varchar(255) NOT NULL,
|
|
||||||
"source_url" text NOT NULL,
|
|
||||||
"position" integer DEFAULT 0 NOT NULL,
|
|
||||||
"file_size" integer NOT NULL,
|
|
||||||
"dimensions" json NOT NULL,
|
|
||||||
"extracted_color" json NOT NULL,
|
|
||||||
CONSTRAINT "images_image_hash_type_base_game_id_position_pk" PRIMARY KEY("image_hash","type","base_game_id","position")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "game_libraries" (
|
|
||||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
|
||||||
"time_deleted" timestamp with time zone,
|
|
||||||
"base_game_id" varchar(255) NOT NULL,
|
|
||||||
"owner_id" varchar(255) NOT NULL,
|
|
||||||
CONSTRAINT "game_libraries_base_game_id_owner_id_pk" PRIMARY KEY("base_game_id","owner_id")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam_accounts" RENAME COLUMN "steam_id" TO "id";--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam_account_credentials" DROP CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_steam_id_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "friends_list" DROP CONSTRAINT "friends_list_steam_id_steam_accounts_steam_id_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "friends_list" DROP CONSTRAINT "friends_list_friend_steam_id_steam_accounts_steam_id_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "members" DROP CONSTRAINT "members_steam_id_steam_accounts_steam_id_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "games" ADD CONSTRAINT "games_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "games" ADD CONSTRAINT "games_categories_fkey" FOREIGN KEY ("category_slug","type") REFERENCES "public"."categories"("slug","type") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "images" ADD CONSTRAINT "images_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_owner_id_steam_accounts_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
CREATE INDEX "idx_categories_type" ON "categories" USING btree ("type");--> statement-breakpoint
|
|
||||||
CREATE INDEX "idx_games_category_slug" ON "games" USING btree ("category_slug");--> statement-breakpoint
|
|
||||||
CREATE INDEX "idx_games_category_type" ON "games" USING btree ("type");--> statement-breakpoint
|
|
||||||
CREATE INDEX "idx_images_type" ON "images" USING btree ("type");--> statement-breakpoint
|
|
||||||
CREATE INDEX "idx_images_game_id" ON "images" USING btree ("base_game_id");--> statement-breakpoint
|
|
||||||
CREATE INDEX "idx_game_libraries_owner_id" ON "game_libraries" USING btree ("owner_id");--> statement-breakpoint
|
|
||||||
ALTER TABLE "steam_account_credentials" ADD CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_friend_steam_id_steam_accounts_id_fk" FOREIGN KEY ("friend_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "members" ADD CONSTRAINT "members_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE restrict;--> statement-breakpoint
|
|
||||||
CREATE INDEX "idx_friends_list_friend_steam_id" ON "friends_list" USING btree ("friend_steam_id");
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE "games" DROP CONSTRAINT "games_categories_fkey";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "games" ADD CONSTRAINT "games_categories_fkey" FOREIGN KEY ("category_slug","type") REFERENCES "public"."categories"("slug","type") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
CREATE INDEX "idx_games_category_slug_type" ON "games" USING btree ("category_slug","type");
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
CREATE TYPE "public"."controller_support" AS ENUM('full', 'unknown');--> statement-breakpoint
|
|
||||||
ALTER TABLE "base_games" ALTER COLUMN "controller_support" SET DATA TYPE controller_support;--> statement-breakpoint
|
|
||||||
ALTER TABLE "base_games" ALTER COLUMN "controller_support" SET NOT NULL;
|
|
||||||