Merge branch 'master' into deploy-machine-ssh-key

This commit is contained in:
Karol Wypchło 2021-05-12 11:02:38 +02:00 committed by GitHub
commit 670cecbebf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2070 additions and 1244 deletions

View File

@ -8,10 +8,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js - uses: actions/setup-node@v2
uses: actions/setup-node@v1
with: with:
node-version: 14.x node-version: 15.x
- name: Install dependencies - name: Install dependencies
run: yarn run: yarn
@ -22,24 +21,27 @@ jobs:
- name: "Static code analysis: health-check" - name: "Static code analysis: health-check"
run: yarn workspace health-check prettier --check . run: yarn workspace health-check prettier --check .
- name: "Static code analysis: webapp" - name: "Static code analysis: website"
run: yarn workspace webapp prettier --check . run: yarn workspace website prettier --check .
- name: "Build webapp" - name: "Static code analysis: dashboard"
run: yarn workspace webapp build run: yarn workspace dashboard prettier --check .
env:
GATSBY_API_URL: "https://siasky.net"
- name: Cypress run # - name: "Build webapp"
uses: cypress-io/github-action@v2 # run: yarn workspace webapp build
env: # env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} # GATSBY_API_URL: "https://siasky.net"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
working-directory: packages/webapp
record: true
start: npx http-server public --port 8000
wait-on: "http://localhost:8000"
- name: Cypress cache prune # - name: Cypress run
run: yarn cypress cache prune # uses: cypress-io/github-action@v2
# env:
# CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# with:
# working-directory: packages/webapp
# record: true
# start: npx http-server public --port 8000
# wait-on: "http://localhost:8000"
# - name: Cypress cache prune
# run: yarn cypress cache prune

View File

@ -15,6 +15,10 @@ services:
depends_on: depends_on:
- accounts - accounts
health-check:
environment:
- ACCOUNTS_ENABLED=1
accounts: accounts:
build: build:
context: ./docker/accounts context: ./docker/accounts

96
docker-compose.jaeger.yml Normal file
View File

@ -0,0 +1,96 @@
version: '3.7'
services:
sia:
environment:
- JAEGER_DISABLED=${JAEGER_DISABLED:-true} # Enable/Disable tracing
- JAEGER_SERVICE_NAME=${PORTAL_NAME:-Skyd} # change to e.g. eu-ger-1
# Configuration
# See https://github.com/jaegertracing/jaeger-client-go#environment-variables
# for all options.
- JAEGER_SAMPLER_TYPE=probabilistic
- JAEGER_SAMPLER_PARAM=0.1
- JAEGER_AGENT_HOST=jaeger-agent
- JAEGER_AGENT_PORT=6831
- JAEGER_REPORTER_LOG_SPANS=false
jaeger-agent:
image: jaegertracing/jaeger-agent
command: [ "--reporter.grpc.host-port=jaeger-collector:14250", "--reporter.grpc.retry.max=1000" ]
container_name: jaeger-agent
restart: on-failure
expose:
- 6831
- 6832
- 5778
environment:
- LOG_LEVEL=debug
networks:
shared:
ipv4_address: 10.10.10.90
depends_on:
- jaeger-collector
jaeger-collector:
image: jaegertracing/jaeger-collector
entrypoint: /wait_to_start.sh
container_name: jaeger-collector
restart: on-failure
expose:
- 14269
- 14268
- 14250
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- LOG_LEVEL=debug
- WAIT_START_CMD=/go/bin/collector-linux --es.num-shards=1 --es.num-replicas=0 --es.server-urls=http://elasticsearch:9200
- WAIT_COMMAND=wget -qO index.html http://elasticsearch:9200
- WAIT_SLEEP=1
- WAIT_LOOPS=600
volumes:
- ./scripts/wait_to_start.sh:/wait_to_start.sh:ro
networks:
shared:
ipv4_address: 10.10.10.91
depends_on:
- elasticsearch
jaeger-query:
image: jaegertracing/jaeger-query
entrypoint: /wait_to_start.sh
container_name: jaeger-query
restart: on-failure
ports:
- "127.0.0.1:16686:16686"
expose:
- 16687
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- LOG_LEVEL=debug
- WAIT_START_CMD=/go/bin/query-linux --es.num-shards=1 --es.num-replicas=0 --es.server-urls=http://elasticsearch:9200
- WAIT_COMMAND=wget -qO index.html http://elasticsearch:9200
- WAIT_SLEEP=1
- WAIT_LOOPS=600
volumes:
- ./scripts/wait_to_start.sh:/wait_to_start.sh:ro
networks:
shared:
ipv4_address: 10.10.10.92
depends_on:
- elasticsearch
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.15
container_name: elasticsearch
restart: on-failure
environment:
- discovery.type=single-node
volumes:
# This dir needs to be chowned to 1000:1000
- ./docker/data/elasticsearch/data:/usr/share/elasticsearch/data
ports:
# We need to expose this port, so we can prune the indexes.
- "127.0.0.1:9200:9200"
networks:
shared:
ipv4_address: 10.10.10.93

View File

@ -80,7 +80,6 @@ services:
- 80 - 80
depends_on: depends_on:
- sia - sia
- health-check
- handshake-api - handshake-api
- website - website
@ -155,12 +154,12 @@ services:
networks: networks:
shared: shared:
ipv4_address: 10.10.10.60 ipv4_address: 10.10.10.60
env_file:
- .env
environment: environment:
- HOSTNAME=0.0.0.0 - HOSTNAME=0.0.0.0
- PORTAL_URL=http://nginx
- STATE_DIR=/usr/app/state - STATE_DIR=/usr/app/state
expose: expose:
- 3100 - 3100
depends_on: depends_on:
- handshake - caddy
- handshake-api

View File

@ -65,7 +65,7 @@
preserve_host: true preserve_host: true
url: "http://accounts:3000" url: "http://accounts:3000"
match: match:
url: "http://oathkeeper<{,:4455}>/<{stripe/prices,stripe/webhook}>" url: "http://oathkeeper<{,:4455}>/<{health,stripe/prices,stripe/webhook}>"
methods: methods:
- GET - GET
- POST - POST

View File

