Move Dockerfile from dockerWebsite repo and create docker-compose files

Signed-off-by: Lukas Schaefer <lukas@lschaefer.xyz>
This commit is contained in:
2026-01-11 22:19:23 -05:00
parent b3ff640b07
commit 8108c0c1aa
11 changed files with 258 additions and 121 deletions

15
.github/FUNDING.yml vendored
View File

@@ -1,15 +0,0 @@
# These are supported funding model platforms
github: lukasdotcom # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "composer" # See documentation for possible values
directory: "/html/" # Location of package manifests
schedule:
interval: "daily"

37
Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
FROM ubuntu:latest
ARG TARGETPLATFORM
ENV DEBIAN_FRONTEND noninteractive
ENV TZ America/Detroit
# Used to install all the required software
RUN apt update
RUN apt install -y apache2 --no-install-recommends
RUN apt install -y mariadb-server
RUN apt install -y php --no-install-recommends
RUN apt install -y php-json php-mysql php-curl php-zip --no-install-recommends
RUN apt install -y python3 --no-install-recommends
RUN apt install -y python3-pip --no-install-recommends
RUN apt install -y python3-requests --no-install-recommends
RUN apt install -y curl --no-install-recommends
# Clean apt cache
RUN apt clean
RUN rm -rf /var/lib/apt/lists/*
# Clones the website
WORKDIR "/var/www/"
RUN rm -r html
COPY html/ html/
COPY python/ python/
WORKDIR "/var/www/python"
RUN pip3 install --break-system-packages -r requirements.txt
WORKDIR "/var/www/html"
# Gives apache the right settings
# COPY 000-default.conf /etc/apache2/sites-available/000-default.conf
# COPY apache2.conf /etc/apache2/apache2.conf
RUN a2enmod rewrite
COPY start.sh /start.sh
RUN chmod +x /start.sh
CMD ["/start.sh"]
# Adds a healthcheck
HEALTHCHECK --timeout=5s CMD curl --fail http://localhost || exit 1
EXPOSE 80

45
docker-compose.prod.yaml Normal file
View File

@@ -0,0 +1,45 @@
version: "3.8"
services:
website:
networks:
- default
- caddy_website
container_name: website
build: .
pull_policy: build
restart: unless-stopped
depends_on:
- db
environment:
# Defaults are specified in restart.py
- DATABASE_USERNAME=${DATABASE_USERNAME} # Username and password are also the default admin credentials
- DATABASE_PASSWORD=${MARIADB_PASSWORD}
- DATABASE_NAME=website
- DATABASE_HOST=website-db
- DATABASE_PORT=3306
- WEBSITE_DEVELOPER=false
- MATOMO_DOMAIN=${MATOMO_DOMAIN}
- MATOMO_SITE_ID=${MATOMO_SITE_ID}
- TURNSTILE_SECRET=${TURNSTILE_SECRET}
- TURNSTILE_SITEKEY=${TURNSTILE_SITEKEY}
- TMDB_API_KEY=${TMDB_API_KEY}
db:
networks:
- default
image: mariadb:latest
container_name: website-db
restart: unless-stopped
volumes:
- /pool/website:/var/lib/mysql
environment:
- MARIADB_RANDOM_ROOT_PASSWORD=true
- MARIADB_DATABASE=website
- MARIADB_USER=${DATABASE_USERNAME}
- MARIADB_PASSWORD=${MARIADB_PASSWORD}
- MARIADB_PORT=3306
networks:
caddy_website:
external: true
default:

50
docker-compose.yaml Normal file
View File

@@ -0,0 +1,50 @@
version: "3.8"
services:
website:
networks:
- default
container_name: website-dev
build: .
pull_policy: build
restart: unless-stopped
depends_on:
- db
ports:
- "80:80"
volumes:
- ./html:/var/www/html
- ./python:/var/www/python
environment:
# Defaults are specified in restart.py
- DATABASE_USERNAME=${DATABASE_USERNAME:-admin}
- DATABASE_PASSWORD=password
- DATABASE_NAME=website
- DATABASE_HOST=website-db-dev
- DATABASE_PORT=3306
- WEBSITE_DEVELOPER=true
- MATOMO_DOMAIN=${MATOMO_DOMAIN:-}
- MATOMO_SITE_ID=${MATOMO_SITE_ID:-1}
- TURNSTILE_SECRET=${TURNSTILE_SECRET:-}
- TURNSTILE_SITEKEY=${TURNSTILE_SITEKEY:-}
- TMDB_API_KEY=${TMDB_API_KEY:-}
db:
networks:
- default
image: mariadb:latest
container_name: website-db-dev
restart: unless-stopped
volumes:
- db_data:/var/lib/mysql
environment:
- MARIADB_RANDOM_ROOT_PASSWORD=true
- MARIADB_DATABASE=website
- MARIADB_USER=${DATABASE_USERNAME:-admin}
- MARIADB_PASSWORD=password
- MARIADB_PORT=3306
networks:
default:
volumes:
db_data:

View File

@@ -1,2 +0,0 @@
#REMOVE LINE 1 WHEN DONE - please fill out all details for the json file as a config and rename to config.json
{ "api": "youtube api key", "database": { "username": "", "name": "", "password": "", "backupLocation": Where the backups should be stored, "backupLength" : how long the backups should be stored }, "developer": true/false Reminder that there should be no quotation marks(This should be a boolean), "throttle":the amount of login requests allowed per time period, "throttleTime":The number of seconds allowed for the throttle time period, "fanStart" : Tells the pi when to start the fan, "fanStop": Tells the pi when to stop the fan, "turnstileSecret": A cloudflare turnstile key for login security, "turnstileClient" : A cloudflare turnstile key for login security, "TMDBApiKey": Api Key for themoviedb.org}

View File

@@ -82,12 +82,12 @@ function sanitize($value) # Used to sanitize a value very strictly
}
function dbConnect()
{ // Is used to connect to the database
$SERVERLOCATION = "localhost";
if (!file_exists($_SERVER["DOCUMENT_ROOT"] . "/config.json")) {
exit();
}
$jsonInfo = file_get_contents($_SERVER["DOCUMENT_ROOT"] . "/config.json");
$jsonData = json_decode($jsonInfo, true);
$SERVERLOCATION = $jsonData["database"]["host"];
$DATA_USERNAME = $jsonData["database"]["username"];
$DATABASENAME = $jsonData["database"]["name"];
$PASSWORD = $jsonData["database"]["password"];

View File

@@ -2,7 +2,6 @@
import mysql.connector as mysql
import os
import json
import time
def readFile(
@@ -33,32 +32,13 @@ def connect(database=""):
)
if not database:
database = dbInfo["database"]["name"]
try:
db = mysql.connect(
host="localhost",
passwd=dbInfo["database"]["password"],
user=dbInfo["database"]["username"],
database=database,
)
except: # Used to automatically create the user and database
path = __file__[: __file__.rindex("/") + 1]
with open(path + "fix.sql") as f:
text = f.read()
text = text.replace("{username}", dbInfo["database"]["username"])
text = text.replace("{password}", dbInfo["database"]["password"])
text = text.replace("{database}", dbInfo["database"]["name"])
with open(path + "fix2.sql", "w") as f:
f.write(text)
os.system(f"mysql < {path}fix2.sql")
os.remove(path + "fix2.sql")
print("Created the user for the database", time.time())
db = mysql.connect(
host="localhost",
passwd=dbInfo["database"]["password"],
user=dbInfo["database"]["username"],
database=database,
)
appendValue("log", ["9", "Created the user for the database", str(time.time())])
db = mysql.connect(
host=dbInfo["database"]["host"],
password=dbInfo["database"]["password"],
user=dbInfo["database"]["username"],
database=database,
port=dbInfo["database"]["port"],
)
cursor = db.cursor()
return db, cursor
@@ -118,11 +98,13 @@ def backUp(fileName):
password = dbInfo["database"]["password"]
database = dbInfo["database"]["name"]
location = dbInfo["database"]["backupLocation"]
host = dbInfo["database"]["host"]
port = dbInfo["database"]["port"]
locationdata = f"{location}/{fileName}"
if not os.path.exists(location):
os.system(f"mkdir {location}")
os.system(
f"mysqldump -u {username} --password={password} --result-file={locationdata} {database}"
f"mysqldump -u {username} --host={host} --port={port} --password={password} --result-file={locationdata} {database}"
)

2
python/requirements.txt Executable file
View File

@@ -0,0 +1,2 @@
mysql-connector-python
requests

View File

@@ -10,23 +10,34 @@ import datetime
import sys
import glob
import string
whitelist = set(string.ascii_letters + string.digits + "/" + "@" + "." + "-" + "_")
def sanitize(txt): # Is a very simple sanitizer that allows all ascii_letters numbers and the / and @
if type(txt) == type(1) or type(txt) == type(True): # Makes sure that if it is an int or a bool it will not be sanitized because that is not neccessary.
def sanitize(
txt,
): # Is a very simple sanitizer that allows all ascii_letters numbers and the / and @
if type(txt) == type(1) or type(txt) == type(
True
): # Makes sure that if it is an int or a bool it will not be sanitized because that is not neccessary.
return txt
else:
return ''.join([ch for ch in txt if ch in whitelist])
return "".join([ch for ch in txt if ch in whitelist])
def error(e):
return "".join(traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__))
return "".join(
traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
)
def temp(): # Returns the temprature of the RPI
def temp(): # Returns the temprature of the RPI
return readFile("/sys/class/thermal/thermal_zone0/temp") / 1000
def writeFile(location, info, permission=True): # Will write info in json format to a file
def writeFile(
location, info, permission=True
): # Will write info in json format to a file
with open(location, "w") as f:
json.dump(info, f)
if permission:
@@ -38,6 +49,7 @@ def readFile(location):
with open(location) as f:
return json.load(f)
try:
# Looks at the configuration at understands the config
try:
@@ -47,34 +59,39 @@ try:
os.system("chown -R root:root " + configFilePath)
configFilePath = configFilePath + "config.json"
# Will find the location where the config should be located.
location = __file__[: __file__.rindex("/python/restart.py") + 1] + "html/"
location = __file__[: __file__.rindex("/python/restart.py") + 1] + "html"
# Creates config with the enviromental variables
if os.getenv("WEBSITE_DEVELOPER", "false") == "true":
developmentMachine = True
else:
developmentMachine = False
developmentMachine = os.getenv("WEBSITE_DEVELOPER", "false") == "true"
# This stores a list for the default config
envConfiguration = [
[["passwordOptions", "cost"], int(os.getenv("PASSWORD_ROUNDS", '10'))],
[["passwordOptions", "cost"], int(os.getenv("PASSWORD_ROUNDS", "10"))],
[["mail", "server"], os.getenv("MAIL_SMTP_SERVER", "smtp.sendgrid.net")],
[["mail", "username"], os.getenv("MAIL_USERNAME", "apikey")],
[["mail", "password"], os.getenv("MAIL_PASSWORD", "none")],
[["mail", "port"], int(os.getenv("MAIL_SMTP_PORT", "587"))],
[["database", "username"], os.getenv("WEBSITE_USER", "admin")],
[["database", "name"], os.getenv("WEBSITE_DATABASE_TABLE", "website")],
[["database", "password"], os.getenv("WEBSITE_PASSWORD", "password")],
[["database", "backupLocation"], os.getenv("WEBSITE_BACKUP_LOCATION", "/backup")],
[["database", "backupLength"], int(os.getenv("WEBSITE_BACKUP_LENGTH", "604800"))],
[["database", "username"], os.getenv("DATABASE_USERNAME", "admin")],
[["database", "name"], os.getenv("DATABASE_NAME", "website")],
[["database", "password"], os.getenv("DATABASE_PASSWORD", "password")],
[["database", "host"], os.getenv("DATABASE_HOST", "localhost")],
[["database", "port"], int(os.getenv("DATABASE_PORT", "3306"))],
[
["database", "backupLocation"],
os.getenv("WEBSITE_BACKUP_LOCATION", "/backup"),
],
[
["database", "backupLength"],
int(os.getenv("WEBSITE_BACKUP_LENGTH", "604800")),
],
[["developer"], developmentMachine],
[["throttle"], int(os.getenv("WEBSITE_THROTTLE", "5"))],
[["throttleTime"], int(os.getenv("WEBSITE_THROTTLE_TIME", '30'))],
[["fanStart"], int(os.getenv("WEBSITE_FAN_START", '43'))],
[["fanStop"], int(os.getenv("WEBSITE_FAN_STOP", '35'))],
[["matomoDomain"], os.getenv("MATOMO_DOMAIN", 'example.com')],
[["matomoSiteId"], int(os.getenv("MATOMO_SITE_ID", '1'))],
[["turnstileSecret"], os.getenv("TURNSTILE_SECRET", '')],
[["turnstileSitekey"], os.getenv("TURNSTILE_SITEKEY", '')],
[["TMDBApiKey"], os.getenv("TMDB_API_KEY", '')],
[["throttleTime"], int(os.getenv("WEBSITE_THROTTLE_TIME", "30"))],
[["fanStart"], int(os.getenv("WEBSITE_FAN_START", "43"))],
[["fanStop"], int(os.getenv("WEBSITE_FAN_STOP", "35"))],
[["matomoDomain"], os.getenv("MATOMO_DOMAIN", "example.com")],
[["matomoSiteId"], int(os.getenv("MATOMO_SITE_ID", "1"))],
[["turnstileSecret"], os.getenv("TURNSTILE_SECRET", "")],
[["turnstileSitekey"], os.getenv("TURNSTILE_SITEKEY", "")],
[["TMDBApiKey"], os.getenv("TMDB_API_KEY", "")],
]
if os.path.exists(location + "/config.json"):
configuration = readFile(location + "/config.json")
@@ -99,11 +116,13 @@ try:
# Checks if the key value pair exists or if it has to be created and the enviromental variable value has to be used
if x[0][0] in configuration:
if x[0][1] in configuration[x[0][0]]:
configuration[x[0][0]][x[0][1]] = sanitize(configuration[x[0][0]][x[0][1]])
configuration[x[0][0]][x[0][1]] = sanitize(
configuration[x[0][0]][x[0][1]]
)
else:
configuration[x[0][0]][x[0][1]] = sanitize(x[1])
else:
configuration[x[0][0]] = {x[0][1] : sanitize(x[1])}
configuration[x[0][0]] = {x[0][1]: sanitize(x[1])}
except:
print("Failed sanitizing the config")
raise Exception
@@ -121,6 +140,7 @@ try:
): # Imports this library in a slow way because it is a pain and likes to not work
try:
import database
break
except:
continue
@@ -176,8 +196,7 @@ try:
return time2
def buttonPress(status): # Will run this script everytime the button is pressed
minimum = database.search(
"internet", "id=(SELECT MIN(id) FROM internet)")
minimum = database.search("internet", "id=(SELECT MIN(id) FROM internet)")
minimum = int(minimum[5]) - 1
if minimum == 0:
minimum = -1
@@ -190,10 +209,10 @@ try:
else:
writeLog("Internet Schedule changed to on due to button", 5)
database.appendValue(
"internet", ["2", "1", "2", "1", str(
time.time() + 3600), str(minimum)]
"internet", ["2", "1", "2", "1", str(time.time() + 3600), str(minimum)]
)
def backupDatabase(): # Used to backup the database
def backupDatabase(): # Used to backup the database
timeData = callTime()
month = timeData[0]
day = timeData[1]
@@ -203,9 +222,12 @@ try:
file = f"{int(time.time())}or{month}-{day}-{year}at{hour}:{minute}.sql"
database.backUp(file)
return file
def dailyMaintenance(): # Will run daily and on boot
try:
if not os.path.isfile(location + "restore.json"): # Makes sure that it does not disrupt a restore
if not os.path.isfile(
location + "restore.json"
): # Makes sure that it does not disrupt a restore
file = backupDatabase()
writeLog(f"Ran backup on server and saved it to {file}", 18)
else:
@@ -216,10 +238,13 @@ try:
backupLocation = configuration["database"]["backupLocation"]
files = glob.glob(f"{backupLocation}/*.sql")
for x in range(len(files)):
files[x] = files[x][len(backupLocation)+1:]
files[x] = files[x][len(backupLocation) + 1 :]
for x in files:
try:
if int(x[:x.find("or")]) < time.time() - configuration["database"]["backupLength"]:
if (
int(x[: x.find("or")])
< time.time() - configuration["database"]["backupLength"]
):
os.remove(f"{backupLocation}/{x}")
writeLog(f"Removed old backup due to age, named {x}", 20)
except:
@@ -227,7 +252,7 @@ try:
# Gets a list of all backups to store in a file for easier access.
files = glob.glob(f"{backupLocation}/*.sql")
for x in range(len(files)):
files[x] = files[x][len(backupLocation)+1:]
files[x] = files[x][len(backupLocation) + 1 :]
files.sort(reverse=True)
writeFile(location + "/backups.json", files)
# Will repair all databases and update them
@@ -237,13 +262,23 @@ try:
for x in repaired:
writeLog(f"Database {x} was corrupted/missing and was restored", 9)
# Will clean the golf games database
deleted = database.trueSearch(f"SELECT ID FROM golfGame WHERE turnStartTime<{time.time()-86400}")
deleted = database.trueSearch(
f"SELECT ID FROM golfGame WHERE turnStartTime<{time.time()-86400}"
)
for x in deleted:
writeLog(f"Game #{x[0]} deleted because it is too old", 16)
database.command(f"DELETE FROM golfGame WHERE turnStartTime<{time.time()-86400}") # Removes games that have not been touched for more than 24 hours
database.command("DELETE FROM golfGamePlayers WHERE NOT EXISTS (SELECT * FROM golfGame WHERE golfGamePlayers.gameID = golfGame.ID)") # Removes players from games that do not exist
database.command("DELETE FROM golfGameCards WHERE NOT EXISTS (SELECT * FROM golfGame WHERE golfGameCards.gameID = golfGame.ID)") # Removes players from games that do not exist
database.command(f"DELETE FROM cookieClicker WHERE lastUpdate<{(time.time()-86400*30)*1000}") # Removes cookie clicker data that has not been touched for a month
database.command(
f"DELETE FROM golfGame WHERE turnStartTime<{time.time()-86400}"
) # Removes games that have not been touched for more than 24 hours
database.command(
"DELETE FROM golfGamePlayers WHERE NOT EXISTS (SELECT * FROM golfGame WHERE golfGamePlayers.gameID = golfGame.ID)"
) # Removes players from games that do not exist
database.command(
"DELETE FROM golfGameCards WHERE NOT EXISTS (SELECT * FROM golfGame WHERE golfGameCards.gameID = golfGame.ID)"
) # Removes players from games that do not exist
database.command(
f"DELETE FROM cookieClicker WHERE lastUpdate<{(time.time()-86400*30)*1000}"
) # Removes cookie clicker data that has not been touched for a month
writeLog("Server maintenance ran succesfully.", 12)
# Makes sure that the vendor folder is blocked
try:
@@ -251,22 +286,22 @@ try:
except:
1
with open(location + "vendor/.htaccess", "w") as f:
f.write("""Order allow,deny
Deny from all""")
f.write(
"""Order allow,deny
Deny from all"""
)
while True: # will wait until connected to internet
# Waits until the database is ready
while True:
try:
urllib.request.urlopen("https://google.com")
db, cursor = database.connect()
cursor.close()
db.close()
break
except Exception:
try:
urllib.request.urlopen("https://bing.com")
break
except Exception:
# Skips the waiting if development machine (So you don't have to wait 2 minutes for the booting)
if (developmentMachine):
break
continue
except Exception as e:
print(f"Database connection failed: {e}")
time.sleep(1)
continue
# Runs stuff that runs every boot
dailyMaintenance()
# Will turn on the internet to make sure that it is on
@@ -312,8 +347,7 @@ Deny from all""")
os.remove(location + "maintenance-mode")
lastBackup = callTime()[1]
try:
minimum = database.search(
"internet", "id=(SELECT MIN(id) FROM internet)")
minimum = database.search("internet", "id=(SELECT MIN(id) FROM internet)")
if not minimum:
database.appendValue("internet", internetOnDeafult)
minimum = internetOnDeafult
@@ -347,21 +381,27 @@ Deny from all""")
# Will check every 2 seconds if the button is pressed and when it is show it on the led and then wait another second to verify that it is an actual press
while True:
time.sleep(2)
if os.path.isfile(location + "restart.json"): # Used to restart the server
if os.path.isfile(location + "restart.json"): # Used to restart the server
writeLog("Server is being restarted", 12)
os.remove(location + "restart.json")
os.system(f"python3 {__file__} restart")
exit()
if os.path.isfile(location + "update.json"): # Used to update the server
if os.path.isfile(location + "update.json"): # Used to update the server
writeLog("Server is being updated.", 19)
os.remove(location + "update.json")
os.system(f"git --work-tree={location[:-6]} --git-dir={location[:-5]}.git reset --hard")
os.system(f"git --work-tree={location[:-6]} --git-dir={location[:-5]}.git pull > {location}updateInfo.log")
os.system(
f"git --work-tree={location[:-6]} --git-dir={location[:-5]}.git reset --hard"
)
os.system(
f"git --work-tree={location[:-6]} --git-dir={location[:-5]}.git pull > {location}updateInfo.log"
)
os.system(f"{location[:-5]}python/update.sh")
os.system(f"composer --working-dir={location} update")
os.system(f"chown www-data:www-data {location}updateInfo.log")
writeLog("Server updated successfully.", 19)
if os.path.isfile(location + "restore.json"): # Used to restore a previous version of the database
if os.path.isfile(
location + "restore.json"
): # Used to restore a previous version of the database
try:
file = readFile(location + "restore.json")
f = open(location + "maintenance-mode", "w")
@@ -371,7 +411,7 @@ Deny from all""")
biggest = 0
for x in backups:
try:
check = int(x[:x.find("or")])
check = int(x[: x.find("or")])
if check > biggest:
biggest = check
file = x
@@ -387,7 +427,7 @@ Deny from all""")
writeLog(f"Backup {file} was succesfully restored", 18)
except:
writeLog(f"Restore for {file} failed", 9)
#Makes sure to clean up after it is done
# Makes sure to clean up after it is done
try:
os.remove(location + "restore.json")
except:

9
start.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -e
# Start Apache in the foreground
apache2ctl -D FOREGROUND &
# Run restart.py in the foreground
python3 /var/www/python/restart.py