Merge branch 'master' into matt/sky-585-update-webportal-repo

This commit is contained in:
Matthew Sevey 2022-05-27 09:39:16 -04:00 committed by GitHub
commit 0450b35016
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 3 additions and 6450 deletions

View File

@ -1,14 +1,6 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/packages/health-check"
schedule:
interval: monthly
- package-ecosystem: docker
directory: "/docker/sia"
schedule:
interval: monthly
- package-ecosystem: docker
directory: "/packages/health-check"
schedule:
interval: monthly

View File

@ -14,7 +14,6 @@ jobs:
matrix:
dockerfile:
- docker/sia/Dockerfile
- packages/health-check/Dockerfile
steps:
- uses: actions/checkout@v3
- uses: hadolint/hadolint-action@v2.0.0

View File

@ -1,23 +0,0 @@
name: Lint - packages/health-check
on:
pull_request:
paths:
- packages/health-check/**
defaults:
run:
working-directory: packages/health-check
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16.x
- run: yarn
- run: yarn prettier --check .

View File

@ -1,23 +0,0 @@
name: Test - packages/health-check
on:
pull_request:
paths:
- packages/health-check/**
defaults:
run:
working-directory: packages/health-check
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16.x
- run: yarn
- run: yarn jest

View File

@ -167,9 +167,9 @@ services:
health-check:
# uncomment "build" and comment out "image" to build from sources
# build:
# context: https://github.com/SkynetLabs/skynet-webportal.git#master
# dockerfile: ./packages/health-check/Dockerfile
image: skynetlabs/health-check
# context: https://github.com/SkynetLabs/webportal-health-check.git#main
# dockerfile: Dockerfile
image: skynetlabs/webportal-health-check:0.1.3
container_name: health-check
restart: unless-stopped
logging: *default-logging

View File

@ -1 +0,0 @@
state/

View File

@ -1 +0,0 @@
/package.json

View File

@ -1,3 +0,0 @@
{
"printWidth": 120
}

View File

@ -1,41 +0,0 @@
FROM node:16.14.2-alpine
RUN apk add --no-cache dnsmasq~=2
WORKDIR /usr/app
ENV PATH="/usr/app/bin:${PATH}"
# schedule critical checks to run every 5 minutes (any failures will disable server)
# schedule extended checks to run on every hour (optional checks, report only)
RUN echo '*/5 * * * * source /etc/environment ; /usr/app/bin/cli run critical >> /proc/1/fd/1' >> /etc/crontabs/root && \
echo '0 * * * * source /etc/environment ; /usr/app/bin/cli run extended >> /proc/1/fd/1' >> /etc/crontabs/root
COPY packages/health-check/package.json \
packages/health-check/yarn.lock \
./
RUN yarn --frozen-lockfile
COPY packages/health-check/src src
COPY packages/health-check/cli cli
COPY packages/health-check/bin bin
EXPOSE 3100
ENV NODE_ENV production
# 1. get public server ip and save it in /etc/environment (passed to cron tasks as env variable)
# 2. start dnsmasq in the background with:
# - alias PORTAL_DOMAIN with current server ip so it overrides potential load balancer request
# - default docker nameserver 127.0.0.11 for any other request
# 3. replace docker nameserver with dnsmasq nameserver in /etc/resolv.conf
# 4. start crond in the background to schedule periodic health checks
# 5. start the health-check api service
CMD [ "sh", "-c", \
"export serverip=$(node src/whatismyip.js) && \
echo \"export serverip=${serverip}\" >> /etc/environment && \
dnsmasq --no-resolv --log-facility=/var/log/dnsmasq.log --address=/$PORTAL_DOMAIN/$serverip --server=127.0.0.11 && \
echo \"$(sed 's/127.0.0.11/127.0.0.1/' /etc/resolv.conf)\" > /etc/resolv.conf && \
crond && \
node src/index.js" \
]

View File

@ -1,94 +0,0 @@
#!/usr/bin/env node
process.env.NODE_ENV = process.env.NODE_ENV || "production";
require("yargs/yargs")(process.argv.slice(2))
.help()
.demandCommand()
.strict(true)
.command(
"__authenticate", // Internal only function - this function will be removed when API keys are implemented
false, // hide this function cli help
() => {},
async () => {
const { getAuthCookie } = require("../src/utils");
console.log(await getAuthCookie(true));
}
)
.command(
"enable",
"Mark portal as enabled",
() => {},
() => {
const db = require("../src/db");
db.set("disabled", false).write();
}
)
.command(
"disable <reason>",
"Mark portal as disabled (provide meaningful reason)",
() => {},
({ reason }) => {
const db = require("../src/db");
db.set("disabled", reason).write();
}
)
.command(
"run <type>",
"Skynet portal health checks",
(yargs) => {
yargs
.positional("type", {
describe: "Type of checks to run",
type: "string",
choices: ["critical", "extended"],
})
.option("portal-url", {
describe: "Skynet portal url",
default: process.env.PORTAL_DOMAIN ? `https://${process.env.PORTAL_DOMAIN}` : "https://siasky.net",
type: "string",
})
.option("state-dir", {
describe: "State directory",
default: process.env.STATE_DIR || "state",
type: "string",
});
},
async ({ type, portalUrl, stateDir }) => {
const { hostname: portalDomain } = new URL(portalUrl); // extract domain from portal url
process.env.PORTAL_DOMAIN = portalDomain;
process.env.STATE_DIR = stateDir;
const util = require("util");
const { getYesterdayISOString } = require("../src/utils");
const createMiddleware = require("../src/checks/middleware");
const db = require("../src/db");
const checks = require(`../src/checks/${type}`);
const middleware = await createMiddleware();
const entry = {
date: new Date().toISOString(),
checks: (await Promise.all(checks.map((check) => new Promise(check)))).filter(Boolean).map(middleware),
};
db.read() // read before writing to make sure no external changes are overwritten
.get(type) // get the list of records of given type
.push(entry) // insert new record
.remove(({ date }) => date < getYesterdayISOString()) // drop old records
.write();
// exit with code 1 if any of the checks report failure
if (entry.checks.some(({ up }) => !up)) {
console.log(
util.inspect(
entry.checks.filter(({ up }) => !up),
{ colors: true, depth: 7 } // increase depth to ensure errors are printed
)
);
process.exit(1);
}
}
).argv;

View File

@ -1,5 +0,0 @@
#!/bin/ash
echo "DEPRECATED: 'cli/disable' command is deprecated, use 'cli disable' instead"
/usr/app/bin/cli disable $@

View File

@ -1,5 +0,0 @@
#!/bin/ash
echo "DEPRECATED: 'cli/enable' command is deprecated, use 'cli enable' instead"
/usr/app/bin/cli enable $@

View File

@ -1,5 +0,0 @@
#!/bin/ash
echo "DEPRECATED: 'cli/run' command is deprecated, use 'cli run' instead"
/usr/app/bin/cli run $@

View File

@ -1,24 +0,0 @@
{
"name": "health-check",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"deep-object-diff": "^1.1.7",
"express": "^4.18.1",
"form-data": "^4.0.0",
"got": "^11.8.2",
"graceful-fs": "^4.2.10",
"hasha": "^5.2.2",
"http-status-codes": "^2.2.0",
"lodash": "^4.17.21",
"lowdb": "^1.0.0",
"skynet-js": "^4.1.0",
"write-file-atomic": "^4.0.1",
"yargs": "^17.4.1"
},
"devDependencies": {
"jest": "^28.0.3",
"prettier": "^2.6.2"
}
}

View File

@ -1,28 +0,0 @@
const fs = require("graceful-fs");
const Base = require("lowdb/adapters/Base");
const { sync: writeFileAtomicSync } = require("write-file-atomic");
class FileSyncAtomic extends Base {
read() {
if (fs.existsSync(this.source)) {
try {
const data = fs.readFileSync(this.source, "utf-8").trim();
return data ? this.deserialize(data) : this.defaultValue;
} catch (e) {
if (e instanceof SyntaxError) {
e.message = `Malformed JSON in file: ${this.source}\n${e.message}`;
}
throw e;
}
} else {
writeFileAtomicSync(this.source, this.serialize(this.defaultValue));
return this.defaultValue;
}
}
write(data) {
return writeFileAtomicSync(this.source, this.serialize(data));
}
}
module.exports = FileSyncAtomic;

View File

@ -1,14 +0,0 @@
const db = require("../db");
const { getYesterdayISOString } = require("../utils");
// returns all critical health check entries
module.exports = (req, res) => {
const yesterday = getYesterdayISOString();
const entries = db
.get("critical")
.orderBy("date", "desc")
.filter(({ date }) => date > yesterday)
.value();
res.send(entries);
};

