Last active
September 13, 2022 10:48
-
-
Save blubbll/7e31abd2ffc37d1ac30081866445c267 to your computer and use it in GitHub Desktop.
shrine starter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"user": "blubbll@", | |
"token": "x", | |
"zone": "y", | |
"record": "z" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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