mirror of
https://github.com/sakaljurgis/pvpn-transmission.git
synced 2026-07-08 20:47:41 +00:00
initial commit
This commit is contained in:
@@ -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,87 @@
|
||||
import { Transmission } from './transmission';
|
||||
import { DockerContainer } from './docker';
|
||||
import { settings } from './settings';
|
||||
|
||||
main().then(() => console.log('All done')).catch(console.error);
|
||||
|
||||
async function main() {
|
||||
console.log('Starting');
|
||||
const transmissionClient = new Transmission(settings.transmission);
|
||||
const transmissionContainer = new DockerContainer(
|
||||
settings.dockerTransmission.service,
|
||||
settings.dockerTransmission.cwd,
|
||||
settings.dockerTransmission.serviceName
|
||||
);
|
||||
|
||||
const containerState = await transmissionContainer.getState();
|
||||
if (containerState?.state !== 'running') {
|
||||
console.log('Transmission service is not running');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Gathering info`);
|
||||
const port = await transmissionClient.getPort();
|
||||
console.log(`Port: ${port}`);
|
||||
const portOpen = await transmissionClient.testPortIfOpen();
|
||||
console.log(`Port is open: ${portOpen}`);
|
||||
const defaultInterface = await transmissionContainer.getDefaultInterface();
|
||||
console.log(`Default interface: ${defaultInterface?.interface} Internal ip: ${defaultInterface?.ip}`);
|
||||
const ipInfo = await transmissionContainer.getIpInfo();
|
||||
console.log(`External ip: ${ipInfo?.ip} Country: ${ipInfo?.country}`);
|
||||
|
||||
if (!defaultInterface || !ipInfo) {
|
||||
console.log('Could not get default interface or ip info');
|
||||
return;
|
||||
}
|
||||
|
||||
const countryOK = !settings.disallowedCountries.some((country) => ipInfo.country.includes(country));
|
||||
const internalIpOK = !settings.disallowedIntIps.some((ip) => defaultInterface.ip.includes(ip));
|
||||
const externalIpOK = !settings.disallowedExtIps.some((ip) => ipInfo.ip.includes(ip));
|
||||
const interfaceOK = settings.allowedInterfaces.some((inter) => defaultInterface.interface.includes(inter));
|
||||
|
||||
console.log(`Country OK: ${countryOK} Internal IP OK: ${internalIpOK} External IP OK: ${externalIpOK} Interface OK: ${interfaceOK}`);
|
||||
|
||||
if (!countryOK || !internalIpOK || !externalIpOK || !interfaceOK) {
|
||||
console.log('Shutting down transmission service');
|
||||
//await dockerContainer.down();
|
||||
return;
|
||||
}
|
||||
|
||||
if (portOpen) {
|
||||
console.log(`Port ${port} is open`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Port ${port} is not open. Fetching new port.`);
|
||||
|
||||
//fetch new port
|
||||
const request = await fetch(`https://connect.pvdatanet.com/v3/Api/port?ip[]=${defaultInterface.ip}`)
|
||||
const data = await request.json() as {status: string, supported: boolean}
|
||||
|
||||
console.log(`Data received: ${JSON.stringify(data)}`);
|
||||
|
||||
if (!data.status || !data.supported) {
|
||||
console.log('Could not fetch new port');
|
||||
return;
|
||||
}
|
||||
|
||||
const newPort= Number(data.status.split(" ")[1]);
|
||||
|
||||
if (!newPort) {
|
||||
console.log('Could not parse new port');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Setting port to ${newPort}`);
|
||||
await transmissionClient.setPort(newPort);
|
||||
|
||||
console.log('Port set');
|
||||
const newPortOpen = await transmissionClient.testPortIfOpen();
|
||||
console.log(`Port ${newPort} is open: ${newPortOpen}`);
|
||||
|
||||
if (!newPortOpen) {
|
||||
console.log('Port is not open, Shutting down transmission service');
|
||||
//await dockerContainer.down();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
export const settings = {
|
||||
ipInfoCommand: `curl ${process.env.IP_INFO_URL ?? 'https://ipinfo.io'}`,
|
||||
interfaceCommand: 'ip route get 8.8.8.8',
|
||||
transmission: {
|
||||
host: process.env.TRANSMISSION_HOST,
|
||||
port: Number(process.env.TRANSMISSION_PORT),
|
||||
},
|
||||
dockerTransmission: {
|
||||
cwd: process.env.DOCKER_TRANSMISSION_CWD ?? __dirname,
|
||||
service: process.env.DOCKER_TRANSMISSION_SERVICE ?? 'transmission',
|
||||
serviceName: process.env.DOCKER_TRANSMISSION_SERVICE_NAME ?? process.env.DOCKER_TRANSMISSION_SERVICE,
|
||||
},
|
||||
allowedInterfaces: process.env.ALLOWED_INTERFACES?.split(',') ?? [],
|
||||
disallowedCountries: process.env.DISALLOWED_COUNTRIES?.split(',') ?? [],
|
||||
disallowedIntIps: process.env.DISALLOWED_INTERNAL_IPS?.split(',') ?? [],
|
||||
disallowedExtIps: process.env.DISALLOWED_EXT_IPS?.split(',') ?? [],
|
||||
} as const;
|
||||
@@ -0,0 +1,50 @@
|
||||
// @ts-ignore
|
||||
import TransmissionClient from 'transmission';
|
||||
|
||||
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']);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user