Browse Source

feat: docker

see the readme for help
pull/6/head
autumn 4 weeks ago
parent
commit
78b5b0a073
Signed by: autumn GPG Key ID: 4E7B627467259C47
  1. 13
      .dockerignore
  2. 2
      .env.docker
  3. 39
      README.md
  4. 9
      backend.Dockerfile
  5. 35
      docker-compose.yml
  6. 10
      docker.miracle-tv.json
  7. 10
      frontend.Dockerfile
  8. 1
      next.config.js
  9. 186
      nginx.conf.template
  10. 3
      package.json
  11. 7
      src/pages/_app.tsx
  12. 4
      src/server/config/index.ts
  13. 15
      src/server/config/local.json
  14. 2
      src/server/graphql/mutations/users/auth.ts
  15. 10
      src/server/server.ts
  16. 13
      src/server/webhooks/index.ts

13
.dockerignore

@ -0,0 +1,13 @@
node_modules/
dist/
yarn-error.log
src/**/*.js
dev.sqlite3
result/
.log/
.vscode/
.media/
miracle-tv.*.zip
result/
.types

2
.env.docker

@ -0,0 +1,2 @@
NEXT_PUBLIC_ENV=local
NEXT_PUBLIC_API_URL=localhost:1337/api/graphql

39
README.md