@ -72,7 +72,6 @@ server {
rewrite ^/skynet/blacklist /skynet/blocklist permanent; rewrite ^/skynet/blacklist /skynet/blocklist permanent;
rewrite ^/account/(.*) https://account.$domain.$tld/$1 permanent; rewrite ^/account/(.*) https://account.$domain.$tld/$1 permanent;
location / {
# This is only safe workaround to reroute based on some conditions # This is only safe workaround to reroute based on some conditions
# See https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ # See https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
recursive_error_pages on; recursive_error_pages on;
@ -89,6 +88,7 @@ server {
return 461; return 461;
} }
location / {
include /etc/nginx/conf.d/include/cors; include /etc/nginx/conf.d/include/cors;
proxy_pass http://website:9000; proxy_pass http://website:9000;
@ -147,7 +147,7 @@ server {
} }
proxy_cache skynet; proxy_cache skynet;
proxy_cache_valid any 10m; # cache stats for 10 minutes proxy_cache_valid any 1m; # cache stats for 1 minute
proxy_set_header User-Agent: Sia-Agent; proxy_set_header User-Agent: Sia-Agent;
proxy_read_timeout 5m; # extend the read timeout proxy_read_timeout 5m; # extend the read timeout
proxy_pass http://siad/skynet/stats; proxy_pass http://siad/skynet/stats;
@ -399,6 +399,13 @@ server {
proxy_pass http://siad; proxy_pass http://siad;
} }
location /skynet/metadata {
include /etc/nginx/conf.d/include/cors;
proxy_set_header User-Agent: Sia-Agent;
proxy_pass http://siad;
}
location ~ "^/(([a-zA-Z0-9-_]{46}|[a-z0-9]{55})(/.*)?)$" { location ~ "^/(([a-zA-Z0-9-_]{46}|[a-z0-9]{55})(/.*)?)$" {
include /etc/nginx/conf.d/include/cors; include /etc/nginx/conf.d/include/cors;
include /etc/nginx/conf.d/include/proxy-buffer; include /etc/nginx/conf.d/include/proxy-buffer;

View File

@ -8,36 +8,32 @@
"start": "next start" "start": "next start"
}, },
"dependencies": { "dependencies": {
"@fontsource/metropolis": "^4.1.0", "@fontsource/metropolis": "4.3.0",
"@ory/kratos-client": "^0.5.4-alpha.1", "@ory/kratos-client": "0.5.4-alpha.1",
"@stripe/react-stripe-js": "^1.4.0", "@stripe/react-stripe-js": "1.4.0",
"@stripe/stripe-js": "^1.13.0", "@stripe/stripe-js": "1.14.0",
"@tailwindcss/forms": "^0.2.1", "@tailwindcss/forms": "0.3.2",
"autoprefixer": "^10.2.5", "autoprefixer": "10.2.5",
"classnames": "^2.2.6", "classnames": "2.3.1",
"clipboardy": "^2.3.0", "clipboardy": "2.3.0",
"dayjs": "^1.10.4", "dayjs": "1.10.4",
"express-jwt": "^6.0.0", "express-jwt": "6.0.0",
"fast-levenshtein": "^3.0.0", "fast-levenshtein": "3.0.0",
"formik": "^2.2.6", "formik": "2.2.6",
"http-status-codes": "^2.1.4", "http-status-codes": "2.1.4",
"jwks-rsa": "^1.12.2",
"ky": "0.25.1", "ky": "0.25.1",
"next": "^10.0.8", "next": "10.2.0",
"postcss": "^8.2.8", "postcss": "8.2.14",
"prettier": "^2.2.1", "prettier": "2.3.0",
"pretty-bytes": "^5.5.0", "pretty-bytes": "5.6.0",
"react": "17.0.1", "react": "17.0.2",
"react-dom": "17.0.1", "react-dom": "17.0.2",
"skynet-js": "^3.0.0", "skynet-js": "3.0.2",
"square": "^9.0.0", "square": "10.0.0",
"stripe": "^8.137.0", "stripe": "8.148.0",
"superagent": "^6.1.0", "superagent": "6.1.0",
"swr": "^0.5.0", "swr": "0.5.6",
"tailwindcss": "^2.0.3", "tailwindcss": "2.1.2",
"yup": "^0.32.9" "yup": "0.32.9"
},
"devDependencies": {
"@tailwindcss/forms": "^0.2.1"
} }
} }

View File

@ -200,7 +200,9 @@ export default function Home({ plans }) {
<div className="ml-5 w-0 flex-1"> <div className="ml-5 w-0 flex-1">
<dt className="text-sm font-medium text-gray-500 truncate">Storage used</dt> <dt className="text-sm font-medium text-gray-500 truncate">Storage used</dt>
<dd className="flex items-baseline"> <dd className="flex items-baseline">
<div className="text-2xl font-semibold text-grey-900">{prettyBytes(stats?.totalUploadsSize ?? 0)}</div> <div className="text-2xl font-semibold text-grey-900">
{prettyBytes(stats?.totalUploadsSize ?? 0)}
</div>
</dd> </dd>
</div> </div>
</div> </div>

View File

@ -12,4 +12,4 @@ RUN echo '0 * * * * /usr/app/cli/run extended > /dev/stdout' >> /etc/crontabs/ro
EXPOSE 3100 EXPOSE 3100
ENV NODE_ENV production ENV NODE_ENV production
CMD [ "sh", "-c", "crond ; node --max-http-header-size=64000 src/index.js" ] CMD [ "sh", "-c", "crond ; echo $(node src/whatismyip.js) siasky.net account.siasky.net >> /etc/hosts ; node --max-http-header-size=64000 src/index.js" ]

View File

@ -6,7 +6,7 @@
"dependencies": { "dependencies": {
"deep-object-diff": "^1.1.0", "deep-object-diff": "^1.1.0",
"express": "^4.17.1", "express": "^4.17.1",
"form-data": "^3.0.1", "form-data": "^4.0.0",
"got": "^11.8.2", "got": "^11.8.2",
"graceful-fs": "^4.2.6", "graceful-fs": "^4.2.6",
"hasha": "^5.2.2", "hasha": "^5.2.2",
@ -14,7 +14,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lowdb": "^1.0.0", "lowdb": "^1.0.0",
"write-file-atomic": "^3.0.3", "write-file-atomic": "^3.0.3",
"yargs": "^16.2.0" "yargs": "^17.0.1"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^2.2.1" "prettier": "^2.2.1"

View File

@ -7,26 +7,28 @@ const { calculateElapsedTime, getResponseContent } = require("../utils");
async function uploadCheck(done) { async function uploadCheck(done) {
const time = process.hrtime(); const time = process.hrtime();
const form = new FormData(); const form = new FormData();
const data = Buffer.from(new Date()); // current date to ensure data uniqueness const payload = Buffer.from(new Date()); // current date to ensure data uniqueness
const data = { up: false };
form.append("file", data, { filename: "time.txt", contentType: "text/plain" }); form.append("file", payload, { filename: "time.txt", contentType: "text/plain" });
let statusCode, errorResponseContent;
try { try {
const response = await got.post(`${process.env.PORTAL_URL}/skynet/skyfile`, { body: form }); const response = await got.post(`${process.env.SKYNET_PORTAL_API}/skynet/skyfile`, { body: form });
statusCode = response.statusCode; data.statusCode = response.statusCode;
data.up = true;
data.ip = response.ip;
} catch (error) { } catch (error) {
statusCode = error?.response?.statusCode || error.statusCode || error.status; data.statusCode = error.response?.statusCode || error.statusCode || error.status;
errorResponseContent = getResponseContent(error?.response); data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
} }
done({ done({
name: "upload_file", name: "upload_file",
up: statusCode === StatusCodes.OK,
statusCode,
errorResponseContent,
time: calculateElapsedTime(time), time: calculateElapsedTime(time),
...data,
}); });
} }
@ -34,26 +36,57 @@ async function uploadCheck(done) {
async function downloadCheck(done) { async function downloadCheck(done) {
const time = process.hrtime(); const time = process.hrtime();
const skylink = "AACogzrAimYPG42tDOKhS3lXZD8YvlF8Q8R17afe95iV2Q"; const skylink = "AACogzrAimYPG42tDOKhS3lXZD8YvlF8Q8R17afe95iV2Q";
let statusCode, errorMessage, errorResponseContent; const data = { up: false };
try { try {
const response = await got(`${process.env.PORTAL_URL}/${skylink}?nocache=true`); const response = await got(`${process.env.SKYNET_PORTAL_API}/${skylink}?nocache=true`);
statusCode = response.statusCode; data.statusCode = response.statusCode;
data.up = true;
data.ip = response.ip;
} catch (error) { } catch (error) {
statusCode = error?.response?.statusCode || error.statusCode || error.status; data.statusCode = error?.response?.statusCode || error.statusCode || error.status;
errorMessage = error.message; data.errorMessage = error.message;
errorResponseContent = getResponseContent(error.response); data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
} }
done({ done({
name: "download_file", name: "download_file",
up: statusCode === StatusCodes.OK,
statusCode,
errorMessage,
errorResponseContent,
time: calculateElapsedTime(time), time: calculateElapsedTime(time),
...data,
}); });
} }
module.exports = [uploadCheck, downloadCheck]; async function accountHealthCheck(done) {
const time = process.hrtime();
const data = { up: false };
try {
const response = await got(`${process.env.SKYNET_DASHBOARD_URL}/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: "account_health",
time: calculateElapsedTime(time),
...data,
});
}
const checks = [uploadCheck, downloadCheck];
if (process.env.ACCOUNTS_ENABLED) {
checks.push(accountHealthCheck);
}
module.exports = checks;

View File

@ -1059,7 +1059,7 @@ async function skylinkVerification(done, expected, { followRedirect = true, meth
const details = { name: expected.name, skylink: expected.skylink }; const details = { name: expected.name, skylink: expected.skylink };
try { try {
const query = `${process.env.PORTAL_URL}/${expected.skylink}`; const query = `${process.env.SKYNET_PORTAL_API}/${expected.skylink}`;
const response = await got[method](query, { followRedirect, headers: { cookie: "nocache=true" } }); const response = await got[method](query, { followRedirect, headers: { cookie: "nocache=true" } });
const entry = { ...details, up: true, statusCode: response.statusCode, time: calculateElapsedTime(time) }; const entry = { ...details, up: true, statusCode: response.statusCode, time: calculateElapsedTime(time) };
const info = {}; const info = {};

View File

@ -1,7 +1,11 @@
process.env.NODE_ENV = process.env.NODE_ENV || "development"; process.env.NODE_ENV = process.env.NODE_ENV || "development";
if (!process.env.PORTAL_URL) { if (!process.env.SKYNET_PORTAL_API) {
throw new Error("You need to provide PORTAL_URL environment variable"); throw new Error("You need to provide SKYNET_PORTAL_API environment variable");
}
if (process.env.ACCOUNTS_ENABLED && !process.env.SKYNET_DASHBOARD_URL) {
throw new Error("You need to provide SKYNET_DASHBOARD_URL environment variable when accounts are enabled");
} }
const express = require("express"); const express = require("express");

View File

@ -12,7 +12,7 @@ require("yargs/yargs")(process.argv.slice(2)).command(
}) })
.option("portal-url", { .option("portal-url", {
describe: "Skynet portal url", describe: "Skynet portal url",
default: process.env.PORTAL_URL || "https://siasky.net", default: process.env.SKYNET_PORTAL_API || "https://siasky.net",
type: "string", type: "string",
}) })
.option("state-dir", { .option("state-dir", {
@ -22,7 +22,7 @@ require("yargs/yargs")(process.argv.slice(2)).command(
}); });
}, },
async ({ type, portalUrl, stateDir }) => { async ({ type, portalUrl, stateDir }) => {
process.env.PORTAL_URL = portalUrl; process.env.SKYNET_PORTAL_API = portalUrl;
process.env.STATE_DIR = stateDir; process.env.STATE_DIR = stateDir;
const db = require("../src/db"); const db = require("../src/db");

View File

@ -0,0 +1,13 @@
const http = require("http");
const request = http.request({ host: "whatismyip.akamai.com" }, (response) => {
response.on("data", (data) => {
process.stdout.write(data);
});
});
request.on("error", (error) => {
console.error(error);
});
request.end();

View File

@ -8,13 +8,13 @@
"axios": "0.21.1", "axios": "0.21.1",
"boolean": "^3.0.2", "boolean": "^3.0.2",
"bytes": "3.1.0", "bytes": "3.1.0",
"classnames": "2.2.6", "classnames": "2.3.1",
"gatsby": "^3.0.4", "gatsby": "^3.0.4",
"gatsby-plugin-manifest": "^3.0.0", "gatsby-plugin-manifest": "^3.0.0",
"gatsby-plugin-matomo": "0.9.0", "gatsby-plugin-matomo": "0.9.0",
"gatsby-plugin-react-helmet": "^4.0.0", "gatsby-plugin-react-helmet": "^4.0.0",
"gatsby-plugin-remove-serviceworker": "1.0.0", "gatsby-plugin-remove-serviceworker": "1.0.0",
"gatsby-plugin-robots-txt": "1.5.5", "gatsby-plugin-robots-txt": "1.5.6",
"gatsby-plugin-sass": "^4.0.2", "gatsby-plugin-sass": "^4.0.2",
"gatsby-source-filesystem": "^3.0.0", "gatsby-source-filesystem": "^3.0.0",
"http-status-codes": "2.1.4", "http-status-codes": "2.1.4",
@ -26,21 +26,21 @@
"react": "17.0.1", "react": "17.0.1",
"react-countup": "4.3.3", "react-countup": "4.3.3",
"react-dom": "17.0.1", "react-dom": "17.0.1",
"react-dropzone": "11.3.1", "react-dropzone": "11.3.2",
"react-helmet": "6.1.0", "react-helmet": "6.1.0",
"react-mailchimp-form": "1.0.2", "react-mailchimp-form": "1.0.2",
"react-mailchimp-subscribe": "^2.1.3", "react-mailchimp-subscribe": "^2.1.3",
"react-syntax-highlighter": "15.4.3", "react-syntax-highlighter": "15.4.3",
"react-visibility-sensor": "5.1.1", "react-visibility-sensor": "5.1.1",
"skynet-js": "3.0.0" "skynet-js": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
"cypress": "^6.6.0", "cypress": "^7.1.0",
"cypress-file-upload": "5.0.3", "cypress-file-upload": "5.0.7",
"eslint": "7.22.0", "eslint": "7.26.0",
"eslint-config-prettier": "8.1.0", "eslint-config-prettier": "8.2.0",
"eslint-plugin-cypress": "2.11.2", "eslint-plugin-cypress": "2.11.2",
"eslint-plugin-react": "7.23.0", "eslint-plugin-react": "7.23.2",
"husky": "4.3.8", "husky": "4.3.8",
"lint-staged": "10.5.4", "lint-staged": "10.5.4",
"prettier": "2.2.1" "prettier": "2.2.1"

View File

@ -19,4 +19,4 @@
module.exports = (on, config) => { module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits // `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config // `config` is the resolved Cypress config
} };

View File

@ -14,7 +14,7 @@
// *********************************************************** // ***********************************************************
// Import commands.js using ES2015 syntax: // Import commands.js using ES2015 syntax:
import './commands' import "./commands";
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')

View File

@ -1,6 +1,7 @@
const { defaultIcons } = require("gatsby-plugin-manifest/common"); const { defaultIcons } = require("gatsby-plugin-manifest/common");
module.exports = { module.exports = {
flags: { PRESERVE_WEBPACK_CACHE: true },
siteMetadata: { siteMetadata: {
title: `Skynet`, title: `Skynet`,
description: `Skynet is a decentralized file sharing and content distribution protocol`, description: `Skynet is a decentralized file sharing and content distribution protocol`,

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,8 @@
"version": "0.1.0", "version": "0.1.0",
"author": "Skynet Labs.", "author": "Skynet Labs.",
"dependencies": { "dependencies": {
"@fontsource/sora": "^4.2.2", "@fontsource/sora": "^4.3.0",
"@fontsource/source-sans-pro": "^4.2.2", "@fontsource/source-sans-pro": "^4.3.0",
"@svgr/webpack": "^5.5.0", "@svgr/webpack": "^5.5.0",
"@tailwindcss/typography": "^0.4.0", "@tailwindcss/typography": "^0.4.0",
"autoprefixer": "^10.2.5", "autoprefixer": "^10.2.5",
@ -14,40 +14,40 @@
"classnames": "^2.3.1", "classnames": "^2.3.1",
"copy-text-to-clipboard": "^3.0.1", "copy-text-to-clipboard": "^3.0.1",
"crypto-browserify": "^3.12.0", "crypto-browserify": "^3.12.0",
"framer-motion": "^4.0.3", "framer-motion": "^4.1.15",
"gatsby": "^3.2.1", "gatsby": "^3.4.2",
"gatsby-background-image": "^1.5.0", "gatsby-background-image": "^1.5.3",
"gatsby-image": "^3.2.0", "gatsby-image": "^3.4.0",
"gatsby-plugin-image": "^1.1.2", "gatsby-plugin-image": "^1.4.1",
"gatsby-plugin-manifest": "^3.0.0", "gatsby-plugin-manifest": "^3.4.0",
"gatsby-plugin-matomo": "^0.9.0", "gatsby-plugin-matomo": "^0.9.0",
"gatsby-plugin-offline": "^4.0.0", "gatsby-plugin-offline": "^4.4.0",
"gatsby-plugin-postcss": "^4.0.0", "gatsby-plugin-postcss": "^4.4.0",
"gatsby-plugin-purgecss": "^6.0.0", "gatsby-plugin-purgecss": "^6.0.0",
"gatsby-plugin-react-helmet": "^4.0.0", "gatsby-plugin-react-helmet": "^4.4.0",
"gatsby-plugin-react-svg": "^3.0.0", "gatsby-plugin-react-svg": "^3.0.0",
"gatsby-plugin-robots-txt": "^1.5.5", "gatsby-plugin-robots-txt": "^1.6.2",
"gatsby-plugin-sharp": "^3.1.2", "gatsby-plugin-sharp": "^3.4.2",
"gatsby-remark-classes": "^1.0.0", "gatsby-remark-classes": "^1.0.0",
"gatsby-remark-copy-linked-files": "^4.0.0", "gatsby-remark-copy-linked-files": "^4.1.0",
"gatsby-remark-images": "^5.0.0", "gatsby-remark-images": "^5.1.0",
"gatsby-remark-prismjs": "^5.0.0", "gatsby-remark-prismjs": "^5.1.0",
"gatsby-remark-responsive-iframe": "^4.0.0", "gatsby-remark-responsive-iframe": "^4.1.0",
"gatsby-remark-smartypants": "^4.0.0", "gatsby-remark-smartypants": "^4.1.0",
"gatsby-source-filesystem": "^3.0.0", "gatsby-source-filesystem": "^3.4.0",
"gatsby-transformer-json": "^3.1.0", "gatsby-transformer-json": "^3.4.0",
"gatsby-transformer-remark": "^4.0.0", "gatsby-transformer-remark": "^4.1.0",
"gatsby-transformer-sharp": "^3.0.0", "gatsby-transformer-sharp": "^3.4.0",
"gatsby-transformer-yaml": "^3.2.0", "gatsby-transformer-yaml": "^3.4.0",
"gbimage-bridge": "^0.1.1", "gbimage-bridge": "^0.1.4",
"http-status-codes": "^2.1.4", "http-status-codes": "^2.1.4",
"jsonp": "^0.2.1", "jsonp": "^0.2.1",
"ms": "^2.1.2", "ms": "^2.1.2",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"polished": "^4.1.1", "polished": "^4.1.2",
"popmotion": "^9.3.4", "popmotion": "^9.3.4",
"postcss": "^8.2.8", "postcss": "^8.2.15",
"preact-svg-loader": "^0.2.1", "preact-svg-loader": "^0.2.1",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^17.0.2", "react": "^17.0.2",
@ -57,17 +57,17 @@
"react-share": "^4.4.0", "react-share": "^4.4.0",
"react-svg-loader": "^3.0.3", "react-svg-loader": "^3.0.3",
"react-syntax-highlighter": "^15.4.3", "react-syntax-highlighter": "^15.4.3",
"react-use": "^17.2.3", "react-use": "^17.2.4",
"skynet-js": "^3.0.2", "skynet-js": "^3.0.2",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"swr": "^0.5.5", "swr": "^0.5.6",
"tailwindcss": "^2.1.1" "tailwindcss": "^2.1.2"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cypress": "^7.1.0", "cypress": "^7.3.0",
"cypress-file-upload": "^5.0.5", "cypress-file-upload": "^5.0.7",
"prettier": "^2.2.1" "prettier": "^2.3.0"
}, },
"keywords": [ "keywords": [
"gatsby" "gatsby"

View File

@ -24,40 +24,34 @@ const aboutCards = [
{ {
Icon: UserAtom, Icon: UserAtom,
title: "Own your data", title: "Own your data",
text: text: "No one owns or controls your account data except for you. Ownership extends to original blogs, music, and videos too. This is all possible through decentralized apps built on decentralized storage.",
"No one owns or controls your account data except for you. Ownership extends to original blogs, music, and videos too. This is all possible through decentralized apps built on decentralized storage.",
}, },
{ {
Icon: Shield, Icon: Shield,
title: "Censorship-resistant content", title: "Censorship-resistant content",
text: text: "Today, censorship can come arbitrarily, top-down, and as a tool to silence expression. Post and share content on Skynet, or use Skynet as a fail-over for your website if a service provider goes down.",
"Today, censorship can come arbitrarily, top-down, and as a tool to silence expression. Post and share content on Skynet, or use Skynet as a fail-over for your website if a service provider goes down.",
}, },
{ {
Icon: Fingerprint, Icon: Fingerprint,
title: "One universal digital identity", title: "One universal digital identity",
text: text: "Log into any Skynet app with just one ID. Once logged in, your storage and data can follow you across the ecosystem. Access your friend lists, followers, and content from any Skynet app.",
"Log into any Skynet app with just one ID. Once logged in, your storage and data can follow you across the ecosystem. Access your friend lists, followers, and content from any Skynet app.",
}, },
{ {
Icon: UserArrows, Icon: UserArrows,
title: "Innovation built for users", title: "Innovation built for users",
text: text: "All Skynet apps are open-source. If you dislike an apps feature or want to make your own improvements, youre welcome to do so. (We of course encourage collaboration and hope you chat with the developer first!) Existing users can then consent to the migration of all their account data to the latest version. ",
"All Skynet apps are open-source. If you dislike an apps feature or want to make your own improvements, youre welcome to do so. (We of course encourage collaboration and hope you chat with the developer first!) Existing users can then consent to the migration of all their account data to the latest version. ",
}, },
{ {
Icon: ComputerScreen, Icon: ComputerScreen,
label: "Coming soon", label: "Coming soon",
title: "Control your content feed", title: "Control your content feed",
text: text: "We believe that users, not tech platforms should fully control how content is moderated. A decentralized internet is not an information free-for-all. It means that the individual holds the power to personalize their online experiences. For example, users will decide what content appears in their social media feeds, not a corporate algorithm.",
"We believe that users, not tech platforms should fully control how content is moderated. A decentralized internet is not an information free-for-all. It means that the individual holds the power to personalize their online experiences. For example, users will decide what content appears in their social media feeds, not a corporate algorithm.",
}, },
{ {
Icon: Cogs, Icon: Cogs,
label: "Coming soon", label: "Coming soon",
title: "Developer and Creator-centric monetization", title: "Developer and Creator-centric monetization",
text: text: "As a content creator, set your own terms and price for your art. You and your collaborators can get paid directly, fairly, and automatically in crypto without relying on advertising as a sole source of income.",
"As a content creator, set your own terms and price for your art. You and your collaborators can get paid directly, fairly, and automatically in crypto without relying on advertising as a sole source of income.",
}, },
]; ];

View File

@ -19,32 +19,27 @@ const reasonCards = [
{ {
Icon: DataSwap, Icon: DataSwap,
title: "Immutable Data, Globally Available & Trustless", title: "Immutable Data, Globally Available & Trustless",
text: text: "Our immutable data layer means files are instantly accessible on any device, by any portal and are fully verifiable, by leveraging trustless, decentralized storage on the Sia blockchain.",
"Our immutable data layer means files are instantly accessible on any device, by any portal and are fully verifiable, by leveraging trustless, decentralized storage on the Sia blockchain.",
}, },
{ {
Icon: Encryption, Icon: Encryption,
title: "Dynamic Content with a User-Focus", title: "Dynamic Content with a User-Focus",
text: text: "SkyDB enables complex apps by providing a key-value store for mutable data secured by the private key of the user.",
"SkyDB enables complex apps by providing a key-value store for mutable data secured by the private key of the user.",
}, },
{ {
Icon: Layers, Icon: Layers,
title: "BYO Frontend Library", title: "BYO Frontend Library",
text: text: "Our SDKs are built with web2 developers in mind. Client-side web apps and static generators are perfect for using Skynet to deploy with.",
"Our SDKs are built with web2 developers in mind. Client-side web apps and static generators are perfect for using Skynet to deploy with.",
}, },
{ {
Icon: Mesh, Icon: Mesh,
title: "Decentralized Stack-Friendly", title: "Decentralized Stack-Friendly",
text: text: "With integrations with HNS & ENS, along with easy-access for off-chain storage, Skynet is positioned to connect with the DWeb and web3 technologies you need.",
"With integrations with HNS & ENS, along with easy-access for off-chain storage, Skynet is positioned to connect with the DWeb and web3 technologies you need.",
}, },
{ {
Icon: Toolkit, Icon: Toolkit,
title: "Hack Today & Activate an Existing User Base", title: "Hack Today & Activate an Existing User Base",
text: text: "Start building without worrying about server overhead costs or where users will come from. Bootstrap the user experience with interoperable storage and user-identity right out of the box.",
"Start building without worrying about server overhead costs or where users will come from. Bootstrap the user experience with interoperable storage and user-identity right out of the box.",
}, },
]; ];

View File

@ -36,32 +36,27 @@ const ecosystemCards = [
{ {
Icon: SkynetUsageSmall, Icon: SkynetUsageSmall,
title: "Easy to use", title: "Easy to use",
text: text: "Decentralized storage without needing to run a node or wallet. Skynet also includes SDKs for popular programming languages and APIs that integrate seamlessly with your existing apps.",
"Decentralized storage without needing to run a node or wallet. Skynet also includes SDKs for popular programming languages and APIs that integrate seamlessly with your existing apps.",
}, },
{ {
Icon: SkynetSpeedSmall, Icon: SkynetSpeedSmall,
title: "Fast", title: "Fast",
text: text: "Skynet's speeds rival centralized providers and surpass all decentralized offerings. A typical Skynet download starts in under 500 ms and can stream at rates as high as 1 Gbps!",
"Skynet's speeds rival centralized providers and surpass all decentralized offerings. A typical Skynet download starts in under 500 ms and can stream at rates as high as 1 Gbps!",
}, },
{ {
Icon: SkynetSiaSmall, Icon: SkynetSiaSmall,
title: "Free to use", title: "Free to use",
text: text: "Focus on building, not overhead server costs. When users own their data, developers aren't asked to pay for it.",
"Focus on building, not overhead server costs. When users own their data, developers aren't asked to pay for it.",
}, },
{ {
Icon: SkynetMonetizationSmall, Icon: SkynetMonetizationSmall,
title: "Monetization", title: "Monetization",
text: text: "Profit directly from the success of your skapp. Now you can truly prioritize your users, instead of advertisers.",
"Profit directly from the success of your skapp. Now you can truly prioritize your users, instead of advertisers.",
}, },
{ {
Icon: SkynetPersistenceSmall, Icon: SkynetPersistenceSmall,
title: "Persistence", title: "Persistence",
text: text: "Your skapp and data stay live, even if corporations pull your access to their resources. You can also use Skynet as a failover site for when centralized providers go down.",
"Your skapp and data stay live, even if corporations pull your access to their resources. You can also use Skynet as a failover site for when centralized providers go down.",
}, },
]; ];

117
scripts/es_cleaner.py Normal file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env python3
import curator
import elasticsearch
import os
import ssl
import sys
TIMEOUT=120
def main():
if len(sys.argv) != 3:
print('USAGE: [INDEX_PREFIX=(default "")] [ARCHIVE=(default false)] ... {} NUM_OF_DAYS http://HOSTNAME[:PORT]'.format(sys.argv[0]))
print('NUM_OF_DAYS ... delete indices that are older than the given number of days.')
print('HOSTNAME ... specifies which Elasticsearch hosts URL to search and delete indices from.')
print('TIMEOUT ... number of seconds to wait for master node response.'.format(TIMEOUT))
print('INDEX_PREFIX ... specifies index prefix.')
print('INDEX_DATE_SEPARATOR ... specifies index date separator.')
print('ARCHIVE ... specifies whether to remove archive indices (only works for rollover) (default false).')
print('ROLLOVER ... specifies whether to remove indices created by rollover (default false).')
print('ES_USERNAME ... The username required by Elasticsearch.')
print('ES_PASSWORD ... The password required by Elasticsearch.')
print('ES_TLS ... enable TLS (default false).')
print('ES_TLS_CA ... Path to TLS CA file.')
print('ES_TLS_CERT ... Path to TLS certificate file.')
print('ES_TLS_KEY ... Path to TLS key file.')
print('ES_TLS_SKIP_HOST_VERIFY ... (insecure) Skip server\'s certificate chain and host name verification.')
sys.exit(1)
client = create_client(os.getenv("ES_USERNAME"), os.getenv("ES_PASSWORD"), str2bool(os.getenv("ES_TLS", 'false')), os.getenv("ES_TLS_CA"), os.getenv("ES_TLS_CERT"), os.getenv("ES_TLS_KEY"), str2bool(os.getenv("ES_TLS_SKIP_HOST_VERIFY", 'false')))
ilo = curator.IndexList(client)
empty_list(ilo, 'Elasticsearch has no indices')
prefix = os.getenv("INDEX_PREFIX", '')
if prefix != '':
prefix += '-'
separator = os.getenv("INDEX_DATE_SEPARATOR", '-')
if str2bool(os.getenv("ARCHIVE", 'false')):
filter_archive_indices_rollover(ilo, prefix)
else:
if str2bool(os.getenv("ROLLOVER", 'false')):
filter_main_indices_rollover(ilo, prefix)
else:
filter_main_indices(ilo, prefix, separator)
empty_list(ilo, 'No indices to delete')
for index in ilo.working_list():
print("Removing", index)
timeout = int(os.getenv("TIMEOUT", TIMEOUT))
delete_indices = curator.DeleteIndices(ilo, master_timeout=timeout)
delete_indices.do_action()
def filter_main_indices(ilo, prefix, separator):
date_regex = "\d{4}" + separator + "\d{2}" + separator + "\d{2}"
time_string = "%Y" + separator + "%m" + separator + "%d"
ilo.filter_by_regex(kind='regex', value=prefix + "jaeger-(span|service|dependencies)-" + date_regex)
empty_list(ilo, "No indices to delete")
# This excludes archive index as we use source='name'
# source `creation_date` would include archive index
ilo.filter_by_age(source='name', direction='older', timestring=time_string, unit='days', unit_count=int(sys.argv[1]))
def filter_main_indices_rollover(ilo, prefix):
ilo.filter_by_regex(kind='regex', value=prefix + "jaeger-(span|service)-\d{6}")
empty_list(ilo, "No indices to delete")
# do not remove active write indices
ilo.filter_by_alias(aliases=[prefix + 'jaeger-span-write'], exclude=True)
empty_list(ilo, "No indices to delete")
ilo.filter_by_alias(aliases=[prefix + 'jaeger-service-write'], exclude=True)
empty_list(ilo, "No indices to delete")
ilo.filter_by_age(source='creation_date', direction='older', unit='days', unit_count=int(sys.argv[1]))
def filter_archive_indices_rollover(ilo, prefix):
# Remove only rollover archive indices
# Do not remove active write archive index
ilo.filter_by_regex(kind='regex', value=prefix + "jaeger-span-archive-\d{6}")
empty_list(ilo, "No indices to delete")
ilo.filter_by_alias(aliases=[prefix + 'jaeger-span-archive-write'], exclude=True)
empty_list(ilo, "No indices to delete")
ilo.filter_by_age(source='creation_date', direction='older', unit='days', unit_count=int(sys.argv[1]))
def empty_list(ilo, error_msg):
try:
ilo.empty_list_check()
except curator.NoIndices:
print(error_msg)
sys.exit(0)
def str2bool(v):
return v.lower() in ('true', '1')
def create_client(username, password, tls, ca, cert, key, skipHostVerify):
context = ssl.create_default_context()
if ca is not None:
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=ca)
elif skipHostVerify:
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
if username is not None and password is not None:
return elasticsearch.Elasticsearch(sys.argv[2:], http_auth=(username, password), ssl_context=context)
elif tls:
context.load_cert_chain(certfile=cert, keyfile=key)
return elasticsearch.Elasticsearch(sys.argv[2:], ssl_context=context)
else:
return elasticsearch.Elasticsearch(sys.argv[2:], ssl_context=context)
if __name__ == "__main__":
main()

23
scripts/wait_to_start.sh Executable file
View File

@ -0,0 +1,23 @@
#!/bin/sh
echo $WAIT_COMMAND
echo $WAIT_START_CMD
is_ready() {
eval "$WAIT_COMMAND"
}
# wait until is ready
i=0
while ! is_ready; do
i=`expr $i + 1`
if [ $i -ge $WAIT_LOOPS ]; then
echo "$(date) - still not ready, giving up"
exit 1
fi
echo "$(date) - waiting to be ready"
sleep $WAIT_SLEEP
done
#start the script
exec $WAIT_START_CMD

View File

@ -5,7 +5,7 @@ set -e # exit on first error
sudo apt-get update sudo apt-get update
sudo apt-get -y install python3-pip sudo apt-get -y install python3-pip
pip3 install discord.py python-dotenv requests pip3 install discord.py python-dotenv requests elasticsearch-curator
# add cron entries to user crontab # add cron entries to user crontab
crontab -u user /home/user/skynet-webportal/setup-scripts/support/crontab crontab -u user /home/user/skynet-webportal/setup-scripts/support/crontab

View File

@ -1,9 +1,9 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpBsw5mPBVIvVd5GX43VXWHWuLeR2h0lfw8vRyDFgmV0TqC9r0POfmWOdSo/QlxHOeI+7S8Ahj/JdDarrx3vJ2vJQkK/tN2bPS30tR0pbCkr0vE/lUWsEuVxOXK132wtFQ/pF3CoVipI8THUS7/Dtap/9fujcEm59dIi3obYGc9F+UetmNtrc+mb6KJ6a1hkaXjD12qP03srSQSDBjch/7nbFFzrRwCZ9DJntMu6Ux6NZ7RcFuXPCg0dK0lggEX/Agzh3KHe69dgiMh8sG0WwCb9vWqd6dtapCt7XKZSnEvyFE1YVZgpsd7bCnGe4vPS3kLsvxeojruDo8Oj3b0exHL9+3Rr4ndVVNHkDxhvlQFbGrd5eiG/brvGjS+ibscTuNukLeiCmBrI5KULObynI2dEQVQKREVywU/qX+xm68noEGBbiRt2L2ImyJvgpNdlyCkDyFhBTo/HtH1WHP1WJijfCHM3jxigeLPRV0GChKK1RbYjZIi6JNsalW7yad/qzHDzht+jBHHAjD4qGlfuNtzP4hs3FErGiQMVZ8g9Tgq8SxPLNOULpcCSwsLLlzfrLYdv52IgkwTIAFR9W+xHGrWypCba9pfskXWXlRNM61qYf3//H0BGHxtuNAASkJrVWwcCuOVN6/EcJOTS9qkg3JiWqs79z0F2I14+AfPFgBKQ== david@nebulouslabs.com
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCr3nrSQ+ag5gEm9LBoiw68UKALboot+Eemv0TbP6BPnvB6bnSdDstb7Eu1Dkla8uiyw3w2ZYi5Pg4dS5W8vnxwXvey8gBd3GYLpjtnSp9ukeYjHK0J2aX4PBC4GXvRSRjKxYfHauUqm8PaA4uQ4sBkblfwWDEH94um1yyqIamTabH6mfsYiaiiwTNu7ldZOAIlKR/G7cXlLmFz46An7Mn2wwbuv2Khin/f2bLtUF/smOolI7pjOH6ifhHR9LxotcY/xL+E5jRbU1XxldFvVXkL5CU8tEinE6oigwMH9zsPZr+Z70Q/wm20cylxNJu8qdMGQW+WhDg3S70KpCmjYlWJ6bF1HL3z9UkN0lS1EM21n13RIx1iEO7SEC3YPl8VqZiZS7P9Uf5D5z/vTG+fWouCsCBMSbq3HUcNXlm5MLGSdBWPKzZsUaCkHkQks/sxHVy21YAM/3xgST1a05PbIJU1RsqJ0wh0J2gg7/fBUE0ljFyKZ36mvfg6BNlwCUydAiVaQt1geqh+8/VRwjTw/jtHb8G7QhSNwDNo1BcQPU3LkdKePqgldyP5EYGl9bI4E4sYc2DooeJ22fXpWfuClLB+JcHGuCJf/Hg6si9IeeXKm8PwaBdxIVytRPEeJR+q5uOwzI4XWNgERdGU/UVbgfnrAPMuVPa9Jhyl96U9uUl+Cw== peterjan.brone@gmail.com
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMmZFdJsqig/zX7Ly7qJMMDomsdAKLgl7W7ET1w7xH9BBM48OCWmozuLCfCG8MUCEYSUD575hA028hNi6CAK40J3fF74IDDyc9DUb+le8Y8EuzHPKxYLE/gWsjr70XOcZcC4IxLcADQgpeLjrPZQs7A4EYfdxnTTLJVYrowZ9RR5ivcKBjyFOiQyCuFSIvtYMo11Xm2gU48SKYGJThhHUiE2kMOlH3notXJ+T81927IGJdza7J3DAyKtMGB2HEMA89ma3mvEvbPTDMggJFJ3VG7sukRLq6UmT7BT+f3BW+Nr87A1o4upkAuXdkL9cUrris7kQN61AcaCNFU/CuIJa4dUZ0nt+z5X7kWtc0zD75EPj3w6AjB+E1+MSPsqnxd5PnGtSCQqHoa5hg4hQMSweC2tQhSKoWDfx9W2fZiLpg1IL6QB5xCxjg+YKCXEJKxRwXDtbh1DHFdJ5N1kM7IDSeeblc80HNxYrJUPNH1ExWsPl11gmBEEWDAiRSet4bAnOmgDYcJ9Aw2KAndb01cNsw5RL0Dg/W63tb8S5Y9kz6spX6X91yz53JzrozZO7VFfKxa17nubPEeWPTqAQ3uRWPvpdbivVnOAoFCLacRvtTfvetuz/vGZ3JTpr6Ylb9Z76cIqpFe70+bnauZwmxjF+EEq2+u3gd2uewuV2//o+CYQ== kwypchlo@gmail.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMmZFdJsqig/zX7Ly7qJMMDomsdAKLgl7W7ET1w7xH9BBM48OCWmozuLCfCG8MUCEYSUD575hA028hNi6CAK40J3fF74IDDyc9DUb+le8Y8EuzHPKxYLE/gWsjr70XOcZcC4IxLcADQgpeLjrPZQs7A4EYfdxnTTLJVYrowZ9RR5ivcKBjyFOiQyCuFSIvtYMo11Xm2gU48SKYGJThhHUiE2kMOlH3notXJ+T81927IGJdza7J3DAyKtMGB2HEMA89ma3mvEvbPTDMggJFJ3VG7sukRLq6UmT7BT+f3BW+Nr87A1o4upkAuXdkL9cUrris7kQN61AcaCNFU/CuIJa4dUZ0nt+z5X7kWtc0zD75EPj3w6AjB+E1+MSPsqnxd5PnGtSCQqHoa5hg4hQMSweC2tQhSKoWDfx9W2fZiLpg1IL6QB5xCxjg+YKCXEJKxRwXDtbh1DHFdJ5N1kM7IDSeeblc80HNxYrJUPNH1ExWsPl11gmBEEWDAiRSet4bAnOmgDYcJ9Aw2KAndb01cNsw5RL0Dg/W63tb8S5Y9kz6spX6X91yz53JzrozZO7VFfKxa17nubPEeWPTqAQ3uRWPvpdbivVnOAoFCLacRvtTfvetuz/vGZ3JTpr6Ylb9Z76cIqpFe70+bnauZwmxjF+EEq2+u3gd2uewuV2//o+CYQ== kwypchlo@gmail.com
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDgiq1etF0aD94rG/UVmYEt4ij5K8MvHZwb4wIUi6Ihr david@nebulouslabs.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDgiq1etF0aD94rG/UVmYEt4ij5K8MvHZwb4wIUi6Ihr david@siasky.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAyIT2HqzDhQs6jS89ZsnY6+GJEklVMqF6fXe/i5s8d7 chris@nebulous.tech ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAyIT2HqzDhQs6jS89ZsnY6+GJEklVMqF6fXe/i5s8d7 chris@siasky.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxLuZzjmFN9CgVOI5vaiVhQgMwG9dLQJ688wrsbpHH/ ivaylo@nebulous.tech ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxLuZzjmFN9CgVOI5vaiVhQgMwG9dLQJ688wrsbpHH/ ivaylo@siasky.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINbAhwjJNAud7YIJvLth2bmeUg3kO20xl7ZfqBTvoXn8 Filip Rysavy ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINbAhwjJNAud7YIJvLth2bmeUg3kO20xl7ZfqBTvoXn8 filip@siasky.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG67M3zC4eDJEjma0iKKksGclteKbB86ONQtBaWY93M6 mjsevey@gmail.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG67M3zC4eDJEjma0iKKksGclteKbB86ONQtBaWY93M6 matt@siasky.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN6Kcx8yetova4/ALUQHigo/PBMJO33ZTKOsg2jxSO2a user@depl.siasky.dev ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF+XC8f0dumhzDE93i9IIMsMp7/MJPwGH+Uc9JFKOvyw karol@siasky.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPM43lzbKjFLChe5rKETxDpWpNlqXCGTBPiWlDN2vlLD pj@siasky.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN6Kcx8yetova4/ALUQHigo/PBMJO33ZTKOsg2jxSO2a user@deploy.siasky.dev

1341
yarn.lock

File diff suppressed because it is too large Load Diff