Evdevkit for private use on Windows + Mac + Linux:
Create file evdevkit.js
Code: Select all
#!/usr/bin/env node
const { spawnSync } = require("child_process");
const evdevkitPath = require.resolve("evdevkit/index.js");
const args = process.argv.slice(2);
let secret;
let privateKey;
let randomPrivateKey = false;
const passthrough = [];
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === "--secret") {
secret = args[++i];
} else if (a === "--privatekey") {
privateKey = args[++i];
} else if (a === "--randomprivatekey") {
randomPrivateKey = true;
} else {
passthrough.push(a);
}
}
if (secret) {
process.env.EV_TENANT_SECRET = secret;
}
if (randomPrivateKey) {
const result = spawnSync(
process.execPath,
[evdevkitPath, "keygen"],
{ encoding: "utf8" }
);
if (result.status !== 0) {
console.error("Failed to generate key pair");
process.exit(1);
}
const privMatch = result.stdout.match(/privateKey:\s*'([^']+)'/);
const pubMatch = result.stdout.match(/publicKey:\s*'([^']+)'/);
if (!privMatch || !pubMatch) {
console.error("Could not parse generated keys");
process.exit(1);
}
privateKey = privMatch[1];
console.log("");
console.log("New key pair generated");
console.log("Private key:", privateKey);
console.log("Public key :", pubMatch[1]);
console.log("");
}
if (privateKey) {
process.env.EV_USER_PRIVATE_KEY = privateKey;
}
const run = spawnSync(
process.execPath,
[evdevkitPath, ...passthrough],
{
stdio: "inherit",
env: process.env
}
);
process.exit(run.status ?? 0);
Code: Select all
{
"name": "evdevkit-bin",
"private": true,
"bin": "evdevkit.js",
"pkg": {
"scripts": [
"node_modules/evdevkit/index.js"
]
}
}
Build with pkg (install with npm i pkg -g):
pkg evdevkit.js --targets node18-linux-x64 --output evdevkit
pkg evdevkit.js --targets node18-win-x64 --output evdevkit.exe
pkg evdevkit.js --targets node18-macos-x64 --output evdevkit-macos
You can now deploy with the file by using two different commands:
./evdevkit --secret seed --privatekey mykey
./evdevkit --secret seed --randomprivatekey
One of them will create a random evdevkit key for you, the other one allows you to input your own.
Everything else is as usual, acquire -i -m rAddress.
Evdevkit as a deamon
When you want to use a portable evdevkit as a deamon you need to use stdin to protect your secret seeds to be visible in the processes. That's an other bin file, and you can see how to pkg it below. You will also see how we need external scripts to grant more memory to the bin file when being ran. Keep in mind that memory leaks (etc) can still happen, so never treat this as secure.
Acquire with:
echo "myseed" | ./evdevkit-runner.sh rAddress
Extend with:
echo "myseed" | ./evdevkit-runner.sh whateveryouwant instanceID rAddress moments
PS: These sh files have hardcoded image, so you can fix that if you want to pass the image in cli.
Build bin with:
pkg evdevkitstdin.js --targets node18-linux-x64 --output evdevkit
Log files in rent folder and instance folder.
That version looks like this:
evdevkitstdin.js
Code: Select all
#!/usr/bin/env node
const fs = require("fs");
const { spawnSync } = require("child_process");
function readStdin() {
try {
return fs.readFileSync(0, "utf8").trim();
} catch {
return "";
}
}
const args = process.argv.slice(2);
let secret;
let privateKey;
let randomPrivateKey = false;
const passthrough = [];
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === "--secret-stdin") {
secret = readStdin();
} else if (a === "--privatekey") {
privateKey = args[++i];
} else if (a === "--randomprivatekey") {
randomPrivateKey = true;
} else {
passthrough.push(a);
}
}
if (secret && secret.length < 20) {
console.error("Invalid secret from stdin");
process.exit(1);
}
if (randomPrivateKey) {
const result = spawnSync(
process.execPath,
[process.argv[1], "keygen"],
{
encoding: "utf8",
stdio: ["ignore", "pipe", "inherit"]
}
);
if (result.status !== 0 || !result.stdout) {
console.error("Failed to generate key pair");
process.exit(1);
}
const privMatch = result.stdout.match(/privateKey:\s*'([^']+)'/);
const pubMatch = result.stdout.match(/publicKey:\s*'([^']+)'/);
if (!privMatch || !pubMatch) {
console.error("Could not parse generated keys");
process.exit(1);
}
privateKey = privMatch[1];
console.log("");
console.log("New key pair generated");
console.log("Public key :", pubMatch[1]);
console.log("");
}
if (secret) {
process.env.EV_TENANT_SECRET = secret;
}
if (privateKey) {
process.env.EV_USER_PRIVATE_KEY = privateKey;
}
process.argv = [
process.argv[0],
process.argv[1],
...passthrough
];
require("evdevkit/index.js");
Create folder /usr/local/bin/evernoderunner and make sure you have permission for it, also maks sure evdevkit-runner.sh and evdevkit-extender.sh got chmod +x.
Remember to have your evdevkit bin file in here aswell with +x.
evdevkit-runner.sh
Code: Select all
#!/bin/bash
set -euo pipefail
read -r SEED || exit 1
ADDRESS="$1"
[ -n "$SEED" ] || exit 1
ulimit -v unlimited
ulimit -m unlimited
ulimit -s unlimited
export WASMTIME_MAX_MEMORY=8G
export WASM_STACK_SIZE=64M
BASE_DIR="/usr/local/bin/evernoderunner"
LOG_DIR="$BASE_DIR/rent"
INSTANCES_DIR="$BASE_DIR/instances"
TS=$(date +%s)
LOG_FILE="$LOG_DIR/rent_${ADDRESS}.log"
mkdir -p "$LOG_DIR" "$INSTANCES_DIR"
chmod 700 "$LOG_DIR" "$INSTANCES_DIR"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
HB_PID=""
cleanup() {
[ -n "$HB_PID" ] && kill "$HB_PID" 2>/dev/null || true
}
trap cleanup EXIT
{
log "=== Instance acquirement started ==="
log "Host: $ADDRESS"
log "User: $(whoami)"
log "Image: yourimage/yourimage:latest"
log "Seed: [REDACTED]"
log "evdevkit: $(command -v evdevkit)"
(
while true; do
sleep 30
log "Still running..."
done
) &
HB_PID=$!
printf '%s' "$SEED" | /usr/local/bin/evdevkit \
--secret-stdin \
--randomprivatekey \
acquire \
-i yourimage/yourimage:latest \
-m 1 \
"$ADDRESS"
unset SEED
kill "$HB_PID" 2>/dev/null || true
INSTANCE_BLOCK=$(sed -n "/Instance created!/,/}/p" "$LOG_FILE")
if [ -z "$INSTANCE_BLOCK" ]; then
log "ERROR: Instance block not found"
log "STATUS: FAILURE"
exit 1
fi
log "Parsing instance metadata"
NAME=$(echo "$INSTANCE_BLOCK" | sed -n "s/.*name: '\([^']*\)'.*/\1/p")
USER_PORT=$(echo "$INSTANCE_BLOCK" | sed -n "s/.*user_port: '\([^']*\)'.*/\1/p")
DOMAIN=$(echo "$INSTANCE_BLOCK" | sed -n "s/.*domain: '\([^']*\)'.*/\1/p")
GP_TCP_PORT=$(echo "$INSTANCE_BLOCK" | sed -n "s/.*gp_tcp_port: '\([^']*\)'.*/\1/p")
GP_UDP_PORT=$(echo "$INSTANCE_BLOCK" | sed -n "s/.*gp_udp_port: '\([^']*\)'.*/\1/p")
CREATED_TS=$(echo "$INSTANCE_BLOCK" | sed -n "s/.*created_timestamp: \([0-9]*\).*/\1/p")
if [ -z "$NAME" ] || [ -z "$CREATED_TS" ]; then
log "ERROR: Failed to extract required fields"
log "STATUS: FAILURE"
exit 1
fi
EXPIRES_TS=$((CREATED_TS + 3600000))
CREATED_AT=$(date -d "@$((CREATED_TS/1000))" "+%Y-%m-%d %H:%M:%S")
EXPIRES_AT=$(date -d "@$((EXPIRES_TS/1000))" "+%Y-%m-%d %H:%M:%S")
jq -n \
--arg instance_id "$NAME" \
--arg host "$ADDRESS" \
--arg domain "$DOMAIN" \
--argjson user_port "$USER_PORT" \
--argjson gp_tcp_port "$GP_TCP_PORT" \
--argjson gp_udp_port "$GP_UDP_PORT" \
--argjson created_ts "$CREATED_TS" \
--argjson expires_ts "$EXPIRES_TS" \
--arg created_at "$CREATED_AT" \
--arg expires_at "$EXPIRES_AT" \
'{
instance_id: $instance_id,
host_address: $host,
domain: $domain,
user_port: $user_port,
gp_tcp_port: $gp_tcp_port,
gp_udp_port: $gp_udp_port,
created_timestamp: $created_ts,
expires_timestamp: $expires_ts,
created_at: $created_at,
expires_at: $expires_at,
status: "active"
}' > "$INSTANCES_DIR/$NAME.json"
log "Instance JSON written: $INSTANCES_DIR/$NAME.json"
log "STATUS: SUCCESS"
log "Finished at $(date)"
} >>"$LOG_FILE" 2>&1
evdevkit-extender.sh
Code: Select all
#!/bin/bash
set -euo pipefail
read -r SEED || exit 1
RUN_ID="$1"
INSTANCE_NAME="$2"
ADDRESS="$3"
MOMENTS="$4"
[ -n "$SEED" ] || exit 1
[ -n "$INSTANCE_NAME" ] || exit 1
[ -n "$ADDRESS" ] || exit 1
[ -n "$MOMENTS" ] || exit 1
if ! [[ "$MOMENTS" =~ ^[0-9]+$ ]]; then
exit 1
fi
ulimit -v unlimited
ulimit -m unlimited
ulimit -s unlimited
export WASMTIME_MAX_MEMORY=8G
export WASM_STACK_SIZE=64M
BASE_DIR="/usr/local/bin/evernoderunner"
EXTEND_DIR="$BASE_DIR/extend"
INSTANCES_DIR="$BASE_DIR/instances"
LOG_FILE="$EXTEND_DIR/extend_${INSTANCE_NAME}.log"
TMP_FILE="/tmp/extend_${INSTANCE_NAME}.txt"
mkdir -p "$EXTEND_DIR"
chmod 700 "$EXTEND_DIR"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
cleanup() {
rm -f "$TMP_FILE"
}
trap cleanup EXIT
{
log "=== EXTEND START RUN_ID=$RUN_ID ==="
log "Instance: $INSTANCE_NAME"
log "Host: $ADDRESS"
log "Moments: $MOMENTS"
log "User: $(whoami)"
log "Seed: [REDACTED]"
log "evdevkit: $(command -v evdevkit)"
echo "${ADDRESS}:${INSTANCE_NAME}:${MOMENTS}" > "$TMP_FILE"
chmod 600 "$TMP_FILE"
log "Instance file created"
if ! printf '%s' "$SEED" | evdevkit \
--secret-stdin \
--randomprivatekey \
extend "$TMP_FILE"
then
log "ERROR: evdevkit extend failed"
log "=== EXTEND FAILURE RUN_ID=$RUN_ID ==="
exit 1
fi
unset SEED
INSTANCE_JSON="$INSTANCES_DIR/$INSTANCE_NAME.json"
if [ ! -f "$INSTANCE_JSON" ]; then
log "ERROR: Instance JSON not found"
log "=== EXTEND FAILURE RUN_ID=$RUN_ID ==="
exit 1
fi
OLD_EXPIRES_TS=$(jq -r '.expires_timestamp' "$INSTANCE_JSON")
if ! [[ "$OLD_EXPIRES_TS" =~ ^[0-9]+$ ]]; then
log "ERROR: Invalid expires_timestamp"
log "=== EXTEND FAILURE RUN_ID=$RUN_ID ==="
exit 1
fi
ADD_MS=$((MOMENTS * 3600000))
NEW_EXPIRES_TS=$((OLD_EXPIRES_TS + ADD_MS))
NEW_EXPIRES_AT=$(date -d "@$((NEW_EXPIRES_TS/1000))" "+%Y-%m-%d %H:%M:%S")
jq \
--argjson ts "$NEW_EXPIRES_TS" \
--arg at "$NEW_EXPIRES_AT" \
'.expires_timestamp = $ts | .expires_at = $at' \
"$INSTANCE_JSON" > "$INSTANCE_JSON.tmp"
mv "$INSTANCE_JSON.tmp" "$INSTANCE_JSON"
log "Instance expiration extended"
log "New expires_at: $NEW_EXPIRES_AT"
log "=== EXTEND SUCCESS RUN_ID=$RUN_ID ==="
log "Finished at $(date)"
} >>"$LOG_FILE" 2>&1