Compare commits
45 Commits
3266dbafa9
...
feature/da
| Author | SHA1 | Date | |
|---|---|---|---|
|
67ed81fd2e
|
|||
|
2ef8e2c38d
|
|||
|
c8eafb6406
|
|||
|
73d11a0f76
|
|||
|
3628e0a4ac
|
|||
|
20dbf6c0df
|
|||
|
8a2cad16ec
|
|||
|
48330f7ef9
|
|||
|
7703bfa4d5
|
|||
|
ba96f9b84b
|
|||
|
0a33a6150d
|
|||
|
c9be5cedcb
|
|||
|
1fb2493848
|
|||
|
9853214d83
|
|||
|
843d2d12a0
|
|||
|
8f962804a4
|
|||
|
9e485265af
|
|||
|
83bde4b218
|
|||
|
52fca38af9
|
|||
|
4db44bb99e
|
|||
|
0e2ad55eb7
|
|||
|
0c20572369
|
|||
|
d2e31bb684
|
|||
|
bbe70647d7
|
|||
|
d6dffc0464
|
|||
|
fb9295c260
|
|||
|
c4ae4561e3
|
|||
|
2902624637
|
|||
|
5049796d07
|
|||
|
5efe4860fc
|
|||
|
499a7e348b
|
|||
|
ed94263050
|
|||
|
4841344d63
|
|||
|
51bcdda5d4
|
|||
|
b38de6ad52
|
|||
|
b642cf7269
|
|||
|
3e3c2fe61e
|
|||
|
04edb8b456
|
|||
|
45c1ea3557
|
|||
|
778c1b3d92
|
|||
|
19806b7b1b
|
|||
|
908a4e0422
|
|||
|
d217309e74
|
|||
|
ce8827ed97
|
|||
|
3b914abf7a
|
@@ -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
|
||||||
|
|
||||||
60
.gitea/workflows/release.yml
Normal file
60
.gitea/workflows/release.yml
Normal 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
|
||||||
12
README.md
12
README.md
@@ -18,6 +18,10 @@ 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
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -106,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
|
||||||
|
```
|
||||||
@@ -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,9 +13,14 @@ 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)
|
||||||
@@ -49,9 +55,8 @@ 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
|
|
||||||
|
|
||||||
r = requests.post(f"http://{Master_IP}:{Master_Port}/add-licence",headers={"key":os.getenv('LICENCE_KEY')})
|
r = requests.post(f"http://{Master_IP}:{Master_Port}/add-licence",headers={"key":os.getenv('LICENCE_KEY')})
|
||||||
if r.status_code == 200:
|
if r.status_code == 200:
|
||||||
@@ -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():
|
||||||
|
|||||||
432
master/main.py
432
master/main.py
@@ -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):
|
||||||
@@ -172,17 +182,22 @@ def list_workers():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
online=True
|
online=True
|
||||||
resp=requests.get("http://"+worker.split(':')[1].strip('\n') + ":5000/status",timeout=2)
|
try:
|
||||||
if (resp.status_code != 200):
|
resp=requests.get("http://"+worker.split(':')[1].strip('\n') + ":5000/status",timeout=2)
|
||||||
online=False
|
|
||||||
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[2].strip('\n'), 'online': online, 'sites': 0, 'status': 'offline'})
|
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'})
|
||||||
|
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'})
|
||||||
|
except:
|
||||||
|
worker_list.append({'worker': worker.split(':')[0],'ip': worker.split(':')[2].strip('\n'), 'online': 'False', 'sites': 0, 'status': 'offline'})
|
||||||
continue
|
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'})
|
|
||||||
|
|
||||||
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'})
|
||||||
@@ -218,6 +233,35 @@ def site_status():
|
|||||||
return jsonify({'success': 'false', 'domain': domain, 'ip': publicIP, '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')
|
||||||
@@ -382,6 +426,364 @@ 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__':
|
||||||
app.run(debug=False, port=5000, host='0.0.0.0')
|
app.run(debug=False, port=5000, host='0.0.0.0')
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user