View File

@ -1,8 +0,0 @@
const db = require("../db");
// returns a disabled flag status
module.exports = (req, res) => {
const disabled = db.get("disabled").value();
res.send({ disabled });
};

View File

@ -1,14 +0,0 @@
const db = require("../db");
const { getYesterdayISOString } = require("../utils");
// returns all extended health check entries
module.exports = (req, res) => {
const yesterday = getYesterdayISOString();
const entries = db
.get("extended")
.orderBy("date", "desc")
.filter(({ date }) => date > yesterday)
.value();
res.send(entries);
};

View File

@ -1,75 +0,0 @@
const { StatusCodes } = require("http-status-codes");
const { sum, sumBy } = require("lodash");
const db = require("../db");
/**
* Get status code that should be returned in the API response.
* - OK (200) in case everything is healthy
* - SERVICE_UNAVAILABLE (503) in case of any failures or if disabled
*/
function getStatusCode() {
// check whether the portal has been manually disabled
const disabled = getDisabled();
if (disabled) {
return StatusCodes.SERVICE_UNAVAILABLE;
}
// grab the most recent critical entry element from DB
const entry = getMostRecentCriticalEntry();
// in case there is no entry yet or at least one check failed in the most recent entry
if (!entry || entry.checks.some(({ up }) => !up)) {
return StatusCodes.SERVICE_UNAVAILABLE;
}
return StatusCodes.OK;
}
/**
* Get the sample of most recent critical entries and
* calculate the average response time of all of them
*/
function getAverageResponseTime() {
// get most recent 10 successfull checks for the calculation
const sample = db
.get("critical")
.orderBy("date", "desc")
.filter(({ checks }) => checks.every(({ up }) => up))
.take(10)
.value();
// calculate average time of response
return Math.round(sum(sample.map(({ checks }) => sumBy(checks, "time"))) / sample.size);
}
/**
* Get one, most current critical entry
*/
function getMostRecentCriticalEntry() {
return db.get("critical").orderBy("date", "desc").head().value();
}
/**
* Get the disabled flag state (manual portal disable)
*/
function getDisabled() {
return db.get("disabled").value();
}
module.exports = (req, res) => {
const statusCode = getStatusCode();
const timeout = statusCode === StatusCodes.OK ? getAverageResponseTime() : 0;
// We want to delay the response for the load balancer to be able to prioritize
// servers based on the successful response time of this endpoint. Load balancer
// will pull the server if the response is an error so there is no point in delaying
// failures, hence 0 timeout on those.
setTimeout(() => {
// include some health information in the response body
const entry = getMostRecentCriticalEntry();
const disabled = getDisabled();
res.status(statusCode).send({ disabled, entry });
}, timeout);
};

View File

@ -1,239 +0,0 @@
const got = require("got");
const FormData = require("form-data");
const { isEqual } = require("lodash");
const { calculateElapsedTime, getResponseContent, getAuthCookie, isPortalModuleEnabled } = require("../utils");
const { SkynetClient, stringToUint8ArrayUtf8, genKeyPairAndSeed } = require("skynet-js");
const MODULE_BLOCKER = "b";
const skynetClient = new SkynetClient(`https://${process.env.PORTAL_DOMAIN}`);
const exampleSkylink = "AACogzrAimYPG42tDOKhS3lXZD8YvlF8Q8R17afe95iV2Q";
// check that any relevant configuration is properly set in skyd
async function skydConfigCheck(done) {
const time = process.hrtime();
const data = { up: false };
try {
const response = await got(`http://10.10.10.10:9980/renter`, { headers: { "User-Agent": "Sia-Agent" } }).json();
// make sure initial funding is set to 10SC
if (response.settings.allowance.paymentcontractinitialfunding !== "10000000000000000000000000") {
throw new Error("Skynet Portal Per-Contract Budget is not set correctly!");
}
data.up = true;
data.ip = response.ip;
} catch (error) {
data.statusCode = error.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
}
done({ name: "skyd_config", time: calculateElapsedTime(time), ...data });
}
// uploadCheck returns the result of uploading a sample file
async function uploadCheck(done) {
const authCookie = await getAuthCookie();
const time = process.hrtime();
const form = new FormData();
const payload = Buffer.from(new Date()); // current date to ensure data uniqueness
const data = { up: false };
form.append("file", payload, { filename: "time.txt", contentType: "text/plain" });
try {
const response = await got.post(`https://${process.env.PORTAL_DOMAIN}/skynet/skyfile`, {
body: form,
headers: { cookie: authCookie },
});
data.statusCode = response.statusCode;
data.up = true;
data.ip = response.ip;
} catch (error) {
data.statusCode = error.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
}
done({ name: "upload_file", time: calculateElapsedTime(time), ...data });
}
// websiteCheck checks whether the main website is working
async function websiteCheck(done) {
return done(await genericAccessCheck("website", `https://${process.env.PORTAL_DOMAIN}`));
}
// downloadCheck returns the result of downloading the hard coded link
async function downloadCheck(done) {
const url = await skynetClient.getSkylinkUrl(exampleSkylink);
return done(await genericAccessCheck("skylink", url));
}
// skylinkSubdomainCheck returns the result of downloading the hard coded link via subdomain
async function skylinkSubdomainCheck(done) {
const url = await skynetClient.getSkylinkUrl(exampleSkylink, { subdomain: true });
return done(await genericAccessCheck("skylink_via_subdomain", url));
}
// handshakeSubdomainCheck returns the result of downloading the skylink via handshake domain
async function handshakeSubdomainCheck(done) {
const url = await skynetClient.getHnsUrl("note-to-self", { subdomain: true });
return done(await genericAccessCheck("hns_via_subdomain", url));
}
// accountWebsiteCheck returns the result of accessing account dashboard website
async function accountWebsiteCheck(done) {
const url = `https://account.${process.env.PORTAL_DOMAIN}/auth/login`;
return done(await genericAccessCheck("account_website", url));
}
// registryWriteAndReadCheck writes to registry and immediately reads and compares the data
async function registryWriteAndReadCheck(done) {
const authCookie = await getAuthCookie();
const time = process.hrtime();
const data = { name: "registry_write_and_read", up: false };
const { privateKey, publicKey } = genKeyPairAndSeed();
const expected = { dataKey: "foo-key", data: stringToUint8ArrayUtf8("foo-data"), revision: BigInt(0) };
try {
await skynetClient.registry.setEntry(privateKey, expected, { customCookie: authCookie });
const { entry } = await skynetClient.registry.getEntry(publicKey, expected.dataKey, { customCookie: authCookie });
if (isEqual(expected, entry)) {
data.up = true;
} else {
data.errors = [{ message: "Data mismatch in registry (read after write)", entry, expected }];
}
} catch (error) {
data.errors = [{ message: error?.response?.data?.message ?? error.message }];
}
return done({ ...data, time: calculateElapsedTime(time) });
}
// directServerApiAccessCheck returns the basic server api check on direct server address
async function directServerApiAccessCheck(done) {
// skip if SERVER_DOMAIN is not set or it equals PORTAL_DOMAIN (single server portals)
if (!process.env.SERVER_DOMAIN || process.env.SERVER_DOMAIN === process.env.PORTAL_DOMAIN) {
return done();
}
const [portalAccessCheck, serverAccessCheck] = await Promise.all([
genericAccessCheck("portal_api_access", `https://${process.env.PORTAL_DOMAIN}`),
genericAccessCheck("server_api_access", `https://${process.env.SERVER_DOMAIN}`),
]);
if (portalAccessCheck.ip !== serverAccessCheck.ip) {
serverAccessCheck.up = false;
serverAccessCheck.errors = serverAccessCheck.errors ?? [];
serverAccessCheck.errors.push({
message: "Access ip mismatch between portal and server access",
response: {
portal: { name: process.env.PORTAL_DOMAIN, ip: portalAccessCheck.ip },
server: { name: process.env.SERVER_DOMAIN, ip: serverAccessCheck.ip },
},
});
}
return done(serverAccessCheck);
}
// accountHealthCheck returns the result of accounts service health checks
async function accountHealthCheck(done) {
const time = process.hrtime();
const data = { up: false };
try {
const response = await got(`https://account.${process.env.PORTAL_DOMAIN}/health`, { responseType: "json" });
data.statusCode = response.statusCode;
data.response = response.body;
data.up = response.body.dbAlive === true;
data.ip = response.ip;
} catch (error) {
data.statusCode = error?.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
}
done({ name: "accounts", time: calculateElapsedTime(time), ...data });
}
// blockerHealthCheck returns the result of blocker container health endpoint
async function blockerHealthCheck(done) {
const time = process.hrtime();
const data = { up: false };
try {
const response = await got(`http://${process.env.BLOCKER_HOST}:${process.env.BLOCKER_PORT}/health`, {
responseType: "json",
});
data.statusCode = response.statusCode;
data.response = response.body;
data.up = response.body.dbAlive === true;
} catch (error) {
data.statusCode = error?.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
}
// this is a no-op but it's added to explicitly document the ip property
// should not be set on the data object to prevent the IP from being compared
// to the server's IP - this is not required for this check and will fail
delete data.ip;
done({ name: "blocker", time: calculateElapsedTime(time), ...data });
}
async function genericAccessCheck(name, url) {
const authCookie = await getAuthCookie();
const time = process.hrtime();
const data = { up: false, url };
try {
const response = await got(url, { headers: { cookie: `nocache=true;${authCookie}` } });
data.statusCode = response.statusCode;
data.up = true;
data.ip = response.ip;
} catch (error) {
data.statusCode = error?.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
}
return { name, time: calculateElapsedTime(time), ...data };
}
const checks = [
skydConfigCheck,
uploadCheck,
websiteCheck,
downloadCheck,
skylinkSubdomainCheck,
handshakeSubdomainCheck,
registryWriteAndReadCheck,
directServerApiAccessCheck,
];
if (process.env.ACCOUNTS_ENABLED === "true") {
checks.push(accountHealthCheck, accountWebsiteCheck);
}
if (isPortalModuleEnabled(MODULE_BLOCKER)) {
checks.push(blockerHealthCheck);
}
module.exports = checks;

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +0,0 @@
const got = require("got");
const { ipCheckService, ipRegex } = require("../utils");
const getCurrentAddress = async () => {
// use serverip env variable when available (set via Dockerfile)
if (process.env.serverip) {
if (ipRegex.test(process.env.serverip)) return process.env.serverip;
// log error to console for future reference but do not break
console.log(`Environment variable serverip contains invalid ip: "${process.env.serverip}"`);
}
try {
const { body } = await got(`http://${ipCheckService}`);
if (ipRegex.test(body)) {
console.info(`Server public ip: ${body} (source: ${ipCheckService})`);
return body;
}
throw new Error(`${ipCheckService} responded with invalid ip: "${body}"`);
} catch (error) {
console.log(error.message); // log error to console for future reference
return null;
}
};
module.exports = async function middleware() {
const ip = await getCurrentAddress();
return (check) => {
// check only if current ip and check ip are provided
if (ip && check.ip && check.ip !== ip) {
check.up = false;
check.errors = check.errors ?? [];
check.errors.push({
message: "Response ip was different than current server ip - possibly there was an error with routing request",
data: { response: check.ip, server: ip },
});
}
return check;
};
};

