*Switch to using a dynamic dns domain
*Add letsencrypt support *Add afraid.org support *Refactor env error checking *Add dynamic ip polling and updating
This commit is contained in:
parent
c89eb93857
commit
660bb85203
15
package.json
15
package.json
|
@ -4,21 +4,34 @@
|
|||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "build/index.js",
|
||||
"author": {
|
||||
"name": "Derrick Hammer",
|
||||
"email": "contact@lumeweb.com"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hyperswarm/dht": "^6.0.1",
|
||||
"@hyperswarm/dht-relay": "^0.3.0",
|
||||
"@root/greenlock": "^4.0.5",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/node-cron": "^3.0.2",
|
||||
"@types/ws": "^8.5.3",
|
||||
"@types/xml2js": "^0.4.11",
|
||||
"async-mutex": "^0.3.2",
|
||||
"express": "^4.18.1",
|
||||
"greenlock-express": "^4.0.3",
|
||||
"jayson": "^3.6.6",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"libskynet": "^0.0.48",
|
||||
"libskynetnode": "^0.1.3",
|
||||
"msgpackr": "^1.6.1",
|
||||
"node-cache": "^5.1.2",
|
||||
"random-access-memory": "^4.1.0"
|
||||
"node-cron": "^3.0.1",
|
||||
"node-fetch": "^3.2.6",
|
||||
"random-access-memory": "^4.1.0",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.13",
|
||||
"hyper-typings": "^1.0.0",
|
||||
"prettier": "^2.7.1"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export const RELAY_PORT = process.env.RELAY_PORT ?? 8080;
|
||||
export const RELAY_DOMAIN = process.env.RELAY_DOMAIN;
|
||||
export const AFRAID_USERNAME = process.env.AFRAID_USERNAME;
|
||||
export const AFRAID_PASSWORD = process.env.AFRAID_PASSWORD;
|
||||
export const RELAY_SEED = process.env.RELAY_SEED;
|
|
@ -0,0 +1,17 @@
|
|||
import * as CONFIG from "./constant_vars.js";
|
||||
|
||||
let error = false;
|
||||
|
||||
for (const constant in CONFIG) {
|
||||
// @ts-ignore
|
||||
if (!CONFIG[constant]) {
|
||||
console.error(`Missing constant ${constant}`);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export * from "./constant_vars.js";
|
|
@ -8,6 +8,7 @@ import {
|
|||
seedPhraseToSeed,
|
||||
validSeedPhrase,
|
||||
} from "libskynet";
|
||||
import { RELAY_SEED } from "./constant_vars";
|
||||
|
||||
let server: {
|
||||
listen: (arg0: ed25519Keypair) => void;
|
||||
|
@ -15,12 +16,6 @@ let server: {
|
|||
};
|
||||
|
||||
async function start() {
|
||||
const RELAY_SEED = process.env.RELAY_SEED ?? null;
|
||||
|
||||
if (!RELAY_SEED) {
|
||||
errorExit("RELAY_SEED missing. Aborting.");
|
||||
}
|
||||
|
||||
let [, err] = validSeedPhrase(RELAY_SEED as string);
|
||||
if (err !== null) {
|
||||
errorExit("RELAY_SEED is invalid. Aborting.");
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
import {
|
||||
AFRAID_USERNAME,
|
||||
AFRAID_PASSWORD,
|
||||
RELAY_PORT,
|
||||
RELAY_DOMAIN,
|
||||
} from "./constants.js";
|
||||
import cron from "node-cron";
|
||||
import fetch from "node-fetch";
|
||||
import { get as getDHT } from "./dht.js";
|
||||
import { overwriteRegistryEntry } from "libskynetnode";
|
||||
import { Buffer } from "buffer";
|
||||
import { blake2b } from "libskynet";
|
||||
import { Parser } from "xml2js";
|
||||
import { URL } from "url";
|
||||
import { errorExit } from "./util.js";
|
||||
import { pack } from "msgpackr";
|
||||
|
||||
const { createHash } = await import("crypto");
|
||||
|
||||
let activeIp: string;
|
||||
|
||||
const REGISTRY_DHT_KEY = "lumeweb-dht-relay";
|
||||
|
||||
async function ipUpdate() {
|
||||
let currentIp = await getCurrentIp();
|
||||
|
||||
if (activeIp && currentIp === activeIp) {
|
||||
return;
|
||||
}
|
||||
|
||||
let domain = await getDomainInfo();
|
||||
|
||||
await fetch(domain.url[0].toString());
|
||||
|
||||
activeIp = domain.address[0];
|
||||
}
|
||||
|
||||
export async function start() {
|
||||
const dht = await getDHT();
|
||||
|
||||
await ipUpdate();
|
||||
|
||||
await overwriteRegistryEntry(
|
||||
dht.defaultKeyPair,
|
||||
hashDataKey(REGISTRY_DHT_KEY),
|
||||
pack(`${RELAY_DOMAIN}:${RELAY_PORT}`)
|
||||
);
|
||||
|
||||
cron.schedule("0 * * * *", ipUpdate);
|
||||
}
|
||||
|
||||
function hashDataKey(dataKey: string): Uint8Array {
|
||||
return blake2b(encodeUtf8String(dataKey));
|
||||
}
|
||||
|
||||
function encodeUtf8String(str: string): Uint8Array {
|
||||
const byteArray = stringToUint8ArrayUtf8(str);
|
||||
const encoded = new Uint8Array(8 + byteArray.length);
|
||||
encoded.set(encodeNumber(byteArray.length));
|
||||
encoded.set(byteArray, 8);
|
||||
return encoded;
|
||||
}
|
||||
|
||||
function stringToUint8ArrayUtf8(str: string): Uint8Array {
|
||||
return Uint8Array.from(Buffer.from(str, "utf-8"));
|
||||
}
|
||||
|
||||
function encodeNumber(num: number): Uint8Array {
|
||||
const encoded = new Uint8Array(8);
|
||||
for (let index = 0; index < encoded.length; index++) {
|
||||
encoded[index] = num & 0xff;
|
||||
num = num >> 8;
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
async function getDomainInfo() {
|
||||
const parser = new Parser();
|
||||
|
||||
const url = new URL("https://freedns.afraid.org/api/");
|
||||
|
||||
const params = url.searchParams;
|
||||
|
||||
params.append("action", "getdyndns");
|
||||
params.append("v", "2");
|
||||
params.append("style", "xml");
|
||||
|
||||
const hash = createHash("sha1");
|
||||
hash.update(`${AFRAID_USERNAME}|${AFRAID_PASSWORD}`);
|
||||
|
||||
params.append("sha", hash.digest().toString("hex"));
|
||||
|
||||
const response = await (await fetch(url.toString())).text();
|
||||
|
||||
if (/could not authenticate/i.test(response)) {
|
||||
errorExit("Failed to authenticate to afraid.org");
|
||||
}
|
||||
|
||||
const json = await parser.parseStringPromise(response);
|
||||
|
||||
let domain = null;
|
||||
|
||||
for (const item of json.xml.item) {
|
||||
if (item.host[0] === RELAY_DOMAIN) {
|
||||
domain = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!domain) {
|
||||
errorExit(`Domain ${RELAY_DOMAIN} not found in afraid.org account`);
|
||||
}
|
||||
|
||||
return domain;
|
||||
}
|
||||
|
||||
async function getCurrentIp(): Promise<string> {
|
||||
const response = await (await fetch("http://checkip.dyndns.org")).text();
|
||||
const parser = new Parser();
|
||||
|
||||
const html = await parser.parseStringPromise(response.trim());
|
||||
|
||||
return html.html.body[0].split(":").pop();
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
import { start as startDns } from "./dns.js";
|
||||
import { start as startRpc } from "./rpc.js";
|
||||
import { start as startRelay } from "./relay.js";
|
||||
|
||||
startRelay();
|
||||
startRpc();
|
||||
await startDns();
|
||||
await startRpc();
|
||||
await startRelay();
|
||||
|
||||
process.on("uncaughtException", function (err) {
|
||||
console.log("Caught exception: " + err);
|
||||
|
|
81
src/relay.ts
81
src/relay.ts
|
@ -7,53 +7,60 @@ import { relay } from "@hyperswarm/dht-relay";
|
|||
// @ts-ignore
|
||||
import Stream from "@hyperswarm/dht-relay/ws";
|
||||
import { get as getDHT } from "./dht.js";
|
||||
import { overwriteRegistryEntry } from "libskynetnode/dist";
|
||||
import { Buffer } from "buffer";
|
||||
import { blake2b } from "libskynet/dist";
|
||||
import { RELAY_DOMAIN, RELAY_PORT } from "./constants.js";
|
||||
// @ts-ignore
|
||||
import GLE from "greenlock-express";
|
||||
// @ts-ignore
|
||||
import Greenlock from "@root/greenlock";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const REGISTRY_DHT_KEY = "lumeweb-dht-relay";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const config = {
|
||||
packageRoot: path.dirname(__dirname),
|
||||
configDir: path.resolve(__dirname, "../", "./data/greenlock.d/"),
|
||||
cluster: false,
|
||||
agreeToTerms: true,
|
||||
staging: true,
|
||||
};
|
||||
|
||||
export async function start() {
|
||||
const RELAY_PORT = process.env.RELAY_PORT ?? (8080 as unknown as string);
|
||||
const greenlock = Greenlock.create(config);
|
||||
await greenlock.add({
|
||||
subject: RELAY_DOMAIN,
|
||||
altnames: [RELAY_DOMAIN],
|
||||
});
|
||||
// @ts-ignore
|
||||
config.greenlock = greenlock;
|
||||
GLE.init(config).ready(async (GLEServer: any) => {
|
||||
let httpsServer = GLEServer.httpsServer();
|
||||
var httpServer = GLEServer.httpServer();
|
||||
|
||||
const server = new WS.Server({
|
||||
port: RELAY_PORT as unknown as number,
|
||||
await new Promise((resolve) => {
|
||||
httpServer.listen(80, "0.0.0.0", function () {
|
||||
console.info("HTTP Listening on ", httpServer.address());
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
|
||||
const dht = await getDHT();
|
||||
|
||||
await overwriteRegistryEntry(
|
||||
dht.defaultKeyPair,
|
||||
hashDataKey(REGISTRY_DHT_KEY),
|
||||
stringToUint8ArrayUtf8(`${dht.localAddress()}:${RELAY_PORT}`)
|
||||
);
|
||||
let wsServer = new WS.Server({ server: httpServer });
|
||||
|
||||
server.on("connection", (socket) => {
|
||||
wsServer.on("connection", (socket: any) => {
|
||||
relay(dht, new Stream(false, socket));
|
||||
});
|
||||
}
|
||||
await new Promise((resolve) => {
|
||||
httpsServer.listen(RELAY_PORT, "0.0.0.0", function () {
|
||||
console.info("Relay started on ", httpsServer.address());
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
|
||||
export function hashDataKey(dataKey: string): Uint8Array {
|
||||
return blake2b(encodeUtf8String(dataKey));
|
||||
}
|
||||
|
||||
function encodeUtf8String(str: string): Uint8Array {
|
||||
const byteArray = stringToUint8ArrayUtf8(str);
|
||||
const encoded = new Uint8Array(8 + byteArray.length);
|
||||
encoded.set(encodeNumber(byteArray.length));
|
||||
encoded.set(byteArray, 8);
|
||||
return encoded;
|
||||
}
|
||||
|
||||
function stringToUint8ArrayUtf8(str: string): Uint8Array {
|
||||
return Uint8Array.from(Buffer.from(str, "utf-8"));
|
||||
}
|
||||
|
||||
function encodeNumber(num: number): Uint8Array {
|
||||
const encoded = new Uint8Array(8);
|
||||
for (let index = 0; index < encoded.length; index++) {
|
||||
encoded[index] = num & 0xff;
|
||||
num = num >> 8;
|
||||
}
|
||||
return encoded;
|
||||
await greenlock.get({
|
||||
servername: RELAY_DOMAIN,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
13
src/rpc.ts
13
src/rpc.ts
|
@ -61,9 +61,7 @@ function getRequestId(request: RPCRequest) {
|
|||
return hash(stringify(clonedRequest));
|
||||
}
|
||||
|
||||
function maybeProcessRequest(item: any) {
|
||||
let request: RPCRequest = unpack(item) as RPCRequest;
|
||||
|
||||
function maybeProcessRequest(request: RPCRequest) {
|
||||
if (!request.chain) {
|
||||
throw new Error("RPC chain missing");
|
||||
}
|
||||
|
@ -137,8 +135,15 @@ async function processRequest(request: RPCRequest): Promise<RPCResponse> {
|
|||
export async function start() {
|
||||
(await getDHT()).on("connection", (socket: any) => {
|
||||
socket.on("data", async (data: any) => {
|
||||
let request: RPCRequest;
|
||||
try {
|
||||
socket.write(pack(await maybeProcessRequest(data)));
|
||||
request = unpack(data) as RPCRequest;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
socket.write(pack(await maybeProcessRequest(request)));
|
||||
} catch (error) {
|
||||
console.trace(error);
|
||||
socket.write(pack({ error }));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export function errorExit(msg: string): void {
|
||||
console.log(msg);
|
||||
console.error(msg);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue