Compare commits

..

15 Commits

Author SHA1 Message Date
c90cb7d000 Update images 2020-09-07 16:41:58 -04:00
ac51f5d3ae Add new overviewer map 2020-09-06 02:53:11 -04:00
7a08ba4ac2 Resume update for September 2020 2020-09-06 02:01:39 -04:00
d449146207 Upgrade server, added old-worlds directory 2019-07-15 10:31:15 -04:00
86e9ab7c88 Update notice 2018-08-22 13:43:06 -04:00
25df631ce8 Add maintenance notice about generating new map
Have to take the server down while map is generating. The minecraft overviewer
1.13 branch is abysmally slow generating a new map.
2018-08-21 20:20:04 -04:00
1fd090e379 More notices about map not working in 1.13 2018-08-17 13:39:19 -04:00
56580c2afe Add upgrade notice to webpage 2018-08-16 12:22:28 -04:00
6845a6f232 Add notices and info about username whitelist 2018-04-15 15:08:04 -04:00
55c01499dd Just use one gunicorn worker to save memory 2018-03-27 15:58:50 -04:00
21caa3f7a1 Sanitize chat input
Don't let someone send a chat message with "^C" and kill the server.
2018-03-27 15:58:33 -04:00
095de45d54 Changing config symlinks to hard links w/ content 2017-12-08 10:30:18 -05:00
e276004266 Add config files to config/ subdir 2017-12-07 15:24:24 -05:00
2d28923d3b Add overviewerConfig.py to repo 2017-12-07 15:18:47 -05:00
75ef33e181 Add script that notifies of joins over IFTTT 2017-12-04 13:34:01 -05:00
15 changed files with 403 additions and 24 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
server.log
map
map-old
secrets.py

View File

@ -1,12 +1,10 @@
import calendar
import logging
import shlex
import subprocess
from datetime import datetime, timedelta
import unicodedata
import redis
from flask import Flask, request
conn = redis.Redis('localhost')
app = Flask(__name__)
@ -16,27 +14,31 @@ def setup_logging():
app.logger.setLevel(logging.INFO)
def sanitize_input(input):
input = "".join(ch for ch in input if unicodedata.category(ch)[0] != "C")
return shlex.quote(input.replace('^', ''))
@app.route('/chat/', methods=['POST'])
def send_chat():
if request.method == 'POST':
ip = request.headers.get('X-Forwarded-For', request.remote_addr)
recent_ips = conn.hgetall("minecraft_chat_recent_ips")
now = datetime.utcnow()
if ip in recent_ips:
if (now - datetime.fromutctimestamp(recent_ips[ip])) > timedelta.seconds(30):
recent_ips[ip] = calendar.timegm(now.utctimetuple())
if request.form.get('email', None):
return 'Text was entered into honeypot!', 200
if not request.form.get('say-text', None):
return 'No message to send!', 422
if request.form.get('say-username', None):
subprocess.call(['/usr/bin/screen', '-S', 'mc-panic-shack', '-p', '0', '-X', 'stuff',
'/say [{}]: {}\015'.format(request.form['say-username'], request.form['say-text'])])
subprocess.call([
'/usr/bin/screen', '-S', 'mc-panic-shack', '-p', '0', '-X', 'stuff',
'/say [{}]: {}\015'.format(
sanitize_input(request.form['say-username']),
sanitize_input(request.form['say-text']))
])
else:
subprocess.call(['/usr/bin/screen', '-S', 'mc-panic-shack', '-p', '0', '-X', 'stuff',
'/say {}\015'.format(request.form['say-text'])])
subprocess.call([
'/usr/bin/screen', '-S', 'mc-panic-shack', '-p', '0', '-X', 'stuff',
'/say {}\015'.format(sanitize_input(request.form['say-text']))
])
return 'Sending chat: ' + request.form.get('say-username', '') + ': ' + request.form['say-text']
if __name__ == "__main__":
app.run(host='0.0.0.0', port="8888")

View File

@ -0,0 +1,13 @@
[Unit]
Description=Gunicorn instance to serve minecraft-chat wsgi service
After=network.target
[Service]
User=thallada
Group=www-data
WorkingDirectory=/var/www/panic-shack/chat
Environment="PATH=/home/thallada/.virtualenvs/minecraft-chat/bin"
ExecStart=/home/thallada/.virtualenvs/minecraft-chat/bin/gunicorn --workers 1 --bind unix:minecraft-chat.sock -m 007 server:app --log-file /srv/minecraft-panic-shack/gunicorn.log
[Install]
WantedBy=multi-user.target

50
config/minecraft@.service Normal file
View File

@ -0,0 +1,50 @@
[Unit]
Description=Minecraft Server %i
After=network.target
[Service]
WorkingDirectory=/srv/minecraft-%i
User=thallada
Group=thallada
ProtectSystem=full
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ExecStart=/bin/sh -c '/usr/bin/screen -DmLS mc-%i /usr/bin/java -server -Xms1G -Xmx2G -XX:+UseG1GC -XX:+CMSIncrementalPacing -XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10 -jar $(ls -v | grep -i "FTBServer.*jar\|minecraft_server.*jar" | head -n 1) nogui'
ExecReload=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "reload"\\015'
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say SERVER SHUTTING DOWN IN 15 SECONDS..."\015'
ExecStop=/bin/sleep 5
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say SERVER SHUTTING DOWN IN 10 SECONDS..."\015'
ExecStop=/bin/sleep 5
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say SERVER SHUTTING DOWN IN 5 SECONDS..."\015'
ExecStop=/bin/sleep 5
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "say SERVER SHUTTING DOWN. Saving map..."\\015'
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "save-all"\\015'
ExecStop=/usr/bin/screen -p 0 -S mc-%i -X eval 'stuff "stop"\\015'
ExecStop=/bin/sleep 2
Restart=on-failure
RestartSec=60s
[Install]
WantedBy=multi-user.target
#########
# HowTo
#########
#
# Create directory in /opt/minecraft-XX where XX is a name like 'survival'
# Add minecraft_server.jar into dir with other conf files for minecraft server
#
# Enable/Start systemd service
# systemctl enable minecraft@survival
# systemctl start minecraft@survival
#
# To run multiple servers simply create a new dir structure and enable/start it
# systemctl enable minecraft@creative
# systemctl start minecraft@creative

12
config/nginx-panic-shack Normal file
View File

@ -0,0 +1,12 @@
server {
listen 80;
listen [::]:80;
root /var/www/panic-shack;
server_name panic-shack.hallada.net;
location / {
return 301 https://panic-shack.hallada.net$request_uri;
}
}

View File

@ -0,0 +1,36 @@
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name panic-shack.hallada.net;
root /var/www/panic-shack;
ssl_certificate /etc/letsencrypt/live/panic-shack.hallada.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panic-shack.hallada.net/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/panic-shack.hallada.net/fullchain.pem;
location /notify {
deny all;
return 404;
}
location /config {
deny all;
return 404;
}
location / {
try_files $uri $uri/ $uri.php?$args =404;
}
location /old-worlds {
autoindex on;
try_files $uri $uri/ $uri.php?$args =404;
}
location /chat {
include uwsgi_params;
proxy_set_header X-Forwarded-Host $host:$server_port; proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://unix:/var/www/panic-shack/chat/minecraft-chat.sock;
}
}

130
config/overviewerConfig.py Normal file
View File

@ -0,0 +1,130 @@
worlds["Death Pit"] = "/srv/minecraft-panic-shack/world"
renders["normalrender"] = {
"world": "Death Pit",
"title": "North",
"rendermode": smooth_lighting,
"dimension": "overworld",
"northdirection": "upper-left",
}
renders["normalrendersouth"] = {
"world": "Death Pit",
"title": "South",
"rendermode": smooth_lighting,
"dimension": "overworld",
"northdirection": "lower-right",
}
renders["normalrenderwest"] = {
"world": "Death Pit",
"title": "West",
"rendermode": smooth_lighting,
"dimension": "overworld",
"northdirection": "upper-right",
}
renders["normalrendereast"] = {
"world": "Death Pit",
"title": "East",
"rendermode": smooth_lighting,
"dimension": "overworld",
"northdirection": "lower-left",
}
renders["nightrender"] = {
"world": "Death Pit",
"title": "Night North",
"rendermode": smooth_night,
"dimension": "overworld",
"northdirection": "upper-left",
}
renders["nightrendersouth"] = {
"world": "Death Pit",
"title": "Night South",
"rendermode": smooth_night,
"dimension": "overworld",
"northdirection": "lower-right",
}
renders["nightrenderwest"] = {
"world": "Death Pit",
"title": "Night West",
"rendermode": smooth_night,
"dimension": "overworld",
"northdirection": "upper-right",
}
renders["nightrendereast"] = {
"world": "Death Pit",
"title": "Night East",
"rendermode": smooth_night,
"dimension": "overworld",
"northdirection": "lower-left",
}
renders["caverender"] = {
"world": "Death Pit",
"title": "Cave North",
"rendermode": cave,
"dimension": "overworld",
"northdirection": "upper-left",
}
renders["caverendersouth"] = {
"world": "Death Pit",
"title": "Cave South",
"rendermode": cave,
"dimension": "overworld",
"northdirection": "lower-right",
}
renders["caverenderwest"] = {
"world": "Death Pit",
"title": "Cave West",
"rendermode": cave,
"dimension": "overworld",
"northdirection": "upper-right",
}
renders["caverendereast"] = {
"world": "Death Pit",
"title": "Cave East",
"rendermode": cave,
"dimension": "overworld",
"northdirection": "lower-left",
}
renders["netherrender"] = {
"world": "Death Pit",
"title": "Nether North",
"rendermode": nether,
"dimension": "nether",
"northdirection": "upper-left",
}
renders["netherrendersouth"] = {
"world": "Death Pit",
"title": "Nether South",
"rendermode": nether,
"dimension": "nether",
"northdirection": "lower-right",
}
renders["netherrenderwest"] = {
"world": "Death Pit",
"title": "Nether West",
"rendermode": nether,
"dimension": "nether",
"northdirection": "upper-right",
}
renders["netherrendereast"] = {
"world": "Death Pit",
"title": "Nether East",
"rendermode": nether,
"dimension": "nether",
"northdirection": "lower-left",
}
outputdir = "/var/www/panic-shack/map/"

View File

@ -68,7 +68,7 @@ ol li code {
margin-bottom: 2px;
}
#map-link {
.map-link {
display: block;
text-align: center;
}
@ -130,3 +130,18 @@ ol li code {
margin-top: 5px;
margin-bottom: 0;
}
.maintenance-notice {
padding: 20px;
background: darkred;
color: white;
}
.maintenance-notice h3 {
margin: 0;
margin-right: 20px;
}
.maintenance-notice h3,span {
display: inline-block;
}

BIN
img/deathpit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

BIN
img/deathpit_shader_sm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -7,9 +7,13 @@
<meta charset="UTF-8">
</head>
<body>
<!-- <div class="maintenance-notice"> -->
<!-- <h3>Server is down</h3> -->
<!-- <span>ETA: 15 hours for generating a new Minecraft 1.13 map.</span> -->
<!-- </div> -->
<div class="width-limit">
<h1>Panic Shack - Minecraft Server</h1>
<img src="img/bridge.jpg" alt="Screenshot from the Minecraft server of a bridge over a lake in a jungle biome">
<img src="img/deathpit_shader_sm.png" alt="Screenshot from the Minecraft server of the swamp and the large house in the distance">
<h2>Server Status</h2>
<div id="server-status-error">
@ -60,6 +64,7 @@
<h2>How to Connect</h2>
<ol>
<li>Email me at tyler@hallada.net with your Minecraft username to request to join the server</li>
<li>Launch Minecraft (version <span id="info-server-name">1.12.2</span>)</li>
<li>Go to "Multiplayer"</li>
<li>Click "Add Server"</li>
@ -69,16 +74,22 @@
<li>You should now see the server in the list and be able to join it!</li>
</ol>
<h2>Server Map</h2>
<a href="map/" id="map-link">
<img src="img/map.jpg" alt="Screenshot from the in-browser map of the server centered on the lake and bridge">
View the server map
<h2>Server Maps</h2>
<a href="map/" class="map-link">
<img src="img/deathpit.png" alt="Screenshot from the in-browser map of the death pit server">
View Death Pit map
</a>
<p class="small-text">
Map is updated every day around 3 am EST. Last updated:&nbsp;
<span id="map-last-updated" class="small-text"></span>
This map is updated every day around 3 am EST. Last updated:&nbsp;
<span id="map-last-updated" class="small-text"></span><br>
</p>
<a href="map-old/" class="map-link">
<img src="img/map-old.jpg" alt="Screenshot from the in-browser map of the panic-shack server centered on the lake and bridge">
View Panic Shack map
</a>
<p class="small-text">
This map was last updated: 04/04/2019.
</p>
<h2>Server Log</h2>
</div>
<pre id="server-log"></pre>
@ -109,6 +120,59 @@
<span id="say-error" class="inline"></span>
<p id="say-notice" class="small-text">It may take a minute or two before the message appears in the above server log.</p>
</form>
<hr>
<h2>Download Old Worlds</h2>
<p>
<a href="/old-worlds">Visit here to download all of the previous worlds on this server.</a> You can load
them in Minecraft and play them in single player by following
<a href="https://minecraft.gamepedia.com/Tutorials/Map_downloads">these instructions</a>.
</p>
<h2>Notices</h2>
<p>
<ul>
<li>
<strong>September 5, 2020</strong>
Server is back up and running on Minecraft 1.16.2 with a new map.
</li>
<li>
<strong>May 18, 2020</strong>
Hibernating this server for a while to save resources for a 7DaysToDie server instead.
</li>
<li>
<strong>April 19, 2020</strong>
I switched the server over to a Spigot 1.15.2 server with the Vivecraft extension installed.
</li>
<li>
<strong>July 14, 2019:</strong>
Upgraded the server to 1.14.3 and regenerated the map. It's now using the seed "hardlead".
</li>
<li>
<strong>August 22, 2018:</strong>
Upgraded the server yet again to 1.13.1.
</li>
<li>
<strong>August 22, 2018:</strong>
The map should now be updating again nightly at 3 am EST.
</li>
<li>
<strong>August 16, 2018:</strong>
I upgraded the server to Minecraft version 1.13. The map is no longer working on this version and
won't update. I'll upgrade the map once it supports 1.13.
</li>
<li>
<strong>April 15, 2018:</strong>
The server has a user white-list. If you are not added to it then you cannot join the server.
Email me at tyler@hallada.net to request to join the server.
</li>
<li>
<strong>April 13, 2018:</strong>
I doubled the memory on the server so the server shouldn't keep running out of memory and crashing.
</li>
</ul>
</p>
</div>
<script src="https://mcapi.us/scripts/minecraft.js"></script>