@ -1,38 +1,46 @@
# MiracleTV
#### A Streaming Suite
Server part of MiracleTV Suite
[![Support Server](https://img.shields.io/discord/836657856325353492.svg?logo=Discord&label=Gensokyo.social&colorB=7289da&style=for-the-badge)](https://gensokyo.social/discord)
## Running requirements
- NodeJS 14>
- Yarn
- RethinkDB 2.4.1>
## Development Requirements
- Nix (Strongly encouraged, but optional)
- NodeJS 14>
- Yarn
- RethinkDB 2.4.1>
## Building and running
### Non-nix
- `yarn`
- `./bin/build.sh`
- `nodejs -r ts-node/register/transpile-only -r tsconfig-paths/register dist/server.js`
## Nix
- `nix-env -f default.nix`
- `miracle-tv`
## Development
### Non-nix
- `yarn`
- `yarn dev`
### Nix
- `nix-shell`
- `yarn-dev`
@ -40,33 +48,35 @@ GraphQL schemas, resolvers and mutations can be found in `src/graphql`.
GraphiQL for testing is available over at `http://localhost:4000/graphql` for testing.
### Before commit
Before commiting, if you have altered packages installed for the project, please regenerate `nix/yarn.nix` using `yarn2nix` and copy `package.json` and `yarn.lock` to `/nix` folder as well. This is to make sure changes to the code and not the dependecies do not trigger extra rebuilds.
If you don't use nix, please reach out to somebody who can do that for you.
# Deploying in Production with nix
### ! Software does not include migrations and is definetely not production ready, deploy at your own risk !
Download one of our [module-releases](./module-releases/) files and import it in your configuration.nix, and then configure it like so
``` sh
```sh
sudo wget https://code.gensokyo.social/Gensokyo.social/miracle-tv/raw/branch/develop/module-releases/0-1-0-2.nix -P /etc/nixos/miracle-module.nix
```
``` nix
```nix
# configuration.nix
{
imports = [
# ... your other imports
./miracle-module.nix
];
# ... your configration.nix
services.miracle-tv = {
enable = true;
settings = {
name = "My MiracleTV instance";
server = {
server = {
port = "8080";
};
};
@ -74,8 +84,19 @@ sudo wget https://code.gensokyo.social/Gensokyo.social/miracle-tv/raw/branch/dev
}
```
- (Optionally) proxy pass `http://localhost:4000` via web server of your choice. GraphQL Playground will be available at `${yourdomain}/graphql`.
* (Optionally) proxy pass `http://localhost:4000` via web server of your choice. GraphQL Playground will be available at `${yourdomain}/graphql`.
`nixos-rebuild switch` should download and compile everything. Look for full list of options inside the module file.
## Docker setup
`nixos-rebuild switch` should download and compile everything. Look for full list of options inside the module file.
A `docker-compose.yml` file is provided for those who would prefer to use Docker.
If you've got a working Docker setup, all you need to do is run:
```sh
$ docker-compose up
```
and it should be ready to go after building. This is largely intended for
development purposes, but all that's stopping this flow from being production-ready
is the ability to add a custom config.

9
backend.Dockerfile

@ -0,0 +1,9 @@
# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install
EXPOSE 4000
CMD yarn run:server /conf/config.json

35
docker-compose.yml

@ -0,0 +1,35 @@
version: "3.9"
services:
nginx:
image: "alqutami/rtmp-hls"
ports:
- "1935:1935"
- "1337:1337"
volumes:
- ./nginx.conf.template:/etc/nginx/nginx.conf
depends_on:
- miracle-backend
- miracle-frontend
rethinkdb:
image: "rethinkdb"
miracle-backend:
build:
context: "."
dockerfile: "backend.Dockerfile"
ports:
- "4000:4000"
depends_on:
- rethinkdb
volumes:
- ./docker.miracle-tv.json:/conf/config.json
miracle-frontend:
build:
context: "."
dockerfile: "frontend.Dockerfile"
ports:
- "3000:3000"
depends_on:
- miracle-backend
environment:
- NEXT_PUBLIC_ENV=local
- NEXT_PUBLIC_API_URL=localhost:1337/api

10
docker.miracle-tv.json

@ -0,0 +1,10 @@
{
"name": "Miracle TV",
"dataDir": "/data/miracle-tv",
"pathPrefix": "api",
"database": {
"host": "rethinkdb",
"port": 28015,
"db": "miracle-tv"
}
}

10
frontend.Dockerfile

@ -0,0 +1,10 @@
# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install
RUN NEXT_PUBLIC_API_URL=http://localhost:1337/api/graphql yarn build:client:docker
EXPOSE 3000
CMD yarn run:client

1
next.config.js

@ -1,3 +1,4 @@
module.exports = {
productionBrowserSourceMaps: true,
distDir: "/dist/client",
};

186
nginx.conf.template

@ -0,0 +1,186 @@
# vim: set filetype=nginx.conf
# Major portions stolen from the following link:
# https://github.com/TareqAlqutami/rtmp-hls-server/blob/master/conf/nginx.conf
# which is licensed under MIT.
# This config does the following:
# 1. Requires streams to go to /miracle, where the stream key is checked.
# 2. Broadcasts back through /live, where...
# 3. HLS and DASH fragments are served, with caching disabled.
# 4. All other requests are forwarded to Miracle.
# This config should be used with Docker, in order to replace the environment
# variables for MIRACLE_PORT, RTMP_PORT, et al.
# It also assumes that HLS fragments can be kept in /mnt/hls/, and DASH in /mnt/dash/.
# TODO: Certain settings could certainly be optimized for speed. I'm not an nginx master,
# so I'm not sure where to start, especially given a setup like this.
events {}
# RTMP config.
rtmp {
server {
# Port for the RTMP stream.
listen 1935;
chunk_size 4096;
# This accepts the external connections, and redirects them to `live`
# if a valid stream key is provided.
application miracle {
# Allow streaming to this endpoint, and disable recording.
live on;
record off;
# Deny listening to this endpoint for non-localhost.
allow play 127.0.0.1;
deny play all;
exec_push /usr/local/bin/ffmpeg -i rtmp://localhost:1935/$app/$name -async 1 -vsync -1
# Callback for when a stream begins.
# On 2xx: Allows streaming.
# On 3xx: Redirects? Not sure what that would be, honestly.
# On 4xx: Denies streaming.
on_publish http://miracle-backend:4000/hook/on_publish;
# Callback for when a stream stops.
on_publish_done http://miracle-backend:4000/hook/on_publish_done;
}
# Application for receiving.
application live {
live on; # Allow streaming from localhost.
deny play all; # Deny receiving as RTMP.
# Only allow publishing from localhost - `miracle` handles publishing here.
allow publish 127.0.0.1;
deny publish all;
# HLS config.
hls on; # Enable HLS.
hls_fragment 3;
hls_playlist_length 10;
hls_path /mnt/hls/;
hls_variant _src BANDWIDTH=4096000; # Source bitrate, source resolution
hls_variant _hd720 BANDWIDTH=2048000; # High bitrate, HD 720p resolution
hls_variant _high BANDWIDTH=1152000; # High bitrate, higher-than-SD resolution
hls_variant _mid BANDWIDTH=448000; # Medium bitrate, SD resolution
hls_variant _low BANDWIDTH=288000; # Low bitrate, sub-SD resolution
# DASH config.
dash on;
dash_path /mnt/dash/;
dash_fragment 3;
dash_playlist_length 10;
}
}
}
# HTTP config; also serves Miracle itself.
http {
# Stops nginx from spamming the console with constant requests.
access_log off;
# Honestly, not sure what these do.
# ??? https://nginx.org/en/docs/http/ngx_http_core_module.html#sendfile
sendfile off;
# Try to send all headers in one packet.
# ... this is disabled by the sendfile off; above, though, isn't it?
# Keeping it here since it's in rtmp-hls-server, why not.
tcp_nopush on;
# Disable fs cache for files over 512 in size. Probably a speed thing?
directio 512;
server {
# Port for the HTTP nginx server.
listen 1337;
# RTMP stats.
# Probably not needed in production; TODO: any way to dynamically set this up?
# We also need to serve the stylesheet. Not sure where that's kept.
# location /stat {
# rtmp_stat all;
# }
# HLS fragments.
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /mnt;
# Don't cache.
add_header Cache-Control no-cache;
# CORS.
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length';
# Allow CORS preflight requests.
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
# DASH fragments; largely similar to HLS config.
# TODO: refactor so these cache bits can be more DRY?
location /dash {
types {
application/dash+xml mpd;
video/mp4 mp4;
}
root /mnt;
}
# Proxy all /api requests to Miracle backend.
location /api {
rewrite ^/a/(.*) /$1 break;
proxy_pass http://miracle-backend:4000;
#proxy_http_version 1.1;
#proxy_set_header Upgrade $http_upgrade;
#proxy_set_header Connection 'upgrade';
#proxy_set_header Host $host;
#proxy_cache_bypass $http_upgrade;
# CORS.
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length';
# Allow CORS preflight requests.
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
# Finally, proxy requests to Miracle frontend.
location / {
proxy_pass http://miracle-frontend:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# CORS.
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length';
# Allow CORS preflight requests.
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
}
}

3
package.json

@ -9,9 +9,10 @@
"daemon:stop": "pm2 delete miracle-tv",
"daemon:restart": "name=miracle-tv pm2 restart miracle-tv --update-env",
"dev:server": "nodemon -e ts,tsx,graphql --ignore src/shared/theme --ignore src/client --ignore src/pages --exec \"yarn run run:server\"",
"run:server": "NODE_ENV=production $node_modules/.bin/ts-node -r tsconfig-paths/register src/server/server.ts",
"run:server": "NODE_ENV=production ts-node -r tsconfig-paths/register src/server/server.ts",
"build:server": "ncc build src/server/server.ts -o ./dist/server",
"build:client": "NODE_ENV=local next build",
"build:client:docker": "NODE_ENV=docker next build",
"run:client": "next start",
"dev:client": "next dev",
"codegen": "graphql-codegen --config codegen.yml"

7
src/pages/_app.tsx

@ -21,14 +21,15 @@ const env = process.env.NEXT_PUBLIC_ENV;
const apiUrls: Record<string, string> = {
development: "https://dev.miracle-tv.live/api/graphql",
local: "http://localhost:4000/graphql",
local: `http://localhost:4000/graphql`,
production: "https://miracle-tv.live/api/graphql",
} as const;
const defaultURI: string = propOr(apiUrls.local, env, apiUrls);
const defaultURI: string =
process.env.NEXT_PUBLIC_API_URL || propOr(apiUrls.local, env, apiUrls);
const uploadLink = createUploadLink({
uri: process.env.NEXT_PUBLIC_API_URL || defaultURI,
uri: defaultURI,
});
const authLink = setContext((_, { headers }) => {

4
src/server/config/index.ts

@ -16,6 +16,7 @@ export type Config = {
name?: string;
domain?: string;
dataDir?: string;
pathPrefix?: string;
server?: ServerConfig;
database?: DBConfig;
};
@ -24,7 +25,8 @@ export const getConfig = (): Config => {
const args = process.argv.slice(2);
const configPath = args[0];
if (configPath) {
return JSON.parse(readFileSync(configPath).toString()) as Config;
const config = JSON.parse(readFileSync(configPath).toString()) as Config;
return { ...(defaultConfig as Config), ...config };
}
return defaultConfig as Config;
};

15
src/server/config/local.json

@ -1,9 +1,10 @@
{
"name": "Miracle TV Test",
"dataDir": "/data/miracle-tv",
"database": {
"host": "localhost",
"port": 28015,
"db": "miracle-tv"
}
"name": "Miracle TV Test",
"dataDir": "/data/miracle-tv",
"path": "",
"database": {
"host": "localhost",
"port": 28015,
"db": "miracle-tv"
}
}

2
src/server/graphql/mutations/users/auth.ts

@ -9,7 +9,7 @@ export const signInMutation: MutationResolvers<ResolverContext>["signIn"] =
async (_, { input: { username, password } }, { db: { users, sessions } }) => {
const userList = await users.getUsers({ username });
const user: DbUser = head<DbUser>(userList);
if (await compare(password, user?.password)) {
if (await compare(password, user?.password || '')) {
return await sessions.createSession(user?.id!);
}
throw new InputErrorLogin();

10
src/server/server.ts

@ -5,7 +5,7 @@ import { graphqlUploadExpress } from "graphql-upload";
import config from "miracle-tv-server/config";
import { green } from "chalk";
const { pathPrefix, dataDir } = config;
// app.get("/", (_, res) => res.send("FUCK!"));
// app.use(graphqlUploadExpress());
@ -15,9 +15,11 @@ const main = async () => {
await setupDB();
await graphqlEndpoint.start();
const app = Express();
const prefix = pathPrefix ? `/${pathPrefix}/` : '/'
app.use(graphqlUploadExpress());
app.use("/media/", Express.static(`${config.dataDir}/media`));
graphqlEndpoint.applyMiddleware({ app });
app.use(`${prefix}media/`, Express.static(`${dataDir}/media`));
const graphqlPath = prefix + 'graphql'
graphqlEndpoint.applyMiddleware({ app, path: prefix + 'graphql' });
app.listen(
config.server?.port || 4000,
config.server?.hostname || "0.0.0.0",
@ -30,7 +32,7 @@ const main = async () => {
console.info(
`Playground: http://${config.domain || "localhost"}:${
config.server?.port || 4000
}/graphql`
}${prefix}graphql`
);
}
);

13
src/server/webhooks/index.ts

@ -0,0 +1,13 @@
import { Router } from "express";
export const webhooks = Router();
webhooks.all('on_publish', (req, res) => {
console.log('on_publish')
res.status(200).send()
})
webhooks.all('on_publish_done', (req, res) => {
console.log('on_publish_done')
res.status(200).send()
})
Loading…
Cancel
Save