View File

@ -1,12 +0,0 @@
const fs = require("graceful-fs");
const low = require("lowdb");
const FileSyncAtomic = require("./adapters/FileSyncAtomic");
if (!fs.existsSync(process.env.STATE_DIR)) fs.mkdirSync(process.env.STATE_DIR);
const adapter = new FileSyncAtomic(`${process.env.STATE_DIR}/state.json`);
const db = low(adapter);
db.defaults({ disabled: false, critical: [], extended: [] }).write();
module.exports = db;

View File

@ -1,322 +0,0 @@
{
"filename": "output",
"subfiles": {
".well-known/brave-rewards-verification.txt": {
"filename": ".well-known/brave-rewards-verification.txt",
"contenttype": "text/plain",
"len": 154
},
"404.html": { "filename": "404.html", "contenttype": "text/html", "offset": 154, "len": 5482 },
"assets/bootstrap/bootstrap-grid.css": {
"filename": "assets/bootstrap/bootstrap-grid.css",
"contenttype": "text/css",
"offset": 5636,
"len": 49901
},
"assets/bootstrap/bootstrap-reboot.css": {
"filename": "assets/bootstrap/bootstrap-reboot.css",
"contenttype": "text/css",
"offset": 55537,
"len": 4187
},
"assets/bootstrap/bootstrap.css": {
"filename": "assets/bootstrap/bootstrap.css",
"contenttype": "text/css",
"offset": 59724,
"len": 172594
},
"assets/css/styles.css": {
"filename": "assets/css/styles.css",
"contenttype": "text/css",
"offset": 232318,
"len": 4887
},
"assets/fonts/dm-serif-display-v4-latin-regular.woff": {
"filename": "assets/fonts/dm-serif-display-v4-latin-regular.woff",
"contenttype": "application/font-woff",
"offset": 237205,
"len": 29916
},
"assets/fonts/dm-serif-display-v4-latin-regular.woff2": {
"filename": "assets/fonts/dm-serif-display-v4-latin-regular.woff2",
"contenttype": "application/octet-stream",
"offset": 267121,
"len": 24980
},
"assets/fonts/open-sans-v16-latin-regular.woff": {
"filename": "assets/fonts/open-sans-v16-latin-regular.woff",
"contenttype": "application/font-woff",
"offset": 292101,
"len": 18100
},
"assets/fonts/open-sans-v16-latin-regular.woff2": {
"filename": "assets/fonts/open-sans-v16-latin-regular.woff2",
"contenttype": "application/octet-stream",
"offset": 310201,
"len": 14380
},
"assets/fonts/questrial-v9-latin-regular.woff": {
"filename": "assets/fonts/questrial-v9-latin-regular.woff",
"contenttype": "application/font-woff",
"offset": 324581,
"len": 23048
},
"assets/fonts/questrial-v9-latin-regular.woff2": {
"filename": "assets/fonts/questrial-v9-latin-regular.woff2",
"contenttype": "application/octet-stream",
"offset": 347629,
"len": 13776
},
"assets/images/blog/2a40df99-1847-4726-9c5b-af4779eeb667-w1920-h1440.jpg": {
"filename": "assets/images/blog/2a40df99-1847-4726-9c5b-af4779eeb667-w1920-h1440.jpg",
"contenttype": "image/jpeg",
"offset": 361405,
"len": 79551
},
"assets/images/blog/2a40df99-1847-4726-9c5b-af4779eeb667-w960-h720.jpg": {
"filename": "assets/images/blog/2a40df99-1847-4726-9c5b-af4779eeb667-w960-h720.jpg",
"contenttype": "image/jpeg",
"offset": 440956,
"len": 31700
},
"assets/images/blog/2a40df99-1847-4726-9c5b-af4779eeb667.jpg": {
"filename": "assets/images/blog/2a40df99-1847-4726-9c5b-af4779eeb667.jpg",
"contenttype": "image/jpeg",
"offset": 472656,
"len": 69094
},
"assets/images/blog/512e4dd1-6b3d-41aa-80a1-b96c3370b3c3-w1920-h1440.jpg": {
"filename": "assets/images/blog/512e4dd1-6b3d-41aa-80a1-b96c3370b3c3-w1920-h1440.jpg",
"contenttype": "image/jpeg",
"offset": 541750,
"len": 219602
},
"assets/images/blog/512e4dd1-6b3d-41aa-80a1-b96c3370b3c3-w960-h720.jpg": {
"filename": "assets/images/blog/512e4dd1-6b3d-41aa-80a1-b96c3370b3c3-w960-h720.jpg",
"contenttype": "image/jpeg",
"offset": 761352,
"len": 67741
},
"assets/images/blog/512e4dd1-6b3d-41aa-80a1-b96c3370b3c3.jpg": {
"filename": "assets/images/blog/512e4dd1-6b3d-41aa-80a1-b96c3370b3c3.jpg",
"contenttype": "image/jpeg",
"offset": 829093,
"len": 226910
},
"assets/images/blog/823a7764-af7c-4687-a42e-bd70768068ab-w1920-h1440.jpg": {
"filename": "assets/images/blog/823a7764-af7c-4687-a42e-bd70768068ab-w1920-h1440.jpg",
"contenttype": "image/jpeg",
"offset": 1056003,
"len": 258292
},
"assets/images/blog/823a7764-af7c-4687-a42e-bd70768068ab-w960-h720.jpg": {
"filename": "assets/images/blog/823a7764-af7c-4687-a42e-bd70768068ab-w960-h720.jpg",
"contenttype": "image/jpeg",
"offset": 1314295,
"len": 93250
},
"assets/images/blog/823a7764-af7c-4687-a42e-bd70768068ab.jpg": {
"filename": "assets/images/blog/823a7764-af7c-4687-a42e-bd70768068ab.jpg",
"contenttype": "image/jpeg",
"offset": 1407545,
"len": 236722
},
"assets/images/blog/9aeea0d6-737c-4be8-8b63-5ec38cbf394b-w1920-h1440.jpg": {
"filename": "assets/images/blog/9aeea0d6-737c-4be8-8b63-5ec38cbf394b-w1920-h1440.jpg",
"contenttype": "image/jpeg",
"offset": 1644267,
"len": 285727
},
"assets/images/blog/9aeea0d6-737c-4be8-8b63-5ec38cbf394b-w960-h720.jpg": {
"filename": "assets/images/blog/9aeea0d6-737c-4be8-8b63-5ec38cbf394b-w960-h720.jpg",
"contenttype": "image/jpeg",
"offset": 1929994,
"len": 115524
},
"assets/images/blog/9aeea0d6-737c-4be8-8b63-5ec38cbf394b.jpg": {
"filename": "assets/images/blog/9aeea0d6-737c-4be8-8b63-5ec38cbf394b.jpg",
"contenttype": "image/jpeg",
"offset": 2045518,
"len": 338905
},
"assets/images/blog/a1ee6dcf-55ef-43cd-ae05-682d2e28e932-w1920-h1440.jpg": {
"filename": "assets/images/blog/a1ee6dcf-55ef-43cd-ae05-682d2e28e932-w1920-h1440.jpg",
"contenttype": "image/jpeg",
"offset": 2384423,
"len": 66608
},
"assets/images/blog/a1ee6dcf-55ef-43cd-ae05-682d2e28e932-w960-h720.jpg": {
"filename": "assets/images/blog/a1ee6dcf-55ef-43cd-ae05-682d2e28e932-w960-h720.jpg",
"contenttype": "image/jpeg",
"offset": 2451031,
"len": 23239
},
"assets/images/blog/a1ee6dcf-55ef-43cd-ae05-682d2e28e932.jpg": {
"filename": "assets/images/blog/a1ee6dcf-55ef-43cd-ae05-682d2e28e932.jpg",
"contenttype": "image/jpeg",
"offset": 2474270,
"len": 82334
},
"assets/images/blog/content/17343f27-a62f-4193-a0e5-4190d948eb2e.png": {
"filename": "assets/images/blog/content/17343f27-a62f-4193-a0e5-4190d948eb2e.png",
"contenttype": "image/png",
"offset": 2556604,
"len": 8571
},
"assets/images/blog/content/1748cc9c-9ea0-47b8-a110-ad3a114408d1.png": {
"filename": "assets/images/blog/content/1748cc9c-9ea0-47b8-a110-ad3a114408d1.png",
"contenttype": "image/png",
"offset": 2565175,
"len": 19776
},
"assets/images/blog/content/27b98c5e-ba57-47e6-9fe7-9b82fb89868b.jpg": {
"filename": "assets/images/blog/content/27b98c5e-ba57-47e6-9fe7-9b82fb89868b.jpg",
"contenttype": "image/jpeg",
"offset": 2584951,
"len": 68054
},
"assets/images/blog/content/39374de9-f24a-46f6-9955-982687607c6d.png": {
"filename": "assets/images/blog/content/39374de9-f24a-46f6-9955-982687607c6d.png",
"contenttype": "image/png",
"offset": 2653005,
"len": 30305
},
"assets/images/blog/content/5c660f5c-04fb-46cd-9846-edccb9a7b778.jpg": {
"filename": "assets/images/blog/content/5c660f5c-04fb-46cd-9846-edccb9a7b778.jpg",
"contenttype": "image/jpeg",
"offset": 2683310,
"len": 10409
},
"assets/images/blog/content/5cb6fb87-75d0-4aa4-99c7-b7815ca7ea70.png": {
"filename": "assets/images/blog/content/5cb6fb87-75d0-4aa4-99c7-b7815ca7ea70.png",
"contenttype": "image/png",
"offset": 2693719,
"len": 123977
},
"assets/images/blog/content/765827f6-192b-48c9-b3e1-cb7b33e3b881.png": {
"filename": "assets/images/blog/content/765827f6-192b-48c9-b3e1-cb7b33e3b881.png",
"contenttype": "image/png",
"offset": 2817696,
"len": 110297
},
"assets/images/blog/content/7b39a2f8-8060-43e7-a439-43f799d3e069.jpg": {
"filename": "assets/images/blog/content/7b39a2f8-8060-43e7-a439-43f799d3e069.jpg",
"contenttype": "image/jpeg",
"offset": 2927993,
"len": 24372
},
"assets/images/blog/content/8af4faff-e011-4e31-ba28-5023f65d1003.png": {
"filename": "assets/images/blog/content/8af4faff-e011-4e31-ba28-5023f65d1003.png",
"contenttype": "image/png",
"offset": 2952365,
"len": 106400
},
"assets/images/blog/content/ae29cd58-f28f-4a0e-bffb-a7e4e1235797.png": {
"filename": "assets/images/blog/content/ae29cd58-f28f-4a0e-bffb-a7e4e1235797.png",
"contenttype": "image/png",
"offset": 3058765,
"len": 33357
},
"assets/images/blog/content/b3be6c1c-725a-4af2-a85f-e47e09bbceef.png": {
"filename": "assets/images/blog/content/b3be6c1c-725a-4af2-a85f-e47e09bbceef.png",
"contenttype": "image/png",
"offset": 3092122,
"len": 37074
},
"assets/images/blog/content/b4e772a3-effb-4a5d-82d9-db9596ccfe51.png": {
"filename": "assets/images/blog/content/b4e772a3-effb-4a5d-82d9-db9596ccfe51.png",
"contenttype": "image/png",
"offset": 3129196,
"len": 79662
},
"assets/images/blog/content/d2731109-b50f-4c1f-b4f9-7ab8cac196da.png": {
"filename": "assets/images/blog/content/d2731109-b50f-4c1f-b4f9-7ab8cac196da.png",
"contenttype": "image/png",
"offset": 3208858,
"len": 104535
},
"assets/images/blog/content/fed0e592-d063-497b-9a3b-2bfc29b04d1a.jpg": {
"filename": "assets/images/blog/content/fed0e592-d063-497b-9a3b-2bfc29b04d1a.jpg",
"contenttype": "image/jpeg",
"offset": 3313393,
"len": 9535
},
"assets/images/blog/e4956336-3662-46ae-bea2-7fd3059919c3-w1920-h1440.jpg": {
"filename": "assets/images/blog/e4956336-3662-46ae-bea2-7fd3059919c3-w1920-h1440.jpg",
"contenttype": "image/jpeg",
"offset": 3322928,
"len": 402770
},
"assets/images/blog/e4956336-3662-46ae-bea2-7fd3059919c3-w960-h720.jpg": {
"filename": "assets/images/blog/e4956336-3662-46ae-bea2-7fd3059919c3-w960-h720.jpg",
"contenttype": "image/jpeg",
"offset": 3725698,
"len": 143539
},
"assets/images/blog/e4956336-3662-46ae-bea2-7fd3059919c3.jpg": {
"filename": "assets/images/blog/e4956336-3662-46ae-bea2-7fd3059919c3.jpg",
"contenttype": "image/jpeg",
"offset": 3869237,
"len": 375170
},
"assets/images/logo.svg": {
"filename": "assets/images/logo.svg",
"contenttype": "image/svg+xml",
"offset": 4244407,
"len": 2183
},
"assets/js/themes.js": {
"filename": "assets/js/themes.js",
"contenttype": "text/javascript",
"offset": 4246590,
"len": 779
},
"blog/building_a_web_farm_with_docker_and_raspberry_pi.html": {
"filename": "blog/building_a_web_farm_with_docker_and_raspberry_pi.html",
"contenttype": "text/html",
"offset": 4247369,
"len": 23111
},
"blog/continuously_deploy_a_static_website_with_azure_pipelines.html": {
"filename": "blog/continuously_deploy_a_static_website_with_azure_pipelines.html",
"contenttype": "text/html",
"offset": 4270480,
"len": 24738
},
"blog/decentralise_your_website_as_much_as_possible.html": {
"filename": "blog/decentralise_your_website_as_much_as_possible.html",
"contenttype": "text/html",
"offset": 4295218,
"len": 14825
},
"blog/developing_smart_contracts_for_business.html": {
"filename": "blog/developing_smart_contracts_for_business.html",
"contenttype": "text/html",
"offset": 4310043,
"len": 25783
},
"blog/getting_to_grips_with_jwt_in_asp_net_core.html": {
"filename": "blog/getting_to_grips_with_jwt_in_asp_net_core.html",
"contenttype": "text/html",
"offset": 4335826,
"len": 20915
},
"blog/index.html": { "filename": "blog/index.html", "contenttype": "text/html", "offset": 4356741, "len": 7345 },
"blog/setting_up_an_asp_net_core_web_farm.html": {
"filename": "blog/setting_up_an_asp_net_core_web_farm.html",
"contenttype": "text/html",
"offset": 4364086,
"len": 11464
},
"favicon-16x16.png": { "filename": "favicon-16x16.png", "contenttype": "image/png", "offset": 4375550, "len": 430 },
"favicon-32x32.png": { "filename": "favicon-32x32.png", "contenttype": "image/png", "offset": 4375980, "len": 540 },
"favicon.ico": { "filename": "favicon.ico", "contenttype": "image/x-icon", "offset": 4376520, "len": 15086 },
"feed.atom": {
"filename": "feed.atom",
"contenttype": "application/octet-stream",
"offset": 4391606,
"len": 95092
},
"index.html": { "filename": "index.html", "contenttype": "text/html", "offset": 4486698, "len": 4981 }
}
}

View File

@ -1,122 +0,0 @@
{
"filename": "skygallery-v0.1.1-76c4c115fcb526716b2564568850f433",
"subfiles": {
"css/app.84a130ed.css": { "filename": "css/app.84a130ed.css", "contenttype": "text/css", "len": 698 },
"css/chunk-5ce44031.d4e78528.css": {
"filename": "css/chunk-5ce44031.d4e78528.css",
"contenttype": "text/css",
"offset": 698,
"len": 45
},
"css/chunk-6bef839b.593aa2be.css": {
"filename": "css/chunk-6bef839b.593aa2be.css",
"contenttype": "text/css",
"offset": 743,
"len": 5013
},
"css/chunk-8ed50a48.8ba8c09d.css": {
"filename": "css/chunk-8ed50a48.8ba8c09d.css",
"contenttype": "text/css",
"offset": 5756,
"len": 7204
},
"css/chunk-eb4c1efc.2a7e25ed.css": {
"filename": "css/chunk-eb4c1efc.2a7e25ed.css",
"contenttype": "text/css",
"offset": 12960,
"len": 45
},
"css/chunk-vendors.b4f58487.css": {
"filename": "css/chunk-vendors.b4f58487.css",
"contenttype": "text/css",
"offset": 13005,
"len": 382063
},
"img/skygallery_logo.2336197e.svg": {
"filename": "img/skygallery_logo.2336197e.svg",
"contenttype": "image/svg+xml",
"offset": 395068,
"len": 923
},
"img/skynet-logo-animated.4d24345c.svg": {
"filename": "img/skynet-logo-animated.4d24345c.svg",
"contenttype": "image/svg+xml",
"offset": 395991,
"len": 2600
},
"index.html": { "filename": "index.html", "contenttype": "text/html", "offset": 398591, "len": 2534 },
"js/app.cff1e0a4.js": {
"filename": "js/app.cff1e0a4.js",
"contenttype": "application/javascript",
"offset": 401125,
"len": 15604
},
"js/app.cff1e0a4.js.map": {
"filename": "js/app.cff1e0a4.js.map",
"contenttype": "application/json",
"offset": 416729,
"len": 54424
},
"js/chunk-5ce44031.7fb55da9.js": {
"filename": "js/chunk-5ce44031.7fb55da9.js",
"contenttype": "application/javascript",
"offset": 471153,
"len": 3644
},
"js/chunk-5ce44031.7fb55da9.js.map": {
"filename": "js/chunk-5ce44031.7fb55da9.js.map",
"contenttype": "application/json",
"offset": 474797,
"len": 13494
},
"js/chunk-6bef839b.b543fe7d.js": {
"filename": "js/chunk-6bef839b.b543fe7d.js",
"contenttype": "application/javascript",
"offset": 488291,
"len": 13349
},
"js/chunk-6bef839b.b543fe7d.js.map": {
"filename": "js/chunk-6bef839b.b543fe7d.js.map",
"contenttype": "application/json",
"offset": 501640,
"len": 46690
},
"js/chunk-8ed50a48.35f8ef35.js": {
"filename": "js/chunk-8ed50a48.35f8ef35.js",
"contenttype": "application/javascript",
"offset": 548330,
"len": 130329
},
"js/chunk-8ed50a48.35f8ef35.js.map": {
"filename": "js/chunk-8ed50a48.35f8ef35.js.map",
"contenttype": "application/json",
"offset": 678659,
"len": 507145
},
"js/chunk-eb4c1efc.57b6e01c.js": {
"filename": "js/chunk-eb4c1efc.57b6e01c.js",
"contenttype": "application/javascript",
"offset": 1185804,
"len": 4407
},
"js/chunk-eb4c1efc.57b6e01c.js.map": {
"filename": "js/chunk-eb4c1efc.57b6e01c.js.map",
"contenttype": "application/json",
"offset": 1190211,
"len": 15355
},
"js/chunk-vendors.1fd55121.js": {
"filename": "js/chunk-vendors.1fd55121.js",
"contenttype": "application/javascript",
"offset": 1205566,
"len": 749829
},
"js/chunk-vendors.1fd55121.js.map": {
"filename": "js/chunk-vendors.1fd55121.js.map",
"contenttype": "application/json",
"offset": 1955395,
"len": 2793251
}
},
"defaultpath": "/index.html"
}

View File

@ -1,658 +0,0 @@
{
"filename": "build",
"subfiles": {
"451.html": { "filename": "451.html", "contenttype": "text/html", "offset": 20181232, "len": 200 },
"asset-manifest.json": {
"filename": "asset-manifest.json",
"contenttype": "application/json",
"offset": 485031,
"len": 4561
},
"favicon.png": { "filename": "favicon.png", "contenttype": "image/png", "offset": 489592, "len": 7072 },
"images/192x192_App_Icon.png": {
"filename": "images/192x192_App_Icon.png",
"contenttype": "image/png",
"offset": 434153,
"len": 50878
},
"images/512x512_App_Icon.png": {
"filename": "images/512x512_App_Icon.png",
"contenttype": "image/png",
"offset": 47542,
"len": 386611
},
"index.html": { "filename": "index.html", "contenttype": "text/html", "len": 3268 },
"locales/de.json": {
"filename": "locales/de.json",
"contenttype": "application/json",
"offset": 7491,
"len": 4376
},
"locales/en.json": {
"filename": "locales/en.json",
"contenttype": "application/json",
"offset": 23709,
"len": 4321
},
"locales/es-AR.json": {
"filename": "locales/es-AR.json",
"contenttype": "application/json",
"offset": 16866,
"len": 3624
},
"locales/es-US.json": {
"filename": "locales/es-US.json",
"contenttype": "application/json",
"offset": 43912,
"len": 3630
},
"locales/it-IT.json": {
"filename": "locales/it-IT.json",
"contenttype": "application/json",
"offset": 3268,
"len": 4223
},
"locales/iw.json": {
"filename": "locales/iw.json",
"contenttype": "application/json",
"offset": 28030,
"len": 3929
},
"locales/ro.json": {
"filename": "locales/ro.json",
"contenttype": "application/json",
"offset": 31959,
"len": 3794
},
"locales/ru.json": {
"filename": "locales/ru.json",
"contenttype": "application/json",
"offset": 11867,
"len": 4999
},
"locales/vi.json": {
"filename": "locales/vi.json",
"contenttype": "application/json",
"offset": 39011,
"len": 4901
},
"locales/zh-CN.json": {
"filename": "locales/zh-CN.json",
"contenttype": "application/json",
"offset": 20490,
"len": 3219
},
"locales/zh-TW.json": {
"filename": "locales/zh-TW.json",
"contenttype": "application/json",
"offset": 35753,
"len": 3258
},
"manifest.json": { "filename": "manifest.json", "contenttype": "application/json", "offset": 20190818, "len": 470 },
"precache-manifest.5ce41899d70d2e0450f591b3e917c2a4.js": {
"filename": "precache-manifest.5ce41899d70d2e0450f591b3e917c2a4.js",
"contenttype": "application/x-javascript",
"offset": 20181432,
"len": 9386
},
"service-worker.js": {
"filename": "service-worker.js",
"contenttype": "application/x-javascript",
"offset": 20191288,
"len": 1183
},
"static/css/4.f04942fe.chunk.css": {
"filename": "static/css/4.f04942fe.chunk.css",
"contenttype": "text/css",
"offset": 496664,
"len": 5331
},
"static/css/4.f04942fe.chunk.css.map": {
"filename": "static/css/4.f04942fe.chunk.css.map",
"contenttype": "application/octet-stream",
"offset": 501995,
"len": 8394
},
"static/js/0.1043efff.chunk.js": {
"filename": "static/js/0.1043efff.chunk.js",
"contenttype": "application/x-javascript",
"offset": 3451819,
"len": 226756
},
"static/js/0.1043efff.chunk.js.map": {
"filename": "static/js/0.1043efff.chunk.js.map",
"contenttype": "application/octet-stream",
"offset": 8495570,
"len": 811341
},
"static/js/1.722d768c.chunk.js": {
"filename": "static/js/1.722d768c.chunk.js",
"contenttype": "application/x-javascript",
"offset": 2503781,
"len": 20289
},
"static/js/1.722d768c.chunk.js.map": {
"filename": "static/js/1.722d768c.chunk.js.map",
"contenttype": "application/octet-stream",
"offset": 11896220,
"len": 44729
},
"static/js/4.cebcd4f8.chunk.js": {
"filename": "static/js/4.cebcd4f8.chunk.js",
"contenttype": "application/x-javascript",
"offset": 11941212,
"len": 1486762
},
"static/js/4.cebcd4f8.chunk.js.LICENSE.txt": {
"filename": "static/js/4.cebcd4f8.chunk.js.LICENSE.txt",
"contenttype": "text/plain",
"offset": 14378677,
"len": 3519
},
"static/js/4.cebcd4f8.chunk.js.map": {
"filename": "static/js/4.cebcd4f8.chunk.js.map",
"contenttype": "application/octet-stream",
"offset": 3678575,
"len": 4816995
},
"static/js/5.428f04e8.chunk.js": {
"filename": "static/js/5.428f04e8.chunk.js",
"contenttype": "application/x-javascript",
"offset": 1887438,
"len": 616343
},
"static/js/5.428f04e8.chunk.js.LICENSE.txt": {
"filename": "static/js/5.428f04e8.chunk.js.LICENSE.txt",
"contenttype": "text/plain",
"offset": 3450983,
"len": 426
},
"static/js/5.428f04e8.chunk.js.map": {
"filename": "static/js/5.428f04e8.chunk.js.map",
"contenttype": "application/octet-stream",
"offset": 10046674,
"len": 1553345
},
"static/js/6.29fcca22.chunk.js": {
"filename": "static/js/6.29fcca22.chunk.js",
"contenttype": "application/x-javascript",
"offset": 11600019,
"len": 296095
},
"static/js/6.29fcca22.chunk.js.map": {
"filename": "static/js/6.29fcca22.chunk.js.map",
"contenttype": "application/octet-stream",
"offset": 13440646,
"len": 938031
},
"static/js/7.8d2bc3b4.chunk.js": {
"filename": "static/js/7.8d2bc3b4.chunk.js",
"contenttype": "application/x-javascript",
"offset": 9306911,
"len": 263
},
"static/js/7.8d2bc3b4.chunk.js.map": {
"filename": "static/js/7.8d2bc3b4.chunk.js.map",
"contenttype": "application/octet-stream",
"offset": 11896114,
"len": 106
},
"static/js/8.3d784f08.chunk.js": {
"filename": "static/js/8.3d784f08.chunk.js",
"contenttype": "application/x-javascript",
"offset": 11940949,
"len": 263
},
"static/js/8.3d784f08.chunk.js.map": {
"filename": "static/js/8.3d784f08.chunk.js.map",
"contenttype": "application/octet-stream",
"offset": 3450877,
"len": 106
},
"static/js/9.08920d68.chunk.js": {
"filename": "static/js/9.08920d68.chunk.js",
"contenttype": "application/x-javascript",
"offset": 2524070,
"len": 626875
},
"static/js/9.08920d68.chunk.js.LICENSE.txt": {
"filename": "static/js/9.08920d68.chunk.js.LICENSE.txt",
"contenttype": "text/plain",
"offset": 3451409,
"len": 410
},
"static/js/9.08920d68.chunk.js.map": {
"filename": "static/js/9.08920d68.chunk.js.map",
"contenttype": "application/octet-stream",
"offset": 512852,
"len": 1374586
},
"static/js/main.d2a5ca05.chunk.js": {
"filename": "static/js/main.d2a5ca05.chunk.js",
"contenttype": "application/x-javascript",
"offset": 3150945,
"len": 299932
},
"static/js/main.d2a5ca05.chunk.js.map": {
"filename": "static/js/main.d2a5ca05.chunk.js.map",
"contenttype": "application/octet-stream",
"offset": 9307174,
"len": 739500
},
"static/js/runtime-main.712341b8.js": {
"filename": "static/js/runtime-main.712341b8.js",
"contenttype": "application/x-javascript",
"offset": 510389,
"len": 2463
},
"static/js/runtime-main.712341b8.js.map": {
"filename": "static/js/runtime-main.712341b8.js.map",
"contenttype": "application/octet-stream",
"offset": 13427974,
"len": 12672
},
"static/media/Inter-Black.09f4068b.woff2": {
"filename": "static/media/Inter-Black.09f4068b.woff2",
"contenttype": "application/octet-stream",
"offset": 16311114,
"len": 104656
},
"static/media/Inter-Black.e3735483.woff": {
"filename": "static/media/Inter-Black.e3735483.woff",
"contenttype": "application/octet-stream",
"offset": 16415770,
"len": 139648
},
"static/media/Inter-BlackItalic.07e69b53.woff": {
"filename": "static/media/Inter-BlackItalic.07e69b53.woff",
"contenttype": "application/octet-stream",
"offset": 15020483,
"len": 145816
},
"static/media/Inter-BlackItalic.daa1ca3c.woff2": {
"filename": "static/media/Inter-BlackItalic.daa1ca3c.woff2",
"contenttype": "application/octet-stream",
"offset": 19675808,
"len": 109900
},
"static/media/Inter-Bold.79260e5b.woff": {
"filename": "static/media/Inter-Bold.79260e5b.woff",
"contenttype": "application/octet-stream",
"offset": 15781749,
"len": 143464
},
"static/media/Inter-Bold.aed27700.woff2": {
"filename": "static/media/Inter-Bold.aed27700.woff2",
"contenttype": "application/octet-stream",
"offset": 16555739,
"len": 107144
},
"static/media/Inter-BoldItalic.8ef77a03.woff2": {
"filename": "static/media/Inter-BoldItalic.8ef77a03.woff2",
"contenttype": "application/octet-stream",
"offset": 17104768,
"len": 112276
},
"static/media/Inter-BoldItalic.e0879d64.woff": {
"filename": "static/media/Inter-BoldItalic.e0879d64.woff",
"contenttype": "application/octet-stream",
"offset": 15483981,
"len": 149360
},
"static/media/Inter-ExtraBold.38bc51bc.woff": {
"filename": "static/media/Inter-ExtraBold.38bc51bc.woff",
"contenttype": "application/octet-stream",
"offset": 19419594,
"len": 143256
},
"static/media/Inter-ExtraBold.92d16aee.woff2": {
"filename": "static/media/Inter-ExtraBold.92d16aee.woff2",
"contenttype": "application/octet-stream",
"offset": 19312290,
"len": 107304
},
"static/media/Inter-ExtraBoldItalic.0e4b21eb.woff": {
"filename": "static/media/Inter-ExtraBoldItalic.0e4b21eb.woff",
"contenttype": "application/octet-stream",
"offset": 16671312,
"len": 149116
},
"static/media/Inter-ExtraBoldItalic.57ea76d0.woff2": {
"filename": "static/media/Inter-ExtraBoldItalic.57ea76d0.woff2",
"contenttype": "application/octet-stream",
"offset": 18732262,
"len": 112656
},
"static/media/Inter-ExtraLight.4bd040df.woff": {
"filename": "static/media/Inter-ExtraLight.4bd040df.woff",
"contenttype": "application/octet-stream",
"offset": 14746958,
"len": 141344
},
"static/media/Inter-ExtraLight.4d9f96f8.woff2": {
"filename": "static/media/Inter-ExtraLight.4d9f96f8.woff2",
"contenttype": "application/octet-stream",
"offset": 18945450,
"len": 105444
},
"static/media/Inter-ExtraLightItalic.54d3d9a5.woff2": {
"filename": "static/media/Inter-ExtraLightItalic.54d3d9a5.woff2",
"contenttype": "application/octet-stream",
"offset": 14888302,
"len": 111804
},
"static/media/Inter-ExtraLightItalic.84c26656.woff": {
"filename": "static/media/Inter-ExtraLightItalic.84c26656.woff",
"contenttype": "application/octet-stream",
"offset": 18569981,
"len": 148416
},
"static/media/Inter-Italic.9528384c.woff2": {
"filename": "static/media/Inter-Italic.9528384c.woff2",
"contenttype": "application/octet-stream",
"offset": 14383371,
"len": 108172
},
"static/media/Inter-Italic.e4ad3666.woff": {
"filename": "static/media/Inter-Italic.e4ad3666.woff",
"contenttype": "application/octet-stream",
"offset": 20037756,
"len": 143476
},
"static/media/Inter-Light.5baca21a.woff2": {
"filename": "static/media/Inter-Light.5baca21a.woff2",
"contenttype": "application/octet-stream",
"offset": 18016306,
"len": 105556
},
"static/media/Inter-Light.b9920de0.woff": {
"filename": "static/media/Inter-Light.b9920de0.woff",
"contenttype": "application/octet-stream",
"offset": 18428717,
"len": 141264
},
"static/media/Inter-LightItalic.0555a46c.woff": {
"filename": "static/media/Inter-LightItalic.0555a46c.woff",
"contenttype": "application/octet-stream",
"offset": 15633341,
"len": 148408
},
"static/media/Inter-LightItalic.adc70179.woff2": {
"filename": "static/media/Inter-LightItalic.adc70179.woff2",
"contenttype": "application/octet-stream",
"offset": 19200250,
"len": 112040
},
"static/media/Inter-Medium.7a8cc724.woff": {
"filename": "static/media/Inter-Medium.7a8cc724.woff",
"contenttype": "application/octet-stream",
"offset": 17763598,
"len": 142780
},
"static/media/Inter-Medium.f6cf0a0b.woff2": {
"filename": "static/media/Inter-Medium.f6cf0a0b.woff2",
"contenttype": "application/octet-stream",
"offset": 17431014,
"len": 106484
},
"static/media/Inter-MediumItalic.417907d2.woff": {
"filename": "static/media/Inter-MediumItalic.417907d2.woff",
"contenttype": "application/octet-stream",
"offset": 16955424,
"len": 149344
},
"static/media/Inter-MediumItalic.565a7104.woff2": {
"filename": "static/media/Inter-MediumItalic.565a7104.woff2",
"contenttype": "application/octet-stream",
"offset": 17318374,
"len": 112640
},
"static/media/Inter-Regular.4dd66a11.woff2": {
"filename": "static/media/Inter-Regular.4dd66a11.woff2",
"contenttype": "application/octet-stream",
"offset": 18844918,
"len": 100368
},
"static/media/Inter-Regular.7c539936.woff": {
"filename": "static/media/Inter-Regular.7c539936.woff",
"contenttype": "application/octet-stream",
"offset": 16820428,
"len": 134996
},
"static/media/Inter-SemiBold.1db6c55c.woff": {
"filename": "static/media/Inter-SemiBold.1db6c55c.woff",
"contenttype": "application/octet-stream",
"offset": 19785708,
"len": 143148
},
"static/media/Inter-SemiBold.dd8a55ef.woff2": {
"filename": "static/media/Inter-SemiBold.dd8a55ef.woff2",
"contenttype": "application/octet-stream",
"offset": 14491543,
"len": 106916
},
"static/media/Inter-SemiBoldItalic.81678d1a.woff": {
"filename": "static/media/Inter-SemiBoldItalic.81678d1a.woff",
"contenttype": "application/octet-stream",
"offset": 19050894,
"len": 149356
},
"static/media/Inter-SemiBoldItalic.ac201e30.woff2": {
"filename": "static/media/Inter-SemiBoldItalic.ac201e30.woff2",
"contenttype": "application/octet-stream",
"offset": 19562850,
"len": 112768
},
"static/media/Inter-Thin.850febbe.woff2": {
"filename": "static/media/Inter-Thin.850febbe.woff2",
"contenttype": "application/octet-stream",
"offset": 17217044,
"len": 101004
},
"static/media/Inter-Thin.ead42837.woff": {
"filename": "static/media/Inter-Thin.ead42837.woff",
"contenttype": "application/octet-stream",
"offset": 18230961,
"len": 137068
},
"static/media/Inter-ThinItalic.a76db065.woff": {
"filename": "static/media/Inter-ThinItalic.a76db065.woff",
"contenttype": "application/octet-stream",
"offset": 16166265,
"len": 144528
},
"static/media/Inter-ThinItalic.e08d9b2a.woff2": {
"filename": "static/media/Inter-ThinItalic.e08d9b2a.woff2",
"contenttype": "application/octet-stream",
"offset": 19930172,
"len": 107584
},
"static/media/Inter-italic.var.2690e3c2.woff2": {
"filename": "static/media/Inter-italic.var.2690e3c2.woff2",
"contenttype": "application/octet-stream",
"offset": 15925213,
"len": 241052
},
"static/media/Inter-roman.var.90e8f61d.woff2": {
"filename": "static/media/Inter-roman.var.90e8f61d.woff2",
"contenttype": "application/octet-stream",
"offset": 17537498,
"len": 226100
},
"static/media/Inter.var.4b976905.woff2": {
"filename": "static/media/Inter.var.4b976905.woff2",
"contenttype": "application/octet-stream",
"offset": 15166461,
"len": 317520
},
"static/media/arrow-down-blue.cd061363.svg": {
"filename": "static/media/arrow-down-blue.cd061363.svg",
"contenttype": "image/svg+xml",
"offset": 17318048,
"len": 326
},
"static/media/arrow-down-grey.c0dedd2f.svg": {
"filename": "static/media/arrow-down-grey.c0dedd2f.svg",
"contenttype": "image/svg+xml",
"offset": 15000106,
"len": 326
},
"static/media/arrow-right-white.337ad716.png": {
"filename": "static/media/arrow-right-white.337ad716.png",
"contenttype": "image/png",
"offset": 15000432,
"len": 12999
},
"static/media/arrow-right.d285b6cf.svg": {
"filename": "static/media/arrow-right.d285b6cf.svg",
"contenttype": "image/svg+xml",
"offset": 19929626,
"len": 263
},
"static/media/blue-loader.904b44c2.svg": {
"filename": "static/media/blue-loader.904b44c2.svg",
"contenttype": "image/svg+xml",
"offset": 19929889,
"len": 283
},
"static/media/circle-grey.ed2a1dad.svg": {
"filename": "static/media/circle-grey.ed2a1dad.svg",
"contenttype": "image/svg+xml",
"offset": 16310793,
"len": 321
},
"static/media/circle.2d975615.svg": {
"filename": "static/media/circle.2d975615.svg",
"contenttype": "image/svg+xml",
"offset": 16555418,
"len": 321
},
"static/media/coinbaseWalletIcon.62578f59.svg": {
"filename": "static/media/coinbaseWalletIcon.62578f59.svg",
"contenttype": "image/svg+xml",
"offset": 18375091,
"len": 53626
},
"static/media/dropdown-blue.b20914ec.svg": {
"filename": "static/media/dropdown-blue.b20914ec.svg",
"contenttype": "image/svg+xml",
"offset": 14382513,
"len": 164
},
"static/media/dropdown.7d32d2fa.svg": {
"filename": "static/media/dropdown.7d32d2fa.svg",
"contenttype": "image/svg+xml",
"offset": 18945286,
"len": 164
},
"static/media/dropup-blue.b96d70e1.svg": {
"filename": "static/media/dropup-blue.b96d70e1.svg",
"contenttype": "image/svg+xml",
"offset": 15166299,
"len": 162
},
"static/media/link.50c67f3c.svg": {
"filename": "static/media/link.50c67f3c.svg",
"contenttype": "image/svg+xml",
"offset": 14382196,
"len": 317
},
"static/media/logo.5827780d.svg": {
"filename": "static/media/logo.5827780d.svg",
"contenttype": "image/svg+xml",
"offset": 15013431,
"len": 7052
},
"static/media/logo_white.edb44e56.svg": {
"filename": "static/media/logo_white.edb44e56.svg",
"contenttype": "image/svg+xml",
"offset": 18368029,
"len": 7062
},
"static/media/magnifying-glass.67440097.svg": {
"filename": "static/media/magnifying-glass.67440097.svg",
"contenttype": "image/svg+xml",
"offset": 16662883,
"len": 8429
},
"static/media/menu.4f2c4440.svg": {
"filename": "static/media/menu.4f2c4440.svg",
"contenttype": "image/svg+xml",
"offset": 18015579,
"len": 727
},
"static/media/metamask.023762b6.png": {
"filename": "static/media/metamask.023762b6.png",
"contenttype": "image/png",
"offset": 14611832,
"len": 114217
},
"static/media/plus-blue.e8021e51.svg": {
"filename": "static/media/plus-blue.e8021e51.svg",
"contenttype": "image/svg+xml",
"offset": 14746469,
"len": 190
},
"static/media/plus-grey.d8e0be7d.svg": {
"filename": "static/media/plus-grey.d8e0be7d.svg",
"contenttype": "image/svg+xml",
"offset": 19675618,
"len": 190
},
"static/media/portisIcon.b234b2bf.png": {
"filename": "static/media/portisIcon.b234b2bf.png",
"contenttype": "image/png",
"offset": 18718397,
"len": 13865
},
"static/media/question-mark.1ae4d9f4.svg": {
"filename": "static/media/question-mark.1ae4d9f4.svg",
"contenttype": "image/svg+xml",
"offset": 14726049,
"len": 818
},
"static/media/question.a46e8bc1.svg": {
"filename": "static/media/question.a46e8bc1.svg",
"contenttype": "image/svg+xml",
"offset": 19928856,
"len": 770
},
"static/media/spinner.be00fc4a.svg": {
"filename": "static/media/spinner.be00fc4a.svg",
"contenttype": "image/svg+xml",
"offset": 14382677,
"len": 694
},
"static/media/trustWallet.edcc1ab5.png": {
"filename": "static/media/trustWallet.edcc1ab5.png",
"contenttype": "image/png",
"offset": 14726867,
"len": 19602
},
"static/media/walletConnectIcon.8215855c.svg": {
"filename": "static/media/walletConnectIcon.8215855c.svg",
"contenttype": "image/svg+xml",
"offset": 14598459,
"len": 13373
},
"static/media/wordmark.b75565ae.svg": {
"filename": "static/media/wordmark.b75565ae.svg",
"contenttype": "image/svg+xml",
"offset": 18121862,
"len": 109099
},
"static/media/wordmark_white.9914390f.svg": {
"filename": "static/media/wordmark_white.9914390f.svg",
"contenttype": "image/svg+xml",
"offset": 17906378,
"len": 109201
},
"static/media/x.5b8e2186.svg": {
"filename": "static/media/x.5b8e2186.svg",
"contenttype": "image/svg+xml",
"offset": 14746659,
"len": 299
}
},
"defaultpath": "/index.html"
}