52
notify/notify_joins.py Executable file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env python
# Read the Minecraft server log and diff it against the server log it saw last. If there any new joins in the diff, send
# a notification.
import codecs
import os
import re
from datetime import datetime
import requests
from secrets import IFTTT_WEBHOOK_KEY_TYLER, IFTTT_WEBHOOK_KEY_KAELAN
LOG_FILENAME = '/srv/minecraft-panic-shack/logs/latest.log'
OLD_LOG_FILENAME = '/srv/minecraft-panic-shack/logs/last-read.log'
USERNAME_BLACKLIST = ['anarchyeight', 'kinedactyl']
IFTTT_EVENT_NAME = 'user_joined_panic_shack'
def read_log(filename):
with codecs.open(filename, encoding='utf-8') as log:
return log.readlines()
def save_log(filename, lines):
with codecs.open(filename, 'w', encoding='utf-8') as log:
log.writelines(lines)
if __name__ == '__main__':
if (datetime.fromtimestamp(os.path.getmtime(LOG_FILENAME)) >
datetime.fromtimestamp(os.path.getmtime(OLD_LOG_FILENAME))):
new_log = read_log(LOG_FILENAME)
old_log = read_log(OLD_LOG_FILENAME)
if new_log[0] != old_log[0]:
# A log rotate occured
old_log = []
if len(new_log) > len(old_log):
for new_line in new_log[len(old_log):]:
match = re.match('[\[][0-9:]+[\]]\s[\[]Server thread/INFO]: (\S+) joined the game', new_line)
if match:
username = match.group(1)
if username not in USERNAME_BLACKLIST:
# IFTTT does not support sharing Applets anymore :(
r = requests.post(
'https://maker.ifttt.com/trigger/{}/with/key/{}'.format(IFTTT_EVENT_NAME,
IFTTT_WEBHOOK_KEY_TYLER),
data={'value1': username})
r = requests.post(
'https://maker.ifttt.com/trigger/{}/with/key/{}'.format(IFTTT_EVENT_NAME,
IFTTT_WEBHOOK_KEY_KAELAN),
data={'value1': username})
save_log(OLD_LOG_FILENAME, new_log)

1
old-worlds Symbolic link
View File

@ -0,0 +1 @@
/srv/minecraft-panic-shack/old-worlds

2
tox.ini Normal file
View File

@ -0,0 +1,2 @@
[flake8]
max_line_length = 120