Initial commit

This commit is contained in:
jkortis committed 2026-09-30 23:06:22 -04:00
commit fafaef850d
10 files changed
+1979

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
node_modules/
.env
.autoscale_bak/
.DS_Store
admin-users.json
user-mappings.json
+59
View File
@@ -0,0 +1,59 @@
# Autoscale Bot
A self‑hosted Discord bot for managing a Headscale instance.
## Features
- Create, delete and manage Headscale users
- Link Discord accounts to Headscale users
- Register and rename nodes
- Ticketing system for support
- **Auto‑Updater**: Checks the GitHub repository for new releases, notifies the bot owner, and offers an in‑Discord update prompt.
## Installation
```bash
# Clone the repository
git clone https://github.com/jadonkortislolz-wq/autoscale.git
cd autoscale
npm install
```
Create a `.env` file (do **not** commit it) with the required variables:
```
BOT_TOKEN=your_discord_bot_token
HEADSCALE_URL=https://your-headscale-url
HEADSCALE_CONTAINER=headscale
BOT_OWNER_ID=your_discord_user_id
# optional tickets config
TICKET_CATEGORY_ID=your_category_id
TICKET_SUPPORT_ROLE_ID=your_support_role_id
```
Start the bot (PM2 recommended):
```bash
npm start # or pm2 start ecosystem.config.js
```
## Usage
All commands are prefixed with `!hs` or `!headscale`.
- `!hs create <username>` – Create a Headscale user
- `!hs discord <username>` – Link your Discord account to a Headscale user
- `!hs link <nodekey>` – Register a device
- `!hs register` – Get the Tailscale registration command
- `!hs list` – List all nodes and IPs
- `!hs rename <id> <new_name>` – Rename your node
- `!hs info` – Bot information
- Ticket commands: `!hs ticket panel`, `!hs ticket close`
- Admin commands: `!hs restart`, `!hs addadmin @user`, etc.
- `!hs update` – Check for a new version and update the bot
## Auto‑Updater
The bot automatically checks the GitHub repository for new releases every hour.
1. If a newer version is found, the bot owner receives a DM:
> "A new version **vX.Y.Z** is available. Run `!hs update` to apply it."
2. Running `!hs update` pulls the latest code, installs dependencies, and restarts the bot.
## Contributing
Feel free to open issues or submit pull requests. Ensure you do not commit any secrets.
## License
ISC
+37
View File
@@ -0,0 +1,37 @@
# Usage Guide for Autoscale Bot
## Command Prefixes
The bot listens for the following prefixes:
- `!hs`
- `!headscale`
## User Commands
| Command | Description |
| ------- | ----------- |
| `!hs create <username>` | Create a Headscale user |
| `!hs discord <username>` | Link your Discord account to a Headscale user |
| `!hs link <nodekey>` | Register a device |
| `!hs register` | Get the Tailscale registration command |
| `!hs list` | List all nodes and their IPs |
| `!hs rename <id> <new_name>` | Rename your node |
| `!hs info` | Show bot information |
| `!hs ticket panel` | Post the ticket panel (admin only) |
| `!hs ticket close` | Close a ticket (admin/support only) |
| `!hs restart` | Restart the bot (admin only) |
| `!hs update` | Check for a new version and update the bot |
## Admin Commands
- `!hs addadmin @user` – Grant admin privileges (owner only)
- `!hs removeadmin @user` – Revoke admin privileges (owner only)
- `!hs listadmins` – List all admin users
- `!hs deleteuser <username>` – Delete a Headscale user
- `!hs removenode <id>` – Force‑remove a node
- `!hs preauth <username>` – Generate a pre‑auth key
## Auto‑Updater
The `!hs update` command triggers the auto‑updater. If a newer release is found on GitHub, the bot will:
1. Pull the latest code.
2. Install any new dependencies.
3. Restart itself.
Make sure the bot has write permissions to its directory.
+16
View File
@@ -0,0 +1,16 @@
module.exports = {
apps : [{
name: 'autoscale-bot',
script: 'index.js',
instances: 1,
exec_mode: 'fork',
autorestart: true,
watch: false,
max_memory_restart: '1G',
restart_delay: 5000,
exp_backoff_restart_delay: 100,
env: {
NODE_ENV: 'production',
}
}]
};
+1478
View File
File diff suppressed because it is too large. Load diff
+299
View File
@@ -0,0 +1,299 @@
{
"name": "autoscale",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "autoscale",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"discord.js": "^14.25.1",
"dotenv": "^17.3.1"
}
},
"node_modules/@discordjs/builders": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz",
"integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==",
"dependencies": {
"@discordjs/formatters": "^0.6.2",
"@discordjs/util": "^1.2.0",
"@sapphire/shapeshift": "^4.0.0",
"discord-api-types": "^0.38.33",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.4",
"tslib": "^2.6.3"
},
"engines": {
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/collection": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
"engines": {
"node": ">=16.11.0"
}
},
"node_modules/@discordjs/formatters": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
"integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
"dependencies": {
"discord-api-types": "^0.38.33"
},
"engines": {
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/rest": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz",
"integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==",
"dependencies": {
"@discordjs/collection": "^2.1.1",
"@discordjs/util": "^1.1.1",
"@sapphire/async-queue": "^1.5.3",
"@sapphire/snowflake": "^3.5.3",
"@vladfrangu/async_event_emitter": "^2.4.6",
"discord-api-types": "^0.38.16",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.21.3"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/util": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
"integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
"dependencies": {
"discord-api-types": "^0.38.33"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/ws": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
"dependencies": {
"@discordjs/collection": "^2.1.0",
"@discordjs/rest": "^2.5.1",
"@discordjs/util": "^1.1.0",
"@sapphire/async-queue": "^1.5.2",
"@types/ws": "^8.5.10",
"@vladfrangu/async_event_emitter": "^2.2.4",
"discord-api-types": "^0.38.1",
"tslib": "^2.6.2",
"ws": "^8.17.0"
},
"engines": {
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@sapphire/async-queue": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@sapphire/shapeshift": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"lodash": "^4.17.21"
},
"engines": {
"node": ">=v16"
}
},
"node_modules/@sapphire/snowflake": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
"integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@types/node": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@vladfrangu/async_event_emitter": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
"integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/discord-api-types": {
"version": "0.38.42",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.42.tgz",
"integrity": "sha512-qs1kya7S84r5RR8m9kgttywGrmmoHaRifU1askAoi+wkoSefLpZP6aGXusjNw5b0jD3zOg3LTwUa3Tf2iHIceQ=="
},
"node_modules/discord.js": {
"version": "14.25.1",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz",
"integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
"dependencies": {
"@discordjs/builders": "^1.13.0",
"@discordjs/collection": "1.5.3",
"@discordjs/formatters": "^0.6.2",
"@discordjs/rest": "^2.6.0",
"@discordjs/util": "^1.2.0",
"@discordjs/ws": "^1.2.3",
"@sapphire/snowflake": "3.5.3",
"discord-api-types": "^0.38.33",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.21.3"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/dotenv": {
"version": "17.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
"integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
},
"node_modules/lodash.snakecase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
},
"node_modules/magic-bytes.js": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz",
"integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg=="
},
"node_modules/ts-mixer": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/undici": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
"engines": {
"node": ">=18.17"
}
},
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"name": "autoscale",
"version": "1.0.0",
"main": "index.js",
"repository": "https://github.com/your-github-username/autoscale",
"scripts": {
"start": "node index.js",
"update": "node updater.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"node-fetch": "^3.3.2",
"discord.js": "^14.25.1",
"dotenv": "^17.3.1"
},
"description": ""
}
+3
View File
@@ -0,0 +1,3 @@
{
"ticketCounter": 0
}
+1
View File
@@ -0,0 +1 @@
{}
+60
View File
@@ -0,0 +1,60 @@
// updater.js - Auto-updater for Autoscale Bot
// Checks the GitHub repository for a newer release and updates the bot.
// Requires node-fetch (added to dependencies).
const fetch = require('node-fetch');
const { execSync } = require('child_process');
const fs = require('fs');
// Configuration - replace with actual repo owner/name if different.
const REPO = 'your-github-username/autoscale';
const GITHUB_API = `https://api.github.com/repos/${REPO}/releases/latest`;
function getCurrentVersion() {
try {
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
return pkg.version;
} catch (e) {
console.error('Failed to read package.json:', e);
return null;
}
}
async function getLatestRelease() {
try {
const resp = await fetch(GITHUB_API, { headers: { 'User-Agent': 'autoscale-updater' } });
if (!resp.ok) throw new Error(`GitHub API error ${resp.status}`);
const data = await resp.json();
return { version: data.tag_name.replace(/^v/, ''), tarball_url: data.tarball_url };
} catch (e) {
console.error('Failed to fetch latest release:', e);
return null;
}
}
async function runUpdate() {
const current = getCurrentVersion();
const latest = await getLatestRelease();
if (!current || !latest) return;
if (current === latest.version) {
console.log(`✅ Already up‑to‑date (v${current}).`);
return;
}
console.log(`🔄 Updating from v${current} to v${latest.version}…`);
try {
// Pull latest code from git (assumes the repo is a git clone)
execSync('git fetch --all', { stdio: 'inherit' });
execSync(`git checkout v${latest.version}`, { stdio: 'inherit' });
// Install any new dependencies
execSync('npm install', { stdio: 'inherit' });
console.log('✅ Update complete. Restart the bot (e.g., pm2 restart autoscale).');
} catch (e) {
console.error('Update failed:', e);
}
}
if (require.main === module) {
runUpdate();
}