Skip to content

Instantly share code, notes, and snippets.

@blubbll
Last active September 13, 2022 10:48
Show Gist options
  • Save blubbll/7e31abd2ffc37d1ac30081866445c267 to your computer and use it in GitHub Desktop.
Save blubbll/7e31abd2ffc37d1ac30081866445c267 to your computer and use it in GitHub Desktop.
shrine starter
/** imports **/
import {
default as fs
} from 'fs';
import {
default as fetchR
} from 'node-fetch';
import {
default as moment
} from 'moment';
import {
default as Pino
} from 'pino';
import {
update,
list
} from 'cf-dns-updater';
/** eh **/
import path from 'path';
import {
fileURLToPath
} from 'url';
const __filename = fileURLToPath(
import.meta.url);
const __dirname = path.dirname(__filename);
/** constants **/
const API = "https://api.shrine.app";
const defaultExpireHour = 4;
const program = "shrineStarter";
const dir = {
authFile: `${process.env.APPDATA}/shrine/config.json`,
authFileReal: __dirname + "/auth.json",
ipFile: __dirname + "/ips.txt",
logDir: __dirname + '/logs',
expireFile: __dirname + '/expire.ts',
cfFile: __dirname + "/cf.json",
waitingFile: __dirname + "/waiting.ts",
startingFile: __dirname + "/starts.ts"
};
const delay = { //all are s
tokenRefresh: 6,
keepAlive: 45,
checkAlive: 29,
startGeneric: 24,
checkAliveInitial: 14,
startRequested: 14,
restart: 5
}
/** generic vars **/
const auth = JSON.parse(fs.readFileSync(dir.authFile, "utf8"));
auth.real = JSON.parse(fs.readFileSync(dir.authFileReal, "utf8"));
let machine = void 0;
let request = void 0;
let apiCalls = 0;
let expire = -1;
let cf = false;
let waiting;
let waitingLocally;
let lastKeptAlive = new Date();
/** setup* */
const logger = Pino({
transport: {
target: 'pino-pretty'
},
});
if (!auth.user) {
auth.email = auth.real.email;
auth.password = auth.real.password;
auth.broken = true;
}
const censor = {
word: _ => {
return _[0] + "*".repeat(_.length - 2) + _.slice(-1);
},
id: _ => {
var regex = /(?<!^).(?!$)/g;
return _.replace(regex, '*');
},
email: _ => {
var arr = _.split("@");
return censor.word(arr[0]) + "@" + censor.word(arr[1]);
}
}
console.log = (data, args) => {
const logTime = _ => {
return `${moment().format()}`
}
if (typeof(data) === "string" && args === void 0 || data.msg === void 0) data = {
msg: data
};
//console.warn(data, args)
args = args || {
msgOnly: false,
console: args !== false,
log: true
};
if (args.log !== false)
fs.appendFile(`${dir.logDir}/${moment().format('YYYY-MM-DD')}.txt`, `\r\n${logTime()}>${JSON.stringify(data, null, "\t")}`, (err) => {
if (err) throw err;
});
if (args.console !== false)
logger.info(args.verbose === true || args === true || args === "verbose" ? data : data.msg);
}
(async function() {
if (fs.existsSync(dir.cfFile)) {
cf = JSON.parse(fs.readFileSync(dir.cfFile, "utf8"));
console.log({
msg: `Setup cloudflare sync for record [${censor.word(cf.record)}]`,
user: censor.email(cf.user),
token: censor.word(cf.token)
});
}
})();
{
var file = dir.expireFile;
try {
expire = moment(+(fs.readFileSync(file, "utf8")));
console.log("loaded saved expiration date");
} catch (e) {
console.log("error reading saved expiration date, creating new");
expire = moment();
expire.add(-defaultExpireHour, "hours");
fs.writeFileSync(file, expire.valueOf().toString(), "utf8");
}
}
{
var file = dir.waitingFile;
try {
waiting = moment(+(fs.readFileSync(file, "utf8")));
console.log({
msg: `loaded saved waiting time ${waiting.format()}`
});
} catch (e) {
console.error(e);
console.log("error reading saved waiting file, creating new");
fs.writeFileSync(file, moment().valueOf().toString(), "utf8");
}
}
auth.userId = auth.broken ? "" : JSON.parse(atob(auth.token.split(".")[1])).userId;
/** generic methods **/
const updateDns = async _ => {
if (!cf) return;
console.log({
msg: `Updating dns entry...`,
zone: cf.zone,
record: cf.record,
value: _
});
const updated = await update({
ip: _, // IP to update record with
email: cf.user, // Cloudflare auth email
apiKey: cf.token, // Cloudflare API key
zone: cf.zone, // Cloudflare zone ID
record: cf.record, // Cloudflare record ID
});
console.log({
msg: "Updated dns entry.",
updated
});
};
const addIp = ip => {
if (ip) {
const file = dir.ipFile;
if (!fs.existsSync(file)) fs.writeFileSync(file, "", "utf8");
const inputData = fs.readFileSync(file).toString()
const line = `${ip}\r\n`
if (!inputData.includes(line)) {
console.log({
msg: "Logged new vm ip",
value: ip
});
fs.appendFile(file, line, (err) => {
if (err) throw err
})
} else console.log({
msg: "vm ip already in list",
value: ip
}, {
console: false
});
}
}
const addStart = entry => {
if (entry) {
const file = dir.startingFile;
if (!fs.existsSync(file)) fs.writeFileSync(file, "", "utf8");
const inputData = fs.readFileSync(file).toString()
const line = `${entry}\r\n`
if (!inputData.includes(line)) {
console.log({
msg: "Logged new start",
value: entry
});
fs.appendFile(file, line, (err) => {
if (err) throw err
})
} else console.log({
msg: "entry already in list",
value: entry
}, {
console: false
});
}
}
const fetch = (url, options) => {
return fetchR(url, options)
.then(response => {
apiCalls++;
return response;
});
}
const processTitle = _ => {
const ipCount = fs.existsSync(dir.ipFile) ? fs.readFileSync(dir.ipFile).toString().split('\n').length - 1 : 0;
const startCount = fs.existsSync(dir.startingFile) ? fs.readFileSync(dir.startingFile).toString().split('\n').length - 1 : 0;
return `[${program}:${moment().format('HH:mm:ss')}, ips logged: ${ipCount}, starts: ${startCount}]`
}
/** shrine methods **/
const shrine = {
header: _ => {
return {
"accept": "application/json, text/plain, */*",
"accept-language": "en-US",
"authorization": "Bearer " + auth.token,
"if-none-match": "W/\"d9-4McGp0AYsOq82rY/CzU7unzadyM\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site"
};
},
headerBrowser: _ => {
return {
"accept": "application/json, text/plain, */*",
"accept-language": "en-US,en;q=0.9",
"authorization": "",
"content-type": "application/json",
"sec-ch-ua": "\"Chromium\";v=\"104\", \" Not A;Brand\";v=\"99\", \"Google Chrome\";v=\"104\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
}
},
login: async (user, pass) => {
pass = pass || auth.password;
user = user || auth.email;
console.log({
msg: `logging in...`
})
const res = await fetch(API + "/auth/login", {
"headers": shrine.headerBrowser(),
"referrerPolicy": "strict-origin-when-cross-origin",
"body": JSON.stringify({
email: user,
password: pass
}),
"method": "POST",
"mode": "cors",
"credentials": "include"
});
const json = await res.json();
if (json.accessToken) {
auth.userId = JSON.parse(atob(json.accessToken.split(".")[1])).userId;
console.log({
msg: "Logged in."
}, {
console: false
});
auth.token = json.accessToken;
//write new token to disk
fs.writeFileSync(dir.authFile, JSON.stringify({
"token": json.accessToken,
"email": user,
"password": pass
}, null, "\t"), "utf8");
console.log("Token on disk refreshed.");
}
if (json.statusCode === 401) {
console.log({
msg: "WRONG AUTH",
message: json.message,
code: json.statusCode
}, {
verbose: true
});
process.exit(1);
}
if (json.statusCode === 429) {
console.log({
msg: "CHILL DOWN",
message: json.message,
code: json.statusCode
}, {
verbose: true
});
process.exit(1);
}
},
getProfile: async cb => {
const out = _ => {
console.log({
msg: `Got profile data (${censor.id(auth.userId)})` + (!refreshed ? " via saved token" : "") + ".",
user: censor.email(auth.email),
id: censor.id(auth.userId),
password: censor.word(auth.password)
});
}
let refreshed = false;
const res = await fetch(API + "/auth/profile", {
"headers": shrine.header(),
"referrer": "https://shrine.app/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
});
try {
const json = await res.json();
if (json.statusCode === 401) {
console.log({
msg: `Token expired, refreshing in ${delay.tokenRefresh} seconds...`
});
setTimeout(async _ => {
await shrine.login();
refreshed = true;
out();
await cb(true);
}, delay.tokenRefresh * 999);
} else {
auth.userId = json.id
out();
return json;
}
} catch (e) {
console.log(e);
}
},
machine: {
start: async id => {
if (id === void 0) id = machine.id;
console.log(`Trying to start machine [#${id}]...`);
const res = await fetch(`${API}/machines/${id}/start`, {
"headers": shrine.header(),
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "POST",
"mode": "cors",
"credentials": "include"
});
const ans = await res.text();
console.log(ans);
return ans === "Ok";
},
stop: async id => {
const res = await fetch(`${API}/machines/${id}/shutdown`, {
"headers": shrine.header(),
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "POST",
"mode": "cors",
"credentials": "include"
});
const ans = await res.text();
return ans === "Ok";
},
getAll: async check => {
const res = await fetch(`${API}/users/${auth.userId}/machines`, {
"headers": shrine.header(),
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
});
try {
const json = await (res.json());
const machines = json;
console.log({
msg: "machines:",
data: json
}, {
console: false
});
if (machines[0] || check)
machine = machines[0];
if (json.statusCode === 401) {
machine = void 0;
console.log({
msg: `Token expired, refreshing in ${delay.tokenRefresh} seconds...`
});
await new Promise(r => setTimeout(r, delay.tokenRefresh * 999));
await shrine.login();
}
} catch (e) {
console.log(e);
}
return
},
getRequest: async _ => {
console.log("Checking requests...");
const res = await fetch(`${API}/users/${auth.userId}/machine_requests?active=true`, {
"headers": shrine.header(),
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
});
const json = await res.json();
const _req = json[0];
if (_req && _req.id) {
fs.writeFileSync(dir.waitingFile, `${+new Date(_req.createdAt)}`, "utf8");
waitingLocally = _req.createdAt;
console.log({
msg: "already requesting machine...",
request_id: _req.id,
}, true);
request = _req;
}
},
reset: async _ => {
console.log("Resetting machine...");
const res = await fetch(`${API}/users/${auth.userId}/machine_images`, {
"headers": shrine.header(),
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "DELETE",
"mode": "cors",
"credentials": "include"
});
const json = await res.text();
if (json === "Ok") {
console.log("Machine image was reset");
return;
} else console.error(json);
},
request: async _ => {
await shrine.machine.getRequest();
if (!request) {
console.log("Requesting new machine...");
const res = await fetch(`${API}/machine_requests`, {
"headers": shrine.header(),
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "POST",
"mode": "cors",
"credentials": "include"
});
expire = moment();
expire.add(defaultExpireHour, 'hours');
fs.writeFileSync(dir.expireFile, expire.valueOf().toString(), "utf8");
const json = await res.json();
if (json.id) {
waitingLocally = json.createdAt;
fs.writeFileSync(dir.waitingFile, `${+new Date(json.createdAt)}`, "utf8");
console.log({
msg: "Started new request...",
request_id: json.id,
type: json.machineType
}, {
verbose: true
});
request = json;
}
if (json.status === 401) {
machine = void 0;
console.log({
msg: `Token expired, refreshing in ${delay.tokenRefresh} seconds...`
});
await new Promise(r => setTimeout(r, delay.tokenRefresh * 999));
}
}
return;
},
keepAlive: async id => {
const res = await fetch(`${API}/machine_requests/${id}/keepalive`, {
"headers": shrine.header(),
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "POST",
"mode": "cors",
"credentials": "include"
});
const json = await res.json();
if (!json.active || !json.id) {
console.log(json, false);
console.log(`Request ${request.id} shrivvelled, requesting new one...`);
machine = void 0;
request = void 0;
}
console.log(json, {
console: false
});
return json;
},
checkAlive: async _ => {
process.title = `${processTitle()}>checking if still alive...`;
console.log("checking alivbe");
//await shrine.machine.getAll(true);
if (machine) {
var id = machine.id;
if (machine.status === "off") {
console.log(`Machine [#${id}] died, starting again...`);
await startMachine(id);
}
if (machine.status === "launching.stopping-machine") {
console.log(`Machine [#${id}] shutting down, please wait a bit...`);
} else
process.title = `${processTitle()}}>machine [#${id}] is still alive [${machine.status}] uwu [should expire in ${expire.fromNow()}]. ApiCalls in session: ${apiCalls}`;
//setTimeout(shrine.machine.checkAlive, delay.checkAlive * 999);
} else {
console.log({
msg: "Something went wrongg :O restarting core loop"
});
setTimeout(start, delay.restart * 999);
}
}
}
}
process.title = processTitle();
const start = async skipProfile => {
if (skipProfile || await shrine.getProfile(start)) {
const loop = async _ => {
await shrine.machine.getAll();
if (machine) {
request = void 0;
var id = machine.id;
var ip = machine.ipAddress;
var status = machine.status || "initializing";
function getTimeDiff(start, end) {
return moment.duration(moment(end, "HH:mm:ss a").diff(moment(start, "HH:mm:ss a")));
}
var diff = getTimeDiff(moment(waiting), moment())
// var msg = `Waiting until machine [#${id}] ready (${status})..., waiting for [${moment(waiting).fromNow(true)}]`;
var msg = `Waiting until machine [#${id}] ready (${status})..., waiting since [${moment().diff(moment(waiting), 'hours')}h and ${diff.minutes()} minutes / ${moment(waitingLocally)}] ApiCalls in session: ${apiCalls}`;
process.title = `${processTitle()}>${msg}`;
console.log({
msg,
ip: ip === null ? void 0 : ip,
status
}, {
console: false
});
if (ip && status === "running") {
console.log(`Machine [#${id}] ready for connection / RDP with IP ${ip}`);
addStart(+new Date());
//if (fs.existsSync(dir.waitingFile)) fs.unlinkSync(dir.waitingFile);
await updateDns(ip);
await addIp(ip);
process.exit(0);
return //setTimeout(shrine.machine.checkAlive, delay.checkAliveInitial * 999)
}
if (ip === null && status === "running") {
console.log(`Machine needs a slap on the butt :3`);
await shrine.machine.start();
return //setTimeout(shrine.machine.checkAlive, delay.checkAliveInitial * 999)
}
if (status === "off") {
console.log(`Machine [#${id}] died, starting...`);
await shrine.machine.start(id)
};
return setTimeout(loop, delay.startGeneric * 999);
} else if (!request) {
await shrine.machine.request();
} else if (request && request.active) {
var msg = `Waiting until request [#${request.id}] fulfilled... ApiCalls in session: ${apiCalls}, requesting since [${moment(request.createdAt).fromNow(true)} (${moment(request.createdAt).format('HH:mm')})]`;
process.title = `${processTitle()}>${msg}`;
if (moment().isAfter(moment(lastKeptAlive).add(delay.keepAlive, 'seconds'))) {
await shrine.machine.keepAlive(request.id);
lastKeptAlive = new Date();
console.log("was kept alive", {
console: false
});
process.title += ` [kept alive at ${moment(lastKeptAlive).format('HH:mm:ss')}]`
} else console.log("was already kept", {
console: false
});
}
setTimeout(loop, delay.startGeneric * 999);
}
loop();
}
}
start();
{
"user": "blubbll@",
"token": "x",
"zone": "y",
"record": "z"
}
{
"name": "shrinestarter",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"cloudflaredjs": "^1.0.5",
"moment": "^2.29.4",
"node-fetch": "^2.6.6",
"pino": "^8.4.2",
"pino-pretty": "^9.1.0"
}
}
@echo OFF
setlocal
cd /d %~dp0
:loop
node index.js
::goto loop
pause
exit /b 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment