first commit

This commit is contained in:
dlinux-host
2025-07-23 22:56:09 -04:00
commit 123f84e703
4 changed files with 355 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
package-lock.json
.env
node_modules

133
README.md Normal file
View File

@@ -0,0 +1,133 @@
# Discord-Linux Premium Store
## Overview
Discord-Linux Premium Store is a Node.js application that integrates with Discord and Docker to manage premium user subscriptions. It automatically adjusts Docker container resources (CPU, memory, and swap) based on users' Discord roles, ensuring that premium subscribers receive upgraded resources while non-premium users are assigned default settings.
## Features
- **Discord Role-Based Resource Management**: Automatically upgrades or downgrades Docker container resources based on specific Discord roles.
- **MySQL Integration**: Maps user IDs to Discord IDs for role verification.
- **Cron Scheduling**: Periodically checks and updates container settings every 30 minutes.
- **Container Resource Management**: Supports default and upgraded resource configurations for CPU, memory, and swap.
- **Cache System**: Maintains a JSON cache (`current_upgraded.json`) of upgraded containers for tracking.
- **Error Handling**: Robust logging and error handling for database queries, Discord API calls, and Docker operations.
- **Configurable Settings**: Uses environment variables for flexible configuration.
## Prerequisites
- **Node.js**: Version 14 or higher.
- **Docker**: Installed and running with access to containers (e.g., SSH containers).
- **MySQL**: A MySQL database with a `users` table containing `uid` and `discord_id` columns.
- **Discord Bot Token**: A Discord bot with the necessary permissions and intents (`Guilds` and `GuildMembers`).
## Installation
1. **Clone the Repository**:
```bash
git clone <repository-url>
cd discord-linux-premium-store
```
2. **Install Dependencies**:
```bash
npm install
```
3. **Set Up Environment Variables**:
Create a `.env` file in the project root and configure the following:
```env
DISCORD_TOKEN=your_discord_bot_token
GUILD_ID=your_discord_guild_id
ROLE_ID_STANDARD=standard_role_id
ROLE_ID_MANUAL_UPGRADE=manual_upgrade_role_id
NO_EXPIRE_CHANNEL_IDS=comma_separated_no_expire_role_ids
SQLHOST=your_mysql_host
SQLUSER=your_mysql_user
SQLDATABASE=your_mysql_database
SQLPASSWORD=your_mysql_password
DEFAULT_CPUS=1
DEFAULT_MEMORY=512
DEFAULT_SWAP=1024
UPGRADED_CPUS=2
UPGRADED_MEMORY=1024
UPGRADED_SWAP=2048
RESET_UNKNOWN_TO_DEFAULT=true
```
4. **Set Up MySQL Database**:
Ensure the MySQL database is running and has a `users` table with at least the following schema:
```sql
CREATE TABLE users (
uid VARCHAR(255) PRIMARY KEY,
discord_id VARCHAR(255) NOT NULL
);
```
5. **Run the Application**:
```bash
node index.js
```
## How It Works
1. **Discord Bot Initialization**:
- The bot logs in using the provided `DISCORD_TOKEN` and connects to the specified Discord guild.
- Required intents: `Guilds` and `GuildMembers`.
2. **Container Checking**:
- A cron job runs every 30 minutes to check all running Docker containers.
- Only containers with names starting with `SSH` are processed.
- For each container:
- The application retrieves the associated Discord ID from the MySQL database using the container name as the `uid`.
- It inspects the container's current resource settings (CPU, memory, swap).
- It checks the user's Discord roles in the specified guild.
3. **Resource Management**:
- **Upgrading**: If the user has the `standard` or `manualUpgrade` role and the container is not already upgraded, the container is updated to use `UPGRADED_CPUS`, `UPGRADED_MEMORY`, and `UPGRADED_SWAP`.
- **Downgrading**: If the user lacks the required roles but the container is upgraded, it is reverted to `DEFAULT_CPUS`, `DEFAULT_MEMORY`, and `DEFAULT_SWAP`.
- **No-Expire Roles**: Containers for users with `noExpire` roles are tracked but not downgraded.
- **Unknown Limits**: If a container has settings that match neither default nor upgraded configurations and `RESET_UNKNOWN_TO_DEFAULT` is `true`, it is reset to default settings.
4. **Caching**:
- Upgraded containers are stored in `/var/www/html/current_upgraded.json` for tracking.
- The cache is updated after each container check cycle.
5. **Delay Between Checks**:
- A 3-second delay is added between container checks to prevent overwhelming the Docker API.
## Configuration
The application uses environment variables for configuration. Key variables include:
- **Discord Settings**:
- `DISCORD_TOKEN`: The Discord bot token.
- `GUILD_ID`: The ID of the Discord guild to monitor.
- `ROLE_ID_STANDARD`: Role ID for standard premium users.
- `ROLE_ID_MANUAL_UPGRADE`: Role ID for manually upgraded users.
- `NO_EXPIRE_CHANNEL_IDS`: Comma-separated list of role IDs for users with non-expiring upgrades.
- **MySQL Settings**:
- `SQLHOST`, `SQLUSER`, `SQLDATABASE`, `SQLPASSWORD`: MySQL connection details.
- **Docker Resource Settings**:
- `DEFAULT_CPUS`, `DEFAULT_MEMORY`, `DEFAULT_SWAP`: Default container resource limits.
- `UPGRADED_CPUS`, `UPGRADED_MEMORY`, `UPGRADED_SWAP`: Upgraded container resource limits.
- **Other**:
- `RESET_UNKNOWN_TO_DEFAULT`: Whether to reset containers with unknown limits to default settings (`true` or `false`).
- `CACHE_FILE`: Path to the JSON cache file (default: `/var/www/html/current_upgraded.json`).
## Dependencies
- `discord.js`: For interacting with the Discord API.
- `dockerode`: For managing Docker containers.
- `cron`: For scheduling container checks.
- `mysql2`: For MySQL database queries.
- `dotenv`: For loading environment variables.
- `fs`: For cache file operations.
## Usage
- Ensure the Discord bot is invited to the target guild with the necessary permissions.
- Populate the MySQL `users` table with user IDs and their corresponding Discord IDs.
- Start the application. It will:
- Log in to Discord.
- Perform an initial container check.
- Schedule recurring checks every 30 minutes.
## Logging
The application logs:
- Container inspection details (CPU, memory, swap).
- Discord role checks and user status.
- Actions taken (upgrading, downgrading, or resetting containers).
- Errors from Discord API, Docker API, or MySQL queries.
- Cache updates.

210
dlinux_premium.js Normal file
View File

@@ -0,0 +1,210 @@
const Discord = require('discord.js');
const Docker = require('dockerode');
const { CronJob } = require('cron');
const fs = require('fs').promises;
require('dotenv').config();
const client = new Discord.Client({
intents: [
Discord.GatewayIntentBits.Guilds,
Discord.GatewayIntentBits.GuildMembers
]
});
const mysql = require('mysql2/promise');
const connection = mysql.createConnection({
host: process.env.SQLHOST,
user: process.env.SQLUSER,
database: process.env.SQLDATABASE,
password: process.env.SQLPASSWORD
});
// Utility function for delay
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function getDiscordID(uid) {
if (!uid || typeof uid !== 'string') {
console.log("Invalid or undefined UID provided");
return "The user does not Exist";
}
try {
const [results] = await (await connection).query(
"SELECT discord_id FROM users WHERE uid = ?",
[uid]
);
if (results.length === 0) {
console.log(`User with UID ${uid} does not exist`);
return "The user does not Exist";
}
return results[0].discord_id;
} catch (err) {
console.log(`Error querying database for UID ${uid}: ${err.message}`);
throw err;
}
}
const docker = new Docker();
const DISCORD_TOKEN = process.env.DISCORD_TOKEN;
const GUILD_ID = process.env.GUILD_ID;
const ROLE_IDS = {
standard: process.env.ROLE_ID_STANDARD,
manualUpgrade: process.env.ROLE_ID_MANUAL_UPGRADE,
noExpire: process.env.NO_EXPIRE_CHANNEL_IDS.split(',')
};
const DEFAULT_CPUS = parseInt(process.env.DEFAULT_CPUS);
const DEFAULT_MEMORY = parseInt(process.env.DEFAULT_MEMORY) * 1024 * 1024;
const DEFAULT_SWAP = parseInt(process.env.DEFAULT_SWAP) * 1024 * 1024;
const UPGRADED_CPUS = parseInt(process.env.UPGRADED_CPUS);
const UPGRADED_MEMORY = parseInt(process.env.UPGRADED_MEMORY) * 1024 * 1024;
const UPGRADED_SWAP = parseInt(process.env.UPGRADED_SWAP) * 1024 * 1024;
const RESET_UNKNOWN_TO_DEFAULT = process.env.RESET_UNKNOWN_TO_DEFAULT === 'true';
const CACHE_FILE = '/var/www/html/current_upgraded.json';
async function updateCache(upgradedContainers) {
try {
const data = JSON.stringify(upgradedContainers, null, 2);
await fs.writeFile(CACHE_FILE, data);
console.log(` ✅ Cache updated at ${CACHE_FILE} with ${upgradedContainers.length} upgraded containers`);
} catch (err) {
console.error(` ❌ Error writing to cache file ${CACHE_FILE}: ${err.message}`);
}
}
async function checkContainers() {
console.log('\n=== Starting Container Check ===\n');
const upgradedContainers = [];
try {
const containers = await docker.listContainers({ all: false });
for (const contInfo of containers) {
const name = contInfo.Names[0].slice(1);
if (name.startsWith('SSH')) {
const container = docker.getContainer(contInfo.Id);
const inspect = await container.inspect();
const currentCpus = inspect.HostConfig.NanoCpus / 1e9;
const currentMem = inspect.HostConfig.Memory;
const currentSwap = inspect.HostConfig.MemorySwap;
console.log(`📦 Container: ${name}`);
console.log(` Current Settings:`);
console.log(` CPUs: ${currentCpus}`);
console.log(` Memory: ${currentMem / 1024 / 1024} MiB`);
console.log(` Swap: ${currentSwap / 1024 / 1024} MiB`);
const isDefault =
currentCpus === DEFAULT_CPUS &&
currentMem === DEFAULT_MEMORY &&
currentSwap === DEFAULT_SWAP;
const isUpgraded =
currentCpus === UPGRADED_CPUS &&
currentMem === UPGRADED_MEMORY &&
currentSwap === UPGRADED_SWAP;
const discordID = await getDiscordID(name);
if (discordID === "The user does not Exist") {
console.log(`User ${name} does not exist`);
continue;
}
if (isUpgraded) {
upgradedContainers.push({
containerName: name,
userId: discordID,
upgradeType: 'standard'
});
}
if (!isDefault && !isUpgraded) {
console.log(` ⚠️ Warning: Unknown limits detected!`);
console.log(` Expected Default: CPUs=${DEFAULT_CPUS}, Memory=${DEFAULT_MEMORY / 1024 / 1024} MiB, Swap=${DEFAULT_SWAP / 1024 / 1024} MiB`);
console.log(` Expected Upgraded: CPUs=${UPGRADED_CPUS}, Memory=${UPGRADED_MEMORY / 1024 / 1024} MiB, Swap=${UPGRADED_SWAP / 1024 / 1024} MiB`);
if (RESET_UNKNOWN_TO_DEFAULT) {
console.log(` 🔄 Resetting to default settings...`);
await container.update({
NanoCpus: DEFAULT_CPUS * 1e9,
Memory: DEFAULT_MEMORY,
MemorySwap: DEFAULT_SWAP
});
console.log(` ✅ Container reset to default settings.`);
} else {
console.log(` ⏭️ Skipping due to unknown limits.`);
continue;
}
}
const guild = client.guilds.cache.get(GUILD_ID);
if (!guild) {
console.log(` ❌ Guild ${GUILD_ID} not found.`);
continue;
}
try {
const member = await guild.members.fetch(discordID); // Use discordID instead of name
const hasNoExpireRole = ROLE_IDS.noExpire.some(roleId => member.roles.cache.has(roleId));
const hasStandardOrManualRole = [ROLE_IDS.standard, ROLE_IDS.manualUpgrade].some(roleId => member.roles.cache.has(roleId));
console.log(` Discord ID: ${discordID}`);
console.log(` Role Check: User ${hasNoExpireRole ? 'has no-expire role' : hasStandardOrManualRole ? 'has standard or manual upgrade role' : 'has no relevant roles'} (${[...ROLE_IDS.noExpire, ROLE_IDS.standard, ROLE_IDS.manualUpgrade].join(' or ')})`);
if (hasNoExpireRole) {
upgradedContainers.push({
containerName: name,
userId: discordID,
upgradeType: 'no-expire'
});
} else if (hasStandardOrManualRole && !isUpgraded) {
console.log(` 🔼 Upgrading container...`);
await container.update({
NanoCpus: UPGRADED_CPUS * 1e9,
Memory: UPGRADED_MEMORY,
MemorySwap: UPGRADED_SWAP
});
console.log(` ✅ Upgraded to: CPUs=${UPGRADED_CPUS}, Memory=${UPGRADED_MEMORY / 1024 / 1024} MiB, Swap=${UPGRADED_SWAP / 1024 / 1024} MiB`);
upgradedContainers.push({ containerName: name, userId: discordID, upgradeType: 'standard' });
} else if (!hasNoExpireRole && !hasStandardOrManualRole && isUpgraded) {
console.log(` 🔽 Downgrading container...`);
await container.update({
NanoCpus: DEFAULT_CPUS * 1e9,
Memory: DEFAULT_MEMORY,
MemorySwap: DEFAULT_SWAP
});
console.log(` ✅ Downgraded to: CPUs=${DEFAULT_CPUS}, Memory=${DEFAULT_MEMORY / 1024 / 1024} MiB, Swap=${DEFAULT_SWAP / 1024 / 1024} MiB`);
const index = upgradedContainers.findIndex(c => c.containerName === name);
if (index !== -1) upgradedContainers.splice(index, 1);
} else {
console.log(` ✅ No action needed. Container settings match role status.`);
}
} catch (err) {
console.log(` ❌ Error fetching member with Discord ID ${discordID}: ${err.message}`);
continue;
}
console.log('----------------------------------------');
// Add 3-second delay between container checks
await sleep(3000);
}
}
await updateCache(upgradedContainers);
console.log('\n=== Container Check Completed ===\n');
} catch (err) {
console.error(`\n❌ Error in container check: ${err.message}\n`);
}
}
client.once('ready', () => {
console.log(`✅ Logged in as ${client.user.tag}. Bot is ready.`);
checkContainers();
const job = new CronJob('*/30 * * * *', checkContainers, null, true, 'UTC');
job.start();
});
client.login(DISCORD_TOKEN);

9
package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"dependencies": {
"cron": "^4.3.2",
"discord.js": "^14.21.0",
"dockerode": "^4.0.7",
"dotenv": "^17.2.0",
"mysql2": "^3.14.2"
}
}