add tracker server, esm, refactor

This commit is contained in:
Jurgis Sakalauskas
2024-05-07 10:22:24 +03:00
parent 416f048421
commit eead7ab522
17 changed files with 1224 additions and 57 deletions
+26
View File
@@ -0,0 +1,26 @@
import { kvDataStorage } from './kv-data-storage';
import { notificationService } from './notification';
export async function dailyCheckIn() {
const lastCheckIn = kvDataStorage.get<number>('lastCheckIn');
if (lastCheckIn && Date.now() - lastCheckIn < 1000 * 60 * 60 * 23) {
return;
}
//get stats
const lastCheckInMessage = new Date(lastCheckIn || Date.now()).toLocaleString('lt-LT');
const numPortsChanged = kvDataStorage.get<number>('numPortsChanged') || 0;
const numRuns = kvDataStorage.get<number>('numRuns') || 0;
const isServiceRunning = kvDataStorage.get<boolean>('isServiceRunning') || false;
const lastRun = kvDataStorage.get<number>('lastRun');
let lastRunMessage = 'never';
if (lastRun) {
lastRunMessage = new Date(lastRun).toLocaleString('lt-LT');
}
//reset data
await kvDataStorage.set({ lastCheckIn: Date.now() , numPortsChanged: 0, numRuns: 0 });
const message = `Daily check-in. Last check-in: ${lastCheckInMessage}. Number of ports changed: ${numPortsChanged}. Number of checks: ${numRuns}. Service running: ${isServiceRunning}. Last run: ${lastRunMessage}`;
await notificationService.sendNotification(message);
}
+63
View File
@@ -0,0 +1,63 @@
import { v2 as compose } from 'docker-compose';
import { settings } from '../settings';
type IPInfo = {
ip: string,
hostname: string,
city: string,
region: string,
country: string,
loc: string,
org: string,
postal: string,
timezone: string,
}
export class DockerContainer {
constructor(private service: string, private cwd: string, private serviceName?: string) {
if (!serviceName) {
this.serviceName = service;
}
}
async up() {
return compose.upOne(this.service, { cwd: this.cwd });
}
async down() {
return compose.downOne(this.service, { cwd: this.cwd });
}
async exec(command: string) {
return await compose.exec(this.service, command, { cwd: this.cwd });
}
async getState() {
const result = await compose.ps({ cwd: this.cwd, commandOptions: [["--format", "json"]] });
return result.data.services.find((service) => service.name === this.serviceName);
}
async getIpInfo(): Promise<IPInfo | null> {
const { exitCode, out: outRaw } = await this.exec(settings.ipInfoCommand);
if (exitCode !== 0) {
return null;
}
return JSON.parse(outRaw) as IPInfo;
}
async getDefaultInterface() {
const { exitCode, out: outRaw } = await this.exec(settings.interfaceCommand);
if (exitCode !== 0) {
return null;
}
const dataRaw = outRaw.trim().replace(/\n| {2}/g, ' ').split(' ');
return {
interface: dataRaw[4],
ip: dataRaw[6],
}
}
}
+21
View File
@@ -0,0 +1,21 @@
import { DockerContainer } from './docker';
import { settings } from '../settings';
export class GluetunContainer extends DockerContainer {
constructor() {
super(
settings.dockerGluetun.service,
settings.dockerGluetun.cwd,
settings.dockerGluetun.serviceName
);
}
async setOpenFirewallPort(port: number) {
const {exitCode: exit0} = await this.exec(`iptables -A INPUT -i tun0 -p tcp --dport ${port} -j ACCEPT`);
const {exitCode: exit1} = await this.exec(`iptables -A INPUT -i tun0 -p udp --dport ${port} -j ACCEPT`);
return exit0 === 0 && exit1 === 0;
}
}
export const gluetunContainer = new GluetunContainer();
+42
View File
@@ -0,0 +1,42 @@
import { writeFile } from 'fs/promises';
import { join } from 'path';
import { existsSync, readFileSync } from 'fs';
import path from 'path';
const __dirname = path.resolve();
class KeyValueDataStorage {
private readonly data: Record<string, unknown> = {};
private readonly filePath: string;
constructor() {
this.filePath = join(__dirname, 'kv-data.json');
this.data = {};
if (existsSync(this.filePath)) {
try {
const rawData = readFileSync(this.filePath);
this.data = JSON.parse(rawData.toString());
} catch (e) {
this.data = {};
}
}
}
get<T>(key: string): T | null {
if (this.data[key]) {
return this.data[key] as T;
}
return null;
}
async set(data: Record<string, unknown>): Promise<void> {
Object.keys(data).forEach((key) => {
this.data[key] = data[key];
});
await writeFile(this.filePath, JSON.stringify(this.data, null, 2));
}
}
export const kvDataStorage = new KeyValueDataStorage();
+24
View File
@@ -0,0 +1,24 @@
import { settings } from '../settings';
class NotificationService {
async sendNotification(message: string, important = false) {
const webhook = settings.discordWebhookUrl;
if (!webhook) {
return;
}
console.log(`Posting message to Discord: ${message}`);
const response = await fetch(webhook, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ content: `${important ? '@here ' :''}${message}` }),
});
console.log(`Discord response: ${response.status}, rate limit remaining: ${response.headers.get('x-ratelimit-remaining')}, rate limit reset in: ${response.headers.get('x-ratelimit-reset-after')}s.`);
}
}
export const notificationService = new NotificationService();
+53
View File
@@ -0,0 +1,53 @@
// @ts-ignore
import TransmissionClient from 'transmission';
import { settings } from '../settings';
type TransmissionOptions = {
url?: string;
host?: string;
port?: number;
ssl?: boolean;
}
export class Transmission {
client: any;
constructor(transmissionOptions: TransmissionOptions) {
this.client = new TransmissionClient(transmissionOptions);
}
async getPort(): Promise<number> {
return new Promise((resolve, reject) => {
this.client.session((err: any, arg: any) => {
if (err) {
reject(err);
}
resolve(arg['peer-port']);
});
});
}
async setPort(port: number) {
return new Promise<void>((resolve, reject) => {
this.client.callServer({ "arguments": { "peer-port": port }, "method": "session-set" }, (err: any, arg: any) => {
if (err) {
reject(err);
}
resolve();
});
});
}
async testPortIfOpen(): Promise<boolean> {
return new Promise((resolve, reject) => {
this.client.callServer({ "method": "port-test" }, (err: any, arg: any) => {
if (err) {
reject(err);
}
resolve(arg['port-is-open']);
});
});
}
}
export const transmissionClient = new Transmission(settings.transmission);
+25
View File
@@ -0,0 +1,25 @@
import { DockerContainer } from './docker';
import { settings } from '../settings';
import { kvDataStorage } from './kv-data-storage';
import { notificationService } from './notification';
export class TransmissionContainer extends DockerContainer {
constructor() {
super(
settings.dockerTransmission.service,
settings.dockerTransmission.cwd,
settings.dockerTransmission.serviceName
);
}
async downTransmission(reason: string) {
console.log(reason);
await kvDataStorage.set({ isServiceRunning: false });
await notificationService.sendNotification(reason, true);
return await this.down();
}
}
export const transmissionContainer = new TransmissionContainer();