View File

@ -1,48 +0,0 @@
process.env.NODE_ENV = process.env.NODE_ENV || "development";
if (!process.env.PORTAL_DOMAIN) {
throw new Error("You need to provide PORTAL_DOMAIN environment variable");
}
if (process.env.ACCOUNTS_ENABLED === "true") {
if (["authenticated", "subscription"].includes(process.env.ACCOUNTS_LIMIT_ACCESS)) {
if (!process.env.ACCOUNTS_TEST_USER_EMAIL) {
throw new Error("ACCOUNTS_TEST_USER_EMAIL cannot be empty");
}
if (!process.env.ACCOUNTS_TEST_USER_PASSWORD) {
throw new Error("ACCOUNTS_TEST_USER_PASSWORD cannot be empty");
}
}
}
const express = require("express");
const db = require("./db");
const host = process.env.HOSTNAME || "0.0.0.0";
const port = Number(process.env.PORT) || 3100;
const server = express();
server.use(express.urlencoded({ extended: false }));
server.use(express.json());
server.use((req, res, next) => {
db.read();
next();
});
server.get("/health-check", require("./api/index"));
server.get("/health-check/critical", require("./api/critical"));
server.get("/health-check/extended", require("./api/extended"));
server.get("/health-check/disabled", require("./api/disabled"));
server.listen(port, host, (error) => {
if (error) throw error;
console.info(`Server listening at http://${host}:${port} (NODE_ENV: ${process.env.NODE_ENV})`);
const { ipRegex } = require("./utils");
if (ipRegex.test(process.env.serverip)) {
console.info(`Server public ip: ${process.env.serverip}`);
}
});

