🧱 Added docs about self-hosting and changed envs
This commit is contained in:
@@ -5,6 +5,7 @@ FROM node:16 as builder
|
|||||||
# ENV API_URL=http://api:80
|
# ENV API_URL=http://api:80
|
||||||
ENV API_URL=https://mawoka.eu
|
ENV API_URL=https://mawoka.eu
|
||||||
ENV REDIS_URL=$REDIS
|
ENV REDIS_URL=$REDIS
|
||||||
|
ENV VITE_HCAPTCHA=ee81b2a1-acf3-4d20-b2a4-a7ea94c7eba5
|
||||||
ENV VITE_SENTRY=https://4981de1f72f24fd7b5e21b8913b93a02@o661934.ingest.sentry.io/6254641
|
ENV VITE_SENTRY=https://4981de1f72f24fd7b5e21b8913b93a02@o661934.ingest.sentry.io/6254641
|
||||||
# change working directory
|
# change working directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-svelte3": "^3.2.1",
|
"eslint-plugin-svelte3": "^3.2.1",
|
||||||
"felte": "^1.0.1",
|
"felte": "^1.0.1",
|
||||||
|
"highlight.js": "^11.4.0",
|
||||||
"ioredis": "^4.28.5",
|
"ioredis": "^4.28.5",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"luxon": "^2.3.1",
|
"luxon": "^2.3.1",
|
||||||
|
|||||||
Generated
+7
@@ -19,6 +19,7 @@ specifiers:
|
|||||||
eslint-config-prettier: ^8.3.0
|
eslint-config-prettier: ^8.3.0
|
||||||
eslint-plugin-svelte3: ^3.2.1
|
eslint-plugin-svelte3: ^3.2.1
|
||||||
felte: ^1.0.1
|
felte: ^1.0.1
|
||||||
|
highlight.js: ^11.4.0
|
||||||
ioredis: ^4.28.5
|
ioredis: ^4.28.5
|
||||||
js-cookie: ^3.0.1
|
js-cookie: ^3.0.1
|
||||||
luxon: ^2.3.1
|
luxon: ^2.3.1
|
||||||
@@ -57,6 +58,7 @@ devDependencies:
|
|||||||
eslint-config-prettier: 8.4.0_eslint@7.32.0
|
eslint-config-prettier: 8.4.0_eslint@7.32.0
|
||||||
eslint-plugin-svelte3: 3.4.1_eslint@7.32.0+svelte@3.46.4
|
eslint-plugin-svelte3: 3.4.1_eslint@7.32.0+svelte@3.46.4
|
||||||
felte: 1.0.1_svelte@3.46.4
|
felte: 1.0.1_svelte@3.46.4
|
||||||
|
highlight.js: 11.4.0
|
||||||
ioredis: 4.28.5
|
ioredis: 4.28.5
|
||||||
js-cookie: 3.0.1
|
js-cookie: 3.0.1
|
||||||
luxon: 2.3.1
|
luxon: 2.3.1
|
||||||
@@ -1450,6 +1452,11 @@ packages:
|
|||||||
function-bind: 1.1.1
|
function-bind: 1.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/highlight.js/11.4.0:
|
||||||
|
resolution: {integrity: sha512-nawlpCBCSASs7EdvZOYOYVkJpGmAOKMYZgZtUqSRqodZE0GRVcFKwo1RcpeOemqh9hyttTdd5wDBwHkuSyUfnA==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/ignore/4.0.6:
|
/ignore/4.0.6:
|
||||||
resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
|
resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
pre code.hljs {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
code.hljs {
|
||||||
|
padding: 3px 5px;
|
||||||
|
} /*!
|
||||||
|
Theme: a11y-dark
|
||||||
|
Author: @ericwbailey
|
||||||
|
Maintainer: @ericwbailey
|
||||||
|
|
||||||
|
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
|
||||||
|
*/
|
||||||
|
.hljs {
|
||||||
|
background: transparent;
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #d4d0ab;
|
||||||
|
}
|
||||||
|
.hljs-deletion,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-tag,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-variable {
|
||||||
|
color: #ffa07a;
|
||||||
|
}
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-link,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-params,
|
||||||
|
.hljs-type {
|
||||||
|
color: #f5ab35;
|
||||||
|
}
|
||||||
|
.hljs-attribute {
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
.hljs-addition,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol {
|
||||||
|
color: #abe338;
|
||||||
|
}
|
||||||
|
.hljs-section,
|
||||||
|
.hljs-title {
|
||||||
|
color: #00e0e0;
|
||||||
|
}
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
color: #dcc6e0;
|
||||||
|
}
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
@media screen and (-ms-high-contrast: active) {
|
||||||
|
.hljs-addition,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-link,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-params,
|
||||||
|
.hljs-quote,
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-type {
|
||||||
|
color: highlight;
|
||||||
|
}
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang='ts'>
|
||||||
import { socket } from '$lib/socket';
|
import { socket } from '$lib/socket';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
|
import * as Sentry from "@sentry/browser";
|
||||||
|
|
||||||
export let game_pin: string;
|
export let game_pin: string;
|
||||||
|
|
||||||
let username = '';
|
let username = '';
|
||||||
let hcaptchaSitekey = 'ee81b2a1-acf3-4d20-b2a4-a7ea94c7eba5';
|
let hcaptchaSitekey = import.meta.env.VITE_HCAPTCHA;
|
||||||
|
|
||||||
let hcaptcha = {
|
let hcaptcha = {
|
||||||
execute: async (_a, _b) => ({ response: '' }),
|
execute: async (_a, _b) => ({ response: '' }),
|
||||||
render: (_a, _b) => {}
|
render: (_a, _b) => {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let hcaptchaWidgetID;
|
let hcaptchaWidgetID;
|
||||||
|
|
||||||
@@ -31,7 +33,8 @@
|
|||||||
if (browser) {
|
if (browser) {
|
||||||
hcaptcha = {
|
hcaptcha = {
|
||||||
execute: async () => ({ response: '' }),
|
execute: async () => ({ response: '' }),
|
||||||
render: () => {}
|
render: () => {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -40,13 +43,23 @@
|
|||||||
if (username.length <= 3) {
|
if (username.length <= 3) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let captcha_resp;
|
||||||
|
try {
|
||||||
const { response } = await hcaptcha.execute(hcaptchaWidgetID, {
|
const { response } = await hcaptcha.execute(hcaptchaWidgetID, {
|
||||||
async: true
|
async: true
|
||||||
});
|
});
|
||||||
|
captcha_resp = response;
|
||||||
|
} catch (e) {
|
||||||
|
if (import.meta.env.VITE_SENTRY !== null){
|
||||||
|
Sentry.captureException(e);
|
||||||
|
}
|
||||||
|
alert('Captcha failed, reloading the page probably helps!');
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
socket.emit('join_game', {
|
socket.emit('join_game', {
|
||||||
username: username,
|
username: username,
|
||||||
game_pin: game_pin,
|
game_pin: game_pin,
|
||||||
captcha: response
|
captcha: captcha_resp
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
socket.on('game_not_found', () => {
|
socket.on('game_not_found', () => {
|
||||||
@@ -56,27 +69,27 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<script src="https://js.hcaptcha.com/1/api.js?render=explicit" async defer></script>
|
<script src='https://js.hcaptcha.com/1/api.js?render=explicit' async defer></script>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{#if game_pin === '' || game_pin.length <= 7}
|
{#if game_pin === '' || game_pin.length <= 7}
|
||||||
<div class="flex flex-col justify-center align-center w-screen h-screen">
|
<div class='flex flex-col justify-center align-center w-screen h-screen'>
|
||||||
<form
|
<form
|
||||||
on:submit|preventDefault={() => {}}
|
on:submit|preventDefault={() => {}}
|
||||||
class="flex-col flex justify-center align-center mx-auto"
|
class='flex-col flex justify-center align-center mx-auto'
|
||||||
>
|
>
|
||||||
<h1 class="text-lg text-center">Game Pin</h1>
|
<h1 class='text-lg text-center'>Game Pin</h1>
|
||||||
<input
|
<input
|
||||||
class="border border-amber-800 self-center text-center text-black"
|
class='border border-amber-800 self-center text-center text-black'
|
||||||
bind:value={game_pin}
|
bind:value={game_pin}
|
||||||
maxlength="8"
|
maxlength='8'
|
||||||
/>
|
/>
|
||||||
<!-- use:tippy={{content: "Please enter the game pin", sticky: true, placement: 'top'}}-->
|
<!-- use:tippy={{content: "Please enter the game pin", sticky: true, placement: 'top'}}-->
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<button
|
<button
|
||||||
class="bg-amber-800 hover:bg-amber-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50 disabled:cursor-not-allowed mt-2"
|
class='bg-amber-800 hover:bg-amber-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50 disabled:cursor-not-allowed mt-2'
|
||||||
type="submit"
|
type='submit'
|
||||||
disabled={game_pin.length <= 7}
|
disabled={game_pin.length <= 7}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
@@ -84,16 +97,16 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col justify-center align-center w-screen h-screen">
|
<div class='flex flex-col justify-center align-center w-screen h-screen'>
|
||||||
<form
|
<form
|
||||||
on:submit|preventDefault={setUsername}
|
on:submit|preventDefault={setUsername}
|
||||||
class="flex-col flex justify-center align-center mx-auto"
|
class='flex-col flex justify-center align-center mx-auto'
|
||||||
>
|
>
|
||||||
<h1 class="text-lg text-center">Username</h1>
|
<h1 class='text-lg text-center'>Username</h1>
|
||||||
<input class="border border-amber-800 text-black text-center" bind:value={username} />
|
<input class='border border-amber-800 text-black text-center' bind:value={username} />
|
||||||
<button
|
<button
|
||||||
class="bg-amber-800 hover:bg-amber-700 text-white font-bold py-2 px-4 rounded disabled:cursor-not-allowed disabled:opacity-50 mt-2"
|
class='bg-amber-800 hover:bg-amber-700 text-white font-bold py-2 px-4 rounded disabled:cursor-not-allowed disabled:opacity-50 mt-2'
|
||||||
type="submit"
|
type='submit'
|
||||||
disabled={username.length <= 3}
|
disabled={username.length <= 3}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
@@ -102,9 +115,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
<div
|
||||||
id="hcaptcha"
|
id='hcaptcha'
|
||||||
class="h-captcha"
|
class='h-captcha'
|
||||||
data-sitekey={hcaptchaSitekey}
|
data-sitekey={hcaptchaSitekey}
|
||||||
data-size="invisible"
|
data-size='invisible'
|
||||||
data-theme="dark"
|
data-theme='dark'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -30,16 +30,17 @@
|
|||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss'>
|
||||||
:global(body) {
|
:global(body) {
|
||||||
background: linear-gradient(to right, #009444, #39b54a, #8dc63f);
|
|
||||||
// height: 100%;
|
// height: 100%;
|
||||||
// width: 100%;
|
// width: 100%;
|
||||||
background-repeat: no-repeat;
|
background: linear-gradient(to right, #009444, #39b54a, #8dc63f) repeat-y;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
/*background: linear-gradient(-225deg, #231557 0%, #44107A 29%, #FF1361 67%, #FFF800 100%); */
|
/*background: linear-gradient(-225deg, #231557 0%, #44107A 29%, #FF1361 67%, #FFF800 100%); */
|
||||||
/*background: linear-gradient(-225deg, #22E1FF 0%, #1D8FE1 48%, #625EB1 100%); */
|
/*background: linear-gradient(-225deg, #22E1FF 0%, #1D8FE1 48%, #625EB1 100%); */
|
||||||
color: beige;
|
color: beige;
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: background_animation 30s ease infinite;
|
// background-size: 400% 400%;
|
||||||
|
|
||||||
|
//animation: background_animation 5s ease infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes background_animation {
|
@keyframes background_animation {
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href='/docs/import-from-kahoot'>Import from Kahoot</a>
|
<a href='/docs/import-from-kahoot'>Import from Kahoot</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href='/docs/self-host'>Self-hosting</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<script lang='ts'>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import '$lib/hljs.css';
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const { default: hljs } = await import('highlight.js/lib/common');
|
||||||
|
hljs.highlightAll();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<article
|
||||||
|
class='prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4'
|
||||||
|
>
|
||||||
|
<h1>Self-Hosting</h1>
|
||||||
|
<p>Since ClassQuiz is open-source, it can also be self-hosted.</p>
|
||||||
|
|
||||||
|
<h2>Requirements</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href='https://docker.com'>Docker</a></li>
|
||||||
|
<li><a href='https://git-scm.com/'>Git</a></li>
|
||||||
|
<li>A <a href='https://redis.com'>Redis</a>-Server (I recommend <a href='https://upstash.com'>Upstash</a>)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Installation</h2>
|
||||||
|
<p>
|
||||||
|
At first, clone the repo:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre><code class='language-bash'>git clone https://github.com/mawoka-myblock/classquiz && cd ClassQuiz</code></pre>
|
||||||
|
<p>
|
||||||
|
Now, set a <b>VALID</b> Redis-URI in <code>frontend/Dockerfile</code> and, if you Sentry want, a valid
|
||||||
|
Sentry-DSN.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You must set a valid hCaptcha-Sitekey in the <code>frontend/Dockerfile</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Configuration</h2>
|
||||||
|
<p>
|
||||||
|
Before you can start your stack, you have to set some environment-variables in your
|
||||||
|
<code>docker-compose.yml</code>.
|
||||||
|
</p>
|
||||||
|
<pre><code class='language-yaml'>version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
frontend:
|
||||||
|
restart: always
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- api
|
||||||
|
environment:
|
||||||
|
REDIS_URL: redis://redis:6379/0?decode_responses=True # For runtime
|
||||||
|
API_URL: http://api:80 # For runtime
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
|
||||||
|
environment:
|
||||||
|
ROOT_ADDRESS: "https://classquiz.mawoka.eu" # Base-URL
|
||||||
|
DB_URL: "postgresql://postgres:classquiz@db:5432/classquiz"
|
||||||
|
MAIL_ADDRESS: "classquiz@mawoka.eu" # Email-Address
|
||||||
|
MAIL_PASSWORD: "MAIL_PASSWORD" # Email-Password
|
||||||
|
MAIL_USERNAME: "classquiz@mawoka.eu" # Email-Username
|
||||||
|
MAIL_SERVER: "smtp.gmail.com" # SMTP-Server
|
||||||
|
MAX_WORKERS: "1" # Very important and don't change it!
|
||||||
|
MAIL_PORT: "587" # SMTP-Port
|
||||||
|
REDIS: "redis://redis:6379/0?decode_responses=True" # decode_response is important!
|
||||||
|
SECRET_KEY: "ghfvfgjgvjgvbh" # openssl rand -hex 32
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES: 30
|
||||||
|
HCAPTCHA_KEY: "" # Private hCaptcha key for verification
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "redis-cli","ping" ]
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:alpine
|
||||||
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: "classquiz"
|
||||||
|
POSTGRES_DB: "classquiz"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- data:/var/lib/postgresql/data
|
||||||
|
proxy:
|
||||||
|
image: caddy:alpine
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile-docker:/etc/caddy/Caddyfile
|
||||||
|
ports:
|
||||||
|
- "8000:8080" # Adjust the 8000 to your needs
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
data:
|
||||||
|
</code></pre>
|
||||||
|
<p>Now build and deploy:</p>
|
||||||
|
<pre><code class='language-bash'>docker compose build && docker compose up -d</code></pre>
|
||||||
|
|
||||||
|
</article>
|
||||||
Reference in New Issue
Block a user