initial commit
This commit is contained in:
commit
6f11b2247d
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
database.db
|
||||
deploy.sh
|
||||
node_modules
|
6
ormconfig.json
Normal file
6
ormconfig.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "sqlite",
|
||||
"database": "database.db",
|
||||
"entities": ["src/entity/*"],
|
||||
"synchronize": true
|
||||
}
|
34
package.json
Normal file
34
package.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"serve": "ts-node src/server.ts",
|
||||
"watch": "nodemon --ext ts,twig --exec ts-node src/server.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/koa": "^2.13.4",
|
||||
"@types/koa-bodyparser": "^4.3.3",
|
||||
"@types/koa-logger": "^3.1.1",
|
||||
"@types/koa-router": "^7.4.4",
|
||||
"@types/koa-static": "^4.0.2",
|
||||
"@types/node": "^16.10.1",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/twig": "^1.12.6",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"koa": "^2.13.3",
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
"koa-logger": "^3.2.1",
|
||||
"koa-router": "^10.1.1",
|
||||
"koa-static": "^5.0.0",
|
||||
"mysql": "^2.18.1",
|
||||
"nodemailer": "^6.6.5",
|
||||
"nodemon": "^2.0.13",
|
||||
"promisify-child-process": "^4.1.1",
|
||||
"sqlite3": "^5.0.2",
|
||||
"svg-captcha": "^1.4.0",
|
||||
"ts-node": "^10.2.1",
|
||||
"twig": "^1.15.4",
|
||||
"typeorm": "^0.2.37",
|
||||
"typescript": "^4.4.3",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
}
|
1
public/spectre.min.css
vendored
Normal file
1
public/spectre.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
83
src/commands.ts
Normal file
83
src/commands.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import * as child_process from 'promisify-child-process'
|
||||
import * as util from 'util'
|
||||
import { renderFile as renderFileSync } from 'twig'
|
||||
import * as path from 'path'
|
||||
import { HOSTS } from './config'
|
||||
const renderFile: any = util.promisify(renderFileSync);
|
||||
|
||||
export async function sshCommand(host: string, cmd: string[]) {
|
||||
console.log(`[${host}] $ ${cmd.join(' ')}`)
|
||||
return await child_process.spawn('ssh', [host, ...cmd], {encoding: 'utf8'})
|
||||
}
|
||||
async function sshCreateFile(host: string, path: string, content: string) {
|
||||
const ps = child_process.spawn('ssh', [host, 'sh', '-c', `cat > ${path}`])
|
||||
ps.stdin.end(content, 'utf-8')
|
||||
return await ps
|
||||
}
|
||||
|
||||
async function uberspaceOpenPort(host: string): Promise<number> {
|
||||
const res = await sshCommand(host, ['uberspace', 'port', 'add'],)
|
||||
const p = (res.stdout as string).match(/Port (\d+) will/)
|
||||
if(!p) {
|
||||
throw new Error('could not open port.\n'+res.stdout+res.stderr)
|
||||
}
|
||||
return parseInt(p[1])
|
||||
}
|
||||
|
||||
async function setupHost(host: string) {
|
||||
try {
|
||||
await sshCommand(host, ['mkdir', '-p', '~/configs', '~/databases'])
|
||||
|
||||
const version = '1.3.4'
|
||||
const url = `https://github.com/mumble-voip/mumble/releases/download/${version}/murmur-static_x86-${version}.tar.bz2`
|
||||
await sshCommand(host, ['sh', '-c', `cd ~/tmp && wget ${url} && tar xvjf murmur-static_x86-${version}.tar.bz2 && mv murmur-static_x86-${version} ~/mumble`])
|
||||
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
export async function createInstance(hostname: string, uuid: string, serverPassword: string, suPassword: string): Promise<number> {
|
||||
const { sshString } = HOSTS.find(h => h.hostname == hostname)
|
||||
try {
|
||||
|
||||
const port = await uberspaceOpenPort(sshString)
|
||||
|
||||
await sshCreateFile(
|
||||
sshString, `~/configs/${uuid}.ini`,
|
||||
await renderFile(path.join(__dirname, 'templates/murmur.ini'), {
|
||||
user: sshString.split('@')[0],
|
||||
serverpassword: serverPassword,
|
||||
hostname,
|
||||
port,
|
||||
uuid,
|
||||
})
|
||||
)
|
||||
await sshCreateFile(
|
||||
sshString, `~/etc/services.d/murmur-${uuid}.ini`,
|
||||
await renderFile(path.join(__dirname, 'templates/service.ini'), { uuid })
|
||||
)
|
||||
|
||||
await sshCommand(sshString, ['~/mumble/murmur.x86', '-fg', '-ini', `~/configs/${uuid}.ini`, '-supw', suPassword])
|
||||
await sshCommand(sshString, ['supervisorctl', 'update'])
|
||||
|
||||
return port
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeInstance(sshString: string, uuid: string, port: number) {
|
||||
const service = 'murmur-'+uuid
|
||||
const res = await sshCommand(sshString, [
|
||||
'supervisorctl', 'stop', service, ';',
|
||||
'supervisorctl', 'remove', service, ';',
|
||||
'rm',
|
||||
`~/etc/services.d/${service}.ini`,
|
||||
`~/databases/${uuid}.db`,
|
||||
`~/configs/${uuid}.ini`, ';',
|
||||
'uberspace', 'port', 'del', port.toString(), '||', 'true'
|
||||
])
|
||||
console.log(res)
|
||||
}
|
45
src/config.ts
Normal file
45
src/config.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
export const BASE_URL = 'https://murmur.uber.space'
|
||||
export const TIME_FRAMES = [
|
||||
{
|
||||
duration: 6*3600,
|
||||
label: '6 hours'
|
||||
},
|
||||
{
|
||||
duration: 24*3600,
|
||||
label: '1 day'
|
||||
},
|
||||
{
|
||||
duration: 3*86400,
|
||||
label: '3 days'
|
||||
},
|
||||
{
|
||||
duration: 7*86400,
|
||||
label: '7 days'
|
||||
},
|
||||
{
|
||||
duration: 14*86400,
|
||||
label: '14 days'
|
||||
},
|
||||
]
|
||||
|
||||
interface Host {
|
||||
sshString: string
|
||||
hostname: string
|
||||
}
|
||||
export const HOSTS: Host[] = [
|
||||
{
|
||||
sshString: 'murmur@auriga.uberspace.de',
|
||||
hostname: 'murmur.uber.space'
|
||||
}
|
||||
]
|
||||
|
||||
export const MAIL_CONFIG = {
|
||||
host: "auriga.uberspace.de",
|
||||
port: 465,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: 'murmur-no-reply-out',
|
||||
pass: process.env.MURMUR_SMTP_PASS
|
||||
}
|
||||
}
|
||||
export const MAIL_FROM = 'no-reply@murmur.uber.space'
|
41
src/cron.ts
Normal file
41
src/cron.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { HOSTS } from './config'
|
||||
import * as https from 'https'
|
||||
import { removeInstance, sshCommand } from './commands'
|
||||
import { createConnection, getConnection, getRepository, Not, Raw } from 'typeorm'
|
||||
import { Instance } from './entity/instance'
|
||||
|
||||
async function main() {
|
||||
await createConnection()
|
||||
const repo = getRepository(Instance)
|
||||
|
||||
console.log('maybe trigger HTTP certificate refresh')
|
||||
for(let host of HOSTS) {
|
||||
await new Promise( (resolve: any) => {
|
||||
const req = https.request({
|
||||
hostname: host.hostname
|
||||
}, (res) => {
|
||||
console.log(host.hostname, res.statusCode)
|
||||
resolve()
|
||||
})
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
const isSqlite = getConnection().options.type == 'sqlite'
|
||||
const instances = await repo.find({
|
||||
state: "running",
|
||||
validUntil: Raw((alias) => isSqlite ? `${alias} < strftime('%Y-%m-%d %H:%M:%f','now')` : `${alias} < NOW()`)
|
||||
})
|
||||
for(let instance of instances) {
|
||||
console.log('remove '+instance.uuid)
|
||||
try {
|
||||
const host = HOSTS.find(h => h.hostname == instance.host)
|
||||
await removeInstance(host.sshString, instance.uuid, instance.port)
|
||||
instance.state = 'deleted'
|
||||
await repo.save(instance)
|
||||
} catch(err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
getConnection().close()
|
||||
}
|
||||
main()
|
32
src/entity/instance.ts
Normal file
32
src/entity/instance.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import {Entity, Column, PrimaryColumn} from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class Instance {
|
||||
|
||||
@PrimaryColumn()
|
||||
uuid: string;
|
||||
|
||||
@Column()
|
||||
state: 'new'|'creating'|'running'|'deleted'
|
||||
|
||||
@Column({nullable: true})
|
||||
host?: string = '';
|
||||
|
||||
@Column({nullable: true})
|
||||
port?: number;
|
||||
|
||||
@Column()
|
||||
serverPassword: string
|
||||
|
||||
@Column()
|
||||
suPassword: string
|
||||
|
||||
@Column()
|
||||
createdAt: Date
|
||||
|
||||
@Column()
|
||||
validUntil: Date
|
||||
|
||||
|
||||
}
|
||||
|
40
src/koa-twig.ts
Normal file
40
src/koa-twig.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { twig, renderFile as renderFileSync } from 'twig'
|
||||
import * as Koa from 'koa';
|
||||
import * as util from 'util'
|
||||
const renderFile: any = util.promisify(renderFileSync);
|
||||
|
||||
|
||||
export default function (config: any) {
|
||||
if (!config.views) {
|
||||
throw new Error("`views` is required in config");
|
||||
}
|
||||
|
||||
const extension = config.extension || "twig";
|
||||
const defaultData = config.data || {};
|
||||
|
||||
return async (ctx: Koa.Context, next: any) => {
|
||||
/**
|
||||
* Render a twig template
|
||||
* @param {string} view
|
||||
* @param {object} data
|
||||
*/
|
||||
async function render(view: string, data = {}) {
|
||||
if (!view) {
|
||||
throw new Error("`view` is required in render");
|
||||
}
|
||||
|
||||
const viewPath = `${config.views}/${view}.${extension}`;
|
||||
|
||||
ctx.type = "text/html";
|
||||
ctx.body = await renderFile(viewPath, {
|
||||
...defaultData,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
(ctx.response as any).render = render;
|
||||
ctx.render = render;
|
||||
await next()
|
||||
}
|
||||
};
|
||||
|
11
src/mail.txt
Normal file
11
src/mail.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
hey!
|
||||
|
||||
We have just created your new Mumble server :)
|
||||
|
||||
Click on the following link for the details:
|
||||
[URL]
|
||||
|
||||
The server will run until the following date:
|
||||
[EXPIRATION_DATE]
|
||||
|
||||
If you don't know what this is about, someone must have incorrectly entered your email. You can simply ignore this email. Nothing else will happen, your address has not been saved.
|
11
src/router.ts
Normal file
11
src/router.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import * as Router from 'koa-router';
|
||||
import { indexPage, create } from './routes/create';
|
||||
import { showOrCreateInstance } from './routes/instance';
|
||||
import { metrics } from './routes/metrics';
|
||||
|
||||
const router = new Router();
|
||||
router.get('/', indexPage);
|
||||
router.post('/create', create);
|
||||
router.get('/admin/:uuid', showOrCreateInstance);
|
||||
router.get('/metrics', metrics)
|
||||
export default router;
|
94
src/routes/create.ts
Normal file
94
src/routes/create.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import * as Koa from 'koa';
|
||||
import { BASE_URL, TIME_FRAMES } from '../config'
|
||||
import { Instance } from '../entity/instance'
|
||||
import { getRepository } from 'typeorm';
|
||||
import { randomWords, sendmail } from '../utils';
|
||||
import * as crypto from 'crypto'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { promises as fs } from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as svgCaptcha from 'svg-captcha'
|
||||
|
||||
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
|
||||
|
||||
const captches_solutions: {[token: string]: string} = {}
|
||||
function getCaptcha() {
|
||||
const captcha = svgCaptcha.create({ size: 6, noise: 10 })
|
||||
const token = crypto.randomBytes(20).toString('hex')
|
||||
captches_solutions[token] = captcha.text
|
||||
|
||||
setTimeout( () => {
|
||||
if(!captches_solutions[token]) return
|
||||
delete captches_solutions[token]
|
||||
}, 10*60*1000)
|
||||
|
||||
return {
|
||||
image: captcha.data,
|
||||
token: token
|
||||
}
|
||||
}
|
||||
|
||||
export async function indexPage(ctx:Koa.Context) {
|
||||
const captcha = getCaptcha()
|
||||
await ctx.render("index", {
|
||||
time_frames: TIME_FRAMES,
|
||||
password: randomWords(4),
|
||||
token: captcha.token,
|
||||
captcha: captcha.image
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const longestDuration = TIME_FRAMES.map(a => a.duration).sort()[0]
|
||||
export async function create(ctx:Koa.Context) {
|
||||
let errors = []
|
||||
const body = ctx.request.body as any
|
||||
if(!body.email) errors.push('email is missing')
|
||||
else if(typeof body.email !== 'string' || !body.email.match(EMAIL_REGEX)) errors.push('invalid mail')
|
||||
|
||||
if(typeof body.password !== 'string') errors.push('server password is invalid')
|
||||
else if(!body.password.trim()) errors.push('server password is missing')
|
||||
|
||||
if(typeof body.duration !== 'string') errors.push('invalid duration')
|
||||
else if(parseInt(body.duration) < 300 || parseInt(body.duration) > longestDuration) errors.push('invalid duration')
|
||||
|
||||
if(!body.captcha || typeof body.captcha !== 'string' || !body.token || typeof body.token !== 'string') errors.push('captcha wrong. try again')
|
||||
else {
|
||||
const solution = captches_solutions[body.token]
|
||||
if(body.captcha.trim() !== solution) errors.push('captcha wrong. try again')
|
||||
}
|
||||
|
||||
if(errors.length) {
|
||||
const captcha = getCaptcha()
|
||||
await ctx.render("index", {
|
||||
time_frames: TIME_FRAMES,
|
||||
errors: errors,
|
||||
email: body.email,
|
||||
password: body.password,
|
||||
token: captcha.token,
|
||||
captcha: captcha.image
|
||||
})
|
||||
return
|
||||
} else {
|
||||
delete captches_solutions[body.token]
|
||||
const email = body.email.trim()
|
||||
const password = body.password.trim()
|
||||
const duration = parseInt(body.duration)
|
||||
const instance = new Instance
|
||||
instance.createdAt = new Date
|
||||
instance.state = 'new'
|
||||
instance.uuid = uuid()
|
||||
instance.serverPassword = password
|
||||
instance.suPassword = crypto.randomBytes(20).toString('base64').slice(0,20)
|
||||
instance.validUntil = new Date(Date.now() + duration*1000)
|
||||
await getRepository(Instance).save(instance)
|
||||
const url = `${BASE_URL}/admin/${instance.uuid}`
|
||||
|
||||
let mailbody = await fs.readFile(path.join(__dirname, '../mail.txt'), 'utf-8')
|
||||
mailbody = mailbody.replace(/\[URL\]/g, url)
|
||||
mailbody = mailbody.replace(/\[EXPIRATION_DATE\]/g, instance.validUntil.toISOString().slice(0,16).replace('T', ' ')+' UTC')
|
||||
await sendmail(email, 'Your new mumble server 🎉', mailbody)
|
||||
await ctx.render("index-success")
|
||||
}
|
||||
}
|
39
src/routes/instance.ts
Normal file
39
src/routes/instance.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import * as Koa from 'koa';
|
||||
import { Instance } from '../entity/instance'
|
||||
import { getRepository } from 'typeorm';
|
||||
import { getLeastLoadedHost } from '../utils';
|
||||
import { createInstance } from '../commands';
|
||||
|
||||
export async function showOrCreateInstance(ctx:Koa.Context) {
|
||||
const uuid = ctx.params.uuid
|
||||
if(!uuid) return await ctx.render("instane-not-found")
|
||||
|
||||
const instance = await getRepository(Instance).findOne(uuid)
|
||||
if(!instance || instance.validUntil.valueOf() < Date.now()) return await ctx.render("instane-not-found")
|
||||
console.log(instance)
|
||||
if(instance.state == 'new') {
|
||||
instance.state = 'creating'
|
||||
await getRepository(Instance).save(instance)
|
||||
const host = await getLeastLoadedHost()
|
||||
createInstance(host.hostname, instance.uuid, instance.serverPassword, instance.suPassword)
|
||||
.then(async (port) => {
|
||||
instance.host = host.hostname
|
||||
instance.port = port
|
||||
instance.state = 'running'
|
||||
await getRepository(Instance).save(instance)
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(instance)
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
if(instance.state == 'running') {
|
||||
await ctx.render("instance", {
|
||||
instance,
|
||||
expirationDate: instance.validUntil.toISOString().slice(0,16).replace('T', ' ')+' UTC'
|
||||
})
|
||||
} else {
|
||||
await ctx.render("instance-creating")
|
||||
}
|
||||
}
|
25
src/routes/metrics.ts
Normal file
25
src/routes/metrics.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import * as Koa from 'koa';
|
||||
import { HOSTS } from '../config'
|
||||
import { Instance } from '../entity/instance'
|
||||
import { getRepository } from 'typeorm';
|
||||
import { sshCommand } from '../commands';
|
||||
|
||||
export async function metrics(ctx: Koa.Context) {
|
||||
let out = ''
|
||||
const repo = getRepository(Instance)
|
||||
out += '# HELP murmur_connections Currently open mumble connections\n'
|
||||
out += '# TYPE murmur_connections gauge\n'
|
||||
for(let host of HOSTS) {
|
||||
console.log(host)
|
||||
const res = await sshCommand(host.sshString, ['/usr/sbin/ss', '-utp', '|', 'grep', 'murmur', '|', 'wc', '-l'])
|
||||
out += `murmur_connections{host="${host.hostname}"} ${parseInt(res.stdout.toString().trim())}\n`
|
||||
}
|
||||
out += '\n'
|
||||
out += '# HELP murmur_instances Running murmur instances\n'
|
||||
out += '# TYPE murmur_instances gauge\n'
|
||||
for(let host of HOSTS) {
|
||||
const instances = await repo.find({ host: host.hostname, state: 'running' })
|
||||
out += `murmur_instances{host="${host.hostname}"} ${instances.length}\n`
|
||||
}
|
||||
ctx.body = out
|
||||
}
|
38
src/server.ts
Normal file
38
src/server.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import "reflect-metadata";
|
||||
import {createConnection} from "typeorm";
|
||||
import * as Koa from 'koa';
|
||||
import * as bodyParser from 'koa-bodyparser'
|
||||
import router from './router'
|
||||
import * as logger from 'koa-logger'
|
||||
import koaTwig from './koa-twig'
|
||||
import * as serve from 'koa-static'
|
||||
import * as path from 'path'
|
||||
const app = new Koa();
|
||||
|
||||
app.use(logger())
|
||||
app.use(serve(path.join(__dirname, '../public')))
|
||||
app.use(bodyParser())
|
||||
app.use(
|
||||
koaTwig({
|
||||
views: `${__dirname}/views`,
|
||||
extension: "twig",
|
||||
// errors: { 404: "not-found" }, // A 404 status code will render the file named `not-found`
|
||||
data: { NODE_ENV: process.env.NODE_ENV }, // Data shared accross all views
|
||||
})
|
||||
);
|
||||
|
||||
app.use(router.routes())
|
||||
|
||||
// Application error logging.
|
||||
app.on('error', console.error);
|
||||
|
||||
const PORT = Number(process.env.PORT) || 3000;
|
||||
|
||||
async function start() {
|
||||
await createConnection()
|
||||
console.log('database connected')
|
||||
app.listen(PORT, () => {
|
||||
console.log('serving on port '+PORT)
|
||||
});
|
||||
}
|
||||
start()
|
414
src/templates/murmur.ini
Normal file
414
src/templates/murmur.ini
Normal file
|
@ -0,0 +1,414 @@
|
|||
; Murmur configuration file.
|
||||
;
|
||||
; General notes:
|
||||
; * Settings in this file are default settings and many of them can be overridden
|
||||
; with virtual server specific configuration via the Ice or DBus interface.
|
||||
; * Due to the way this configuration file is read some rules have to be
|
||||
; followed when specifying variable values (as in variable = value):
|
||||
; * Make sure to quote the value when using commas in strings or passwords.
|
||||
; NOT variable = super,secret BUT variable = "super,secret"
|
||||
; * Make sure to escape special characters like '\' or '"' correctly
|
||||
; NOT variable = """ BUT variable = "\""
|
||||
; NOT regex = \w* BUT regex = \\w*
|
||||
|
||||
; Path to database. If blank, will search for
|
||||
; murmur.sqlite in default locations or create it if not found.
|
||||
database=/home/{{ user }}/databases/{{ uuid }}.db
|
||||
|
||||
; Murmur defaults to using SQLite with its default rollback journal.
|
||||
; In some situations, using SQLite's write-ahead log (WAL) can be
|
||||
; advantageous.
|
||||
; If you encounter slowdowns when moving between channels and similar
|
||||
; operations, enabling the SQLite write-ahead log might help.
|
||||
;
|
||||
; To use SQLite's write-ahead log, set sqlite_wal to one of the following
|
||||
; values:
|
||||
;
|
||||
; 0 - Use SQLite's default rollback journal.
|
||||
; 1 - Use write-ahead log with synchronous=NORMAL.
|
||||
; If Murmur crashes, the database will be in a consistent state, but
|
||||
; the most recent changes might be lost if the operating system did
|
||||
; not write them to disk yet. This option can improve Murmur's
|
||||
; interactivity on busy servers, or servers with slow storage.
|
||||
; 2 - Use write-ahead log with synchronous=FULL.
|
||||
; All database writes are synchronized to disk when they are made.
|
||||
; If Murmur crashes, the database will be include all completed writes.
|
||||
;sqlite_wal=0
|
||||
|
||||
; If you wish to use something other than SQLite, you'll need to set the name
|
||||
; of the database above, and also uncomment the below.
|
||||
; Sticking with SQLite is strongly recommended, as it's the most well tested
|
||||
; and by far the fastest solution.
|
||||
;
|
||||
;dbDriver=QMYSQL
|
||||
;dbUsername=
|
||||
;dbPassword=
|
||||
;dbHost=
|
||||
;dbPort=
|
||||
;dbPrefix=murmur_
|
||||
;dbOpts=
|
||||
|
||||
; Murmur defaults to not using D-Bus. If you wish to use dbus, which is one of the
|
||||
; RPC methods available in Murmur, please specify so here.
|
||||
;
|
||||
;dbus=session
|
||||
|
||||
; Alternate D-Bus service name. Only use if you are running distinct
|
||||
; murmurd processes connected to the same D-Bus daemon.
|
||||
;dbusservice=net.sourceforge.mumble.murmur
|
||||
|
||||
; If you want to use ZeroC Ice to communicate with Murmur, you need
|
||||
; to specify the endpoint to use. Since there is no authentication
|
||||
; with ICE, you should only use it if you trust all the users who have
|
||||
; shell access to your machine.
|
||||
; Please see the ICE documentation on how to specify endpoints.
|
||||
;ice="tcp -h 127.0.0.1 -p 6502"
|
||||
|
||||
; Ice primarily uses local sockets. This means anyone who has a
|
||||
; user account on your machine can connect to the Ice services.
|
||||
; You can set a plaintext "secret" on the Ice connection, and
|
||||
; any script attempting to access must then have this secret
|
||||
; (as context with name "secret").
|
||||
; Access is split in read (look only) and write (modify)
|
||||
; operations. Write access always includes read access,
|
||||
; unless read is explicitly denied (see note below).
|
||||
;
|
||||
; Note that if this is uncommented and with empty content,
|
||||
; access will be denied.
|
||||
|
||||
;icesecretread=
|
||||
icesecretwrite=
|
||||
|
||||
; If you want to expose Murmur's experimental gRPC API, you
|
||||
; need to specify an address to bind on.
|
||||
; Note: not all builds of Murmur support gRPC. If gRPC is not
|
||||
; available, Murmur will warn you in its log output.
|
||||
;grpc="127.0.0.1:50051"
|
||||
; Specifying both a certificate and key file below will cause gRPC to use
|
||||
; secured, TLS connections.
|
||||
; When using secured connections you need to also set the list of authorized
|
||||
; clients. grpcauthorized is a space delimited list of SHA256 fingerprints
|
||||
; for authorized client certificates.
|
||||
; Get this from the command line:
|
||||
; openssl x509 -in cert.pem -SHA256 -noout -fingerprint
|
||||
;grpccert=""
|
||||
;grpckey=""
|
||||
;grpcauthorized=""
|
||||
|
||||
; Specifies the file Murmur should log to. By default, Murmur
|
||||
; logs to the file 'murmur.log'. If you leave this field blank
|
||||
; on Unix-like systems, Murmur will force itself into foreground
|
||||
; mode which logs to the console.
|
||||
logfile=/home/{{ user }}/logs/murmur-{{ uuid }}.log
|
||||
|
||||
; If set, Murmur will write its process ID to this file
|
||||
; when running in daemon mode (when the -fg flag is not
|
||||
; specified on the command line). Only available on
|
||||
; Unix-like systems.
|
||||
;pidfile=
|
||||
|
||||
; The below will be used as defaults for new configured servers.
|
||||
; If you're just running one server (the default), it's easier to
|
||||
; configure it here than through D-Bus or Ice.
|
||||
;
|
||||
; Welcome message sent to clients when they connect.
|
||||
; If the welcome message is set to an empty string,
|
||||
; no welcome message will be sent to clients.
|
||||
welcometext="<br />Welcome to this server running <b>Murmur</b>.<br />Enjoy your stay!<br />"
|
||||
|
||||
; The welcometext can also be read from an external file which might be useful
|
||||
; if you want to specify a rather lengthy text. If a value for welcometext is
|
||||
; set, the welcometextfile will not be read.
|
||||
;welcometextfile=
|
||||
|
||||
; Port to bind TCP and UDP sockets to.
|
||||
port={{ port }}
|
||||
|
||||
; Specific IP or hostname to bind to.
|
||||
; If this is left blank (default), Murmur will bind to all available addresses.
|
||||
;host=
|
||||
|
||||
; Password to join server.
|
||||
serverpassword={{ serverpassword|json_encode }}
|
||||
|
||||
; Maximum bandwidth (in bits per second) clients are allowed
|
||||
; to send speech at.
|
||||
bandwidth=48000 #72000
|
||||
|
||||
; Murmur and Mumble are usually pretty good about cleaning up hung clients, but
|
||||
; occasionally one will get stuck on the server. The timeout setting will cause
|
||||
; a periodic check of all clients who haven't communicated with the server in
|
||||
; this many seconds - causing zombie clients to be disconnected.
|
||||
;
|
||||
; Note that this has no effect on idle clients or people who are AFK. It will
|
||||
; only affect people who are already disconnected, and just haven't told the
|
||||
; server.
|
||||
timeout=60
|
||||
|
||||
; Maximum number of concurrent clients allowed.
|
||||
users=100
|
||||
|
||||
; Where users sets a blanket limit on the number of clients per virtual server,
|
||||
; usersperchannel sets a limit on the number per channel. The default is 0, for
|
||||
; no limit.
|
||||
;usersperchannel=0
|
||||
|
||||
; Per-user rate limiting
|
||||
;
|
||||
; These two settings allow to configure the per-user rate limiter for some
|
||||
; command messages sent from the client to the server. The messageburst setting
|
||||
; specifies an amount of messages which are allowed in short bursts. The
|
||||
; messagelimit setting specifies the number of messages per second allowed over
|
||||
; a longer period. If a user hits the rate limit, his packages are then ignored
|
||||
; for some time. Both of these settings have a minimum of 1 as setting either to
|
||||
; 0 could render the server unusable.
|
||||
messageburst=100
|
||||
messagelimit=50
|
||||
|
||||
; Respond to UDP ping packets.
|
||||
;
|
||||
; Setting to true exposes the current user count, the maximum user count, and
|
||||
; the server's maximum bandwidth per client to unauthenticated users. In the
|
||||
; Mumble client, this information is shown in the Connect dialog.
|
||||
allowping=false
|
||||
|
||||
; Amount of users with Opus support needed to force Opus usage, in percent.
|
||||
; 0 = Always enable Opus, 100 = enable Opus if it's supported by all clients.
|
||||
;opusthreshold=100
|
||||
|
||||
; Maximum depth of channel nesting. Note that some databases like MySQL using
|
||||
; InnoDB will fail when operating on deeply nested channels.
|
||||
;channelnestinglimit=10
|
||||
|
||||
; Maximum number of channels per server. 0 for unlimited. Note that an
|
||||
; excessive number of channels will impact server performance
|
||||
;channelcountlimit=1000
|
||||
|
||||
; Regular expression used to validate channel names.
|
||||
; (Note that you have to escape backslashes with \ )
|
||||
;channelname=[ \\-=\\w\\#\\[\\]\\{\\}\\(\\)\\@\\|]+
|
||||
|
||||
; Regular expression used to validate user names.
|
||||
; (Note that you have to escape backslashes with \ )
|
||||
;username=[-=\\w\\[\\]\\{\\}\\(\\)\\@\\|\\.]+
|
||||
|
||||
; If a user has no stored channel (they've never been connected to the server
|
||||
; before, or rememberchannel is set to false) and the client hasn't been given
|
||||
; a URL that includes a channel path, the default behavior is that they will
|
||||
; end up in the root channel.
|
||||
;
|
||||
; You can set this setting to a channel ID, and the user will automatically be
|
||||
; moved into that channel instead. Note that this is the numeric ID of the
|
||||
; channel, which can be a little tricky to get (you'll either need to use an
|
||||
; RPC mechanism, watch the console of a debug client, or root around through
|
||||
; the Murmur Database to get it).
|
||||
;
|
||||
;defaultchannel=0
|
||||
|
||||
; When a user connects to a server they've already been on, by default the
|
||||
; server will remember the last channel they were in and move them to it
|
||||
; automatically. Toggling this setting to false will disable that feature.
|
||||
;
|
||||
rememberchannel=true
|
||||
|
||||
; How many seconds should the server remember the last channel of a user.
|
||||
; Set to 0 (default) to remember forever. This option has no effect if
|
||||
; rememberchannel is set to false.
|
||||
;rememberchannelduration=0
|
||||
|
||||
; Maximum length of text messages in characters. 0 for no limit.
|
||||
;textmessagelength=5000
|
||||
|
||||
; Maximum length of text messages in characters, with image data. 0 for no limit.
|
||||
;imagemessagelength=131072
|
||||
|
||||
; Allow clients to use HTML in messages, user comments and channel descriptions?
|
||||
allowhtml=false
|
||||
|
||||
; Murmur retains the per-server log entries in an internal database which
|
||||
; allows it to be accessed over D-Bus/ICE.
|
||||
; How many days should such entries be kept?
|
||||
; Set to 0 to keep forever, or -1 to disable logging to the DB.
|
||||
logdays=-1
|
||||
|
||||
; To enable public server registration, the serverpassword must be blank, and
|
||||
; this must all be filled out.
|
||||
; The password here is used to create a registry for the server name; subsequent
|
||||
; updates will need the same password. Don't lose your password.
|
||||
; The URL is your own website, and only set the registerHostname for static IP
|
||||
; addresses.
|
||||
; Location is typically the country of typical users of the server, in
|
||||
; two-letter TLD style (ISO 3166-1 alpha-2 country code)
|
||||
;
|
||||
; If you only wish to give your "Root" channel a custom name, then only
|
||||
; uncomment the 'registerName' parameter.
|
||||
;
|
||||
;registerName=Mumble Server
|
||||
;registerPassword=secret
|
||||
;registerUrl=http://www.mumble.info/
|
||||
;registerHostname=
|
||||
;registerLocation=
|
||||
|
||||
; If this option is enabled, the server will announce its presence via the
|
||||
; bonjour service discovery protocol. To change the name announced by bonjour
|
||||
; adjust the registerName variable.
|
||||
; See http://developer.apple.com/networking/bonjour/index.html for more information
|
||||
; about bonjour.
|
||||
;bonjour=True
|
||||
|
||||
; If you have a proper SSL certificate, you can provide the filenames here.
|
||||
; Otherwise, Murmur will create its own certificate automatically.
|
||||
sslCert=/home/{{ user }}/etc/certificates/{{ hostname }}.key
|
||||
sslKey=/home/{{ user }}/etc/certificates/{{ hostname }}.crt
|
||||
|
||||
; If the keyfile specified above is encrypted with a passphrase, you can enter
|
||||
; it in this setting. It must be plaintext, so you may wish to adjust the
|
||||
; permissions on your murmur.ini file accordingly.
|
||||
;sslPassPhrase=
|
||||
|
||||
; If your certificate is signed by an authority that uses a sub-signed or
|
||||
; "intermediate" certificate, you probably need to bundle it with your
|
||||
; certificate in order to get Murmur to accept it. You can either concatenate
|
||||
; the two certificates into one file, or you can put it in a file by itself and
|
||||
; put the path to that PEM-file in sslCA.
|
||||
;sslCA=
|
||||
|
||||
; The sslDHParams option allows you to specify a PEM-encoded file with
|
||||
; Diffie-Hellman parameters, which will be used as the default Diffie-
|
||||
; Hellman parameters for all virtual servers.
|
||||
;
|
||||
; Instead of pointing sslDHParams to a file, you can also use the option
|
||||
; to specify a named set of Diffie-Hellman parameters for Murmur to use.
|
||||
; Murmur comes bundled with the Diffie-Hellman parameters from RFC 7919.
|
||||
; These parameters are available by using the following names:
|
||||
;
|
||||
; @ffdhe2048, @ffdhe3072, @ffdhe4096, @ffdhe6144, @ffdhe8192
|
||||
;
|
||||
; By default, Murmur uses @ffdhe2048.
|
||||
;sslDHParams=@ffdhe2048
|
||||
|
||||
; The sslCiphers option chooses the cipher suites to make available for use
|
||||
; in SSL/TLS. This option is server-wide, and cannot be set on a
|
||||
; per-virtual-server basis.
|
||||
;
|
||||
; This option is specified using OpenSSL cipher list notation (see
|
||||
; https://www.openssl.org/docs/apps/ciphers.html#CIPHER-LIST-FORMAT).
|
||||
;
|
||||
; It is recommended that you try your cipher string using 'openssl ciphers <string>'
|
||||
; before setting it here, to get a feel for which cipher suites you will get.
|
||||
;
|
||||
; After setting this option, it is recommend that you inspect your Murmur log
|
||||
; to ensure that Murmur is using the cipher suites that you expected it to.
|
||||
;
|
||||
; Note: Changing this option may impact the backwards compatibility of your
|
||||
; Murmur server, and can remove the ability for older Mumble clients to be able
|
||||
; to connect to it.
|
||||
;sslCiphers=EECDH+AESGCM:EDH+aRSA+AESGCM:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:AES256-SHA:AES128-SHA
|
||||
|
||||
; If Murmur is started as root, which user should it switch to?
|
||||
; This option is ignored if Murmur isn't started with root privileges.
|
||||
;uname=
|
||||
|
||||
; By default, in log files and in the user status window for privileged users,
|
||||
; Mumble will show IP addresses - in some situations you may find this unwanted
|
||||
; behavior. If obfuscate is set to true, Murmur will randomize the IP addresses
|
||||
; of connecting users.
|
||||
;
|
||||
; The obfuscate function only affects the log file and DOES NOT effect the user
|
||||
; information section in the client window.
|
||||
obfuscate=True
|
||||
|
||||
; If this options is enabled, only clients which have a certificate are allowed
|
||||
; to connect.
|
||||
;certrequired=False
|
||||
|
||||
; If enabled, clients are sent information about the servers version and operating
|
||||
; system.
|
||||
sendversion=False
|
||||
|
||||
; You can set a recommended minimum version for your server, and clients will
|
||||
; be notified in their log when they connect if their client does not meet the
|
||||
; minimum requirements. suggestVersion expects the version in the format X.X.X.
|
||||
;
|
||||
; Note that the suggest* options appeared after 1.2.3 and will have no effect
|
||||
; on client versions 1.2.3 and earlier.
|
||||
;
|
||||
;suggestVersion=
|
||||
|
||||
; Setting this to "true" will alert any user who does not have positional audio
|
||||
; enabled that the server administrators recommend enabling it. Setting it to
|
||||
; "false" will have the opposite effect - if you do not care whether the user
|
||||
; enables positional audio or not, set it to blank. The message will appear in
|
||||
; the log window upon connection, but only if the user's settings do not match
|
||||
; what the server requests.
|
||||
;
|
||||
; Note that the suggest* options appeared after 1.2.3 and will have no effect
|
||||
; on client versions 1.2.3 and earlier.
|
||||
;
|
||||
;suggestPositional=
|
||||
|
||||
; Setting this to "true" will alert any user who does not have Push-To-Talk
|
||||
; enabled that the server administrators recommend enabling it. Setting it to
|
||||
; "false" will have the opposite effect - if you do not care whether the user
|
||||
; enables PTT or not, set it to blank. The message will appear in the log
|
||||
; window upon connection, but only if the user's settings do not match what the
|
||||
; server requests.
|
||||
;
|
||||
; Note that the suggest* options appeared after 1.2.3 and will have no effect
|
||||
; on client versions 1.2.3 and earlier.
|
||||
;
|
||||
;suggestPushToTalk=
|
||||
|
||||
; This sets password hash storage to legacy mode (1.2.4 and before)
|
||||
; (Note that setting this to true is insecure and should not be used unless absolutely necessary)
|
||||
;legacyPasswordHash=false
|
||||
|
||||
; By default a strong amount of PBKDF2 iterations are chosen automatically. If >0 this setting
|
||||
; overrides the automatic benchmark and forces a specific number of iterations.
|
||||
; (Note that you should only change this value if you know what you are doing)
|
||||
;kdfIterations=-1
|
||||
|
||||
; In order to prevent misconfigured, impolite or malicious clients from
|
||||
; affecting the low-latency of other users, Murmur has a rudimentary global-ban
|
||||
; system. It's configured using the autobanAttempts, autobanTimeframe and
|
||||
; autobanTime settings.
|
||||
;
|
||||
; If a client attempts autobanAttempts connections in autobanTimeframe seconds,
|
||||
; they will be banned for autobanTime seconds. This is a global ban, from all
|
||||
; virtual servers on the Murmur process. It will not show up in any of the
|
||||
; ban-lists on the server, and they can't be removed without restarting the
|
||||
; Murmur process - just let them expire. A single, properly functioning client
|
||||
; should not trip these bans.
|
||||
;
|
||||
; To disable, set autobanAttempts or autobanTimeframe to 0. Commenting these
|
||||
; settings out will cause Murmur to use the defaults:
|
||||
;
|
||||
; To avoid autobanning successful connection attempts from the same IP address,
|
||||
; set autobanSuccessfulConnections=False.
|
||||
;
|
||||
autobanAttempts=0
|
||||
autobanTimeframe=0
|
||||
autobanTime=0
|
||||
;autobanSuccessfulConnections=True
|
||||
|
||||
; Enables logging of group changes. This means that every time a group in a
|
||||
; channel changes, the server will log all groups and their members from before
|
||||
; the change and after the change. Deault is false. This option was introduced
|
||||
; with Murmur 1.4.0.
|
||||
;
|
||||
;loggroupchanges=false
|
||||
|
||||
; Enables logging of ACL changes. This means that every time the ACL in a
|
||||
; channel changes, the server will log all ACLs from before the change and
|
||||
; after the change. Default is false. This option was introduced with Murmur
|
||||
; 1.4.0.
|
||||
;
|
||||
;logaclchanges=false
|
||||
|
||||
; You can configure any of the configuration options for Ice here. We recommend
|
||||
; leave the defaults as they are.
|
||||
; Please note that this section has to be last in the configuration file.
|
||||
;
|
||||
[Ice]
|
||||
Ice.Warn.UnknownProperties=1
|
||||
Ice.MessageSizeMax=65536
|
2
src/templates/service.ini
Normal file
2
src/templates/service.ini
Normal file
|
@ -0,0 +1,2 @@
|
|||
[program:murmur-{{ uuid }}]
|
||||
command=%(ENV_HOME)s/mumble/murmur.x86 -fg -ini %(ENV_HOME)s/configs/{{ uuid }}.ini
|
1
src/types.d.ts
vendored
Normal file
1
src/types.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare module 'koa-twig';
|
34
src/utils.ts
Normal file
34
src/utils.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { HOSTS } from './config'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as crypto from 'crypto'
|
||||
import * as nodemailer from 'nodemailer'
|
||||
import { MAIL_CONFIG, MAIL_FROM } from './config'
|
||||
|
||||
export async function getLeastLoadedHost() {
|
||||
if(HOSTS.length == 1) return HOSTS[0]
|
||||
throw new Error('getLeastLoadedHost() is not implemented yet')
|
||||
}
|
||||
|
||||
const wordlist = fs.readFileSync(path.join(__dirname, 'wordlist'), 'utf-8').trim().split('\n')
|
||||
|
||||
export function randomWords(length: number) {
|
||||
let words = ''
|
||||
for(let i=0;i<length;i++) {
|
||||
const word = wordlist[crypto.randomInt(0, 2048)]
|
||||
words += word[0].toLocaleUpperCase()
|
||||
words += word.slice(1)
|
||||
}
|
||||
return words
|
||||
}
|
||||
|
||||
const transport = nodemailer.createTransport(MAIL_CONFIG)
|
||||
|
||||
export async function sendmail(to: string, subject: string, body: string) {
|
||||
await transport.sendMail({
|
||||
from: MAIL_FROM,
|
||||
to,
|
||||
subject,
|
||||
text: body
|
||||
})
|
||||
}
|
59
src/views/base.twig
Normal file
59
src/views/base.twig
Normal file
|
@ -0,0 +1,59 @@
|
|||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="/spectre.min.css">
|
||||
<title>murmur.uber.space | exchange logic free mumble hosting</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
#wrapper {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 450px;
|
||||
}
|
||||
#header {
|
||||
background: #fff none repeat scroll 0% 0%;
|
||||
border-bottom: 1px solid rgb(238, 238, 238);
|
||||
font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
|
||||
letter-spacing: 0.05em;
|
||||
margin: 0px auto;
|
||||
text-align: center;
|
||||
}
|
||||
#header h1 {
|
||||
font-weight: 100;
|
||||
font-size: 200%;
|
||||
font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
|
||||
margin-bottom: 0;
|
||||
line-height: 1.25;
|
||||
color: black;
|
||||
}
|
||||
#header a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
#header h2 {
|
||||
color: #666;
|
||||
font-size: 125%;
|
||||
font-weight: 100;
|
||||
margin-top: 0;
|
||||
}
|
||||
#content {
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<div id="header">
|
||||
<h1><a href="/">murmur.uber.space</a></h1>
|
||||
<h2>exchange logic free mumble hosting</h2>
|
||||
</div>
|
||||
<div id="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
32
src/views/donation.twig
Normal file
32
src/views/donation.twig
Normal file
|
@ -0,0 +1,32 @@
|
|||
<p>We want to give everyone the opportunity to get their own Mumble server in an uncomplicated way, regardless of their financial and technical possibilities.</p>
|
||||
|
||||
|
||||
<p>Unfortunately, the offer involves financial costs. If you have the financial possibilities, you can support us. All the money goes to our hosting provider <a href="https://uberspace.de">uberspace</a>, even if it is too much, because we want to support them in their great work.</p>
|
||||
|
||||
<ul>
|
||||
<li><b>SEPA transfer</b>
|
||||
<div style="padding-left: 1em;opacity:0.8;font-family: monospace">
|
||||
Jonas Pasche<br>
|
||||
IBAN: DE35 5505 0120 0200 0039 78<br>
|
||||
BIC: MALADE51MNZ<br>
|
||||
»Uberspace murmur«
|
||||
</div>
|
||||
</li>
|
||||
<li><b>Cash</b>
|
||||
<div style="padding-left: 1em;opacity:0.8">
|
||||
Simply send a letter to following address including a piece of paper saying »murmur«
|
||||
<div style="font-family:monospace">
|
||||
Jonas Pasche<br>
|
||||
Kaiserstr. 15<br>
|
||||
55116 Mainz<br>
|
||||
GERMANY
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><b>Uberspace transfer</b>
|
||||
<div style="padding-left: 1em;opacity:0.8">
|
||||
If you own an uberspce account, you can also transfer money to our account »murmur« via the <a href="https://dashboard.uberspace.de/dashboard/accounting">accounting page</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<i style="opactiy: 0.4;font-size:0.7">Please note that the person behind the account is not responsible for this offer! please don't write him for anything related to the mumble hosting.</i>
|
13
src/views/index-success.twig
Normal file
13
src/views/index-success.twig
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>Create a mumble instance</h2>
|
||||
<div class="toast toast-success">
|
||||
Yuhu: Your mumble instance got created! 🎉 check the link in your email inbox for further details.
|
||||
</div>
|
||||
<p></p>
|
||||
<h2>Donation</h2>
|
||||
{% include "donation.twig" %}
|
||||
{% endblock %}
|
||||
|
48
src/views/index.twig
Normal file
48
src/views/index.twig
Normal file
|
@ -0,0 +1,48 @@
|
|||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>Create a mumble instance</h2>
|
||||
<form action="/create" method="post" class="form-group">
|
||||
{% if errors|length %}
|
||||
<div class="pure-error">
|
||||
Error:
|
||||
<ul>
|
||||
{% for err in errors %}
|
||||
<li>{{ err }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<fieldset>
|
||||
{# <legend></legend> #}
|
||||
<label for="email" class="form-label">Your Email</label>
|
||||
<input type="email" name="email" id="email" class="form-input" value="{{ email|default('') }}" />
|
||||
|
||||
<label for="password" class="form-label">Server-Password</label>
|
||||
<input type="text" name="password" id="password" value="{{ password }}" spellcheck="false" class="form-input" />
|
||||
|
||||
<label for="duration" class="form-label">Expire after...</label>
|
||||
<select id="duration" name="duration" class="form-select">
|
||||
{% for d in time_frames %}
|
||||
<option value="{{ d.duration }}">{{ d.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="captcha" class="form-label">{{ captcha }}</label>
|
||||
<input type="text" name="captcha" id="captcha" placeholder="please enter the text" spellcheck="false" class="form-input" />
|
||||
<small style="opacity:0.4;font-size:0.7em">If you can't read the text in the image, write us a mail: murmur@systemli.org</small>
|
||||
<input type="hidden" name="token" value="{{ token }}" />
|
||||
<p></p>
|
||||
<button type="submit" class="btn btn-primary input-group-btn">Create server</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<h2>Donation</h2>
|
||||
{% include "donation.twig" %}
|
||||
|
||||
|
||||
<p></p>
|
||||
<h2>Contact</h2>
|
||||
Do you need a longer duration? contact us! <a href="mailto:murmur@systemli.org">murmur@systemli.org</a>
|
||||
{% endblock %}
|
||||
|
73
src/views/instance-creating.twig
Normal file
73
src/views/instance-creating.twig
Normal file
|
@ -0,0 +1,73 @@
|
|||
{% extends "base.twig" %}
|
||||
|
||||
{% block head %}
|
||||
|
||||
<style>
|
||||
.lds-ellipsis {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ellipsis div {
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
background: #fcf;
|
||||
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||
}
|
||||
.lds-ellipsis div:nth-child(1) {
|
||||
left: 8px;
|
||||
animation: lds-ellipsis1 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(2) {
|
||||
left: 8px;
|
||||
animation: lds-ellipsis2 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(3) {
|
||||
left: 32px;
|
||||
animation: lds-ellipsis2 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(4) {
|
||||
left: 56px;
|
||||
animation: lds-ellipsis3 0.6s infinite;
|
||||
}
|
||||
@keyframes lds-ellipsis1 {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes lds-ellipsis3 {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
@keyframes lds-ellipsis2 {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate(24px, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<meta http-equiv="refresh" content="2" >
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div style="text-align:center">
|
||||
<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
|
||||
<div>Your mumble instance is starting, please wait...</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
35
src/views/instance.twig
Normal file
35
src/views/instance.twig
Normal file
|
@ -0,0 +1,35 @@
|
|||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>your mumble server</h2>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-right">Host</td>
|
||||
<td><samp>{{ instance.host }}</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right">Port</td>
|
||||
<td><samp>{{ instance.port }}</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right">Server Password</td>
|
||||
<td><samp>{{ instance.serverPassword }}</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right">SuperUser Password</td>
|
||||
<td><samp>{{ instance.suPassword }}</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-right">running until</td>
|
||||
<td><samp>{{ expirationDate }}</samp></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p></p>
|
||||
<h2>Donation</h2>
|
||||
{% include "donation.twig" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
10
src/views/instane-not-found.twig
Normal file
10
src/views/instane-not-found.twig
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="toast toast-error">
|
||||
<b>Error:</b> Mumble instance could not be found. Either the address is wrong or it did expire.
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
2048
src/wordlist
Normal file
2048
src/wordlist
Normal file
File diff suppressed because it is too large
Load diff
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"module": "commonjs",
|
||||
"lib": ["es2017"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"noImplicitAny": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue