2 Commits

Author SHA1 Message Date
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
8 changed files with 41 additions and 126 deletions

View File

@@ -21,12 +21,10 @@ jobs:
run : |
cd master
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/}}"
tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
tag=${tag//\//-}
tag_num=${GITHUB_RUN_NUMBER}
echo "tag_num=$tag_num"
if [[ "$tag" == "main" ]]; then
tag="latest"
else
@@ -62,10 +60,10 @@ jobs:
run : |
cd discord-bot
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/}}"
tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
tag=${tag//\//-}
tag_num=${GITHUB_RUN_NUMBER}
if [[ "$tag" == "main" ]]; then
tag="latest"
else
@@ -77,3 +75,4 @@ jobs:
docker push git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag_num
docker tag hnshosting-bot:$tag_num git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag
docker push git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag

View File

@@ -1,16 +0,0 @@
name: Build Docker for Release
run-name: Build Docker Images
on:
push:
tags:
- '*'
jobs:
TEST:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Print tag
run: echo "tag=${GITHUB_REF#refs/tags/}"

View File

@@ -11,20 +11,12 @@ The master server will be used to manage the worker servers.
The worker servers will be used to host the wordpress sites.
The bot will be used to provide an easier way to manage the master server.
![Overview of system](assets/overview.png)
| Legend | Description |
| --- | --- |
| Red Connections | Secured by VPN or over LAN ONLY. (NOT API SECURED) |
| Yellow Connections | HTTP/HTTPS public traffic |
## Usage
After installing the master and discord bot you can use the following commands (as bot owner).
```
/addworker <ip> <private ip> <name> | add a worker to the master server pool
/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)
/listworkers | list all workers
/licence | Creates a licence key (valid for 1 wp site)
```
@@ -82,22 +74,15 @@ cd hnshosting-wp/worker
chmod +x install.sh
./install.sh
```
Just press enter when it shows any prompts.
Start the worker api server using
Then to start the worker api server
```sh
screen -dmS hnshosting-worker python3 main.py
```
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.
Add worker to master server:
```sh
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>
curl -X POST http://master-server-ip:5000/add-worker?worker=worker-name&ip=worker-server-ip -H "key: api-key"
```
## Discord bot install

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

View File

@@ -3,7 +3,6 @@ from dotenv import load_dotenv
import discord
from discord import app_commands
import requests
import asyncio
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
@@ -22,9 +21,9 @@ client = discord.Client(intents=intents)
tree = app_commands.CommandTree(client)
@tree.command(name="addworker", description="Adds a worker to the master server")
async def addworker(ctx, ip: str,privateip: str, name: str):
async def addworker(ctx, ip: str, name: str):
if ctx.user.id == ADMINID:
r = requests.post(f"http://{Master_IP}:{Master_Port}/add-worker?worker={name}&ip={ip}&priv={privateip}",headers={"key":os.getenv('WORKER_KEY')})
r = requests.post(f"http://{Master_IP}:{Master_Port}/add-worker?worker={name}&ip={ip}",headers={"key":os.getenv('WORKER_KEY')})
if r.status_code == 200:
await ctx.response.send_message(f"Worker {name} added to the master server",ephemeral=True)
else:
@@ -70,22 +69,7 @@ async def createsite(ctx, domain: str, licence: str):
if r.status_code == 200:
json = r.json()
if json['success'] == "true":
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'])
await ctx.response.send_message(f"Site {domain} creating...\nPlease wait a few minutes and then send /siteinfo domain:{domain}")
else:
await ctx.response.send_message(f"Error creating site\n" + json['error'])
else:
@@ -104,17 +88,6 @@ async def siteinfo(ctx, domain: str):
else:
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
@client.event
async def on_ready():

View File

@@ -107,10 +107,9 @@ def new_site():
def add_worker():
worker=request.args.get('worker')
worker_IP=request.args.get('ip')
worker_PRIV=request.args.get('priv')
# Get API header
api_key = request.headers.get('key')
if api_key == None or worker == None or worker_IP == None or worker_PRIV == None:
if api_key == None or worker == None or worker_IP == None:
return jsonify({'error': 'Invalid API key or worker info', 'success': 'false'})
if api_key != os.getenv('WORKER_KEY'):
return jsonify({'error': 'Invalid API key', 'success': 'false'})
@@ -131,11 +130,11 @@ def add_worker():
# Add worker to file
workers_file = open('/data/workers.txt', 'a')
workers_file.write(worker + ":" + worker_PRIV + ":"+ worker_IP + '\n')
workers_file.write(worker + ":" + worker_IP + '\n')
workers_file.close()
online=True
resp=requests.get("http://"+worker_PRIV + ":5000/ping",timeout=2)
resp=requests.get("http://"+worker_IP + ":5000/ping",timeout=2)
if (resp.status_code != 200):
online=False
@@ -169,23 +168,17 @@ def list_workers():
for worker in workers:
# Check worker status
if not worker.__contains__(':'):
continue
return jsonify({'error': 'No workers available', 'success': 'false'})
online=True
resp=requests.get("http://"+worker.split(':')[1].strip('\n') + ":5000/status",timeout=2)
if (resp.status_code != 200):
online=False
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[2].strip('\n'), 'online': online, 'sites': 0, 'status': 'offline'})
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[1].strip('\n'), 'online': online, 'sites': 0, 'ready': 0})
continue
sites = resp.json()['num_sites']
availability = resp.json()['availability']
if availability == True:
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[2].strip('\n'), 'online': online, 'sites': sites, 'status': 'ready'})
else:
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[2].strip('\n'), 'online': online, 'sites': sites, 'status': 'full'})
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[1].strip('\n'), 'online': online, 'sites': sites, 'ready': 1})
if len(worker_list) == 0:
return jsonify({'error': 'No workers available', 'success': 'false'})
return jsonify({'success': 'true', 'workers': worker_list})
@app.route('/site-info', methods=['GET'])
@@ -204,18 +197,17 @@ def site_status():
return jsonify({'error': 'Domain does not exist', 'success': 'false'})
# Get worker ip
ip = workerIP_PRIV(worker)
ip = workerIP(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 jsonify({'success': 'true', 'domain': domain, 'ip': publicIP, 'tlsa': tlsa})
return jsonify({'success': 'true', 'domain': domain, 'ip': ip, 'tlsa': tlsa})
else:
return jsonify({'success': 'false', 'domain': domain, 'ip': publicIP, 'tlsa': 'none','error': 'No TLSA record found'})
return jsonify({'success': 'false', 'domain': domain, 'ip': ip, 'tlsa': 'none','error': 'No TLSA record found'})
@app.route('/tlsa', methods=['GET'])
@@ -234,7 +226,7 @@ def tlsa():
return jsonify({'error': 'Domain does not exist', 'success': 'false'})
# Get worker ip
ip = workerIP_PRIV(worker)
ip = workerIP(worker)
# Get TLSA record
resp=requests.get("http://"+ip + ":5000/tlsa?domain=" + domain,timeout=2)
@@ -345,24 +337,6 @@ def site_worker(domain):
sites_file.close()
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):
# If file doesn't exist, create it
try:
@@ -382,6 +356,7 @@ def workerIP(worker):
return ip
# Start the server
if __name__ == '__main__':
app.run(debug=False, port=5000, host='0.0.0.0')

View File

@@ -2,29 +2,23 @@
# Initial install for all prerequisites for the project.
# This makes it quicker to get each site up and running.
# Stop kernel prompts
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
echo "Dpkg::Options { \"--force-confdef\"; \"--force-confold\"; };" | sudo tee /etc/apt/apt.conf.d/local
KERNEL_VERSION=$(uname -r)
sudo apt-mark hold linux-image-generic linux-headers-generic linux-generic linux-image-$KERNEL_VERSION linux-headers-$KERNEL_VERSION
# Update the system
sudo apt update && sudo apt upgrade -y
# 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 -y
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
sudo apt update
apt-cache policy docker-ce
sudo apt install docker-ce docker-compose -y
sudo apt install docker-ce -y
sudo apt install docker-compose -y
# Install NGINX
sudo apt install nginx -y
# Install python prerequisites
sudo apt install python3-pip -y
python3 -m pip install -r requirements.txt
cp .env.example .env
chmod +x wp.sh tlsa.sh
# Pull docker images to save time later
docker pull mysql:5.7 &
docker pull wordpress:latest &
wait

View File

@@ -67,11 +67,16 @@ def ping():
return 'pong'
def get_sites_count():
# Get number of files in nginx/sites
dir = os.listdir('/etc/nginx/sites-available')
num_Sites = len(dir) - 1
# If file doesn't exist, create it
try:
sites_file = open('sites.txt', 'r')
except FileNotFoundError:
sites_file = open('sites.txt', 'w')
sites_file.close()
sites_file = open('sites.txt', 'r')
print(sites_file.readlines())
# Return number of lines in file
return num_Sites
return len(sites_file.readlines())
def site_exists(domain):
# If file doesn't exist, create it