52 Commits

Author SHA1 Message Date
67ed81fd2e main: Added info link to domains in admin page
All checks were successful
Build Docker / Build Bot (push) Successful in 21s
Build Docker / Build Master (push) Successful in 27s
2023-08-25 18:22:20 +10:00
2ef8e2c38d main: Fixed reused function name
All checks were successful
Build Docker / Build Bot (push) Successful in 24s
Build Docker / Build Master (push) Successful in 26s
2023-08-25 18:17:12 +10:00
c8eafb6406 main: Updated domain info page
All checks were successful
Build Docker / Build Bot (push) Successful in 27s
Build Docker / Build Master (push) Successful in 28s
2023-08-25 18:13:36 +10:00
73d11a0f76 main: Fixed customer side
All checks were successful
Build Docker / Build Bot (push) Successful in 26s
Build Docker / Build Master (push) Successful in 28s
2023-08-25 18:01:49 +10:00
3628e0a4ac main: Added customer self service
All checks were successful
Build Docker / Build Bot (push) Successful in 22s
Build Docker / Build Master (push) Successful in 28s
2023-08-25 17:57:51 +10:00
20dbf6c0df main: Fixed a few bits of the admin panel
All checks were successful
Build Docker / Build Bot (push) Successful in 21s
Build Docker / Build Master (push) Successful in 27s
2023-08-25 17:52:17 +10:00
8a2cad16ec main: Fixed typo
All checks were successful
Build Docker / Build Master (push) Successful in 27s
Build Docker / Build Bot (push) Successful in 26s
2023-08-25 17:48:42 +10:00
48330f7ef9 main: Added licence and site creation for admin
All checks were successful
Build Docker / Build Bot (push) Successful in 24s
Build Docker / Build Master (push) Successful in 27s
2023-08-25 17:45:46 +10:00
7703bfa4d5 main: Catch for offline worker
All checks were successful
Build Docker / Build Bot (push) Successful in 22s
Build Docker / Build Master (push) Successful in 28s
2023-08-25 17:31:32 +10:00
ba96f9b84b main: Added new worker admin
All checks were successful
Build Docker / Build Master (push) Successful in 28s
Build Docker / Build Bot (push) Successful in 27s
2023-08-25 17:18:37 +10:00
0a33a6150d main: Fixed worker info
All checks were successful
Build Docker / Build Bot (push) Successful in 26s
Build Docker / Build Master (push) Successful in 27s
2023-08-25 17:13:34 +10:00
c9be5cedcb main: Initial admin page
All checks were successful
Build Docker / Build Bot (push) Successful in 27s
Build Docker / Build Master (push) Successful in 29s
2023-08-25 17:07:44 +10:00
1fb2493848 main: Updated form
All checks were successful
Build Docker / Build Bot (push) Successful in 20s
Build Docker / Build Master (push) Successful in 26s
2023-08-25 16:51:19 +10:00
9853214d83 main: Add failed login page
All checks were successful
Build Docker / Build Bot (push) Successful in 22s
Build Docker / Build Master (push) Successful in 28s
2023-08-25 16:47:22 +10:00
843d2d12a0 main: Fixed login typo
All checks were successful
Build Docker / Build Bot (push) Successful in 25s
Build Docker / Build Master (push) Successful in 26s
2023-08-25 16:38:34 +10:00
8f962804a4 main: Login fixes
All checks were successful
Build Docker / Build Bot (push) Successful in 25s
Build Docker / Build Master (push) Successful in 27s
2023-08-25 16:36:53 +10:00
9e485265af main: Added the post method
All checks were successful
Build Docker / Build Bot (push) Successful in 24s
Build Docker / Build Master (push) Successful in 26s
2023-08-25 16:32:50 +10:00
83bde4b218 main: Added admin page
All checks were successful
Build Docker / Build Bot (push) Successful in 21s
Build Docker / Build Master (push) Successful in 27s
2023-08-25 16:29:25 +10:00
52fca38af9 main: Added return to dash
All checks were successful
Build Docker / Build Bot (push) Successful in 20s
Build Docker / Build Master (push) Successful in 26s
2023-08-25 16:07:25 +10:00
4db44bb99e actions: Try run on feature branch
All checks were successful
Build Docker / Build Bot (push) Successful in 26s
Build Docker / Build Master (push) Successful in 27s
2023-08-25 16:03:45 +10:00
0e2ad55eb7 Revert "actions: Attempt to fix"
This reverts commit 0c20572369.
2023-08-25 16:01:30 +10:00
0c20572369 actions: Attempt to fix 2023-08-25 15:53:55 +10:00
d2e31bb684 main: Initial dashboard 2023-08-25 15:51:57 +10:00
bbe70647d7 docs: Added info on free mode
All checks were successful
Build Docker / Build Master (push) Successful in 23s
Build Docker / Build Bot (push) Successful in 27s
2023-08-25 14:26:59 +10:00
d6dffc0464 main: Small fixed for licences
All checks were successful
Build Docker / Build Master (push) Successful in 23s
Build Docker / Build Bot (push) Successful in 25s
2023-08-25 14:20:58 +10:00
fb9295c260 bot: Made licence an optional arg
All checks were successful
Build Docker / Build Master (push) Successful in 20s
Build Docker / Build Bot (push) Successful in 24s
2023-08-25 14:17:12 +10:00
c4ae4561e3 main: Cleared up invalid licence response
All checks were successful
Build Docker / Build Bot (push) Successful in 25s
Build Docker / Build Master (push) Successful in 26s
2023-08-25 14:13:29 +10:00
2902624637 bot: Fix to enable free licences of FREE MODE
All checks were successful
Build Docker / Build Master (push) Successful in 21s
Build Docker / Build Bot (push) Successful in 25s
2023-08-25 14:09:26 +10:00
5049796d07 bot: Added free mode
All checks were successful
Build Docker / Build Master (push) Successful in 21s
Build Docker / Build Bot (push) Successful in 24s
2023-08-25 14:01:36 +10:00
5efe4860fc actions: Fix syntax
All checks were successful
Build Docker for Release / Build Master (push) Successful in 24s
Build Docker for Release / Build Bot (push) Successful in 24s
Build Docker / Build Master (push) Successful in 29s
Build Docker / Build Bot (push) Successful in 27s
2023-08-25 13:55:31 +10:00
499a7e348b Merge branch 'develop' into main
All checks were successful
Build Docker / Build Master (push) Successful in 23s
Build Docker / Build Bot (push) Successful in 21s
2023-08-25 13:53:58 +10:00
ed94263050 actions: Don't run dev action when tag is pushed
All checks were successful
Build Docker / Build Master (push) Successful in 25s
Build Docker / Build Bot (push) Successful in 23s
2023-08-25 13:53:28 +10:00
4841344d63 actions: Finished adding release
All checks were successful
Build Docker / Build Master (push) Successful in 26s
Build Docker / Build Bot (push) Successful in 26s
2023-08-25 13:48:39 +10:00
51bcdda5d4 actions: Initial release job
All checks were successful
Build Docker / Build Bot (push) Successful in 21s
Build Docker / Build Master (push) Successful in 23s
Build Docker for Release / TEST (push) Successful in 5s
2023-08-25 13:44:13 +10:00
b38de6ad52 actions: Use same numbering on bot deployment
All checks were successful
Build Docker / Build Bot (push) Successful in 21s
Build Docker / Build Master (push) Successful in 23s
2023-08-25 13:39:26 +10:00
b642cf7269 actions: Try using the actions run variable
All checks were successful
Build Docker / Build Master (push) Successful in 24s
Build Docker / Build Bot (push) Successful in 22s
2023-08-25 13:37:24 +10:00
3e3c2fe61e actions: Test 4 change numbering
All checks were successful
Build Docker / Build Bot (push) Successful in 23s
Build Docker / Build Master (push) Successful in 25s
2023-08-25 13:28:06 +10:00
04edb8b456 actions: Test 3 change numbering
Some checks failed
Build Docker / Build Master (push) Failing after 21s
Build Docker / Build Bot (push) Successful in 27s
2023-08-25 13:26:41 +10:00
45c1ea3557 actions: Test 2 change numbering
All checks were successful
Build Docker / Build Bot (push) Successful in 22s
Build Docker / Build Master (push) Successful in 25s
2023-08-25 13:25:00 +10:00
778c1b3d92 actions: Change numbering
All checks were successful
Build Docker / Build Bot (push) Successful in 28s
Build Docker / Build Master (push) Successful in 29s
2023-08-25 13:21:24 +10:00
19806b7b1b Merge branch 'feature/send_info_on_ready' into develop
All checks were successful
Build Docker / Build Master (push) Successful in 26s
Build Docker / Build Bot (push) Successful in 26s
2023-08-25 12:59:19 +10:00
908a4e0422 bot: Check for install every 5 seconds
All checks were successful
Build Docker / Build Master (push) Successful in 22s
Build Docker / Build Bot (push) Successful in 26s
2023-08-25 12:54:31 +10:00
d217309e74 bot: Fixed check if site ready function args
All checks were successful
Build Docker / Build Master (push) Successful in 21s
Build Docker / Build Bot (push) Successful in 24s
2023-08-25 12:51:58 +10:00
ce8827ed97 worker: Install missing update repo
All checks were successful
Build Docker / Build Bot (push) Successful in 24s
Build Docker / Build Master (push) Successful in 25s
2023-08-25 12:49:29 +10:00
3b914abf7a bot: Send a dm with the site info when the site is ready
All checks were successful
Build Docker / Build Master (push) Successful in 23s
Build Docker / Build Bot (push) Successful in 26s
2023-08-25 12:46:08 +10:00
3266dbafa9 Merge branch 'develop' into main
All checks were successful
Build Docker / Build Master (push) Successful in 26s
Build Docker / Build Bot (push) Successful in 25s
2023-08-25 12:36:40 +10:00
d7c6e1cf70 docs: Cleared up some info
All checks were successful
Build Docker / Build Master (push) Successful in 24s
Build Docker / Build Bot (push) Successful in 22s
2023-08-25 12:36:23 +10:00
dbbb60cab8 bot: Add private ip to new worker command
All checks were successful
Build Docker / Build Bot (push) Successful in 27s
Build Docker / Build Master (push) Successful in 27s
2023-08-25 12:26:26 +10:00
f54d805371 main: Initial private ip for api 2023-08-25 12:25:23 +10:00
b56ece2216 Merge branch 'develop' into main
All checks were successful
Build Docker / Build Bot (push) Successful in 25s
Build Docker / Build Master (push) Successful in 25s
2023-08-25 12:14:06 +10:00
0383b47b8e Merge branch 'develop' into main
All checks were successful
Build Docker / Build Bot (push) Successful in 21s
Build Docker / Build Master (push) Successful in 23s
2023-08-24 18:36:46 +10:00
afd5286ef5 Merge branch 'develop' into main
All checks were successful
Build Docker / Build Master (push) Successful in 22s
Build Docker / Build Bot (push) Successful in 20s
2023-08-24 18:15:16 +10:00
6 changed files with 585 additions and 40 deletions

