mirror of
https://github.com/sakaljurgis/pvpn-transmission.git
synced 2026-07-08 20:47:41 +00:00
add tracker server, esm, refactor
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user