initial commit
This commit is contained in:
commit
ed45048920
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/*/**/config.json
|
||||
/node_modules/
|
||||
/package-lock.json
|
1
applications/distrib-ipfs/.gitignore
vendored
Normal file
1
applications/distrib-ipfs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.db
|
48
applications/distrib-ipfs/index.js
Normal file
48
applications/distrib-ipfs/index.js
Normal file
@ -0,0 +1,48 @@
|
||||
const express = require('express')
|
||||
const path = require('path')
|
||||
const sqlite = require('sqlite')
|
||||
const crypto = require('crypto')
|
||||
|
||||
const router = express.Router()
|
||||
const cfgLoader = require(path.join('..', '..', 'config-loader'))(path.join(__dirname, 'config.json'), {
|
||||
database: 'shortened.db',
|
||||
gateway: 'https://distributed.icynet.eu',
|
||||
tokens: {}
|
||||
})
|
||||
|
||||
async function init () {
|
||||
let config = await cfgLoader
|
||||
|
||||
const dbPromise = Promise.resolve()
|
||||
.then(() => sqlite.open(path.join(__dirname, config.database), { Promise, cache: true }))
|
||||
.then(db => db.migrate({ migrationsPath: path.join(__dirname, 'migrations') }))
|
||||
|
||||
router.get('/:hash', async (req, res, next) => {
|
||||
let db = await dbPromise
|
||||
let translation = await db.get('SELECT * FROM Translation WHERE translation = ?', req.params.hash)
|
||||
if (!translation) return res.status(404).end()
|
||||
|
||||
res.header('Cache-Control', 'max-age=' + 7 * 24 * 60 * 60 * 1000)
|
||||
res.redirect(config.gateway + '/ipfs/' + translation.file_hash)
|
||||
})
|
||||
|
||||
router.post('/publish', async (req, res, next) => {
|
||||
let token = req.header('token') || req.body.token
|
||||
if (!token || !config.tokens[token]) return res.status(402).send('Forbidden')
|
||||
|
||||
let baseurl = config.tokens[token]
|
||||
let hash = req.query.hash || req.body.hash
|
||||
let filename = req.query.filename || req.body.filename
|
||||
if (!hash) return res.status(400).send('Invalid request: missing IPFS hash')
|
||||
if (!filename) filename = crypto.randomBytes(8).toString('hex')
|
||||
|
||||
let db = await dbPromise
|
||||
await db.run('INSERT INTO Translation (translation,file_hash,timeat) VALUES (?,?,?)', filename, hash, new Date())
|
||||
|
||||
res.send(baseurl + filename)
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
module.exports = init
|
10
applications/distrib-ipfs/migrations/001-initial.sql
Normal file
10
applications/distrib-ipfs/migrations/001-initial.sql
Normal file
@ -0,0 +1,10 @@
|
||||
-- Up
|
||||
|
||||
CREATE TABLE Translation (
|
||||
translation TEXT,
|
||||
file_hash TEXT,
|
||||
timeat TEXT
|
||||
);
|
||||
|
||||
-- Down
|
||||
DROP TABLE Translation;
|
282
applications/dyndns/index.js
Normal file
282
applications/dyndns/index.js
Normal file
@ -0,0 +1,282 @@
|
||||
const express = require('express')
|
||||
const util = require('util')
|
||||
const exec = util.promisify(require('child_process').exec)
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const fsp = fs.promises
|
||||
|
||||
const strs = [
|
||||
'%sdmn IN A %ip',
|
||||
'%sdmn IN AAAA %ip'
|
||||
]
|
||||
|
||||
const router = express.Router()
|
||||
const cfgLoader = require(path.join('..', '..', 'config-loader'))(path.join(__dirname, 'config.json'), {
|
||||
named: '/var/named/',
|
||||
tokens: {}
|
||||
})
|
||||
|
||||
let memcache = {}
|
||||
|
||||
function fmtip (i, sd, ip) {
|
||||
return strs[i].replace('%sdmn', sd).replace('%ip', ip)
|
||||
}
|
||||
|
||||
async function updateSerial (zonefile, contents) {
|
||||
let returnContents = contents != null
|
||||
if (!contents) {
|
||||
contents = await fsp.readFile(zonefile, {encoding: 'utf8'})
|
||||
}
|
||||
|
||||
// Split the file into lines
|
||||
let lines = contents.split('\n')
|
||||
|
||||
// Find 'serial'
|
||||
let prv = false
|
||||
let found = false
|
||||
for (let i in lines) {
|
||||
if (found) break
|
||||
|
||||
let line = lines[i]
|
||||
if (line.indexOf('SOA') !== -1) {
|
||||
prv = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (prv && line.toLowerCase().indexOf('serial') !== -1) {
|
||||
// Ladies and gentlemen, we got 'em
|
||||
found = true
|
||||
line = line.replace(/\d+/, Math.floor(Date.now() / 1000))
|
||||
lines[i] = line
|
||||
break
|
||||
}
|
||||
|
||||
if (prv) {
|
||||
if (line.indexOf(',') === -1 && line.indexOf('(') === -1
|
||||
&& line.indexOf(')') === -1 && line.indexOf('IN') === -1) {
|
||||
found = true
|
||||
line = line.replace(/\d+/, Math.floor(Date.now() / 1000))
|
||||
lines[i] = line
|
||||
break
|
||||
} else {
|
||||
// Just give up, not in this script's readability scope..
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
prv = false
|
||||
}
|
||||
|
||||
contents = lines.join('\n')
|
||||
if (!found) return contents
|
||||
if (returnContents) return contents
|
||||
await fsp.writeFile(zonefile, contents)
|
||||
}
|
||||
|
||||
async function updateZone (cfg, v4, v6) {
|
||||
let zone = cfg.domain + '.zone'
|
||||
let zfile = path.join(cfg.root, zone)
|
||||
await fsp.access(zfile, fs.constants.F_OK | fs.constants.W_OK)
|
||||
|
||||
if (!memcache[cfg.token]) {
|
||||
memcache[cfg.token] = {}
|
||||
} else if (memcache[cfg.token]['v4'] === v4 && memcache[cfg.token]['v6'] === v6) {
|
||||
// Don't update when it's identical
|
||||
return false
|
||||
}
|
||||
|
||||
memcache[cfg.token].v6 = v6
|
||||
memcache[cfg.token].v4 = v4
|
||||
|
||||
// If subdomain exists, use that file instead and update serial on primary
|
||||
if (cfg.subdomain && cfg.subdomain !== '@') {
|
||||
let zone2 = cfg.subdomain + '.' + zone
|
||||
let zfile2 = path.join(cfg.root, zone2)
|
||||
await fsp.access(zfile2, fs.constants.F_OK | fs.constants.W_OK)
|
||||
|
||||
let file = '; GENERATED BY DYNDNS'
|
||||
|
||||
if (v4) {
|
||||
file += '\n' + fmtip(0, cfg.subdomain, v4)
|
||||
}
|
||||
|
||||
if (v6) {
|
||||
file += '\n' + fmtip(1, cfg.subdomain, v6)
|
||||
}
|
||||
|
||||
await fsp.writeFile(zfile2, file)
|
||||
await updateSerial(zfile)
|
||||
await exec('rndc reload ' + cfg.domain)
|
||||
return true
|
||||
}
|
||||
|
||||
// Subdomain not set, just update '@' target in main zone file
|
||||
let zoneFile = await fsp.readFile(zfile, {encoding: 'utf8'})
|
||||
let atlines = []
|
||||
let lines = zoneFile.split('\n')
|
||||
for (let i in lines) {
|
||||
let line = lines[i]
|
||||
if (line.indexOf('@') === 0 && line.indexOf('SOA') === -1 && line.indexOf('A') !== -1) {
|
||||
atlines.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
if (atlines.length === 0) throw new Error('I dont know what to do with this zone file.')
|
||||
let set6 = false
|
||||
let set4 = false
|
||||
for (let j in atlines) {
|
||||
let line = lines[atlines[j]]
|
||||
if (line.indexOf('AAAA') !== -1) {
|
||||
if (set6 || v6 === null) {
|
||||
lines.splice(atlines[j], 1)
|
||||
continue
|
||||
}
|
||||
if (v6) {
|
||||
lines[atlines[j]] = fmtip(1, '@', v6)
|
||||
set6 = true
|
||||
} else if (v4 && atlines.length !== 1) {
|
||||
lines[atlines[j]] = fmtip(0, '@', v4)
|
||||
set4 = true
|
||||
}
|
||||
if (v4 && atlines.length === 1) {
|
||||
lines.splice(atlines[j], 0, fmtip(0, '@', v4))
|
||||
set4 = true
|
||||
}
|
||||
} else {
|
||||
if (set4 || v4 === null) {
|
||||
lines.splice(atlines[j], 1)
|
||||
continue
|
||||
}
|
||||
if (v4) {
|
||||
lines[atlines[j]] = fmtip(0, '@', v4)
|
||||
set4 = true
|
||||
} else if (v6 && atlines.length !== 1) {
|
||||
lines[atlines[j]] = fmtip(1, '@', v6)
|
||||
set6 = true
|
||||
}
|
||||
if (v6 && atlines.length === 1) {
|
||||
lines.splice(atlines[j], 0, fmtip(1, '@', v6))
|
||||
set6 = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoneFile = await updateSerial(null, lines.join('\n'))
|
||||
if (!zoneFile) throw new Error('I dont know what to do with this zone file.')
|
||||
await fsp.writeFile(zfile, zoneFile)
|
||||
await exec('rndc reload ' + cfg.domain)
|
||||
}
|
||||
|
||||
function validv4 (ipaddress) {
|
||||
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function validv6 (value) {
|
||||
// See https://blogs.msdn.microsoft.com/oldnewthing/20060522-08/?p=31113 and
|
||||
// https://4sysops.com/archives/ipv6-tutorial-part-4-ipv6-address-syntax/
|
||||
const components = value.split(':')
|
||||
if (components.length < 2 || components.length > 8) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (components[0] !== '' || components[1] !== '') {
|
||||
// Address does not begin with a zero compression ("::")
|
||||
if (!components[0].match(/^[\da-f]{1,4}/i)) {
|
||||
// Component must contain 1-4 hex characters
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let numberOfZeroCompressions = 0
|
||||
for (let i = 1; i < components.length; ++i) {
|
||||
if (components[i] === '') {
|
||||
// We're inside a zero compression ("::")
|
||||
++numberOfZeroCompressions
|
||||
if (numberOfZeroCompressions > 1) {
|
||||
// Zero compression can only occur once in an address
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (!components[i].match(/^[\da-f]{1,4}/i)) {
|
||||
// Component must contain 1-4 hex characters
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function init () {
|
||||
let config = await cfgLoader
|
||||
await fsp.access(config.named, fs.constants.F_OK)
|
||||
|
||||
router.post('/', async (req, res, next) => {
|
||||
let token = req.header('token') || req.body.token
|
||||
|
||||
// Check token
|
||||
if (!token || !config.tokens[token]) return res.status(402).send('Forbidden')
|
||||
|
||||
let v4 = null
|
||||
let qv4 = req.query.ipv4 || req.body.ipv4
|
||||
|
||||
let v6 = null
|
||||
let qv6 = req.query.ipv6 || req.body.ipv6
|
||||
|
||||
// Lets begin our trials
|
||||
// Determine Address from request headers
|
||||
if (req.header('http_x_forwarded_for')) {
|
||||
v4 = req.header('http_x_forwarded_for')
|
||||
} else if (req.header('remote_addr')) {
|
||||
v4 = req.header('remote_addr')
|
||||
} else {
|
||||
v4 = req.connection.remoteAddress
|
||||
}
|
||||
|
||||
if (!validv4(v4)) {
|
||||
v6 = v4
|
||||
v4 = null
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if (qv4 && validv4(qv4)) {
|
||||
v4 = qv4
|
||||
}
|
||||
|
||||
if (qv4 === 'ignore') {
|
||||
v4 = null
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (qv6 && validv6(qv6)) {
|
||||
v6 = qv6
|
||||
}
|
||||
|
||||
if (qv6 === 'ignore') {
|
||||
v6 = null
|
||||
}
|
||||
|
||||
if (v4 === null && v6 === null) {
|
||||
res.send('Nothing to do')
|
||||
}
|
||||
|
||||
try {
|
||||
await updateZone(Object.assign({
|
||||
token: token,
|
||||
root: config.named,
|
||||
}, config.tokens[token]), v4, v6)
|
||||
} catch (e) {
|
||||
console.error(e.stack)
|
||||
return res.status(500).send('Internal Server Error')
|
||||
}
|
||||
|
||||
res.status(204).end()
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
module.exports = init
|
66
applications/highlight-termbin/index.js
Normal file
66
applications/highlight-termbin/index.js
Normal file
@ -0,0 +1,66 @@
|
||||
const express = require('express')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const fsp = fs.promises
|
||||
|
||||
const router = express.Router()
|
||||
const cfgLoader = require(path.join('..', '..', 'config-loader'))(path.join(__dirname, 'config.json'), {
|
||||
root: path.join(process.cwd(), '..', 'fiche', 'code'),
|
||||
style: 'tomorrow-night'
|
||||
})
|
||||
|
||||
async function init () {
|
||||
let config = await cfgLoader
|
||||
let root = path.resolve(config.root)
|
||||
|
||||
await fsp.access(root, fs.constants.F_OK)
|
||||
|
||||
router.get('/:name', async (req, res, next) => {
|
||||
let name = req.params.name
|
||||
if (name.length != 4) {
|
||||
try {
|
||||
let text = await fsp.readFile(path.join(root, 'index.html'))
|
||||
res.send(text)
|
||||
} catch (e) {
|
||||
res.status(403)
|
||||
}
|
||||
return res.end()
|
||||
}
|
||||
|
||||
let fichePath = path.join(root, name, 'index.txt')
|
||||
let reqRaw = req.query.raw != null
|
||||
|
||||
let text
|
||||
try {
|
||||
text = await fsp.readFile(fichePath, {encoding: 'utf8'})
|
||||
} catch (e) {
|
||||
return res.status(404).end()
|
||||
}
|
||||
|
||||
if (reqRaw) {
|
||||
return res.set('Content-Type', 'text/plain').send(text)
|
||||
}
|
||||
|
||||
let etext = text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
||||
let payload =
|
||||
'<?xml version="1.0" encoding="UTF-8"?>' +
|
||||
'<html xmlns="http://www.w3.org/1999/xhtml" class="hljs">' +
|
||||
'<head>' +
|
||||
'<title>' + name + '</title>' +
|
||||
'<link rel="stylesheet" href="/assets/css/t.css" />' +
|
||||
'<link rel="stylesheet" href="/assets/css/t-styles/' + config.style + '.css" />' +
|
||||
'<script src="/assets/js/highlight.min.js"></script>' +
|
||||
'</head>' +
|
||||
'<body>' +
|
||||
'<pre><code>' + etext + '</code></pre>' +
|
||||
'<script>hljs.initHighlightingOnLoad();</script>' +
|
||||
'</body>' +
|
||||
'</html>'
|
||||
|
||||
res.set('Content-Type', 'application/xhtml+xml').send(payload)
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
module.exports = init
|
12
config-loader.js
Normal file
12
config-loader.js
Normal file
@ -0,0 +1,12 @@
|
||||
const fs = require('fs').promises
|
||||
|
||||
module.exports = async function (file, defaults) {
|
||||
let f
|
||||
try {
|
||||
f = await fs.readFile(file)
|
||||
f = JSON.parse(f)
|
||||
} catch (e) {
|
||||
return defaults
|
||||
}
|
||||
return Object.assign({}, defaults, f)
|
||||
}
|
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "serve-lunasqu.ee",
|
||||
"version": "1.0.0",
|
||||
"description": "lunasqu.ee - unite segregated services",
|
||||
"main": "runner.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node runner.js"
|
||||
},
|
||||
"private": true,
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.18.3",
|
||||
"express": "^4.16.4",
|
||||
"sqlite": "^3.0.1"
|
||||
}
|
||||
}
|
3
runner.js
Normal file
3
runner.js
Normal file
@ -0,0 +1,3 @@
|
||||
const path = require('path')
|
||||
|
||||
require(path.join(__dirname, 'server'))().catch((e) => console.error(e))
|
60
server.js
Normal file
60
server.js
Normal file
@ -0,0 +1,60 @@
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const fsPromises = fs.promises
|
||||
|
||||
const app = express()
|
||||
|
||||
let apps = process.env.APPS ? JSON.parse(process.env.APPS) : ['highlight-termbin', 'dyndns', 'distrib-ipfs']
|
||||
let sock = process.env.SOCKET || '/tmp/serve-lunasqu.ee.t.sock'
|
||||
|
||||
app.enable('trust proxy', 1)
|
||||
app.disable('x-powered-by')
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: false }))
|
||||
app.use(bodyParser.json())
|
||||
|
||||
function requireNoCache (file) {
|
||||
let fullPath = path.resolve(file)
|
||||
|
||||
let data = require(fullPath)
|
||||
|
||||
if (require.cache && require.cache[fullPath]) {
|
||||
delete require.cache[fullPath]
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async function enableApp (name) {
|
||||
let papp = path.join(__dirname, 'applications', name)
|
||||
await fsPromises.access(papp, fs.constants.F_OK)
|
||||
|
||||
let runner = requireNoCache(papp)
|
||||
if (!runner) throw new Error('No such application.')
|
||||
let router = await runner()
|
||||
|
||||
app.use('/' + name, router)
|
||||
}
|
||||
|
||||
async function init () {
|
||||
for (let i in apps) {
|
||||
let papp = apps[i]
|
||||
try {
|
||||
await enableApp(papp)
|
||||
console.log(' =>', papp, 'was enabled successfully.')
|
||||
} catch (e) {
|
||||
console.error('Failed to start application', papp)
|
||||
console.error(e.stack)
|
||||
}
|
||||
}
|
||||
|
||||
try { await fsPromises.unlink(sock) } catch (e) {}
|
||||
|
||||
app.listen(sock, function () {
|
||||
console.log('Started server on', sock)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = init
|
Loading…
Reference in New Issue
Block a user