soiz1 commited on
Commit
01fcadf
·
verified ·
1 Parent(s): 990f606

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.changeset/config.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
3
+ "changelog": ["@changesets/changelog-github", { "repo": "nebulaservices/nebula" }],
4
+ "commit": false,
5
+ "fixed": [],
6
+ "linked": [],
7
+ "access": "public",
8
+ "baseBranch": "main",
9
+ "updateInternalDependencies": "patch",
10
+ "ignore": [],
11
+ "privatePackages": { "version": true, "tag": true }
12
+ }
.dockerignore ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules/
2
+ .vscode
3
+ npm-debug.log
4
+ yarn-error.log
5
+ .github/
6
+ .env.example
7
+ .env
8
+ dist/
9
+ .git/
10
+ .astro/
11
+ ~/
12
+ .gitignore
13
+ biome.json
14
+ docker-compose.yml
15
+ Dockerfile
16
+ README.md
17
+ db/
.gitattributes CHANGED
@@ -1,35 +1,37 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ database_assets/com.nebula.retro/terminal.ttf filter=lfs diff=lfs merge=lfs -text
37
+ src/assets/credits/motortruck1221.png filter=lfs diff=lfs merge=lfs -text
.github/workflows/docker.yml ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build Docker image
2
+ on:
3
+ push:
4
+ tags:
5
+ - v*
6
+ workflow_dispatch:
7
+
8
+ env:
9
+ REGISTRY: ghcr.io
10
+ IMAGE_NAME: ${{ github.repository }}
11
+
12
+ jobs:
13
+ build-and-push:
14
+ name: Build and push Docker image to registry
15
+ runs-on: ubuntu-latest
16
+ if: github.repository_owner == 'nebulaservices'
17
+ permissions:
18
+ contents: write
19
+ packages: write
20
+ steps:
21
+ - name: Checkout repo
22
+ uses: actions/checkout@v3
23
+ - name: Setup docker buildx
24
+ uses: docker/setup-buildx-action@v3
25
+ - name: Login To registry ${{ env.REGISTRY }}
26
+ uses: docker/login-action@v3
27
+ with:
28
+ registry: ${{ env.REGISTRY }}
29
+ username: ${{ github.actor }}
30
+ password: ${{ github.token }}
31
+ - name: Extract Docker metadata
32
+ id: meta
33
+ uses: docker/metadata-action@v3
34
+ with:
35
+ images: ${{ env.REGISTRY }}/nebulaservice/nebula
36
+ - name: Build and push
37
+ id: build-and-push
38
+ uses: docker/build-push-action@v4
39
+ with:
40
+ context: .
41
+ platforms: linux/amd64,linux/arm64
42
+ file: ./Dockerfile
43
+ name: nebula
44
+ push: ${{ github.event_name != 'pull_request' }}
45
+ tags: ${{ steps.meta.outputs.tags }}
46
+ labels: ${{ steps.meta.outputs.labels }}
47
+ cache-from: type=gha
48
+ cache-to: type=gha,mode=max
.github/workflows/release.yml ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+
9
+ defaults:
10
+ run:
11
+ shell: bash
12
+
13
+ env:
14
+ FORCE_COLOR: true
15
+
16
+ jobs:
17
+ changelog:
18
+ name: Release TAG
19
+ if: ${{ github.repository_owner == 'nebulaservices' }}
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: write
23
+ id-token: write
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - name: Setup PNPM
28
+ uses: pnpm/action-setup@v3
29
+ with:
30
+ version: 9.1.1
31
+
32
+ - name: Setup Node
33
+ uses: actions/setup-node@v4
34
+ with:
35
+ node-version: 18
36
+ cache: "pnpm"
37
+
38
+ - name: Install dependencies
39
+ run: pnpm install --no-frozen-lockfile
40
+
41
+ - name: Create Release Pull Request or Publish
42
+ id: changesets
43
+ uses: changesets/action@v1
44
+ with:
45
+ version: pnpm run version
46
+ publish: pnpm exec changeset publish
47
+ commit: "[ci] release"
48
+ title: "[ci] release"
49
+ env:
50
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.gitignore ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # build output
2
+ dist/
3
+ server/*.js
4
+
5
+ # generated types
6
+ .astro/
7
+
8
+ # dependencies
9
+ node_modules/
10
+ package-lock.json
11
+
12
+ #external assets
13
+ database_assets/
14
+ !database_assets/com.nebula.gruvbox/
15
+ !database_assets/com.nebula.lightTheme/
16
+ !database_assets/com.nebula.oled/
17
+ !database_assets/com.nebula.retro/
18
+
19
+ # logs
20
+ npm-debug.log*
21
+ yarn-debug.log*
22
+ yarn-error.log*
23
+ pnpm-debug.log*
24
+
25
+ # environment variables
26
+ .env
27
+ .env.production
28
+
29
+ # macOS-specific files
30
+ .DS_Store
31
+
32
+ # jetbrains setting folder
33
+ .idea/
34
+
35
+ # nebula catalog database
36
+ database.sqlite
37
+
38
+
39
+ # YOUR config
40
+ config.toml
41
+
42
+ # Goofy PNPM problem
43
+ ~/
.gitmodules ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [submodule "workerware"]
2
+ path = workerware
3
+ url = https://github.com/mercuryworkshop/workerware
.vscode/extensions.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "recommendations": ["astro-build.astro-vscode"],
3
+ "unwantedRecommendations": []
4
+ }
.vscode/launch.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "command": "./node_modules/.bin/astro dev",
6
+ "name": "Development server",
7
+ "request": "launch",
8
+ "type": "node-terminal"
9
+ }
10
+ ]
11
+ }
CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # v9.0.0
2
+
3
+ - The first release of Nebula V9! And with it bring a whole host of changes:
4
+ - More stable the V8
5
+ - Adds a Marketplace where users can create their own themes & plugins
6
+ - Switches to Astro for speed
7
+ - Other general bug fixes
8
+
9
+ # v9.0.1
10
+
11
+ - Bumps dependencies
12
+ - Fixes bugs
13
+
14
+ # 9.0.2
15
+
16
+ - Adds the ability for a custom wisp server back
17
+ - Increases upload fileSize capacity in hopes that bigger files can now be uploaded
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:22-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package*.json .
6
+ COPY . .
7
+
8
+ RUN apk update
9
+ RUN apk add python3 py3-pip alpine-sdk openssl-dev build-base python3-dev
10
+ RUN python3 -m pip install setuptools --break-system-packages
11
+ RUN cp -n config.example.toml config.toml
12
+ RUN npm i -g pnpm
13
+ RUN pnpm install
14
+ RUN pnpm run build
15
+ RUN export TERM=xterm-256color
16
+ VOLUME /app
17
+ EXPOSE 8080
18
+ ENTRYPOINT ["pnpm"]
19
+ CMD ["start", "--color"]
README.md CHANGED
@@ -1,10 +1,373 @@
1
- ---
2
- title: Nebula2
3
- emoji: 😻
4
- colorFrom: indigo
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ sdk: gradio
3
+ ---
4
+ <div align="center">
5
+
6
+ <img src="https://socialify.git.ci/nebulaservices/nebula/image?description=1&font=Inter&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Dark" alt="ruby" width="640" height="320" />
7
+
8
+ <img alt="repo size" src="https://img.shields.io/github/repo-size/nebulaservices/nebula?style=for-the-badge"></img>
9
+ <img alt="website status" src="https://img.shields.io/website?url=https%3A%2F%2Fnebulaproxy.io&style=for-the-badge"></img>
10
+ <img alt="commit a week" src="https://img.shields.io/github/commit-activity/w/nebulaservices/nebula?style=for-the-badge"></img>
11
+
12
+ </div>
13
+
14
+ <div align="center">
15
+ <h2>Get Started</h2>
16
+ <a>To get started, press one of the buttons below to deploy Nebula</a>
17
+ <br />
18
+ <br />
19
+ <a href="#terminal">
20
+ <img src="https://img.shields.io/badge/terminal-%23121011.svg?style=for-the-badge&logo=gnu-bash&logoColor=white" alt="Terminal">
21
+ </img>
22
+ </a>
23
+ <a href="#docker">
24
+ <img src="https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white" alt="Docker">
25
+ </img>
26
+ </a>
27
+ </div>
28
+
29
+ ## NOTE:
30
+
31
+ - This will **NOT** deploy on GitHub Pages, Netlify, Vercel, Gitlab Pages, or any other _static_ host
32
+ - This will **NOT** work on Render
33
+ ---
34
+
35
+ ## How to get links
36
+
37
+ [![Nebula Services Discord](https://invidget.switchblade.xyz/unblocker?theme=darl)](https://discord.gg/unblocker)
38
+ [![Titanium Network Discord](https://invidget.switchblade.xyz/unblock?theme=dark)](https://discord.gg/unblock)
39
+
40
+ ---
41
+
42
+ ## Features
43
+
44
+ - Multiple Proxy "Backends":
45
+ - [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)
46
+ - [RammerHead](https://github.com/binary-person/rammerhead)
47
+ ---
48
+
49
+ ## Contributors
50
+
51
+ - [Rifting](https://github.com/rifting) - Owner & Maintainer
52
+ - [MotorTruck1221](https://motortruck1221.com) - Maintainer
53
+ ---
54
+
55
+ ## Tech Stack
56
+
57
+ - [Astro](https://astro.build)
58
+ - [Fastify](https://fastify.dev)
59
+ - [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)
60
+ - [RammerHead](https://github.com/binary-person/rammerhead)
61
+ - [Epoxy](https://github.com/mercuryworkshop/epoxy-tls)
62
+ - [Libcurl.js](https://github.com/ading2210/libcurl.js)
63
+ - HTML, CSS, and JavaScript (DUH)
64
+ ---
65
+
66
+ ## Catalog/Marketplace
67
+
68
+ - By default, the marketplace is enabled and uses SQLite
69
+ - If you would like to disable the catalog, see [#config](#config)
70
+ - For big production instances, I recommend using PostgreSQL rather than SQLite. To do this see [#config](#config)
71
+ - To use PostgreSQL via the provided docker-compose files, see [#docker](#docker)
72
+
73
+ ### How to make a theme
74
+
75
+ - Themes allow you to customize Nebula's *look*.
76
+
77
+ #### Prerequisites:
78
+ - Make sure you have our [Discord server](https://discord.gg/unblocker) so you can submit your theme
79
+
80
+ ##### Making the themes:
81
+
82
+ 1. Firstly, copy the CSS vars:
83
+ ```css
84
+ :root {
85
+ --background-primary: /*Your stuff here */;
86
+ --background-lighter: ;
87
+ --navbar-color: ;
88
+ --navbar-text-color: ;
89
+ --navbar-link-color: ;
90
+ --navbar-link-hover-color: ;
91
+ --input-text-color: ;
92
+ --input-placeholder-color: ;
93
+ --input-background-color: ;
94
+ --input-border-color: ;
95
+ --tab-color: ;
96
+ --border-color: ;
97
+ }
98
+ ```
99
+
100
+ > [!NOTE]
101
+ >
102
+ > You can add a custom font as well! To do so, add this to your `:root`
103
+ >
104
+ > ```css
105
+ > --font-family: /* Font family name */;
106
+ > ```
107
+ >
108
+ > And this to the bottom of your CSS file/submission:
109
+ > ```css
110
+ > @font-face {
111
+ > font-family: /* Name */;
112
+ > src: url(/* Where the font is located! Local or external work! */);
113
+ > }
114
+ > ```
115
+ >
116
+ > A good example of using a custom font is the built-in `retro` theme [here](./database_assets/com.nebula.retro)
117
+
118
+ 2. Add your colors and test! (Either with a self-hosted version of Nebula OR via a live preview (no clue when this will happen)
119
+
120
+ 3. Once you're satisfied with the colors, submit your theme to the [Discord Server](https://discord.gg/unblocker)!
121
+
122
+ ---
123
+ ### How to make a plugin
124
+
125
+ - Plugins extend the functionality of either the proxied page(s) or the service worker.
126
+ - This guide provides an incredibly basic example of how to make either.
127
+
128
+ #### Prerequisites:
129
+ - Make sure you have joined our [Discord server](https://discord.gg/unblocker) so you can submit your plugin.
130
+ - Some knowledge of JS/TS
131
+
132
+ ##### Serviceworker plugin:
133
+
134
+ - These plugins are handled by Workerware see [here](https://github.com/mercuryworkshop/workerware) for docs.
135
+
136
+ 1. Create an index.js (or other file name) file:
137
+ ```bash
138
+ touch index.js
139
+ ```
140
+
141
+ 2. Edit that file to include either of these:
142
+ - Code encased in a string:
143
+ ```js
144
+ function setup() {
145
+ // This function MUST return the following attributes:
146
+ return {
147
+ function: `console.log('Example code.')`,
148
+ name: 'com.example', // Technically, it could be named anything. It is recommended to use the same name for everything (name when submitting and this)
149
+ events: ['fetch'] // See: https://github.com/mercuryworkshop/workerware for the event types you can use. (Also typed if you are using typescript)
150
+ }
151
+ }
152
+
153
+ //This can be named anything. However, it's recommended to use `entryFunc` (with types, the naming IS enforced)
154
+ self.entryFunc = setup; //DO NOT call the function here. Only assign the reference otherwise, it will error.
155
+ ```
156
+ - Code in an arrow function:
157
+ ```js
158
+ const example = () => {
159
+ console.log('Example code')
160
+ }
161
+
162
+ function setup() {
163
+ //This function MUST return the following attributes:
164
+ return {
165
+ function: example, //Do not call the function, only assign the reference to the function.
166
+ name: 'com.example', // Technicall could be name anything. Recommended to use the same name for everything (name when submitting and this)
167
+ event: ['fetch'] // Se https://github.com/mercuryworkshop/workerware for the event types you can use. (Also typed if using typescript)
168
+ }
169
+ }
170
+
171
+ //This can be named anything. However, it's recommended to use `entryFunc` (with types, the naming IS enforced)
172
+ self.entryFunc = setup; //DO NOT call the function here. Only assign the reference; otherwise, it will result in an error.
173
+ ```
174
+
175
+ > [!WARNING]
176
+ > The only *allowed* way to pass code to the `function` param is either a string or an arrow function. Named functions ***WILL NOT WORK***.
177
+ >
178
+ > Example of a named function: `function example() {/* Some form of code */}`.
179
+ >
180
+ > If a named function is used where it shouldn't be, your plugin will not be approved, nor will it work properly.
181
+
182
+ 3. Submit your plugin in the [Discord](https://discord.gg/unblocker)!
183
+
184
+ ##### Proxied page plugins
185
+
186
+ - They allow modification of websites that UV proxies, (EX: you could add Vencord to Discord with this)
187
+
188
+ 1. Create an index.js file (or another file name)
189
+ ```bash
190
+ touch index.js
191
+ ```
192
+
193
+ 2. Edit that file with your code and the following:
194
+ ```js
195
+ //Name this whatever.
196
+ function example() {
197
+ //You MUST return the following
198
+ return {
199
+ host: "example.com", //The host to match (so if the user visits example.com it will inject the html below.
200
+ html: "<script>console.log('Example')</script>", //Must return a string (and be valid HTML or your plugin will break). How you get that string is up to you
201
+ injectTo: "head" // Can be "head" or "body"
202
+ }
203
+ }
204
+
205
+ // Technically, this could be named anything, it is recommended to call it `entryFunc`
206
+ self.entryFunc = example; //DO NOT run the function here. That will cause errors. Only assign the reference to the function here.
207
+ ```
208
+
209
+ 3. Submit it in our [Discord](https://discord.gg/unblocker)!
210
+
211
+ ---
212
+
213
+ ## Deployment
214
+
215
+ ### Terminal
216
+
217
+ Prerequisites:
218
+ - Node & npm
219
+ - Git
220
+
221
+ 1. Clone the repo:
222
+ ```bash
223
+ git clone https://github.com/nebulaservices/nebula --recursive && cd nebula
224
+ ```
225
+
226
+ 2. Install all of the dependencies:
227
+ ```bash
228
+ npm i
229
+ ```
230
+
231
+ 3. Create a `config.toml` file
232
+ ```bash
233
+ cp config.example.toml config.toml
234
+ ```
235
+
236
+ 4. Modify the `config.toml` file to your liking (docs [here](#environment))
237
+ ```
238
+ nano config.toml
239
+ ```
240
+
241
+ 5. Build the front end & server:
242
+ ```bash
243
+ npm run build
244
+ ```
245
+
246
+ 6. Start the server
247
+ ```bash
248
+ npm start
249
+ ```
250
+
251
+ > [!NOTE]
252
+ > You can run `npm run bstart` to build and start together
253
+ ---
254
+
255
+ ### Docker
256
+
257
+ - There are two ways to deploy with docker:
258
+ - [Normal docker](#normal-docker)
259
+ - [Docker Compose](#docker-compose)
260
+
261
+ #### Normal Docker
262
+
263
+ Prerequisites:
264
+ - Git
265
+ - Docker
266
+
267
+ 1. Clone the repo (skip if using a prebuilt image):
268
+ ```bash
269
+ git clone https://github.com/nebulaservices/nebula --recursive && cd nebula
270
+ ```
271
+
272
+ 2. Create an `config.toml` file (if using prebuilt image, copy the example from the repo):
273
+ ```bash
274
+ cp config.example.toml config.toml
275
+ ```
276
+
277
+ 3. Modify the `config.toml` file to your liking (docs [here](#environment))
278
+ ```bash
279
+ nano config.toml
280
+ ```
281
+
282
+ 4. Build the docker image (skip if using prebuilt):
283
+ ```bash
284
+ docker build nebula:latest
285
+ ```
286
+ 5. Run the docker images:
287
+
288
+ - Prebuilt:
289
+ ```bash
290
+ docker run -v ./config.toml:/app/config.toml ghcr.io/nebulaservices/nebula:latest
291
+ ```
292
+ - Image you built yourself:
293
+ ```bash
294
+ docker run -v ./config.toml:/app/config.toml nebula:latest
295
+ ```
296
+
297
+ #### Docker Compose
298
+
299
+ Prerequisites:
300
+ - Git
301
+ - Docker w/compose
302
+
303
+ 1. Clone the repo (skip if using a prebuilt image):
304
+ ```bash
305
+ git clone https://github.com/nebulaservices/nebula --recursive
306
+ ```
307
+
308
+ 2. Create an `config.toml` file (if using prebuilt image, copy the example from the repo):
309
+ ```bash
310
+ cp config.example.toml config.toml
311
+ ```
312
+
313
+ 3. Modify the `config.toml` file to your liking (docs on that [here](#environment)]
314
+ ```bash
315
+ nano config.toml
316
+ ```
317
+
318
+ 4. Build the docker image (skip if using prebuilt):
319
+ ```bash
320
+ docker compose -f ./docker-compose.build.yml build
321
+ ```
322
+
323
+ 5. Run the docker image:
324
+
325
+ - Prebuilt:
326
+ ```bash
327
+ docker compose up
328
+ ```
329
+ - Image you built yourself:
330
+ ```bash
331
+ docker compose -f ./docker-compose.build.yml up
332
+ ```
333
+ #### Extra (Postgres)
334
+
335
+ - To use Postgres over SQLite, uncomment the DB section in the `docker-compose` file (or use your own Postgres DB!). Then, modify the `config.toml` (See: [#config](#config) for knowledge on how to do this)
336
+ - To use Postgres over SQLite in a normal docker environment (no compose), you'll have to set one up and then modify the `config.toml` to use it. (See: [#config](#config) for knowledge on how to do this)
337
+
338
+ ---
339
+
340
+ ## Config
341
+
342
+ There are a couple of configuration options for Nebula. The defaults are fine most of the time, but there are instances where you may not want certain options enabled or certain things running.
343
+ - An example config file is located [here](./config.example.toml).
344
+ - Config format is in TOML
345
+
346
+ | Variable | Description | Type | Default |
347
+ |:----------:|:-------------:|:------:|:---------:|
348
+ | `marketplace` | The options below are for the marketplace section | `object` | N/A |
349
+ | `enabled` | Enable marketplace functionality | `boolean` | `true` |
350
+ | `psk` | The password and authentication key for the marketplace. ***CHANGE FROM DEFAULT*** | `string` | `CHANGEME` |
351
+ |----------------------------| ----------------------------------------------------------------------------|------------|--------------|
352
+ | `db` | The below options are for the db (database) section | `object` | N/A |
353
+ | `name` | The database name to use | `string` | `database` |
354
+ | `username` | The username for the DB | `string` | `username` |
355
+ | `password` | The database password. ***CHANGE FROM DEFAULT VALUE*** | `string` | `password` |
356
+ | `postgres` | Whether to use postgres over sqlite *(recommended for large production instances)* | `boolean` | `false` |
357
+ |----------------------------| ----------------------------------------------------------------------------|------------|--------------|
358
+ | `postgres` | The below options are for the postgres section. (Only worry about this if you enabled postgres in the db section.) | `object` | N/A |
359
+ | `domain` | Either the TLD or the IP address of your postgres server. | `string` | `''` |
360
+ | `port` | The port your postgres server is listening on | `number` | `5432` |
361
+ |----------------------------| ----------------------------------------------------------------------------|------------|--------------|
362
+ | `server.server` | The below options are to configure the server. | `object` | N/A |
363
+ | `port` | What port the server should listen on. *(Note: Can also be configured via environment variable `PORT`)* | `number` | `8080` |
364
+ | `wisp` | Whether the server should use the inbuilt wisp server. (Disabled if your using an external wisp server) | `boolean` | `true` |
365
+ | `logging` | Whether or not to enable logging. *Note: Logs are massive* | `boolean` | `true` |
366
+ |----------------------------| ----------------------------------------------------------------------------|------------|--------------|
367
+
368
+ ## Deploying
369
+ ### Koyeb
370
+ - Fork this repo
371
+ - Create new koyeb service, and select webservice
372
+ - Select import from github and import your forked repo
373
+ - Change package to dockerfile and press deploy!
app.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ from flask import Flask, send_from_directory, abort, render_template_string
4
+ import subprocess
5
+
6
+ # リポジトリをクローンするディレクトリ
7
+ temp_dir = "./"
8
+
9
+ # リポジトリのクローンとセットアップを行う
10
+ def clone_and_setup_repo():
11
+
12
+ os.system("npm i")
13
+
14
+ # server/server.ts の fastifyHelmet 設定を置換する ← 追加
15
+ print("Patching server.ts for iframe embedding...")
16
+ os.system(r"""sed -i.bak '/fastifyHelmet/s/});/ frameguard: false\\n});/' server/server.ts""")
17
+
18
+ os.system("cp config.example.toml config.toml")
19
+ os.system("npm run build")
20
+ os.system("npm start")
21
+
22
+ # index.htmlをカレントディレクトリに移動
23
+ index_html_path = os.path.join(temp_dir, 'index.html')
24
+ if os.path.exists(index_html_path):
25
+ if os.path.exists('index.html'):
26
+ os.remove('index.html')
27
+ shutil.move(index_html_path, '.')
28
+
29
+ # 静的ファイルをstaticディレクトリに移動
30
+ if not os.path.exists('static'):
31
+ os.mkdir('static')
32
+ for item in os.listdir(temp_dir):
33
+ if item != 'index.html':
34
+ shutil.move(os.path.join(temp_dir, item), os.path.join('static', item))
35
+
36
+ # クローンとセットアップを実行
37
+ clone_and_setup_repo()
38
+
39
+ # Flaskアプリケーションの設定
40
+ app = Flask(__name__)
41
+
42
+ # ルートでindex.htmlを表示
43
+ @app.route('/')
44
+ def index():
45
+ # index.htmlが存在しない場合は404エラー
46
+ if not os.path.exists("index.html"):
47
+ return abort(404, description="index.html not found.")
48
+
49
+ # index.htmlの内容を読み込む
50
+ with open("index.html", "r") as file:
51
+ index_html_content = file.read()
52
+
53
+ return render_template_string(index_html_content)
54
+
55
+ # 静的ファイルを提供するためのルート
56
+ @app.route('/<path:filename>')
57
+ def static_files(filename):
58
+ return send_from_directory('static', filename)
59
+
60
+ # main.jsの存在を確認するエンドポイント
61
+ @app.route('/check_main_js')
62
+ def check_main_js():
63
+ if os.path.exists('static/main.js'):
64
+ return "main.js exists."
65
+ else:
66
+ return "main.js does not exist."
67
+
68
+ if __name__ == '__main__':
69
+ # port 7860でFlaskアプリを起動
70
+ app.run(host='0.0.0.0', port=7860)
astro.config.ts ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fileURLToPath } from "node:url";
2
+ import node from "@astrojs/node";
3
+ import svelte from "@astrojs/svelte";
4
+ import tailwind from "@astrojs/tailwind";
5
+ import { baremuxPath } from "@mercuryworkshop/bare-mux/node";
6
+ import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
7
+ import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
8
+ import playformCompress from "@playform/compress";
9
+ import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
10
+ import icon from "astro-icon";
11
+ import { defineConfig, envField } from "astro/config";
12
+ import { viteStaticCopy } from "vite-plugin-static-copy";
13
+ import { version } from "./package.json";
14
+ import { parsedDoc } from "./server/config.js";
15
+ const workerwarePath = fileURLToPath(new URL("./workerware/src", import.meta.url));
16
+
17
+ export default defineConfig({
18
+ experimental: {
19
+ env: {
20
+ schema: {
21
+ VERSION: envField.string({
22
+ context: "client",
23
+ access: "public",
24
+ optional: true,
25
+ default: version
26
+ }),
27
+ MARKETPLACE_ENABLED: envField.boolean({
28
+ context: "client",
29
+ access: "public",
30
+ optional: true,
31
+ default: parsedDoc.marketplace.enabled
32
+ })
33
+ }
34
+ }
35
+ },
36
+ integrations: [
37
+ tailwind(),
38
+ icon(),
39
+ svelte(),
40
+ playformCompress({
41
+ CSS: false,
42
+ HTML: true,
43
+ Image: true,
44
+ JavaScript: true,
45
+ SVG: true
46
+ })
47
+ ],
48
+ vite: {
49
+ plugins: [
50
+ viteStaticCopy({
51
+ targets: [
52
+ {
53
+ src: `${uvPath}/**/*`.replace(/\\/g, "/"),
54
+ dest: "uv",
55
+ overwrite: false
56
+ },
57
+ {
58
+ src: `${epoxyPath}/**/*`.replace(/\\/g, "/"),
59
+ dest: "epoxy",
60
+ overwrite: false
61
+ },
62
+ {
63
+ src: `${libcurlPath}/**/*`.replace(/\\/g, "/"),
64
+ dest: "libcurl",
65
+ overwrite: false
66
+ },
67
+ {
68
+ src: `${baremuxPath}/**/*`.replace(/\\/g, "/"),
69
+ dest: "baremux",
70
+ overwrite: false
71
+ },
72
+ {
73
+ src: `${workerwarePath}/**/*`.replace(/\\/g, "/"),
74
+ dest: "workerware",
75
+ overwrite: false
76
+ }
77
+ ]
78
+ })
79
+ ],
80
+ server: {
81
+ proxy: {
82
+ "/api/catalog-stats": {
83
+ target: "http://localhost:8080/api/catalog-stats",
84
+ changeOrigin: true,
85
+ rewrite: (path) => path.replace(/^\/api\/catalog-stats/, "")
86
+ },
87
+ "/api/catalog-assets": {
88
+ target: "http://localhost:8080/api/catalog-assets",
89
+ changeOrigin: true,
90
+ rewrite: (path) => path.replace(/^\/api\/catalog-assets/, "")
91
+ },
92
+ "/api/packages": {
93
+ target: "http://localhost:8080/api/packages",
94
+ changeOrigin: true,
95
+ rewrite: (path) => path.replace(/^\/api\/packages/, "")
96
+ },
97
+ "/packages": {
98
+ target: "http://localhost:8080",
99
+ changeOrigin: true
100
+ },
101
+ "/wisp/": {
102
+ target: "ws://localhost:8080/wisp/",
103
+ changeOrigin: true,
104
+ ws: true,
105
+ rewrite: (path) => path.replace(/^\/wisp\//, "")
106
+ },
107
+ "/styles": {
108
+ target: "http://localhost:8080",
109
+ changeOrigin: true
110
+ }
111
+ }
112
+ }
113
+ },
114
+ output: "server",
115
+ adapter: node({
116
+ mode: "middleware"
117
+ })
118
+ });
biome.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
3
+ "files": {
4
+ "ignore": ["~/", "**/dist/**", ".github/**"],
5
+ "include": ["**/**", "server/**"]
6
+ },
7
+ "formatter": {
8
+ "indentStyle": "space",
9
+ "indentWidth": 4,
10
+ "lineWidth": 100,
11
+ "ignore": ["pnpm-lock.yaml", "package.json"]
12
+ },
13
+ "organizeImports": { "enabled": true },
14
+ "linter": { "enabled": false },
15
+ "javascript": {
16
+ "formatter": {
17
+ "trailingCommas": "none",
18
+ "quoteStyle": "double",
19
+ "semicolons": "always"
20
+ }
21
+ },
22
+ "json": {
23
+ "parser": {
24
+ "allowComments": true,
25
+ "allowTrailingCommas": true
26
+ },
27
+ "formatter": {
28
+ "indentStyle": "space",
29
+ "trailingCommas": "none"
30
+ }
31
+ }
32
+ }
config.example.toml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [marketplace]
2
+ enabled = true # Turn on or off the marketplace entirely
3
+ psk = "CHANGEME" # Change this to something more secure.
4
+ level = 1
5
+
6
+ [db]
7
+ name = "database" # Your databsae name
8
+ username = "username" # The username of your DB (SQLITE just ignores this)
9
+ password = "password" # The password to your DB (SQLITE ignores this)
10
+ postgres = false # Enable to use postgres over sqlite (recommended for large prod instances)
11
+
12
+ [postgres] # Set the "domain" to either and ip address or a actual domain
13
+ domain = ""
14
+ port = 5432
15
+
16
+ [server.server]
17
+ port = 7860
18
+ wisp = true
19
+ logging = true # Disable for the tons & tons of logs to go away (useful for debugging but otherwise eh)
docker-compose.build.yml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ nebula:
3
+ image: ghcr.io/nebulaservices/nebula:latest
4
+ container_name: nebula
5
+ build: .
6
+ restart: unless-stopped
7
+ ports:
8
+ # HOST:CONTAINER (DO NOT CHANGE THE CONTAINER PORT, UNLESS EDITED IN THE config.toml FILE)
9
+ - 8080:8080
10
+ volumes:
11
+ - ./config.toml:/app/config.toml
12
+ # Uncomment the the below stuff to use POSTGRES!
13
+ # db:
14
+ # image: postgres
15
+ # restart: unless-stopped
16
+ # environment:
17
+ # POSTGRES_PASSWORD: password #CHANGE THIS
18
+ # POSTGRES_USER: username
19
+ # POSTGRES_DB: db
20
+ # volumes:
21
+ # - ./db:/var/lib/postgresql/data
docker-compose.yml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ nebula:
3
+ image: ghcr.io/nebulaservices/nebula:latest
4
+ container_name: nebula
5
+ restart: unless-stopped
6
+ ports:
7
+ # HOST:CONTAINER (DO NOT CHANGE THE CONTAINER PORT, UNLESS EDITED IN THE config.toml FILE)
8
+ - 8080:8080
9
+ volumes:
10
+ - ./config.toml:/app/config.toml
11
+ # Uncomment the the below stuff to use POSTGRES!
12
+ # db:
13
+ # image: postgres
14
+ # restart: unless-stopped
15
+ # environment:
16
+ # POSTGRES_PASSWORD: password #CHANGE THIS
17
+ # POSTGRES_USER: username
18
+ # POSTGRES_DB: db
19
+ # volumes:
20
+ # - ./db:/var/lib/postgresql/data
package.json ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "nebula",
3
+ "type": "module",
4
+ "version": "9.0.2",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "concurrently \"astro dev --host 0.0.0.0\" \"tsx --watch server/server.ts\"",
8
+ "start": "node server/server.js",
9
+ "build:server": "tsc -p server",
10
+ "build:client": "astro check && astro build",
11
+ "build": "concurrently \"npm:build:server\" \"npm:build:client\"",
12
+ "bstart": "npm run build && npm run start",
13
+ "preview": "astro preview",
14
+ "astro": "astro",
15
+ "format:code": "biome format . --write",
16
+ "format:imports": "biome check . --write",
17
+ "format": "concurrently -m 1 \"npm:format:code\" \"npm:format:imports\"",
18
+ "version": "changeset version"
19
+ },
20
+ "dependencies": {
21
+ "@astrojs/check": "^0.8.3",
22
+ "@astrojs/node": "^8.3.4",
23
+ "@astrojs/svelte": "^5.7.3",
24
+ "@astrojs/tailwind": "^5.1.2",
25
+ "@fastify/compress": "^8.0.1",
26
+ "@fastify/helmet": "^12.0.1",
27
+ "@fastify/middie": "^9.0.2",
28
+ "@fastify/multipart": "^9.0.1",
29
+ "@fastify/static": "^8.0.2",
30
+ "@iconify-json/ph": "^1.2.1",
31
+ "@mercuryworkshop/bare-mux": "^2.1.6",
32
+ "@mercuryworkshop/epoxy-transport": "2.1.13",
33
+ "@mercuryworkshop/libcurl-transport": "^1.3.12",
34
+ "@playform/compress": "^0.1.6",
35
+ "@svelte-drama/suspense": "0.5.1",
36
+ "@titaniumnetwork-dev/ultraviolet": "^3.2.10",
37
+ "@types/node": "^22.9.0",
38
+ "@types/sequelize": "^4.28.20",
39
+ "astro": "^4.16.10",
40
+ "astro-icon": "^1.1.2",
41
+ "chalk": "^5.3.0",
42
+ "concurrently": "^8.2.2",
43
+ "fastify": "^5.1.0",
44
+ "form-data": "^4.0.1",
45
+ "gradient-string": "^3.0.0",
46
+ "nanostores": "^0.10.3",
47
+ "ora": "^8.1.1",
48
+ "pg": "^8.13.1",
49
+ "pg-hstore": "^2.3.4",
50
+ "sequelize": "^6.37.5",
51
+ "smol-toml": "^1.3.0",
52
+ "sqlite3": "^5.1.7",
53
+ "svelte": "^4.2.19",
54
+ "svelte-french-toast": "^1.2.0",
55
+ "tailwindcss": "^3.4.14",
56
+ "typescript": "^5.6.3",
57
+ "vite-plugin-static-copy": "^1.0.6",
58
+ "wisp-server-node": "^1.1.7"
59
+ },
60
+ "devDependencies": {
61
+ "@biomejs/biome": "^1.9.4",
62
+ "@changesets/cli": "^2.27.9",
63
+ "bufferutil": "^4.0.8",
64
+ "sharp": "^0.33.5",
65
+ "tsx": "^4.19.2"
66
+ }
67
+ }
pnpm-lock.yaml ADDED
The diff for this file is too large to render. See raw diff
 
public/classic_theme.png ADDED
public/cloaks/canvas.ico ADDED
public/cloaks/classroom.png ADDED
public/cloaks/google.png ADDED
public/cloaks/ps.ico ADDED
public/cloaks/wikipedia.ico ADDED
public/favicon.svg ADDED
public/nebula.css ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --background-primary: #191724;
3
+ --background-lighter: #16121f;
4
+ --navbar-color: #26233a;
5
+ --navbar-height: 60px;
6
+ --navbar-text-color: #7967dd;
7
+ --navbar-link-color: #e0def4;
8
+ --navbar-link-hover-color: gray;
9
+ --navbar-font: "Roboto";
10
+ --input-text-color: #e0def4;
11
+ --input-placeholder-color: white;
12
+ --input-background-color: #1f1d2e;
13
+ --input-border-color: #eb6f92;
14
+ --input-border-size: 1.3px;
15
+ --navbar-logo-filter: none;
16
+ --dropdown-option-hover-color: #312a49;
17
+ --tab-color: var(--black);
18
+ --border-color: #16121f;
19
+ }
public/sw.js ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ importScripts("/uv/uv.bundle.js");
2
+ importScripts("/uv/uv.config.js");
3
+ importScripts("/workerware/workerware.js");
4
+ importScripts(__uv$config.sw || "/uv/uv.sw.js");
5
+ const uv = new UVServiceWorker();
6
+ const ww = new WorkerWare({ debug: false });
7
+
8
+ //me when Firefox (thanks vk6)
9
+ if (navigator.userAgent.includes("Firefox")) {
10
+ Object.defineProperty(globalThis, "crossOriginIsolated", {
11
+ value: true,
12
+ writable: true
13
+ });
14
+ }
15
+
16
+ //where we handle our plugins!!!
17
+ self.addEventListener("message", function (event) {
18
+ console.log(event.data);
19
+ uv.config.inject = [];
20
+ //loop over the required data (we don't verify here as types will take care of us :D)
21
+ event.data.forEach((data) => {
22
+ if (data.remove) {
23
+ if (data.type === "page") {
24
+ const idx = uv.config.inject.indexOf(data.host);
25
+ uv.config.inject.splice(idx, 1);
26
+ } else if (data.type === "serviceWorker") {
27
+ ww.deleteByName(data.name);
28
+ }
29
+ } else {
30
+ if (data.type === "page") {
31
+ uv.config.inject.push({
32
+ host: data.host,
33
+ html: data.html,
34
+ injectTo: data.injectTo
35
+ });
36
+ } else if (data.type === "serviceWorker") {
37
+ const wwFunction = eval(data.function);
38
+ ww.use({
39
+ function: wwFunction ? wwFunction : new Function(data.function),
40
+ name: data.name,
41
+ events: data.events
42
+ });
43
+ } else {
44
+ console.error("NO type exists for that. Only serviceWorker & page exist.");
45
+ return;
46
+ }
47
+ }
48
+ });
49
+ });
50
+
51
+ self.addEventListener("fetch", function (event) {
52
+ event.respondWith(
53
+ (async () => {
54
+ const wwRes = await ww.run(event)();
55
+ if (wwRes.includes(null)) {
56
+ return;
57
+ }
58
+ if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
59
+ return await uv.fetch(event);
60
+ } else {
61
+ return await fetch(event.request);
62
+ }
63
+ })()
64
+ );
65
+ });
public/uv/uv.config.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ self.__uv$config = {
2
+ prefix: "/~/uv/",
3
+ bare: "/bare/",
4
+ encodeUrl: function encode(str) {
5
+ if (!str) return str;
6
+ return encodeURIComponent(
7
+ str
8
+ .toString()
9
+ .split("")
10
+ .map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 3) : char))
11
+ .join("")
12
+ );
13
+ },
14
+ decodeUrl: function decode(str) {
15
+ if (!str) return str;
16
+ let [input, ...search] = str.split("?");
17
+
18
+ return (
19
+ decodeURIComponent(input)
20
+ .split("")
21
+ .map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 3) : char))
22
+ .join("") + (search.length ? "?" + search.join("?") : "")
23
+ );
24
+ },
25
+ handler: "/uv/uv.handler.js",
26
+ client: "/uv/uv.client.js",
27
+ bundle: "/uv/uv.bundle.js",
28
+ config: "/uv/uv.config.js",
29
+ sw: "/uv/uv.sw.js"
30
+ };
public/workerware/workerware.js ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ importScripts("/workerware/WWError.js");
2
+ const dbg = console.log.bind(console, "[WorkerWare]");
3
+ const time = console.time.bind(console, "[WorkerWare]");
4
+ const timeEnd = console.timeEnd.bind(console, "[WorkerWare]");
5
+
6
+ /*
7
+ OPTS:
8
+ debug - Enables debug logging.
9
+ randomNames - Generate random names for middlewares.
10
+ timing - Logs timing for each middleware.
11
+ */
12
+
13
+ const defaultOpt = {
14
+ debug: false,
15
+ randomNames: false,
16
+ timing: false
17
+ };
18
+
19
+ const validEvents = [
20
+ "abortpayment",
21
+ "activate",
22
+ "backgroundfetchabort",
23
+ "backgroundfetchclick",
24
+ "backgroundfetchfail",
25
+ "backgroundfetchsuccess",
26
+ "canmakepayment",
27
+ "contentdelete",
28
+ "cookiechange",
29
+ "fetch",
30
+ "install",
31
+ "message",
32
+ "messageerror",
33
+ "notificationclick",
34
+ "notificationclose",
35
+ "paymentrequest",
36
+ "periodicsync",
37
+ "push",
38
+ "pushsubscriptionchange",
39
+ "sync"
40
+ ];
41
+
42
+ class WorkerWare {
43
+ constructor(opt) {
44
+ this._opt = Object.assign({}, defaultOpt, opt);
45
+ this._middlewares = [];
46
+ }
47
+ info() {
48
+ return {
49
+ version: "0.1.0",
50
+ middlewares: this._middlewares,
51
+ options: this._opt
52
+ };
53
+ }
54
+ use(middleware) {
55
+ let validateMW = this.validateMiddleware(middleware);
56
+ if (validateMW.error) throw new WWError(validateMW.error);
57
+ // This means the middleware is an anonymous function, or the user is silly and named their function "function"
58
+ if (middleware.function.name == "function") middleware.name = crypto.randomUUID();
59
+ if (!middleware.name) middleware.name = middleware.function.name;
60
+ if (this._opt.randomNames) middleware.name = crypto.randomUUID();
61
+ if (this._opt.debug) dbg("Adding middleware:", middleware.name);
62
+ this._middlewares.push(middleware);
63
+ }
64
+ // Run all middlewares for the event type passed in.
65
+ run(event) {
66
+ const middlewares = this._middlewares;
67
+ const returnList = [];
68
+ let fn = async () => {
69
+ for (let i = 0; i < middlewares.length; i++) {
70
+ if (middlewares[i].events.includes(event.type)) {
71
+ if (this._opt.timing) console.time(middlewares[i].name);
72
+ // Add the configuration to the event object.
73
+ event.workerware = {
74
+ config: middlewares[i].configuration || {}
75
+ };
76
+ if (!middlewares[i].explicitCall) {
77
+ let res = await middlewares[i].function(event);
78
+ if (this._opt.timing) console.timeEnd(middlewares[i].name);
79
+ returnList.push(res);
80
+ }
81
+ }
82
+ }
83
+ return returnList;
84
+ };
85
+ return fn;
86
+ }
87
+ deleteByName(middlewareID) {
88
+ if (this._opt.debug) dbg("Deleting middleware:", middlewareID);
89
+ this._middlewares = this._middlewares.filter((mw) => mw.name !== middlewareID);
90
+ }
91
+ deleteByEvent(middlewareEvent) {
92
+ if (this._opt.debug) dbg("Deleting middleware by event:", middlewareEvent);
93
+ this._middlewares = this._middlewares.filter((mw) => !mw.events.includes(middlewareEvent));
94
+ }
95
+ get() {
96
+ return this._middlewares;
97
+ }
98
+ /*
99
+ Run a single middleware by ID.
100
+ This assumes that the user knows what they're doing, and is running the middleware on an event that it's supposed to run on.
101
+ */
102
+ runMW(name, event) {
103
+ const middlewares = this._middlewares;
104
+ if (this._opt.debug) dbg("Running middleware:", name);
105
+ // if (middlewares.includes(name)) {
106
+ // return middlewares[name](event);
107
+ // } else {
108
+ // throw new WWError("Middleware not found!");
109
+ // }
110
+ let didCall = false;
111
+ for (let i = 0; i < middlewares.length; i++) {
112
+ if (middlewares[i].name == name) {
113
+ didCall = true;
114
+ event.workerware = {
115
+ config: middlewares[i].configuration || {}
116
+ };
117
+ if (this._opt.timing) console.time(middlewares[i].name);
118
+ let call = middlewares[i].function(event);
119
+ if (this._opt.timing) console.timeEnd(middlewares[i].name);
120
+ return call;
121
+ }
122
+ }
123
+ if (!didCall) {
124
+ throw new WWError("Middleware not found!");
125
+ }
126
+ }
127
+ // type middlewareManifest = {
128
+ // function: Function,
129
+ // name?: string,
130
+ // events: string[], // Should be a union of validEvents.
131
+ // configuration?: Object // Optional configuration for the middleware.
132
+ // }
133
+ validateMiddleware(middleware) {
134
+ if (!middleware.function)
135
+ return {
136
+ error: "middleware.function is required"
137
+ };
138
+ if (typeof middleware.function !== "function")
139
+ return {
140
+ error: "middleware.function must be typeof function"
141
+ };
142
+ if (
143
+ typeof middleware.configuration !== "object" &&
144
+ middleware.configuration !== undefined
145
+ ) {
146
+ return {
147
+ error: "middleware.configuration must be typeof object"
148
+ };
149
+ }
150
+ if (!middleware.events)
151
+ return {
152
+ error: "middleware.events is required"
153
+ };
154
+ if (!Array.isArray(middleware.events))
155
+ return {
156
+ error: "middleware.events must be an array"
157
+ };
158
+ if (middleware.events.some((ev) => !validEvents.includes(ev)))
159
+ return {
160
+ error: "Invalid event type! Must be one of the following: " + validEvents.join(", ")
161
+ };
162
+ if (middleware.explicitCall && typeof middleware.explicitCall !== "boolean") {
163
+ return {
164
+ error: "middleware.explicitCall must be typeof boolean"
165
+ };
166
+ }
167
+ return {
168
+ error: undefined
169
+ };
170
+ }
171
+ }
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ flask
server/config.ts ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import chalk from "chalk";
4
+ import { TomlPrimitive, parse } from "smol-toml";
5
+
6
+ interface TomlData {
7
+ marketplace: {
8
+ enabled: boolean;
9
+ psk: String;
10
+ };
11
+ server: {
12
+ server: {
13
+ port: number;
14
+ wisp: boolean;
15
+ logging: boolean;
16
+ };
17
+ };
18
+ db: {
19
+ name: string;
20
+ username: string;
21
+ password: string;
22
+ postgres: boolean;
23
+ };
24
+ postgres: {
25
+ domain: string;
26
+ port: number;
27
+ };
28
+ }
29
+
30
+ interface Verify {
31
+ name: string;
32
+ typeOF: any;
33
+ type: any;
34
+ }
35
+
36
+ let doc = readFileSync(fileURLToPath(new URL("../config.toml", import.meta.url))).toString();
37
+ const parsedDoc = parse(doc) as unknown as TomlData;
38
+
39
+ function verify(t: Verify[]) {
40
+ for (let i: number = 0; i !== t.length; i++) {
41
+ if (typeof t[i].typeOF !== t[i].type) {
42
+ throw new Error(`Invalid structure: "${t[i].name}" should be a(n) ${t[i].type}`);
43
+ }
44
+ }
45
+ }
46
+
47
+ verify([
48
+ { name: "marketplace", typeOF: parsedDoc.marketplace, type: "object" },
49
+ { name: "marketplace.enabled", typeOF: parsedDoc.marketplace.enabled, type: "boolean" },
50
+ { name: "marketplace.psk", typeOF: parsedDoc.marketplace.psk, type: "string" },
51
+ { name: "server", typeOF: parsedDoc.server, type: "object" },
52
+ { name: "server.server", typeOF: parsedDoc.server.server, type: "object" },
53
+ { name: "server.server.port", typeOF: parsedDoc.server.server.port, type: "number" },
54
+ { name: "server.server.wisp", typeOF: parsedDoc.server.server.wisp, type: "boolean" },
55
+ { name: "server.server.logging", typeOF: parsedDoc.server.server.logging, type: "boolean" },
56
+ { name: "db", typeOF: parsedDoc.db, type: "object" },
57
+ { name: "db.name", typeOF: parsedDoc.db.name, type: "string" },
58
+ { name: "db.username", typeOF: parsedDoc.db.username, type: "string" },
59
+ { name: "db.password", typeOF: parsedDoc.db.password, type: "string" },
60
+ { name: "db.postgres", typeOF: parsedDoc.db.postgres, type: "boolean" },
61
+ { name: "postgres", typeOF: parsedDoc.postgres, type: "object" },
62
+ { name: "postgres.domain", typeOF: parsedDoc.postgres.domain, type: "string" },
63
+ { name: "postgres.port", typeOF: parsedDoc.postgres.port, type: "number" }
64
+ ]);
65
+
66
+ if (parsedDoc.marketplace.psk === "CHANGEME") {
67
+ console.warn(chalk.yellow.bold('PSK should be changed from "CHANGEME"'));
68
+ }
69
+ if (parsedDoc.db.password === "password") {
70
+ console.warn(chalk.red.bold("You should change your DB password!!"));
71
+ }
72
+
73
+ export { TomlData, parsedDoc };
server/dbSetup.ts ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fileURLToPath } from "node:url";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { ModelStatic } from "sequelize";
5
+ import { Catalog, CatalogModel } from "./marketplace.js";
6
+
7
+ interface Items extends Omit<Catalog, "background_video" | "background_image"> {
8
+ background_video?: string;
9
+ background_image?: string;
10
+ }
11
+
12
+ async function installItems(db: ModelStatic<CatalogModel>, items: Items[]) {
13
+ items.forEach(async (item) => {
14
+ await db.create({
15
+ package_name: item.package_name,
16
+ title: item.title,
17
+ image: item.image,
18
+ author: item.author,
19
+ version: item.version,
20
+ description: item.description,
21
+ tags: item.tags,
22
+ payload: item.payload,
23
+ background_video: item.background_video,
24
+ background_image: item.background_image,
25
+ type: item.type
26
+ });
27
+ });
28
+ }
29
+
30
+ async function setupDB(db: ModelStatic<CatalogModel>) {
31
+ //We have some packages that need to be installed if they aren't.
32
+ const items: Items[] = [
33
+ {
34
+ package_name: "com.nebula.gruvbox",
35
+ title: "Gruvbox",
36
+ image: "gruvbox.jpeg",
37
+ author: "Nebula Services",
38
+ version: "1.0.0",
39
+ description: "The gruvbox theme",
40
+ tags: ["Theme", "Simple"],
41
+ payload: "gruvbox.css",
42
+ type: "theme"
43
+ },
44
+ {
45
+ package_name: "com.nebula.oled",
46
+ title: "Oled theme",
47
+ image: "oled.jpg",
48
+ author: "Nebula Services",
49
+ version: "1.0.0",
50
+ description: "A sleek & simple Oled theme for Nebula",
51
+ tags: ["Theme", "Simple", "Sleek"],
52
+ payload: "oled.css",
53
+ type: "theme"
54
+ },
55
+ {
56
+ package_name: "com.nebula.lightTheme",
57
+ title: "Light Theme",
58
+ image: "light.png",
59
+ author: "Nebula Services",
60
+ version: "1.0.0",
61
+ description: "A sleek light theme for Nebula",
62
+ tags: ["Theme", "Simple", "Light"],
63
+ payload: "light.css",
64
+ type: "theme"
65
+ },
66
+ {
67
+ package_name: "com.nebula.retro",
68
+ title: "Retro Theme",
69
+ image: "retro.png",
70
+ author: "Nebula Services",
71
+ version: "1.0.0",
72
+ description: "Give a retro look to Nebula",
73
+ tags: ["Theme", "Simple", "Dark", "Retro"],
74
+ payload: "retro.css",
75
+ type: "theme"
76
+ }
77
+ //To add plugins: plugin types consist of plugin-sw (workerware) & plugin-page (uv.config.inject)
78
+ ];
79
+ const dbItems = await db.findAll();
80
+ if (dbItems.length === 0) {
81
+ const spinner = ora(chalk.hex("#7967dd")("Performing DB setup...")).start();
82
+ await installItems(db, items);
83
+ spinner.succeed(chalk.hex("#eb6f92")("DB setup complete!"));
84
+ }
85
+ }
86
+
87
+ export { setupDB };
server/env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ declare module "@rubynetwork/rammerhead/src/server/index.js";
server/marketplace.ts ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createWriteStream } from "node:fs";
2
+ import { constants, access, mkdir } from "node:fs/promises";
3
+ import { pipeline } from "node:stream/promises";
4
+ import { fileURLToPath } from "node:url";
5
+ import { FastifyInstance, FastifyRequest } from "fastify";
6
+ import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
7
+ import { parsedDoc } from "./config.js";
8
+
9
+ const db = new Sequelize(parsedDoc.db.name, parsedDoc.db.username, parsedDoc.db.password, {
10
+ host: parsedDoc.db.postgres ? `${parsedDoc.postgres.domain}` : "localhost",
11
+ port: parsedDoc.db.postgres ? parsedDoc.postgres.port : undefined,
12
+ dialect: parsedDoc.db.postgres ? "postgres" : "sqlite",
13
+ logging: parsedDoc.server.server.logging,
14
+ storage: "database.sqlite" //this is sqlite only
15
+ });
16
+
17
+ type CatalogType = "theme" | "plugin-page" | "plugin-sw";
18
+
19
+ interface Catalog {
20
+ package_name: string;
21
+ title: string;
22
+ description: string;
23
+ author: string;
24
+ image: string;
25
+ tags: object;
26
+ version: string;
27
+ background_image: string;
28
+ background_video: string;
29
+ payload: string;
30
+ type: CatalogType;
31
+ }
32
+
33
+ interface CatalogModel
34
+ extends Catalog,
35
+ Model<InferAttributes<CatalogModel>, InferCreationAttributes<CatalogModel>> {}
36
+
37
+ const catalogAssets = db.define<CatalogModel>("catalog_assets", {
38
+ package_name: { type: DataTypes.STRING, unique: true },
39
+ title: { type: DataTypes.TEXT },
40
+ description: { type: DataTypes.TEXT },
41
+ author: { type: DataTypes.TEXT },
42
+ image: { type: DataTypes.TEXT },
43
+ tags: { type: DataTypes.JSON, allowNull: true },
44
+ version: { type: DataTypes.TEXT },
45
+ background_image: { type: DataTypes.TEXT, allowNull: true },
46
+ background_video: { type: DataTypes.TEXT, allowNull: true },
47
+ payload: { type: DataTypes.TEXT },
48
+ type: { type: DataTypes.TEXT }
49
+ });
50
+
51
+ function marketplaceAPI(app: FastifyInstance) {
52
+ app.get("/api/catalog-stats/", (request, reply) => {
53
+ reply.send({
54
+ version: "1.0.0",
55
+ spec: "Nebula Services",
56
+ enabled: true
57
+ });
58
+ });
59
+
60
+ // This API returns a list of the assets in the database (SW plugins and themes).
61
+ // It also returns the number of pages in the database.
62
+ // It can take a `?page=x` argument to display a different page, with a limit of 20 assets per page.
63
+ type CatalogAssetsReq = FastifyRequest<{ Querystring: { page: string } }>;
64
+ app.get("/api/catalog-assets/", async (request: CatalogAssetsReq, reply) => {
65
+ try {
66
+ const { page } = request.query;
67
+ const pageNum: number = parseInt(page, 10) || 1;
68
+ if (pageNum < 1) {
69
+ reply.status(400).send({ error: "Page must be a positive number!" });
70
+ }
71
+ const offset = (pageNum - 1) * 20;
72
+ const totalItems = await catalogAssets.count();
73
+ const dbAssets = await catalogAssets.findAll({ offset: offset, limit: 20 });
74
+ const assets = dbAssets.reduce((acc, asset) => {
75
+ acc[asset.package_name] = {
76
+ title: asset.title,
77
+ description: asset.description,
78
+ author: asset.author,
79
+ image: asset.image,
80
+ tags: asset.tags,
81
+ version: asset.version,
82
+ background_image: asset.background_image,
83
+ background_video: asset.background_video,
84
+ payload: asset.payload,
85
+ type: asset.type
86
+ };
87
+ return acc;
88
+ }, {});
89
+ return reply.send({ assets, pages: Math.ceil(totalItems / 20) });
90
+ } catch (error) {
91
+ return reply.status(500).send({ error: "An error occured" });
92
+ }
93
+ });
94
+
95
+ type PackageReq = FastifyRequest<{ Params: { package: string } }>;
96
+ app.get("/api/packages/:package", async (request: PackageReq, reply) => {
97
+ try {
98
+ const packageRow = await catalogAssets.findOne({
99
+ where: { package_name: request.params.package }
100
+ });
101
+ if (!packageRow) return reply.status(404).send({ error: "Package not found!" });
102
+ const details = {
103
+ title: packageRow.get("title"),
104
+ description: packageRow.get("description"),
105
+ image: packageRow.get("image"),
106
+ author: packageRow.get("author"),
107
+ tags: packageRow.get("tags"),
108
+ version: packageRow.get("version"),
109
+ background_image: packageRow.get("background_image"),
110
+ background_video: packageRow.get("background_video"),
111
+ payload: packageRow.get("payload"),
112
+ type: packageRow.get("type")
113
+ };
114
+ reply.send(details);
115
+ } catch (error) {
116
+ reply.status(500).send({ error: "An unexpected error occured" });
117
+ }
118
+ });
119
+
120
+ type UploadReq = FastifyRequest<{ Headers: { psk: string; packagename: string } }>;
121
+ type CreateReq = FastifyRequest<{
122
+ Headers: { psk: string };
123
+ Body: {
124
+ uuid: string;
125
+ title: string;
126
+ image: string;
127
+ author: string;
128
+ version: string;
129
+ description: string;
130
+ tags: object | any;
131
+ payload: string;
132
+ background_video: string;
133
+ background_image: string;
134
+ type: CatalogType;
135
+ };
136
+ }>;
137
+ interface VerifyStatus {
138
+ status: number;
139
+ error?: Error;
140
+ }
141
+ async function verifyReq(
142
+ request: UploadReq | CreateReq,
143
+ upload: Boolean,
144
+ data: any
145
+ ): Promise<VerifyStatus> {
146
+ if (request.headers.psk !== parsedDoc.marketplace.psk) {
147
+ return { status: 403, error: new Error("PSK isn't correct!") };
148
+ } else if (upload && !request.headers.packagename) {
149
+ return { status: 500, error: new Error("No packagename defined!") };
150
+ } else if (upload && !data) {
151
+ return { status: 400, error: new Error("No file uploaded!") };
152
+ } else {
153
+ return { status: 200 };
154
+ }
155
+ }
156
+
157
+ app.post("/api/upload-asset", async (request: UploadReq, reply) => {
158
+ const data = await request.file();
159
+ const verify: VerifyStatus = await verifyReq(request, true, data);
160
+ if (verify.error !== undefined) {
161
+ reply.status(verify.status).send({ status: verify.error.message });
162
+ } else {
163
+ try {
164
+ await pipeline(
165
+ data.file,
166
+ createWriteStream(
167
+ fileURLToPath(
168
+ new URL(
169
+ `../database_assets/${request.headers.packagename}/${data.filename}`,
170
+ import.meta.url
171
+ )
172
+ )
173
+ )
174
+ );
175
+ } catch (error) {
176
+ return reply.status(500).send({
177
+ status: `File couldn't be uploaded! (Package most likely doesn't exist)`
178
+ });
179
+ }
180
+ return reply.status(verify.status).send({ status: "File uploaded successfully!" });
181
+ }
182
+ });
183
+
184
+ app.post("/api/create-package", async (request: CreateReq, reply) => {
185
+ const verify: VerifyStatus = await verifyReq(request, false, undefined);
186
+ if (verify.error !== undefined) {
187
+ reply.status(verify.status).send({ status: verify.error.message });
188
+ } else {
189
+ const body: Catalog = {
190
+ package_name: request.body.uuid,
191
+ title: request.body.title,
192
+ image: request.body.image,
193
+ author: request.body.author,
194
+ version: request.body.version,
195
+ description: request.body.description,
196
+ tags: request.body.tags,
197
+ payload: request.body.payload,
198
+ background_video: request.body.background_video,
199
+ background_image: request.body.background_image,
200
+ type: request.body.type as CatalogType
201
+ };
202
+ await catalogAssets.create({
203
+ package_name: body.package_name,
204
+ title: body.title,
205
+ image: body.image,
206
+ author: body.author,
207
+ version: body.version,
208
+ description: body.description,
209
+ tags: body.tags,
210
+ payload: body.payload,
211
+ background_video: body.background_video,
212
+ background_image: body.background_image,
213
+ type: body.type
214
+ });
215
+ const assets = fileURLToPath(new URL("../database_assets", import.meta.url));
216
+ try {
217
+ await access(`${assets}/${body.package_name}/`, constants.F_OK);
218
+ return reply.status(500).send({ status: "Package already exists!" });
219
+ } catch (err) {
220
+ await mkdir(`${assets}/${body.package_name}/`);
221
+ return reply
222
+ .status(verify.status)
223
+ .send({ status: "Package created successfully!" });
224
+ }
225
+ }
226
+ });
227
+ }
228
+
229
+ export { marketplaceAPI, db, catalogAssets, Catalog, CatalogModel };
server/server.ts ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createWriteStream } from "node:fs";
2
+ import { constants, access, mkdir } from "node:fs/promises";
3
+ import { pipeline } from "node:stream/promises";
4
+ import { fileURLToPath } from "node:url";
5
+ import fastifyCompress from "@fastify/compress";
6
+ import fastifyHelmet from "@fastify/helmet";
7
+ import fastifyMiddie from "@fastify/middie";
8
+ import fastifyMultipart from "@fastify/multipart";
9
+ import fastifyStatic from "@fastify/static";
10
+ import chalk from "chalk";
11
+ import Fastify, { FastifyReply, FastifyRequest } from "fastify";
12
+ import gradient from "gradient-string";
13
+ //@ts-ignore WHY would I want this typechecked AT ALL
14
+ import { handler as ssrHandler } from "../dist/server/entry.mjs";
15
+ import { parsedDoc } from "./config.js";
16
+ import { setupDB } from "./dbSetup.js";
17
+ import { catalogAssets, marketplaceAPI } from "./marketplace.js";
18
+ import { serverFactory } from "./serverFactory.js";
19
+
20
+ const app = Fastify({
21
+ logger: parsedDoc.server.server.logging,
22
+ ignoreDuplicateSlashes: true,
23
+ ignoreTrailingSlash: true,
24
+ serverFactory: serverFactory
25
+ });
26
+
27
+ await app.register(fastifyCompress, {
28
+ encodings: ["br", "gzip", "deflate"]
29
+ });
30
+
31
+ await app.register(fastifyMultipart, {
32
+ limits: {
33
+ fileSize: 25 * 1024 * 1024,
34
+ parts: Infinity
35
+ },
36
+ });
37
+
38
+ await app.register(fastifyHelmet, {
39
+ xFrameOptions: false,
40
+ crossOriginEmbedderPolicy: true,
41
+ crossOriginOpenerPolicy: true,
42
+ contentSecurityPolicy: false
43
+ });
44
+
45
+ await app.register(fastifyStatic, {
46
+ root: fileURLToPath(new URL("../dist/client", import.meta.url))
47
+ });
48
+
49
+ //Our marketplace API. Not middleware as I don't want to deal with that LOL. Just a function that passes our app to it.
50
+ if (parsedDoc.marketplace.enabled) {
51
+ await app.register(fastifyStatic, {
52
+ root: fileURLToPath(new URL("../database_assets", import.meta.url)),
53
+ prefix: "/packages/",
54
+ decorateReply: false
55
+ });
56
+ marketplaceAPI(app);
57
+ }
58
+
59
+ await app.register(fastifyMiddie);
60
+
61
+ app.use(ssrHandler);
62
+
63
+ const port: number =
64
+ parseInt(process.env.PORT as string) || parsedDoc.server.server.port || parseInt("8080");
65
+ const titleText = `
66
+ _ _ _ _ ____ _
67
+ | \\ | | ___| |__ _ _| | __ _ / ___| ___ _ ____ _(_) ___ ___ ___
68
+ | \\| |/ _ \\ '_ \\| | | | |/ _' | \\___ \\ / _ \\ '__\\ \\ / / |/ __/ _ \\/ __|
69
+ | |\\ | __/ |_) | |_| | | (_| | ___) | __/ | \\ V /| | (_| __/\\__ \\
70
+ |_| \\_|\\___|_.__/ \\__,_|_|\\__,_| |____/ \\___|_| \\_/ |_|\\___\\___||___/
71
+ `;
72
+ const titleColors = {
73
+ purple: "#7967dd",
74
+ pink: "#eb6f92"
75
+ };
76
+
77
+ console.log(gradient(Object.values(titleColors)).multiline(titleText as string));
78
+ app.listen({ port: port, host: "0.0.0.0" }).then(async () => {
79
+ console.log(
80
+ chalk.hex("#7967dd")(
81
+ `Server listening on ${chalk.hex("#eb6f92").bold("http://localhost:" + port + "/")}`
82
+ )
83
+ );
84
+ console.log(
85
+ chalk.hex("#7967dd")(
86
+ `Server also listening on ${chalk.hex("#eb6f92").bold("http://0.0.0.0:" + port + "/")}`
87
+ )
88
+ );
89
+ if (parsedDoc.marketplace.enabled) {
90
+ await catalogAssets.sync();
91
+ await setupDB(catalogAssets);
92
+ }
93
+ });
server/serverFactory.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createServer } from "node:http";
2
+ import { FastifyServerFactory, FastifyServerFactoryHandler, RawServerDefault } from "fastify";
3
+ import wisp from "wisp-server-node";
4
+ import { LOG_LEVEL, WispOptions } from "wisp-server-node/dist/Types.js";
5
+ import { parsedDoc } from "./config.js";
6
+
7
+ const wispOptions: WispOptions = {
8
+ logLevel: parsedDoc.server.server.logging ? LOG_LEVEL.DEBUG : LOG_LEVEL.NONE,
9
+ pingInterval: 30
10
+ };
11
+
12
+ const serverFactory: FastifyServerFactory = (
13
+ handler: FastifyServerFactoryHandler
14
+ ): RawServerDefault => {
15
+ const httpServer = createServer();
16
+ httpServer.on("request", (req, res) => {
17
+ handler(req, res);
18
+ });
19
+ httpServer.on("upgrade", (req, socket, head) => {
20
+ if (parsedDoc.server.server.wisp) {
21
+ if (req.url?.endsWith("/wisp/")) {
22
+ wisp.routeRequest(req, socket as any, head, wispOptions);
23
+ }
24
+ }
25
+ });
26
+ return httpServer;
27
+ };
28
+
29
+ export { serverFactory };
server/tsconfig.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "noEmit": false,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "paths": {}
10
+ }
11
+ }
src/assets/classic_theme.png ADDED
src/assets/credits/libcurl.png ADDED
src/assets/credits/mercury.png ADDED
src/assets/credits/motortruck1221.png ADDED

Git LFS Details

  • SHA256: 8b38ebaff80223cb1557f28d1b3e7c7dbf00ccc495395ce0f3841df75809749e
  • Pointer size: 131 Bytes
  • Size of remote file: 192 kB
src/assets/credits/rammerhead.png ADDED
src/assets/credits/rift.jpeg ADDED
src/assets/credits/uv.png ADDED
src/assets/fortnite.jpg ADDED
src/components/Card.astro ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ interface Props {
3
+ title: string;
4
+ body: string;
5
+ href: string;
6
+ }
7
+
8
+ const { href, title, body } = Astro.props;
9
+ ---
10
+
11
+ <li class="link-card">
12
+ <a href={href}>
13
+ <h2>
14
+ {title}
15
+ <span>&rarr;</span>
16
+ </h2>
17
+ <p>
18
+ {body}
19
+ </p>
20
+ </a>
21
+ </li>
22
+ <style>
23
+ .link-card {
24
+ list-style: none;
25
+ display: flex;
26
+ padding: 1px;
27
+ background-color: #23262d;
28
+ background-image: none;
29
+ background-size: 400%;
30
+ border-radius: 7px;
31
+ background-position: 100%;
32
+ transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
33
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
34
+ }
35
+ .link-card > a {
36
+ width: 100%;
37
+ text-decoration: none;
38
+ line-height: 1.4;
39
+ padding: calc(1.5rem - 1px);
40
+ border-radius: 8px;
41
+ color: white;
42
+ background-color: #23262d;
43
+ opacity: 0.8;
44
+ }
45
+ h2 {
46
+ margin: 0;
47
+ font-size: 1.25rem;
48
+ transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
49
+ }
50
+ p {
51
+ margin-top: 0.5rem;
52
+ margin-bottom: 0;
53
+ }
54
+ .link-card:is(:hover, :focus-within) {
55
+ background-position: 0;
56
+ background-image: var(--accent-gradient);
57
+ }
58
+ .link-card:is(:hover, :focus-within) h2 {
59
+ color: rgb(var(--accent-light));
60
+ }
61
+ </style>
src/components/Header.astro ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ import { MARKETPLACE_ENABLED } from "astro:env/client";
3
+ import { Icon } from "astro-icon/components";
4
+ import { getLangFromUrl, useTranslations } from "../i18n/utils";
5
+ import { isMobileNavOpen } from "../store.js";
6
+ import HeaderButton from "./HeaderButton.astro";
7
+ import Logo from "./Logo.astro";
8
+ const lang = getLangFromUrl(Astro.url);
9
+ const t = useTranslations(lang);
10
+ ---
11
+
12
+ <div
13
+ id="navbar"
14
+ class="flex h-16 flex-row items-center justify-end border-b-2 border-border-color bg-navbar-color px-4 z-30 relative"
15
+ >
16
+ <div class="w-1/8">
17
+ {/* Typical desktop menu */}
18
+ <div class="relative flex-row hidden lg:flex">
19
+ <HeaderButton text={t("header.home")} route={`/${lang}/`}>
20
+ <Icon
21
+ name="ph:house-bold"
22
+ class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
23
+ />
24
+ </HeaderButton>
25
+ <HeaderButton text={t("header.games")} route={`/${lang}/games/`}>
26
+ <Icon
27
+ name="ph:cube"
28
+ class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
29
+ />
30
+ {
31
+ /* Astro won't let us pass the icon as a prop so it's going into the outlet here. */
32
+ }
33
+ </HeaderButton>
34
+ <HeaderButton
35
+ text={t("header.settings")}
36
+ route={`/${lang}/settings/appearance`}
37
+ >
38
+ <Icon
39
+ name="ph:wrench-fill"
40
+ class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
41
+ />
42
+ </HeaderButton>
43
+ {MARKETPLACE_ENABLED &&
44
+ <HeaderButton text={t("header.catalog")} route={`/${lang}/catalog/1`}>
45
+ <Icon
46
+ name="ph:shopping-bag-open-fill"
47
+ class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
48
+ />
49
+ </HeaderButton>
50
+ }
51
+ <HeaderButton text={t("header.morelinks")}>
52
+ <Icon
53
+ name="ph:link-bold"
54
+ class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
55
+ />
56
+ </HeaderButton>
57
+ </div>
58
+ {/* Mobile hamburger menu */}
59
+ <div class="flex lg:hidden" id="mobileNavTrigger" transition:persist>
60
+ <Icon
61
+ name="ph:text-align-justify-bold"
62
+ class="h-9 w-9 text-text-color"
63
+ id="hamburger_menu"
64
+ />
65
+ <Icon
66
+ name="ph:caret-right-bold"
67
+ class="h-9 w-9 text-text-color hidden"
68
+ id="right_caret"
69
+ />
70
+ </div>
71
+ </div>
72
+ </div>
73
+ <script>
74
+ import { isMobileNavOpen } from "../store.js";
75
+ let isMobileNavOpenLocal = true;
76
+ const right_caret = document.getElementById("right_caret");
77
+ const hamburger_menu = document.getElementById("hamburger_menu");
78
+ const mobileNavTrigger = document.getElementById("mobileNavTrigger");
79
+ // Create a copy of the nano store so we can make this a toggle
80
+
81
+ // Set the store to true when the button is clicked
82
+ function openDialog() {
83
+ if (isMobileNavOpenLocal == false) {
84
+ isMobileNavOpen.set(true);
85
+ if (hamburger_menu && right_caret) {
86
+ hamburger_menu.style.display = "none";
87
+ right_caret.style.display = "block";
88
+ }
89
+ } else {
90
+ isMobileNavOpen.set(false);
91
+ if (hamburger_menu && right_caret) {
92
+ hamburger_menu.style.display = "block";
93
+ right_caret.style.display = "none";
94
+ }
95
+ }
96
+ }
97
+
98
+ isMobileNavOpen.subscribe((open) => {
99
+ if (open) {
100
+ isMobileNavOpenLocal = true;
101
+ if (hamburger_menu && right_caret) {
102
+ hamburger_menu.style.display = "none";
103
+ right_caret.style.display = "block";
104
+ }
105
+ } else {
106
+ isMobileNavOpenLocal = false;
107
+ if (hamburger_menu && right_caret) {
108
+ hamburger_menu.style.display = "block";
109
+ right_caret.style.display = "none";
110
+ }
111
+ }
112
+ });
113
+
114
+ // Add an event listener to the button
115
+
116
+ if (mobileNavTrigger) {
117
+ mobileNavTrigger.addEventListener("click", openDialog);
118
+ }
119
+ </script>
src/components/HeaderButton.astro ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ const { text, route } = Astro.props;
3
+ ---
4
+
5
+ <a
6
+ class="group flex w-full flex-row items-center justify-center border-t-2 border-solid border-navbar-text-color p-4 lg:border-none h-1/3 lg:h-fit"
7
+ id="header_anchor"
8
+ href={route}
9
+ >
10
+ <slot />
11
+ <span
12
+ class="font-roboto pl-2 text-center text-3xl font-bold text-text-color roboto transition duration-500 group-hover:text-text-hover-color lg:text-xl text-nowrap"
13
+ >
14
+ {text}
15
+ </span>
16
+ </a>