View File

@ -1,144 +0,0 @@
const ipCheckService = "whatismyip.akamai.com";
const ipRegex = new RegExp(
`^(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}$`
);
/**
* Get the time between start and now in milliseconds
*/
function calculateElapsedTime(start) {
const diff = process.hrtime(start);
return Math.round((diff[0] * 1e9 + diff[1]) / 1e6); // msec
}
/**
* Get the ISO string with yesterday's date set (- 24 hours)
*/
function getYesterdayISOString() {
const date = new Date();
date.setDate(date.getDate() - 1);
return date.toISOString();
}
/**
* Get response from response object if available
*/
function getResponseContent(response) {
try {
return JSON.parse(response?.body || response?.text);
} catch {
return response?.body || response?.text;
}
}
/**
* Ensures that the object serializes to JSON properly
*/
function ensureValidJSON(object) {
const replacer = (key, value) => (value === undefined ? "--undefined--" : value);
const stringified = JSON.stringify(object, replacer);
return JSON.parse(stringified);
}
/**
* Get variable value from environment (process.env)
* Exit with code 1 if variable is not set or empty
* @param {string} name variable name
* @returns {string}
*/
function getRequiredEnvironmentVariable(name) {
const value = process.env[name];
if (!value) {
console.log(`${name} cannot be empty`);
process.exit(1);
}
return value;
}
/**
* Authenticate with given credentials and return auth cookie
* Creates new account if username does not exist
* Only authenticates when portal is set to authenticated users only mode
* @param {boolean} forceAuth forcibly ensure authentication with test credentials
*/
function getAuthCookie(forceAuth = false) {
// cache auth promise so only one actual request will be made
if (getAuthCookie.cache) return getAuthCookie.cache;
// accounts disabled, do not try to authenticate
if (!isPortalModuleEnabled("a")) return "";
// do not authenticate if it is not required by portal limit access rule
if (!forceAuth && !["authenticated", "subscription"].includes(process.env.ACCOUNTS_LIMIT_ACCESS)) return "";
// assign all required environment variables
const portalDomain = getRequiredEnvironmentVariable("PORTAL_DOMAIN");
const email = getRequiredEnvironmentVariable("ACCOUNTS_TEST_USER_EMAIL");
const password = getRequiredEnvironmentVariable("ACCOUNTS_TEST_USER_PASSWORD");
async function authenticate() {
const got = require("got");
try {
// authenticate with given test user credentials
const response = await got.post(`https://account.${portalDomain}/api/login`, {
json: { email, password },
});
// extract set-cookie from successful authentication request
const cookies = response.headers["set-cookie"];
// throw meaningful error when set-cookie header is missing
if (!cookies) throw new Error(`Auth successful (code ${response.statusCode}) but 'set-cookie' header is missing`);
// find the skynet-jwt cookie
const jwtcookie = cookies.find((cookie) => cookie.startsWith("skynet-jwt"));
// throw meaningful error when skynet-jwt cookie is missing
if (!jwtcookie) throw new Error(`Header 'set-cookie' found but 'skynet-jwt' cookie is missing`);
// extract just the cookie value (no set-cookie props) from set-cookie
return jwtcookie.match(/skynet-jwt=[^;]+;/)[0];
} catch (error) {
// 401 means that service worked but user could not have been authenticated
if (error.response && error.response.statusCode === 401) {
// sign up with the given credentials
await got.post(`https://account.${portalDomain}/api/user`, {
json: { email, password },
});
// retry authentication
return authenticate();
}
// rethrow unhandled exception
throw error;
}
}
return (getAuthCookie.cache = authenticate());
}
/**
* isPortalModuleEnabled returns true if the given module is enabled
*/
function isPortalModuleEnabled(module) {
return process.env.PORTAL_MODULES && process.env.PORTAL_MODULES.indexOf(module) !== -1;
}
module.exports = {
calculateElapsedTime,
getYesterdayISOString,
getResponseContent,
ensureValidJSON,
getAuthCookie,
isPortalModuleEnabled,
ipCheckService,
ipRegex,
};

View File

@ -1,19 +0,0 @@
describe("ipRegex", () => {
const { ipRegex } = require("./utils");
test("should test true for valid ip", () => {
expect(ipRegex.test("8.8.8.8")).toEqual(true);
expect(ipRegex.test("127.0.0.1")).toEqual(true);
expect(ipRegex.test("192.168.0.1")).toEqual(true);
expect(ipRegex.test("10.10.10.10")).toEqual(true);
expect(ipRegex.test("135.124.12.47")).toEqual(true);
});
test("should test false for invalid ip", () => {
expect(ipRegex.test("888.8.8.8")).toEqual(false);
expect(ipRegex.test("....")).toEqual(false);
expect(ipRegex.test(null)).toEqual(false);
expect(ipRegex.test("foo")).toEqual(false);
expect(ipRegex.test("")).toEqual(false);
});
});

View File

@ -1,18 +0,0 @@
const http = require("http");
const { ipCheckService, ipRegex } = require("./utils");
const request = http.request({ host: ipCheckService }, (response) => {
response.on("data", (data) => {
if (ipRegex.test(data)) {
process.stdout.write(data);
} else {
throw new Error(`${ipCheckService} responded with invalid ip: "${data}"`);
}
});
});
request.on("error", (error) => {
throw error; // throw error to exit with code 1
});
request.end();

File diff suppressed because it is too large Load Diff