View File

@@ -1,6 +1,12 @@
name: Build Docker name: Build Docker
run-name: Build Docker Images run-name: Build Docker Images
on: [push] on:
push:
branches:
- '*'
- '*/*'
tags-ignore:
- '*'
jobs: jobs:
Build Master: Build Master:
@@ -21,10 +27,12 @@ jobs:
run : | run : |
cd master cd master
echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin
tag_num=$(git rev-parse --short HEAD)
echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
tag=${tag//\//-} tag=${tag//\//-}
tag_num=${GITHUB_RUN_NUMBER}
echo "tag_num=$tag_num"
if [[ "$tag" == "main" ]]; then if [[ "$tag" == "main" ]]; then
tag="latest" tag="latest"
else else
@@ -60,10 +68,10 @@ jobs:
run : | run : |
cd discord-bot cd discord-bot
echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin
tag_num=$(git rev-parse --short HEAD)
echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
tag=${tag//\//-} tag=${tag//\//-}
tag_num=${GITHUB_RUN_NUMBER}
if [[ "$tag" == "main" ]]; then if [[ "$tag" == "main" ]]; then
tag="latest" tag="latest"
else else
@@ -75,4 +83,3 @@ jobs:
docker push git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag_num docker push git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag_num
docker tag hnshosting-bot:$tag_num git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag docker tag hnshosting-bot:$tag_num git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag
docker push git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag docker push git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag

View File

@@ -0,0 +1,60 @@
name: Build Docker for Release
run-name: Build Docker Images
on:
push:
tags:
- '*'
jobs:
Build Master:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Docker
run : |
apt-get install ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install docker-ce-cli -y
- name: Build Docker image
run : |
cd master
echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin
tag=${GITHUB_REF#refs/tags/}
docker build -t hnshosting-master:release-$tag .
docker tag hnshosting-master:release-$tag git.woodburn.au/nathanwoodburn/hnshosting-master:release-$tag
docker push git.woodburn.au/nathanwoodburn/hnshosting-master:release-$tag
docker tag hnshosting-master:release-$tag git.woodburn.au/nathanwoodburn/hnshosting-master:release
docker push git.woodburn.au/nathanwoodburn/hnshosting-master:release
Build Bot:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Docker
run : |
apt-get install ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install docker-ce-cli -y
- name: Build Docker image
run : |
cd discord-bot
echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin
tag=${GITHUB_REF#refs/tags/}
docker build -t hnshosting-bot:release-$tag .
docker tag hnshosting-bot:release-$tag git.woodburn.au/nathanwoodburn/hnshosting-bot:release-$tag
docker push git.woodburn.au/nathanwoodburn/hnshosting-bot:release-$tag
docker tag hnshosting-bot:release-$tag git.woodburn.au/nathanwoodburn/hnshosting-bot:release
docker push git.woodburn.au/nathanwoodburn/hnshosting-bot:release

View File

@@ -18,13 +18,17 @@ The bot will be used to provide an easier way to manage the master server.
| Red Connections | Secured by VPN or over LAN ONLY. (NOT API SECURED) | | Red Connections | Secured by VPN or over LAN ONLY. (NOT API SECURED) |
| Yellow Connections | HTTP/HTTPS public traffic | | Yellow Connections | HTTP/HTTPS public traffic |
## Features
- [x] Add new worker server to master server pool
- [x] Create wordpress site on random worker server
- [x] Optional Free mode (see Bot section)
## Usage ## Usage
After installing the master and discord bot you can use the following commands (as bot owner). After installing the master and discord bot you can use the following commands (as bot owner).
``` ```
/addworker <ip> <name> | add a worker to the master server pool (Make sure the master can access port 5000 on the worker, and don't allow anyone else to access it) /addworker <ip> <private ip> <name> | add a worker to the master server pool
/listworkers | list all workers /listworkers | list all workers
/licence | Creates a licence key (valid for 1 wp site) /licence | Creates a licence key (valid for 1 wp site)
``` ```
@@ -43,7 +47,7 @@ General commands (as anyone)
Docker is the easiest way to install the master server. Docker is the easiest way to install the master server.
```sh ```sh
docker run -d -p 5000:5000 -e LICENCE-API=your-api-key -e WORKER_KEY=your-api-key --name hnshosting-master git.woodburn.au/nathanwoodburn/hnshosting-master:latest -v ./data:/data docker run -d -p 5000:5000 -e LICENCE_KEY=your-api-key -e WORKER_KEY=your-api-key -e ADMIN_KEY=admin-key --name hnshosting-master git.woodburn.au/nathanwoodburn/hnshosting-master:latest -v ./data:/data
``` ```
You can also mount a docker volume to /data to store the files instead of mounting a host directory. You can also mount a docker volume to /data to store the files instead of mounting a host directory.
@@ -90,9 +94,14 @@ screen -dmS hnshosting-worker python3 main.py
``` ```
Add worker to master server pool: Add worker to master server pool:
The master server will need to be able to access port 5000 on the worker server over the PRIVATE ip. This is not secured by the api key so make sure you don't allow anyone else to access it.
```sh ```sh
curl -X POST http://master-server-ip:5000/add-worker?worker=worker-name&ip=worker-server-ip -H "key: api-key" curl -X POST http://master-server-ip:5000/add-worker?worker=worker-name&ip=worker-server-ip&priv=worker-server-private-ip -H "key: api-key"
```
Alternatively you can use the discord bot to add the worker to the master server pool.
```
/addworker <ip> <private ip> <name>
``` ```
## Discord bot install ## Discord bot install
@@ -101,3 +110,9 @@ Docker install
```sh ```sh
docker run -d -e MASTER_IP=<MASTER SERVER IP> -e DISCORD_TOKEN=<YOUR-BOT-TOKEN> -e LICENCE_KEY=your-api-key -e WORKER_KEY=your-api-key --name hnshosting-bot git.woodburn.au/nathanwoodburn/hnshosting-bot:latest docker run -d -e MASTER_IP=<MASTER SERVER IP> -e DISCORD_TOKEN=<YOUR-BOT-TOKEN> -e LICENCE_KEY=your-api-key -e WORKER_KEY=your-api-key --name hnshosting-bot git.woodburn.au/nathanwoodburn/hnshosting-bot:latest
``` ```
Enable the free mode by setting the following environment variable.
This will allow you to create a wordpress site without using a licence key using the /createsite command.
```
FREE_MODE: true
```

View File

@@ -3,6 +3,7 @@ from dotenv import load_dotenv
import discord import discord
from discord import app_commands from discord import app_commands
import requests import requests
import asyncio
load_dotenv() load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN') TOKEN = os.getenv('DISCORD_TOKEN')
@@ -12,18 +13,23 @@ Master_Port = os.getenv('MASTER_PORT')
if Master_Port == None: if Master_Port == None:
Master_Port = "5000" Master_Port = "5000"
FREE_LICENCE = os.getenv('FREE_LICENCE') FREE_LICENCE = os.getenv('FREE_MODE')
if FREE_LICENCE == None: if FREE_LICENCE == None:
FREE_LICENCE = False FREE_LICENCE = False
else:
if FREE_LICENCE.lower() == "true":
FREE_LICENCE = True
else:
FREE_LICENCE = False
intents = discord.Intents.default() intents = discord.Intents.default()
client = discord.Client(intents=intents) client = discord.Client(intents=intents)
tree = app_commands.CommandTree(client) tree = app_commands.CommandTree(client)
@tree.command(name="addworker", description="Adds a worker to the master server") @tree.command(name="addworker", description="Adds a worker to the master server")
async def addworker(ctx, ip: str, name: str): async def addworker(ctx, ip: str,privateip: str, name: str):
if ctx.user.id == ADMINID: if ctx.user.id == ADMINID:
r = requests.post(f"http://{Master_IP}:{Master_Port}/add-worker?worker={name}&ip={ip}",headers={"key":os.getenv('WORKER_KEY')}) r = requests.post(f"http://{Master_IP}:{Master_Port}/add-worker?worker={name}&ip={ip}&priv={privateip}",headers={"key":os.getenv('WORKER_KEY')})
if r.status_code == 200: if r.status_code == 200:
await ctx.response.send_message(f"Worker {name} added to the master server",ephemeral=True) await ctx.response.send_message(f"Worker {name} added to the master server",ephemeral=True)
else: else:
@@ -49,7 +55,6 @@ async def listworkers(ctx):
@tree.command(name="licence", description="Gets a licence key") @tree.command(name="licence", description="Gets a licence key")
async def license(ctx): async def license(ctx):
if ctx.user.id != ADMINID: if ctx.user.id != ADMINID:
if FREE_LICENCE == False: # If free licences are enabled then anyone can get a licence
await ctx.response.send_message("You do not have permission to use this command",ephemeral=True) await ctx.response.send_message("You do not have permission to use this command",ephemeral=True)
return return
@@ -64,12 +69,37 @@ async def license(ctx):
await ctx.response.send_message(f"Error getting license\n" + r.text,ephemeral=True) await ctx.response.send_message(f"Error getting license\n" + r.text,ephemeral=True)
@tree.command(name="createsite", description="Create a new WordPress site") @tree.command(name="createsite", description="Create a new WordPress site")
async def createsite(ctx, domain: str, licence: str): async def createsite(ctx, domain: str, licence: str = None):
if FREE_LICENCE == True: # If free licences are enabled then auto generate a licence
r = requests.post(f"http://{Master_IP}:{Master_Port}/add-licence",headers={"key":os.getenv('LICENCE_KEY')})
if r.status_code == 200:
json = r.json()
if json['success'] == "true":
licence = json['licence_key']
else:
await ctx.response.send_message(f"Error getting license\n" + json['error'])
return
r = requests.post(f"http://{Master_IP}:{Master_Port}/new-site?domain={domain}",headers={"key":licence}) r = requests.post(f"http://{Master_IP}:{Master_Port}/new-site?domain={domain}",headers={"key":licence})
if r.status_code == 200: if r.status_code == 200:
json = r.json() json = r.json()
if json['success'] == "true": if json['success'] == "true":
await ctx.response.send_message(f"Site {domain} creating...\nPlease wait a few minutes and then send /siteinfo domain:{domain}") await ctx.response.send_message(f"Site {domain} creating...\nI'll send you a message when it's ready")
ready = False
while ready == False:
ready = await check_site_ready(domain)
if ready == False:
await asyncio.sleep(5)
r = requests.get(f"http://{Master_IP}:{Master_Port}/site-info?domain={domain}")
json = r.json()
if json['success'] == "true":
await ctx.user.send(f"Site {domain} is ready!\nHere is the site info for {json['domain']}\nA: `{json['ip']}`\nTLSA: `{json['tlsa']}`\nMake sure you put the TLSA in either `_443._tcp.{domain}` or `*.{domain}`")
else:
await ctx.user.send(f"Error getting site info\n" + json['error'])
else: else:
await ctx.response.send_message(f"Error creating site\n" + json['error']) await ctx.response.send_message(f"Error creating site\n" + json['error'])
else: else:
@@ -88,6 +118,17 @@ async def siteinfo(ctx, domain: str):
else: else:
await ctx.response.send_message(f"Error getting site info\n" + r.text) await ctx.response.send_message(f"Error getting site info\n" + r.text)
async def check_site_ready(domain):
r = requests.get(f"http://{Master_IP}:{Master_Port}/site-info?domain={domain}")
if r.status_code == 200:
json = r.json()
if json['success'] == "true":
return True
else:
return False
else:
return False
# When the bot is ready # When the bot is ready
@client.event @client.event
async def on_ready(): async def on_ready():

View File

@@ -1,4 +1,4 @@
from flask import Flask, request, jsonify from flask import Flask, make_response, redirect, request, jsonify
import dotenv import dotenv
import os import os
import requests import requests
@@ -10,12 +10,14 @@ dotenv.load_dotenv()
app = Flask(__name__) app = Flask(__name__)
logins = []
# API add license key (requires API key in header) # API add license key (requires API key in header)
@app.route('/add-licence', methods=['POST']) @app.route('/add-licence', methods=['POST'])
def add_license(): def add_license():
# Get API header # Get API header
api_key = request.headers.get('key') api_key = request.headers.get('key')
if api_key != os.getenv('LICENCE-API'): if api_key != os.getenv('LICENCE_KEY'):
return jsonify({'error': 'Invalid API key', 'success': 'false'}) return jsonify({'error': 'Invalid API key', 'success': 'false'})
# Generate licence key # Generate licence key
@@ -37,14 +39,22 @@ def new_site():
# Verify both API key and domain exist # Verify both API key and domain exist
if api_key == None: if api_key == None:
return jsonify({'error': 'Missing API key', 'success': 'false'}) return jsonify({'error': 'No licence provided', 'success': 'false'})
if domain == None: if domain == None:
return jsonify({'error': 'Missing domain', 'success': 'false'}) return jsonify({'error': 'Missing domain', 'success': 'false'})
# Check if API key is a valid site key # Check if API key is a valid site key
if api_key not in open('/data/licence_key.txt', 'r').read(): key_file = open('/data/licence_key.txt', 'r')
return jsonify({'error': 'Invalid API key', 'success': 'false'}) valid_key = False
for line in key_file.readlines():
if api_key == line.strip('\n'):
valid_key = True
break
key_file.close()
if not valid_key:
return jsonify({'error': 'Invalid licence', 'success': 'false'})
# Check if domain already exists # Check if domain already exists
if site_exists(domain): if site_exists(domain):
@@ -107,9 +117,10 @@ def new_site():
def add_worker(): def add_worker():
worker=request.args.get('worker') worker=request.args.get('worker')
worker_IP=request.args.get('ip') worker_IP=request.args.get('ip')
worker_PRIV=request.args.get('priv')
# Get API header # Get API header
api_key = request.headers.get('key') api_key = request.headers.get('key')
if api_key == None or worker == None or worker_IP == None: if api_key == None or worker == None or worker_IP == None or worker_PRIV == None:
return jsonify({'error': 'Invalid API key or worker info', 'success': 'false'}) return jsonify({'error': 'Invalid API key or worker info', 'success': 'false'})
if api_key != os.getenv('WORKER_KEY'): if api_key != os.getenv('WORKER_KEY'):
return jsonify({'error': 'Invalid API key', 'success': 'false'}) return jsonify({'error': 'Invalid API key', 'success': 'false'})
@@ -130,11 +141,11 @@ def add_worker():
# Add worker to file # Add worker to file
workers_file = open('/data/workers.txt', 'a') workers_file = open('/data/workers.txt', 'a')
workers_file.write(worker + ":" + worker_IP + '\n') workers_file.write(worker + ":" + worker_PRIV + ":"+ worker_IP + '\n')
workers_file.close() workers_file.close()
online=True online=True
resp=requests.get("http://"+worker_IP + ":5000/ping",timeout=2) resp=requests.get("http://"+worker_PRIV + ":5000/ping",timeout=2)
if (resp.status_code != 200): if (resp.status_code != 200):
online=False online=False
@@ -171,17 +182,22 @@ def list_workers():
continue continue
online=True online=True
try:
resp=requests.get("http://"+worker.split(':')[1].strip('\n') + ":5000/status",timeout=2) resp=requests.get("http://"+worker.split(':')[1].strip('\n') + ":5000/status",timeout=2)
if (resp.status_code != 200): if (resp.status_code != 200):
online=False online=False
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[1].strip('\n'), 'online': online, 'sites': 0, 'status': 'offline'}) worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[2].strip('\n'), 'online': online, 'sites': 0, 'status': 'offline'})
continue continue
sites = resp.json()['num_sites'] sites = resp.json()['num_sites']
availability = resp.json()['availability'] availability = resp.json()['availability']
if availability == True: if availability == True:
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[1].strip('\n'), 'online': online, 'sites': sites, 'status': 'ready'}) worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[2].strip('\n'), 'online': online, 'sites': sites, 'status': 'ready'})
else: else:
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[1].strip('\n'), 'online': online, 'sites': sites, 'status': 'full'}) worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[2].strip('\n'), 'online': online, 'sites': sites, 'status': 'full'})
except:
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[2].strip('\n'), 'online': 'False', 'sites': 0, 'status': 'offline'})
continue
if len(worker_list) == 0: if len(worker_list) == 0:
return jsonify({'error': 'No workers available', 'success': 'false'}) return jsonify({'error': 'No workers available', 'success': 'false'})
@@ -203,19 +219,49 @@ def site_status():
return jsonify({'error': 'Domain does not exist', 'success': 'false'}) return jsonify({'error': 'Domain does not exist', 'success': 'false'})
# Get worker ip # Get worker ip
ip = workerIP(worker) ip = workerIP_PRIV(worker)
# Get TLSA record # Get TLSA record
resp=requests.get("http://"+ip + ":5000/tlsa?domain=" + domain,timeout=2) resp=requests.get("http://"+ip + ":5000/tlsa?domain=" + domain,timeout=2)
json = resp.json() json = resp.json()
publicIP = workerIP(worker)
if "tlsa" in json: if "tlsa" in json:
tlsa = json['tlsa'] tlsa = json['tlsa']
return jsonify({'success': 'true', 'domain': domain, 'ip': ip, 'tlsa': tlsa}) return jsonify({'success': 'true', 'domain': domain, 'ip': publicIP, 'tlsa': tlsa})
else: else:
return jsonify({'success': 'false', 'domain': domain, 'ip': ip, 'tlsa': 'none','error': 'No TLSA record found'}) return jsonify({'success': 'false', 'domain': domain, 'ip': publicIP, 'tlsa': 'none','error': 'No TLSA record found'})
@app.route('/info')
def site_status_human():
domain = request.args.get('domain')
if domain == None:
return "<h1>Invalid domain</h1>"
# Check if domain exists
if not site_exists(domain):
return "<h1>Domain does not exist</h1>"
# Get worker
worker = site_worker(domain)
if worker == None:
return "<h1>Domain does not exist</h1>"
# Get worker ip
ip = workerIP_PRIV(worker)
# Get TLSA record
resp=requests.get("http://"+ip + ":5000/tlsa?domain=" + domain,timeout=2)
json = resp.json()
publicIP = workerIP(worker)
if "tlsa" in json:
tlsa = json['tlsa']
return "<h1>Domain: " + domain + "</h1><br><p>IP: " + publicIP + "</p><br><p>TLSA: " + tlsa + "</p><br><p>Make sure to add the TLSA record to `_443._tcp." + domain + "` or `*." + domain + "`</p>"
else:
return "<h1>Domain: " + domain + "</h1><br><p>IP: " + publicIP + "</p><br><p>TLSA: none</p><br><p>No TLSA record found</p>"
@app.route('/tlsa', methods=['GET']) @app.route('/tlsa', methods=['GET'])
def tlsa(): def tlsa():
domain = request.args.get('domain') domain = request.args.get('domain')
@@ -232,7 +278,7 @@ def tlsa():
return jsonify({'error': 'Domain does not exist', 'success': 'false'}) return jsonify({'error': 'Domain does not exist', 'success': 'false'})
# Get worker ip # Get worker ip
ip = workerIP(worker) ip = workerIP_PRIV(worker)
# Get TLSA record # Get TLSA record
resp=requests.get("http://"+ip + ":5000/tlsa?domain=" + domain,timeout=2) resp=requests.get("http://"+ip + ":5000/tlsa?domain=" + domain,timeout=2)
@@ -343,6 +389,24 @@ def site_worker(domain):
sites_file.close() sites_file.close()
return worker return worker
def workerIP_PRIV(worker):
# If file doesn't exist, create it
try:
workers_file = open('/data/workers.txt', 'r')
except FileNotFoundError:
workers_file = open('/data/workers.txt', 'w')
workers_file.close()
workers_file = open('/data/workers.txt', 'r')
ip = None
for line in workers_file.readlines():
if worker == line.split(':')[0]:
ip = line.split(':')[2].strip('\n')
break
workers_file.close()
return ip
def workerIP(worker): def workerIP(worker):
# If file doesn't exist, create it # If file doesn't exist, create it
try: try:
@@ -362,6 +426,363 @@ def workerIP(worker):
return ip return ip
# Home page
@app.route('/')
def home():
# Show stats and info
# Get worker info
workers = []
try:
workers_file = open('/data/workers.txt', 'r')
workers = workers_file.readlines()
workers_file.close()
except FileNotFoundError:
pass
# Get site info
sites = []
try:
sites_file = open('/data/sites.txt', 'r')
sites = sites_file.readlines()
sites_file.close()
except FileNotFoundError:
pass
# Get licence info
licences = []
try:
licences_file = open('/data/licence_key.txt', 'r')
licences = licences_file.readlines()
licences_file.close()
except FileNotFoundError:
pass
# Create html page
html = "<h1>Welcome</h1><br>"
html += "<h2>Create a site</h2>"
html += "<form action='/add-site' method='POST'>"
html += "<p>Domain: <input type='text' name='domain'></p>"
html += "<p>Licence key: <input type='text' name='licence'></p>"
html += "<input type='submit' value='Create site'>"
html += "</form>"
html += "<br><h2>Stats</h2><br>"
html += "<h2>Workers</h2>"
html += "<p>Number of workers: " + str(len(workers)) + "</p>"
html += "<p>Workers:</p>"
html += "<ul>"
for worker in workers:
html += "<li>Name: " + worker.split(':')[0] + " | IP " + worker.split(':')[2].strip('\n') + "</li>"
html += "</ul>"
html += "<h2>Sites</h2>"
html += "<p>Number of sites: " + str(len(sites)) + "</p>"
html += "<p>Sites:</p>"
html += "<ul>"
for site in sites:
html += "<li>Domain: " + site.split(':')[0] + " | Worker: " + site.split(':')[1].strip('\n') + "</li>"
html += "</ul>"
html += "<h2>Licences</h2>"
html += "<p>Number of licences: " + str(len(licences)) + "</p>"
html += "<h2><a href='/admin'>Admin</a></h2>"
return html
# Admin page
@app.route('/admin')
def admin():
# Check if logged in
login_key = request.cookies.get('login_key')
if login_key == None:
return "<h1>Admin</h1><br><form action='/login' method='POST'><input type='password' name='password'><input type='submit' value='Login'></form>"
if login_key not in logins:
return "<h1>Admin</h1><br><form action='/login' method='POST'><input type='password' name='password'><input type='submit' value='Login'></form>"
# Show some admin stuff
licences = []
try:
licences_file = open('/data/licence_key.txt', 'r')
licences = licences_file.readlines()
licences_file.close()
except FileNotFoundError:
pass
# Create html page
html = "<h1>Admin</h1><br>"
html += "<h2>Licences</h2>"
html += "<p>Number of licences: " + str(len(licences)) + "</p>"
html += "<p>Licences:</p>"
html += "<ul>"
for licence in licences:
html += "<li>" + licence.strip('\n') + "</li>"
html += "</ul>"
html += "<h2>API</h2>"
html += "<p>API key: " + os.getenv('LICENCE_KEY') + "</p>"
html += "<p>Worker key: " + os.getenv('WORKER_KEY') + "</p>"
html += "<h2>Stripe</h2>"
# Check if stripe is enabled
if os.getenv('STRIPE_SECRET') == None:
html += "<p>Stripe is not enabled</p>"
else:
html += "<p>Stripe is enabled</p>"
html += "<br><br><h2>Workers</h2>"
workers = []
try:
workers_file = open('/data/workers.txt', 'r')
workers = workers_file.readlines()
workers_file.close()
except FileNotFoundError:
pass
for worker in workers:
if not worker.__contains__(':'):
continue
html += "<p>Name: " + worker.split(':')[0] + " | Public IP " + worker.split(':')[2].strip('\n') + " | Private IP " + worker.split(':')[1]
# Check worker status
online=True
try:
resp=requests.get("http://"+worker.split(':')[1].strip('\n') + ":5000/status",timeout=2)
if (resp.status_code != 200):
html += " | Status: Offline"
else:
html += " | Status: Online | Sites: " + str(resp.json()['num_sites']) + " | Availability: " + str(resp.json()['availability'])
except:
html += " | Status: Offline"
html += "</p>"
html += "<h2>Sites</h2>"
sites = []
try:
sites_file = open('/data/sites.txt', 'r')
sites = sites_file.readlines()
sites_file.close()
except FileNotFoundError:
pass
for site in sites:
if not site.__contains__(':'):
continue
domain = site.split(':')[0]
html += "<p>Domain: <a href='https://"+ domain + "'>" + domain + "</a> | Worker: " + site.split(':')[1].strip('\n') + " | <a href='/info?domain=" + domain + "'>Info</a></p>"
html += "<br><br>"
# Form to add worker
html += "<h2>Add worker</h2>"
html += "<form action='/new-worker' method='POST'>"
html += "<p>Name: <input type='text' name='name'></p>"
html += "<p>Public IP: <input type='text' name='ip'></p>"
html += "<p>Private IP: <input type='text' name='priv'></p>"
html += "<input type='submit' value='Add worker'>"
html += "</form>"
html += "<br><h2><a href='/licence'>Add Licence</a></h2><br>"
# Form to add site
html += "<h2>Add site</h2>"
html += "<form action='/add-site' method='POST'>"
html += "<p>Domain: <input type='text' name='domain'></p>"
html += "<input type='submit' value='Add site'>"
html += "</form>"
html += "<br><a href='/logout'>Logout</a></h2>"
return html
@app.route('/add-site', methods=['POST'])
def addsite():
# Check for licence key
if 'licence' not in request.form:
# Check cookie
login_key = request.cookies.get('login_key')
if login_key == None:
return redirect('/admin')
if login_key not in logins:
return redirect('/admin')
else:
# Use licence key
licence_key = request.form['licence']
# Check if licence key is valid
key_file = open('/data/licence_key.txt', 'r')
valid_key = False
for line in key_file.readlines():
if licence_key == line.strip('\n'):
valid_key = True
break
key_file.close()
if not valid_key:
return jsonify({'error': 'Invalid licence', 'success': 'false'})
# Delete licence key
key_file = open('/data/licence_key.txt', 'r')
lines = key_file.readlines()
key_file.close()
key_file = open('/data/licence_key.txt', 'w')
for line in lines:
if line.strip("\n") != licence_key:
key_file.write(line)
key_file.close()
# Get domain
domain = request.form['domain']
if domain == None:
return jsonify({'error': 'No domain sent', 'success': 'false'})
# Check if domain already exists
if site_exists(domain):
return jsonify({'error': 'Domain already exists', 'success': 'false'})
# Check if domain contains http:// or https://
if domain.startswith("http://") or domain.startswith("https://"):
return jsonify({'error': 'Domain should not contain http:// or https://', 'success': 'false'})
# Check if worker file exists
workers = None
try:
worker_file = open('/data/workers.txt', 'r')
workers = worker_file.readlines()
worker_file.close()
except FileNotFoundError:
return jsonify({'error': 'No workers available', 'success': 'false'})
# Get a worker that has available slots
worker = None
for line in workers:
if not line.__contains__(':'):
continue
ip = line.split(':')[1].strip('\n')
resp=requests.get("http://"+ip + ":5000/status",timeout=2)
if (resp.status_code == 200):
if resp.json()['availability'] == True:
worker = line
break
if worker == None:
return jsonify({'error': 'No workers available', 'success': 'false'})
# Add domain to file
sites_file = open('/data/sites.txt', 'a')
sites_file.write(domain + ':' + worker.split(':')[0] + '\n')
sites_file.close()
# Send worker request
requests.post("http://"+ worker.split(':')[1].strip('\n') + ":5000/new-site?domain=" + domain)
html = "<h1>Site creating...</h1><br>"
html += "<p>Domain: " + domain + "</p>"
html += "<p>Worker: " + worker.split(':')[0] + "</p>"
html += "<p>Worker IP: " + worker.split(':')[1].strip('\n') + "</p>"
html += "<p><a href='/info?domain=" + domain + "'>Check status</a></p>"
return html
@app.route('/licence')
def licence():
# Check cookie
login_key = request.cookies.get('login_key')
if login_key == None:
return redirect('/admin')
if login_key not in logins:
return redirect('/admin')
licence_key = os.urandom(16).hex()
# Add license key to file
key_file = open('/data/licence_key.txt', 'a')
key_file.write(licence_key + '\n')
key_file.close()
return "<h1>Licence key</h1><br><p>" + licence_key + "</p><br><a href='/admin'>Back</a>"
@app.route('/new-worker', methods=['POST'])
def new_worker():
# Check cookie
login_key = request.cookies.get('login_key')
if login_key == None:
return redirect('/admin')
if login_key not in logins:
return redirect('/admin')
worker = request.form['name']
worker_IP = request.form['ip']
worker_PRIV = request.form['priv']
# Check worker file
try:
workers_file = open('/data/workers.txt', 'r')
except FileNotFoundError:
workers_file = open('/data/workers.txt', 'w')
workers_file.close()
workers_file = open('/data/workers.txt', 'r')
# Check if worker already exists
if worker in workers_file.read():
return jsonify({'error': 'Worker already exists', 'success': 'false'})
workers_file.close()
# Add worker to file
workers_file = open('/data/workers.txt', 'a')
workers_file.write(worker + ":" + worker_PRIV + ":"+ worker_IP + '\n')
workers_file.close()
return redirect('/admin')
@app.route('/logout')
def logout():
login_key = request.cookies.get('login_key')
if login_key == None:
return redirect('/admin')
if login_key not in logins:
return redirect('/admin')
logins.remove(login_key)
return redirect('/admin')
@app.route('/login', methods=['POST'])
def login():
# Handle login
print('Login attempt', flush=True)
# Check if form contains password
if 'password' not in request.form:
print('Login failed', flush=True)
return redirect('/failed-login')
password = request.form['password']
if os.getenv('ADMIN_KEY') == password:
print('Login success', flush=True)
# Generate login key
login_key = os.urandom(32).hex()
logins.append(login_key)
# Set cookie
resp = make_response(redirect('/admin'))
resp.set_cookie('login_key', login_key)
return resp
print('Login failed', flush=True)
return redirect('/failed-login')
@app.route('/failed-login')
def failed_login():
return "<h1>Failed login</h1><br><form action='/login' method='POST'><input type='password' name='password'><input type='submit' value='Login'></form>"
# Start the server # Start the server
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -11,6 +11,7 @@ KERNEL_VERSION=$(uname -r)
sudo apt-mark hold linux-image-generic linux-headers-generic linux-generic linux-image-$KERNEL_VERSION linux-headers-$KERNEL_VERSION sudo apt-mark hold linux-image-generic linux-headers-generic linux-generic linux-image-$KERNEL_VERSION linux-headers-$KERNEL_VERSION
# Install Docker # Install Docker
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common python3-pip nginx -y sudo apt install apt-transport-https ca-certificates curl software-properties-common python3-pip nginx -y
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null