Compare commits
No commits in common. "main" and "feature/send_info_on_ready" have entirely different histories.
main
...
feature/se
@ -1,16 +1,10 @@
|
||||
name: Build Docker
|
||||
run-name: Build Docker Images
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '*/*'
|
||||
tags-ignore:
|
||||
- '*'
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
Build Master:
|
||||
runs-on: [ubuntu-latest, arm]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@ -27,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
|
||||
@ -51,7 +43,7 @@ jobs:
|
||||
|
||||
|
||||
Build Bot:
|
||||
runs-on: [ubuntu-latest, arm]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@ -68,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
|
||||
@ -82,4 +74,5 @@ jobs:
|
||||
docker tag hnshosting-bot:$tag_num 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 push git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag
|
||||
docker push git.woodburn.au/nathanwoodburn/hnshosting-bot:$tag
|
||||
|
@ -1,60 +0,0 @@
|
||||
name: Build Docker for Release
|
||||
run-name: Build Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
Build Master:
|
||||
runs-on: [ubuntu-latest,arm]
|
||||
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,arm]
|
||||
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
|
25
README.md
@ -18,10 +18,6 @@ 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) |
|
||||
| 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
|
||||
|
||||
@ -47,7 +43,7 @@ General commands (as anyone)
|
||||
Docker is the easiest way to install the master server.
|
||||
|
||||
```sh
|
||||
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
|
||||
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
|
||||
```
|
||||
You can also mount a docker volume to /data to store the files instead of mounting a host directory.
|
||||
|
||||
@ -109,21 +105,4 @@ Alternatively you can use the discord bot to add the worker to the master server
|
||||
Docker install
|
||||
```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
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
## Hip2
|
||||
HIP2 allows sending HNS to a domain.
|
||||
To enable HIP2 on your wordpress site you should
|
||||
|
||||
1. Download the [HIP2 plugin](https://git.woodburn.au/nathanwoodburn/hnshosting-wp/raw/branch/main/assets/hns-wallet-plugin.zip)
|
||||
2. Upload the plugin to your wordpress site
|
||||
3. Activate the plugin
|
||||
4. Go to the settings page and enter your HNS wallet address
|
||||
5. Ensure it works by settings the permalink to post name and saving
|
||||
```
|
@ -13,14 +13,9 @@ Master_Port = os.getenv('MASTER_PORT')
|
||||
if Master_Port == None:
|
||||
Master_Port = "5000"
|
||||
|
||||
FREE_LICENCE = os.getenv('FREE_MODE')
|
||||
FREE_LICENCE = os.getenv('FREE_LICENCE')
|
||||
if FREE_LICENCE == None:
|
||||
FREE_LICENCE = False
|
||||
else:
|
||||
if FREE_LICENCE.lower() == "true":
|
||||
FREE_LICENCE = True
|
||||
else:
|
||||
FREE_LICENCE = False
|
||||
|
||||
intents = discord.Intents.default()
|
||||
client = discord.Client(intents=intents)
|
||||
@ -51,13 +46,13 @@ async def listworkers(ctx):
|
||||
await ctx.response.send_message(f"Error listing workers\n" + r.text,ephemeral=True)
|
||||
else:
|
||||
await ctx.response.send_message("You do not have permission to use this command",ephemeral=True)
|
||||
update_bot_status()
|
||||
|
||||
@tree.command(name="licence", description="Gets a licence key")
|
||||
async def license(ctx):
|
||||
if ctx.user.id != ADMINID:
|
||||
await ctx.response.send_message("You do not have permission to use this command",ephemeral=True)
|
||||
return
|
||||
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)
|
||||
return
|
||||
|
||||
r = requests.post(f"http://{Master_IP}:{Master_Port}/add-licence",headers={"key":os.getenv('LICENCE_KEY')})
|
||||
if r.status_code == 200:
|
||||
@ -70,30 +65,12 @@ async def license(ctx):
|
||||
await ctx.response.send_message(f"Error getting license\n" + r.text,ephemeral=True)
|
||||
|
||||
@tree.command(name="createsite", description="Create a new WordPress site")
|
||||
async def createsite(ctx, domain: str, licence: str = None):
|
||||
# Verify domain is valid
|
||||
if domain == None:
|
||||
await ctx.response.send_message("You must specify a domain",ephemeral=True)
|
||||
return
|
||||
if "http://" in domain or "https://" in domain:
|
||||
await ctx.response.send_message("You must specify a domain without http:// or https://",ephemeral=True)
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
async def createsite(ctx, domain: str, licence: str):
|
||||
r = requests.post(f"http://{Master_IP}:{Master_Port}/new-site?domain={domain}",headers={"key":licence})
|
||||
if r.status_code == 200:
|
||||
json = r.json()
|
||||
if json['success'] == "true":
|
||||
await ctx.response.send_message(f"Site https://{domain} creating...\nI'll send you a message when it's ready")
|
||||
await ctx.response.send_message(f"Site {domain} creating...\nI'll send you a message when it's ready")
|
||||
|
||||
ready = False
|
||||
while ready == False:
|
||||
@ -104,7 +81,7 @@ async def createsite(ctx, domain: str, licence: str = None):
|
||||
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 https://{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}`")
|
||||
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'])
|
||||
|
||||
@ -113,7 +90,6 @@ async def createsite(ctx, domain: str, licence: str = None):
|
||||
await ctx.response.send_message(f"Error creating site\n" + json['error'])
|
||||
else:
|
||||
await ctx.response.send_message(f"Error creating site\n" + r.text)
|
||||
update_bot_status()
|
||||
|
||||
|
||||
@tree.command(name="siteinfo", description="Get info about a WordPress site")
|
||||
@ -138,17 +114,6 @@ async def check_site_ready(domain):
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_site_count():
|
||||
r = requests.get(f"http://{Master_IP}:{Master_Port}/site-count")
|
||||
if r.status_code == 200:
|
||||
return r.text
|
||||
else:
|
||||
return "Error getting site count\n" + r.text
|
||||
|
||||
def update_bot_status():
|
||||
site_count = get_site_count()
|
||||
client.loop.create_task(client.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="over " + site_count + " wordpress sites")))
|
||||
|
||||
# When the bot is ready
|
||||
@client.event
|
||||
@ -156,9 +121,6 @@ async def on_ready():
|
||||
global ADMINID
|
||||
ADMINID = client.application.owner.id
|
||||
await tree.sync()
|
||||
|
||||
# Get the number of sites
|
||||
site_count = get_site_count()
|
||||
await client.loop.create_task(client.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="over " + site_count + " wordpress sites")))
|
||||
await client.loop.create_task(client.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="over HNSHosting wordpress")))
|
||||
|
||||
client.run(TOKEN)
|
@ -1,10 +0,0 @@
|
||||
html {
|
||||
background-color: black;
|
||||
color: white;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
Before Width: | Height: | Size: 6.5 KiB |
503
master/main.py
@ -1,4 +1,4 @@
|
||||
from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory
|
||||
from flask import Flask, request, jsonify
|
||||
import dotenv
|
||||
import os
|
||||
import requests
|
||||
@ -10,14 +10,12 @@ dotenv.load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
logins = []
|
||||
|
||||
# API add license key (requires API key in header)
|
||||
@app.route('/add-licence', methods=['POST'])
|
||||
def add_license():
|
||||
# Get API header
|
||||
api_key = request.headers.get('key')
|
||||
if api_key != os.getenv('LICENCE_KEY'):
|
||||
if api_key != os.getenv('LICENCE-API'):
|
||||
return jsonify({'error': 'Invalid API key', 'success': 'false'})
|
||||
|
||||
# Generate licence key
|
||||
@ -39,22 +37,14 @@ def new_site():
|
||||
|
||||
# Verify both API key and domain exist
|
||||
if api_key == None:
|
||||
return jsonify({'error': 'No licence provided', 'success': 'false'})
|
||||
return jsonify({'error': 'Missing API key', 'success': 'false'})
|
||||
|
||||
if domain == None:
|
||||
return jsonify({'error': 'Missing domain', 'success': 'false'})
|
||||
|
||||
# Check if API key is a valid site key
|
||||
key_file = open('/data/licence_key.txt', 'r')
|
||||
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'})
|
||||
|
||||
if api_key not in open('/data/licence_key.txt', 'r').read():
|
||||
return jsonify({'error': 'Invalid API key', 'success': 'false'})
|
||||
|
||||
# Check if domain already exists
|
||||
if site_exists(domain):
|
||||
@ -182,23 +172,18 @@ def list_workers():
|
||||
continue
|
||||
|
||||
online=True
|
||||
try:
|
||||
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'})
|
||||
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'})
|
||||
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'})
|
||||
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:
|
||||
return jsonify({'error': 'No workers available', 'success': 'false'})
|
||||
return jsonify({'success': 'true', 'workers': worker_list})
|
||||
@ -206,7 +191,6 @@ def list_workers():
|
||||
@app.route('/site-info', methods=['GET'])
|
||||
def site_status():
|
||||
domain = request.args.get('domain')
|
||||
domain = domain.lower()
|
||||
if domain == None:
|
||||
return jsonify({'error': 'Invalid domain', 'success': 'false'})
|
||||
|
||||
@ -233,10 +217,10 @@ def site_status():
|
||||
else:
|
||||
return jsonify({'success': 'false', 'domain': domain, 'ip': publicIP, 'tlsa': 'none','error': 'No TLSA record found'})
|
||||
|
||||
|
||||
@app.route('/tlsa', methods=['GET'])
|
||||
def tlsa():
|
||||
domain = request.args.get('domain')
|
||||
domain = domain.lower()
|
||||
if domain == None:
|
||||
return jsonify({'error': 'Invalid domain', 'success': 'false'})
|
||||
|
||||
@ -277,14 +261,7 @@ def stripeapi():
|
||||
return jsonify({'success': 'false'})
|
||||
|
||||
if event.type == 'payment_intent.succeeded':
|
||||
# Only for payments for licences
|
||||
payment_intent = event.data.object
|
||||
if payment_intent['amount'] != 1000:
|
||||
return jsonify({'success': 'true'})
|
||||
|
||||
if payment_intent['description'] != "Subscription creation":
|
||||
return jsonify({'success': 'true'})
|
||||
|
||||
# Get email
|
||||
email = payment_intent['receipt_email']
|
||||
# Create licence key
|
||||
@ -308,7 +285,7 @@ def stripeapi():
|
||||
message = "From: " + from_email + "\nTo: " + email + \
|
||||
"\nSubject: Your Licence key\n\nHello,\n\n"\
|
||||
+"This email contains your licence key for your new wordpress site.\n" \
|
||||
+"You can redeem this key via the discord bot or at https://hnshosting.au/register\n\n"\
|
||||
+"You can redeem this key via the discord bot or api.\n\n"\
|
||||
+"Your licence key is: " + licence_key +"\nThanks,\nHNSHosting"
|
||||
|
||||
server.sendmail(from_email, email, message)
|
||||
@ -380,7 +357,7 @@ def workerIP_PRIV(worker):
|
||||
ip = None
|
||||
for line in workers_file.readlines():
|
||||
if worker == line.split(':')[0]:
|
||||
ip = line.split(':')[1].strip('\n')
|
||||
ip = line.split(':')[2].strip('\n')
|
||||
break
|
||||
|
||||
workers_file.close()
|
||||
@ -398,453 +375,13 @@ def workerIP(worker):
|
||||
ip = None
|
||||
for line in workers_file.readlines():
|
||||
if worker == line.split(':')[0]:
|
||||
ip = line.split(':')[2].strip('\n')
|
||||
ip = line.split(':')[1].strip('\n')
|
||||
break
|
||||
|
||||
workers_file.close()
|
||||
return ip
|
||||
|
||||
|
||||
# Home page
|
||||
@app.route('/')
|
||||
def home():
|
||||
# Show index template
|
||||
# Get site info
|
||||
sites = []
|
||||
try:
|
||||
sites_file = open('/data/sites.txt', 'r')
|
||||
sites = sites_file.readlines()
|
||||
sites_file.close()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
return render_template('index.html', site_count = str(len(sites)))
|
||||
|
||||
# Register page
|
||||
@app.route('/register', methods=['GET'])
|
||||
def register():
|
||||
buy_licence_link = os.getenv('BUY_LICENCE_LINK')
|
||||
|
||||
# Show register template
|
||||
return render_template('register.html', buy_licence_link=buy_licence_link, ERROR_MESSAGE="")
|
||||
|
||||
@app.route('/register', methods=['POST'])
|
||||
def register_post():
|
||||
buy_licence_link = os.getenv('BUY_LICENCE_LINK')
|
||||
if 'licence' not in request.form:
|
||||
return render_template('register.html', buy_licence_link=buy_licence_link, ERROR_MESSAGE="No licence key provided")
|
||||
|
||||
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 render_template('register.html', buy_licence_link=buy_licence_link, ERROR_MESSAGE="Invalid licence key")
|
||||
|
||||
# Get domain
|
||||
domain = request.form['domain']
|
||||
if domain == None:
|
||||
return render_template('register.html', buy_licence_link=buy_licence_link, ERROR_MESSAGE="No domain provided")
|
||||
# Check if domain already exists
|
||||
if site_exists(domain):
|
||||
return render_template('register.html', buy_licence_link=buy_licence_link, ERROR_MESSAGE="Domain already exists")
|
||||
|
||||
# Check if domain contains http:// or https://
|
||||
if domain.startswith("http://") or domain.startswith("https://"):
|
||||
return render_template('register.html', buy_licence_link=buy_licence_link, ERROR_MESSAGE="Domain should not contain http:// or https://")
|
||||
|
||||
# Set domain to lowercase
|
||||
domain = domain.lower()
|
||||
|
||||
# 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 render_template('register.html', buy_licence_link=buy_licence_link, ERROR_MESSAGE="No workers available\nPlease contact support")
|
||||
|
||||
# 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 render_template('register.html', buy_licence_link=buy_licence_link, ERROR_MESSAGE="No workers available\nPlease contact support")
|
||||
|
||||
# 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()
|
||||
|
||||
# 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)
|
||||
|
||||
return redirect('/success?domain=' + domain + '&status=creating')
|
||||
|
||||
@app.route('/success')
|
||||
@app.route('/info')
|
||||
def success():
|
||||
if 'domain' not in request.args:
|
||||
return redirect('/')
|
||||
domain = request.args.get('domain')
|
||||
domain = domain.lower()
|
||||
if not site_exists(domain):
|
||||
return render_template('success.html', title="Your site is installing.<br>Please wait...",message="")
|
||||
|
||||
if 'status' not in request.args:
|
||||
# Get worker
|
||||
worker = site_worker(domain)
|
||||
if worker == None:
|
||||
return render_template('success.html', title="Your site is installing.<br>Please wait...",message="Error: Domain does not exist<br>Please contact support")
|
||||
# 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 render_template('success.html', title="Your site is ready!",message="Success<br>Domain: <code>" + domain + "</code><br>IP: <code>" + publicIP + "</code><br>TLSA: <code>" + tlsa + "</code><br>Make sure to add the TLSA record to <code>_443._tcp." + domain + "</code> or <code>*." + domain + "</code>")
|
||||
else:
|
||||
return render_template('success.html', title="Your site is installing.<br>Please wait...",message="Domain: <code>" + domain + "</code><br>IP: <code>" + publicIP + "</code><br>TLSA: Pending<br>No TLSA record found")
|
||||
|
||||
elif request.args.get('status') == 'creating':
|
||||
return render_template('success.html')
|
||||
|
||||
|
||||
@app.route('/site-count')
|
||||
def site_count_route():
|
||||
return str(get_sites_count())
|
||||
|
||||
|
||||
|
||||
# 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'})
|
||||
|
||||
domain = domain.lower()
|
||||
# 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>"
|
||||
|
||||
# Assets
|
||||
@app.route('/assets/<path:path>')
|
||||
def send_report(path):
|
||||
return send_from_directory('templates/assets', path)
|
||||
|
||||
|
||||
|
||||
|
||||
# Start the server
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=False, port=5000, host='0.0.0.0')
|
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 57 KiB |
@ -1,61 +0,0 @@
|
||||
(function() {
|
||||
"use strict"; // Start of use strict
|
||||
|
||||
function initParallax() {
|
||||
|
||||
if (!('requestAnimationFrame' in window)) return;
|
||||
if (/Mobile|Android/.test(navigator.userAgent)) return;
|
||||
|
||||
var parallaxItems = document.querySelectorAll('[data-bss-parallax]');
|
||||
|
||||
if (!parallaxItems.length) return;
|
||||
|
||||
var defaultSpeed = 0.5;
|
||||
var visible = [];
|
||||
var scheduled;
|
||||
|
||||
window.addEventListener('scroll', scroll);
|
||||
window.addEventListener('resize', scroll);
|
||||
|
||||
scroll();
|
||||
|
||||
function scroll() {
|
||||
|
||||
visible.length = 0;
|
||||
|
||||
for (var i = 0; i < parallaxItems.length; i++) {
|
||||
var rect = parallaxItems[i].getBoundingClientRect();
|
||||
var speed = parseFloat(parallaxItems[i].getAttribute('data-bss-parallax-speed'), 10) || defaultSpeed;
|
||||
|
||||
if (rect.bottom > 0 && rect.top < window.innerHeight) {
|
||||
visible.push({
|
||||
speed: speed,
|
||||
node: parallaxItems[i]
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cancelAnimationFrame(scheduled);
|
||||
|
||||
if (visible.length) {
|
||||
scheduled = requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function update() {
|
||||
|
||||
for (var i = 0; i < visible.length; i++) {
|
||||
var node = visible[i].node;
|
||||
var speed = visible[i].speed;
|
||||
|
||||
node.style.transform = 'translate3d(0, ' + (-window.scrollY * speed) + 'px, 0)';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
initParallax();
|
||||
})(); // End of use strict
|
||||
|
@ -1,12 +0,0 @@
|
||||
// Wait for 10 seconds
|
||||
setTimeout(function() {
|
||||
// Get the 'domain' parameter from the current URL
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var domain = urlParams.get('domain');
|
||||
|
||||
// Construct the new URL with the 'domain' parameter
|
||||
var newURL = "https://hnshosting.au/info?domain=" + domain;
|
||||
|
||||
// Redirect to the new URL
|
||||
window.location.href = newURL;
|
||||
}, 10000);
|
@ -1,173 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="dark" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Home - HNSHosting</title>
|
||||
<link rel="canonical" href="https://wp.hnshosting.au/">
|
||||
<meta property="og:url" content="https://wp.hnshosting.au/">
|
||||
<meta name="description" content="HNS Hosting Wordpress">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "HNSHosting",
|
||||
"url": "https://wp.hnshosting.au"
|
||||
}
|
||||
</script>
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&display=swap">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
|
||||
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><span class="bs-icon-sm bs-icon-circle bs-icon-primary shadow d-flex justify-content-center align-items-center me-2 bs-icon" style="background: transparent;"><img src="assets/img/favicon.png" style="width: 100%;"></span><span>HNSHosting</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
|
||||
<div class="collapse navbar-collapse" id="navcol-1">
|
||||
<ul class="navbar-nav mx-auto">
|
||||
<li class="nav-item"><a class="nav-link active" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link active" href="/#contact">Contact</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="register">Register Site</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="bg-dark">
|
||||
<div class="container pt-4 pt-xl-5">
|
||||
<div class="row pt-5">
|
||||
<div class="col-md-8 col-xl-6 text-center text-md-start mx-auto">
|
||||
<div class="text-center">
|
||||
<h1 class="fw-bold">The best solution for your Handshake domain</h1>
|
||||
</div>
|
||||
<p class="text-center" style="padding-bottom: 50px;padding-top: 25px;">Wordpress allows you to have an easily customizable website on your Handshake domain.</p>
|
||||
<p class="text-center" style="font-size: 22px;">Currently hosting {{site_count}} sites!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<section style="background: rgb(39,38,46);">
|
||||
<div class="container bg-dark py-5" style="background: rgb(39, 38, 46);">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-xl-6 text-center mx-auto">
|
||||
<p class="fw-bold text-success mb-2">Our Services</p>
|
||||
<h3 class="fw-bold">What we do for you</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-5 p-lg-5">
|
||||
<div class="row row-cols-1 row-cols-md-2 mx-auto" style="max-width: 900px;">
|
||||
<div class="col mb-5">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body px-4 py-5 px-md-5">
|
||||
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-shield-lock text-success">
|
||||
<path d="M5.338 1.59a61.44 61.44 0 0 0-2.837.856.481.481 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.725 10.725 0 0 0 2.287 2.233c.346.244.652.42.893.533.12.057.218.095.293.118a.55.55 0 0 0 .101.025.615.615 0 0 0 .1-.025c.076-.023.174-.061.294-.118.24-.113.547-.29.893-.533a10.726 10.726 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.775 11.775 0 0 1-2.517 2.453 7.159 7.159 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7.158 7.158 0 0 1-1.048-.625 11.777 11.777 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 62.456 62.456 0 0 1 5.072.56z"></path>
|
||||
<path d="M9.5 6.5a1.5 1.5 0 0 1-1 1.415l.385 1.99a.5.5 0 0 1-.491.595h-.788a.5.5 0 0 1-.49-.595l.384-1.99a1.5 1.5 0 1 1 2-1.415z"></path>
|
||||
</svg></div>
|
||||
<h5 class="fw-bold card-title">Secured over SSL with DANE</h5>
|
||||
<p class="text-muted card-text mb-4">Anyone with a resolver supporting DANE will be redirected to the HTTPS version on your site and will have an encrypted connection using DANE for verification.</p><a class="btn btn-primary shadow" role="button" target="_blank" href="https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mb-5" style="margin-top: 150px;">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body px-4 py-5 px-md-5">
|
||||
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-building-lock text-success">
|
||||
<path d="M2 1a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6.5a.5.5 0 0 1-1 0V1H3v14h3v-2.5a.5.5 0 0 1 .5-.5H8v4H3a1 1 0 0 1-1-1V1Z"></path>
|
||||
<path d="M4.5 2a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1Zm2.5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1Zm3.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1ZM4 5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1ZM7.5 5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1Zm2.5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1ZM4.5 8a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1Zm2.5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1ZM9 13a1 1 0 0 1 1-1v-1a2 2 0 1 1 4 0v1a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1v-2Zm3-3a1 1 0 0 0-1 1v1h2v-1a1 1 0 0 0-1-1Z"></path>
|
||||
</svg></div>
|
||||
<h5 class="fw-bold card-title">Hosted with security in mind</h5>
|
||||
<p class="text-muted card-text mb-4">The worker servers are fully secured so that the only access is HTTP for web traffic.<br>Any terminal access for maintenance is over a VPN tunnel to stop any unauthorized access. All SSH connections send a push notification to the admin's phone to alert him to any access.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mb-4" style="margin-top: -150px;">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body px-4 py-5 px-md-5">
|
||||
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="text-success">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"></path>
|
||||
</svg></div>
|
||||
<h5 class="fw-bold card-title">Regular backups</h5>
|
||||
<p class="text-muted card-text mb-4">The worker server has regular encrypted backups to an offsite location to allow recovery of any data if the server goes down.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="container py-5">
|
||||
<div class="mx-auto" style="max-width: 900px;">
|
||||
<div class="row row-cols-1 row-cols-md-2 d-flex justify-content-center">
|
||||
<div class="col mb-4">
|
||||
<div class="card bg-primary-light">
|
||||
<div class="card-body text-center px-4 py-5 px-md-5">
|
||||
<p class="fw-bold text-primary card-text mb-2">Fully Managed</p>
|
||||
<h5 class="fw-bold card-title mb-3">Control over plugins, users, access and everything with your site.</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mb-4">
|
||||
<div class="card bg-secondary-light">
|
||||
<div class="card-body text-center px-4 py-5 px-md-5">
|
||||
<p class="fw-bold text-secondary card-text mb-2">Free Licences</p>
|
||||
<h5 class="fw-bold card-title mb-3">We offer a free tier with 1 GB storage capacity.<br>If you would like more please contact us to find an acceptable price for you.<br>Join our Discord for a free licence</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="contact" class="py-5">
|
||||
<div class="container">
|
||||
<div class="row mb-5">
|
||||
<div class="col-md-8 col-xl-6 text-center mx-auto">
|
||||
<p class="fw-bold text-success mb-2">Contacts</p>
|
||||
<h2 class="fw-bold">Contact Us</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row d-flex justify-content-center">
|
||||
<div class="col-md-4 col-xl-4 d-flex justify-content-center justify-content-xl-start">
|
||||
<div class="d-flex flex-wrap flex-md-column justify-content-md-start align-items-md-start h-100">
|
||||
<div class="d-flex align-items-center p-3">
|
||||
<div class="bs-icon-md bs-icon-circle bs-icon-primary shadow d-flex flex-shrink-0 justify-content-center align-items-center d-inline-block bs-icon bs-icon-md"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord">
|
||||
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z"></path>
|
||||
</svg></div>
|
||||
<div class="px-2">
|
||||
<h6 class="fw-bold mb-0">Discord</h6>
|
||||
<p class="text-muted mb-0"><a href="https://l.woodburn.au/discord" target="_blank">Join our server</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center p-3">
|
||||
<div class="bs-icon-md bs-icon-circle bs-icon-primary shadow d-flex flex-shrink-0 justify-content-center align-items-center d-inline-block bs-icon bs-icon-md"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-envelope">
|
||||
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"></path>
|
||||
</svg></div>
|
||||
<div class="px-2">
|
||||
<h6 class="fw-bold mb-0">Email</h6>
|
||||
<p class="text-muted mb-0"><a href="mailto:hosting@nathan.woodburn.au">hosting@nathan.woodburn.au</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="bg-dark">
|
||||
<div class="container py-4 py-lg-5">
|
||||
<hr>
|
||||
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
|
||||
<p class="mb-0">Copyright © 2023 HNSHosting</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bold-and-dark.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,70 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="dark" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Sign up - HNSHosting</title>
|
||||
<link rel="canonical" href="https://wp.hnshosting.au/register.html">
|
||||
<meta property="og:url" content="https://wp.hnshosting.au/register.html">
|
||||
<meta name="description" content="HNS Hosting Wordpress">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&display=swap">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
|
||||
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><span class="bs-icon-sm bs-icon-circle bs-icon-primary shadow d-flex justify-content-center align-items-center me-2 bs-icon" style="background: transparent;"><img src="assets/img/favicon.png" style="width: 100%;"></span><span>HNSHosting</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
|
||||
<div class="collapse navbar-collapse" id="navcol-1">
|
||||
<ul class="navbar-nav mx-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/#contact">Contact</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="register">Register Site</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row mb-4 mb-lg-5">
|
||||
<div class="col-md-8 col-xl-6 text-center mx-auto">
|
||||
<p class="fw-bold text-success mb-2">Register site</p>
|
||||
<h2 class="fw-bold">Get started with your Hosting here.</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row d-flex justify-content-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="card">
|
||||
<div class="card-body text-center d-flex flex-column align-items-center">
|
||||
<form method="post" action="/register">
|
||||
<div class="mb-3"><input type="text" name="domain" placeholder="Domain"></div>
|
||||
<div class="mb-3"><input type="text" placeholder="Licence Key" name="licence"></div>
|
||||
<div class="mb-3">
|
||||
<p style="color: rgb(255,0,0);">{{ERROR_MESSAGE}}</p><button class="btn btn-primary shadow d-block w-100" type="submit">Create site</button>
|
||||
</div>
|
||||
<p class="text-muted">Don't have a licence?<br><a href="{{buy_licence_link}}" target="_blank">Click here to buy one</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="bg-dark">
|
||||
<div class="container py-4 py-lg-5">
|
||||
<hr>
|
||||
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
|
||||
<p class="mb-0">Copyright © 2023 HNSHosting</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bold-and-dark.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,58 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="dark" lang="en" style="height: 100%;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Home - HNSHosting</title>
|
||||
<link rel="canonical" href="https://wp.hnshosting.au/success.html">
|
||||
<meta property="og:url" content="https://wp.hnshosting.au/success.html">
|
||||
<meta name="description" content="HNS Hosting Wordpress">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="508x430" href="assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&display=swap">
|
||||
</head>
|
||||
|
||||
<body style="height: 100%;">
|
||||
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
|
||||
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><span class="bs-icon-sm bs-icon-circle bs-icon-primary shadow d-flex justify-content-center align-items-center me-2 bs-icon" style="background: transparent;"><img src="assets/img/favicon.png" style="width: 100%;"></span><span>HNSHosting</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
|
||||
<div class="collapse navbar-collapse" id="navcol-1">
|
||||
<ul class="navbar-nav mx-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/#contact">Contact</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="register">Register Site</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="bg-dark" style="height: 75%;">
|
||||
<div class="container pt-4 pt-xl-5">
|
||||
<div class="row pt-5" style="height: 100%;">
|
||||
<div class="col-md-8 col-xl-6 text-center text-md-start mx-auto">
|
||||
<div class="text-center">
|
||||
<p class="fw-bold text-success mb-2">Success</p>
|
||||
<h1 class="fw-bold">{{title| safe}}</h1>
|
||||
</div>
|
||||
<p class="text-center">{{message | safe}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<footer class="bg-dark">
|
||||
<div class="container py-4 py-lg-5">
|
||||
<hr>
|
||||
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
|
||||
<p class="mb-0">Copyright © 2023 HNSHosting</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="assets/js/bold-and-dark.js"></script>
|
||||
<script src="assets/js/status_update.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -24,21 +24,6 @@ python3 -m pip install -r requirements.txt
|
||||
cp .env.example .env
|
||||
chmod +x wp.sh tlsa.sh
|
||||
|
||||
# Add proxy to docker
|
||||
mkdir ~/.docker
|
||||
echo """{
|
||||
\"proxies\": {
|
||||
\"default\": {
|
||||
\"httpProxy\": \"http://proxy.hnsproxy.au:80\",
|
||||
\"httpsProxy\": \"https://proxy.hnsproxy.au:443\",
|
||||
\"noProxy\": \"localhost\"
|
||||
}
|
||||
}
|
||||
}""" > ~/.docker/config.json
|
||||
|
||||
# Restart docker
|
||||
sudo systemctl restart docker
|
||||
|
||||
# Pull docker images to save time later
|
||||
docker pull mysql:5.7 &
|
||||
docker pull wordpress:latest &
|
||||
|
@ -44,8 +44,8 @@ def tlsa():
|
||||
tlsa_file = open('wordpress-'+domain+'/tlsa.txt', 'r')
|
||||
tlsa = tlsa_file.readlines()
|
||||
tlsa_file.close()
|
||||
except FileNotFoundError as e:
|
||||
return jsonify({'error': 'TLSA record not found', 'success': 'false', 'ex': str(e)})
|
||||
except FileNotFoundError:
|
||||
return jsonify({'error': 'TLSA record not found', 'success': 'false'})
|
||||
|
||||
# Remove newlines
|
||||
tlsa = tlsa[0].strip('\n')
|
||||
|
14
worker/wp.sh
@ -43,8 +43,6 @@ services:
|
||||
MYSQL_DATABASE: WordPressDatabase
|
||||
MYSQL_USER: WordPressUser
|
||||
MYSQL_PASSWORD: $MYSQL_PASSWORD
|
||||
volumes:
|
||||
- mysql:/var/lib/mysql
|
||||
wordpress:
|
||||
depends_on:
|
||||
- ${DOMAIN}db
|
||||
@ -58,11 +56,9 @@ services:
|
||||
WORDPRESS_DB_PASSWORD: $MYSQL_PASSWORD
|
||||
WORDPRESS_DB_NAME: WordPressDatabase
|
||||
volumes:
|
||||
- data:/var/www/html
|
||||
|
||||
[\"./:/var/www/html\"]
|
||||
volumes:
|
||||
mysql:
|
||||
data:
|
||||
mysql: {}
|
||||
""" > docker-compose.yml
|
||||
|
||||
# Start the containers
|
||||
@ -89,12 +85,6 @@ printf "server {
|
||||
sub_filter_once on;
|
||||
|
||||
}
|
||||
location = /.well-known/wallets/HNS {
|
||||
proxy_pass $URL;
|
||||
proxy_set_header Host \$http_host;
|
||||
rewrite ^(.*)$ \$1/ break;
|
||||
}
|
||||
|
||||
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/ssl/$DOMAIN.crt;
|
||||
|