aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormjfernez <mjfernez@gmail.com>2021-06-07 00:03:12 -0400
committermjfernez <mjfernez@gmail.com>2021-06-07 00:40:39 -0400
commitca5b85fb744221588859f3639ba7e4da0bc82649 (patch)
tree2595d1e315e09e5d35c362d95a35585416a7a2fb
downloadezcms-ca5b85fb744221588859f3639ba7e4da0bc82649.tar.gz
first commit
-rw-r--r--.gitignore138
-rw-r--r--README178
-rw-r--r--mimetypes.csv92
-rw-r--r--requirements.txt9
-rw-r--r--server.py198
-rw-r--r--siteconfig.py21
-rw-r--r--static/main.css48
-rw-r--r--templates/base.html28
-rw-r--r--templates/index.html26
-rw-r--r--templates/site/about/.description1
-rw-r--r--templates/site/about/faq.html3
-rw-r--r--templates/site/about/whoami.html1
-rw-r--r--templates/site/files/.description1
-rw-r--r--templates/site/files/1.html11
-rw-r--r--templates/site/fun/.description1
-rw-r--r--templates/site/fun/.links1
-rw-r--r--templates/site/home.html4
-rw-r--r--templates/site/license.html17
-rw-r--r--templates/site/thoughts/.description3
-rw-r--r--templates/site/thoughts/rants/dontread.txt2
-rw-r--r--templates/site/tutorials/.description3
-rw-r--r--templates/site/tutorials/.links1
-rw-r--r--templates/site/tutorials/.secret/secretfile.txt1
-rw-r--r--templates/site/tutorials/how-to-make-this-site.html1
-rw-r--r--templates/site/tutorials/linux/linuz1.html1
-rw-r--r--templates/site/tutorials/python/.description1
-rw-r--r--templates/site/tutorials/python/py-style.md93
-rw-r--r--templates/site/tutorials/python/test.py2
28 files changed, 886 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a81c8ee
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,138 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
diff --git a/README b/README
new file mode 100644
index 0000000..d723a5c
--- /dev/null
+++ b/README
@@ -0,0 +1,178 @@
+# EZCMS - a minimal and simple way to manage a website
+
+## Requirements
+
+Python 3.7+
+
+
+## Huh/What/Why?
+
+EZCMS (or "Easy CMS" for those of you who call that "zed" instead) is a minimal
+and simple way of managing content and serving static files on a website. It
+was mostly made for my own website which I wanted to be simple, but with some
+ability to easily add new pages in a template I like.
+
+EZCMS is designed around the idea that web servers are really just glorified
+file cabinets and draws heavily from the Unix philosophy of "everything is a
+file". When a user finds a site managed by this program, they are primarily
+greeted with a series of folders and files, but in a neat and easy to
+understand index. Each folder--preferably labeled appropriately so users
+know what they're links to other sites--will index itself on click, containing
+a short description of the directory, sub-directories, files, and links
+elsewhere.
+
+Why not just use a database or the million other CMS software packages out
+there? Zero reason not to! I just wanted to see what it would look like to
+build something from the ground up WITHOUT having to copy HTML over and over
+again. Databases are great, but so are filesystems, and I see no reason to
+overcomplicate when making a simple home page. This software is primarily
+geared towards bloggers or people who want a home page like it's 1999, but
+Flask has great documentation so I think you'll find it a pleasure to build on
+top of.
+
+
+## Quick start
+
+It's recommended to run each server in it's own virtual environment. This
+program uses python 3.7, so change `python` to either `python3` or `python3.7`
+depending on your needs. First clone this repo (with git clone, or download the
+zip), change into the directory, then:
+
+
+`python -m venv env`
+`pip install -r requirements.txt`
+`python server.py`
+
+
+Your server will (by default) be hosted on http://localhost:8000
+
+
+## Adding Pages
+
+To add a new page, all you need to do is add a new file (or a folder and a
+group of files) somewhere under one of the folders in `site`. This folder in
+particular is special since it contains the top-level folders which will be
+used to navigate your site, but any folders beneath will be automatically
+indexed.
+
+As an excercise, add a file to the `templates/site/thoughts/rants` folder
+called `myrant.html` and put the following content:
+
+`<p>I don't like spam!</p>`
+
+The new page will be rendered with your navbar on top and footer on the bottom
+when navigated to in the `rants folder` HTML files will by default be rendered
+in page, and all other types of files (like txt) will be returned without
+rendering.
+
+An important note, since these HTML files are being rendered by Flask,
+*you can make full use of the Jinja templating language*! So in other words,
+any template you've developed for flask is fully usable here--but remember it
+will be rendered *inside* the `templates/base.html` template. If you need to
+make tweaks to the navbar or footer, you'll want to edit that file instead.
+
+
+## Customization (or things you'll want to change right now)
+
+To make customization easier, this program comes with a configuration file with
+variables to tweak the display of your site call `siteconfig.py`.
+For example by default this program makes the navbar out of the directories in
+the `templates/site` directory, but you might want include other directories,
+or even external sites. Examples of how to change these options are provided
+in the comments on that file. Customization is also provided through the use of
+specific files.
+
+
+### Navbar customization
+
+Be default, the top navbar is populated by indexing and sorting the top-level
+`templates/site/` directory. You can override this to include any directories
+you want in any order, so long as they exist, but it's advised to still keep
+them all in the `site` directory to avoid confusion.
+
+
+### Index File Configuration
+
+This program uses a single master index file which is used when navigating to
+any directory--instead of having to put in an 'index.html' in each folder, or
+using the default apache/nginx/httpd auto-indexer. In it's place, you can
+optionally put a `.description` file to provide a short description of what's
+in the directory or a `.links` file
+
+The `.description` file should just be a text file with no formatting. If you
+want to add formatting, you can edit the `templates\index.html` file around the
+`{{ description }}` variable (for example, you could wrap it in <i></i> for
+*italics*
+
+The `.links` file is a pseudo-csv file which should contain a comma separated
+list containing a description and a relative or absolute URL to be linked. For
+example this line:
+
+`About,/about`
+
+Produces (roughly) the following HTML on your index page:
+
+`<li><a href="/about">About</a></li>`
+
+You can of course link to external sites, but you must specify the protocol
+(i.e. https://google.com, not google.com). Otherwise, it will be interpreted as
+a relative path like `example.net/google.com`.
+
+
+### Making Secret Directories and Files
+
+This program follows the Unix convention of placing a "." before directories
+and files to make them hidden. Aside from the special files mentioned above,
+this program will not index any file or directory with a "." prepended--and a
+user will receive a 404 error if they attempt to do so.
+
+
+### Mimetype Configuration
+
+Default mime types are primarily sourced from this page with some of my own
+additions for common source code files (like .c or .py):
+
+https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
+
+You can add your own by editing the 'mimetypes.csv' file in the following
+format:
+
+`.file_extension,type/yourcoolmimetype`
+
+Otherwise, the default mime type is `application/octet-stream`, which (for most
+browsers) triggers the browser to download the file
+
+
+### License Configuration
+
+The default license type is the same as this program's: CC0. The HTML is from
+Creative Commons, with some modifications I like to add. You can of course
+replace the HTML with your own license (or none), by editing
+`templates/site/license.html`
+
+
+### Other tips
+
+There are a few special directories linked that are needed to
+customize your site. First the `static` directory, which holds your static
+files like CSS templates and images. Second the `raw` directory, which allows
+the user to access files the `templates/site` as raw files instead of HTML.
+You can disable it by deleting the code under `send_file_from_site` or
+`send_file_from_static` in `server.py`.
+
+
+## Deploying a server
+
+You should NOT run this server as in the quick start, but instead deploy it in
+an appropriate container. Refer to https://flask.palletsprojects.com/en/2.0.x/deploying/
+for options, but an easy option I like is to use uswgi. On a Debian-like distro
+
+
+`sudo apt install uwsgi`
+`python -m venv env`
+`uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=server:app`
+
+Then point your main http daemon (niginx, apache, httpd) to the socket you
+made. See nginx as an example here, more in the same doc:
+
+https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html
diff --git a/mimetypes.csv b/mimetypes.csv
new file mode 100644
index 0000000..aa3b678
--- /dev/null
+++ b/mimetypes.csv
@@ -0,0 +1,92 @@
+.aac,audio/aac
+.abw,application/x-abiword
+.arc,application/x-freearc
+.avi,video/x-msvideo
+.azw,application/vnd.amazon.ebook
+.bin,application/octet-stream
+.bmp,image/bmp
+.bz,application/x-bzip
+.bz2,application/x-bzip2
+.cda,application/x-cdf
+.csh,application/x-csh
+.css,text/css
+.csv,text/csv
+.doc,application/msword
+.docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document
+.eot,application/vnd.ms-fontobject
+.epub,application/epub+zip
+.gz,application/gzip
+.gif,image/gif
+.html,text/html
+.ico,image/vnd.microsoft.icon
+.ics,text/calendar
+.jar,application/java-archive
+.jpeg,image/jpeg
+.js,text/javascript
+.json,application/json
+.jsonld,application/ld+json
+.midi,audio/midi audio/x-midi
+.mjs,text/javascript
+.mp3,audio/mpeg
+.mp4,video/mp4
+.mpeg,video/mpeg
+.mpkg,application/vnd.apple.installer+xml
+.odp,application/vnd.oasis.opendocument.presentation
+.ods,application/vnd.oasis.opendocument.spreadsheet
+.odt,application/vnd.oasis.opendocument.text
+.oga,audio/ogg
+.ogv,video/ogg
+.ogx,application/ogg
+.opus,audio/opus
+.otf,font/otf
+.png,image/png
+.pdf,application/pdf
+.php,application/x-httpd-php
+.ppt,application/vnd.ms-powerpoint
+.pptx,application/vnd.openxmlformats-officedocument.presentationml.presentation
+.rar,application/vnd.rar
+.rtf,application/rtf
+.sh,application/x-sh
+.svg,image/svg+xml
+.swf,application/x-shockwave-flash
+.tar,application/x-tar
+.tiff,image/tiff
+.ts,video/mp2t
+.ttf,font/ttf
+.txt,text/plain
+.vsd,application/vnd.visio
+.wav,audio/wav
+.weba,audio/webm
+.webm,video/webm
+.webp,image/webp
+.woff,font/woff
+.woff2,font/woff2
+.xhtml,application/xhtml+xml
+.xls,application/vnd.ms-excel
+.xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+.xml,application/xml
+.xul,application/vnd.mozilla.xul+xml
+.zip,application/zip
+.7z,application/x-7z-compressed
+.c,text/plain
+.py,text/plain
+.php,text/plain
+.java,text/plain
+.sh,text/plain
+.bash,text/plain
+.fish,text/plain
+.lua,text/plain
+.cpp,text/plain
+.asm,text/plain
+.vbs,text/plain
+.ksh,text/plain
+.csh,text/plain
+.zsh,text/plain
+.h,text/plain
+.hpp,text/plain
+.awk,text/plain
+.bat,text/plain
+.pl,text/plain
+.R,text/plain
+.yml,text/plain
+.yaml,text/plain \ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..565424f
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,9 @@
+click==8.0.1
+Flask==2.0.1
+itsdangerous==2.0.1
+Jinja2==3.0.1
+MarkupSafe==2.0.1
+pip==20.3.4
+pkg-resources==0.0.0
+setuptools==44.1.1
+Werkzeug==2.0.1
diff --git a/server.py b/server.py
new file mode 100644
index 0000000..8614fe3
--- /dev/null
+++ b/server.py
@@ -0,0 +1,198 @@
+import os
+from flask import Flask
+from flask import request, send_from_directory, abort
+from flask import render_template, render_template_string
+from siteconfig import siteconfig
+
+app = Flask(__name__)
+
+# bit of a hack.
+# Brackets don't play nicely with Jinja so instead of using .format,
+# we just replace the special charcater $
+CONTENT_BLOCK = (
+ "{% extends 'base.html' %}"
+ "{% block content %}"
+ "$"
+ "{% endblock %}"
+)
+
+
+def default_context():
+ """
+ default_context - returns the minimum info needed to render a template--the
+ domain name (for the home directory), and the top site directories which
+ make up the navbar
+ """
+ return {
+ 'domain': app.config['DOMAIN'],
+ 'navbar': sorted(app.config['MAIN_SITE_DIRS'])
+ }
+
+
+def index_dir(path):
+ """
+ index_dir - Given a directory at `path`, list it's contents,
+ and sort each item as a file or a directory.
+
+ return - a tuple with the values:
+ > a list of directories in `path`,
+ > a list of files in `path`,
+ > (if present), a list of external links to add to the index.html of the
+ directory
+ > (if present), a short description (string) of what the directory contains
+
+ Lists are sorted alphabetically
+ """
+ dirs = []
+ files = []
+ links = []
+ description = ""
+ contents = os.listdir(path)
+ for obj in contents:
+ if os.path.isfile(path + '/' + obj):
+ if obj == siteconfig.LINKS_FILE:
+ with open(path + '/' + obj) as f:
+ links = f.readlines()
+ elif obj == siteconfig.DESC_FILE:
+ with open(path + '/' + obj) as f:
+ description = f.read()
+ elif obj.startswith('.'):
+ continue
+ else:
+ files.append(obj)
+ elif os.path.isdir(path + '/' + obj):
+ if obj.startswith('.'):
+ continue
+ else:
+ dirs.append(obj)
+
+ return sorted(dirs), sorted(files), sorted(links), description
+
+
+def is_hidden_path(path):
+ """
+ Tests if last object specified in `path` is hidden.
+ Inspired by Unix. On Windows, diretories won't actually be "hidden" but
+ they are still not indexed by this program
+ """
+ return path.split('/')[-1].startswith('.')
+
+
+@app.route("/")
+def home():
+ """
+ home - renders the template `home.html` as the main index file
+
+ If you'd like to customize your home page, that is the file you want to
+ edit, though you can optionally change the title here if you wish
+ """
+ context = default_context()
+ context.update(
+ {
+ 'title': app.config['HOME_TITLE'],
+ 'parent_dir': '/'
+ }
+ )
+ return render_template("site/home.html", **context)
+
+
+# from: https://pythonise.com/series/learning-flask/sending-files-with-flask
+@app.route("/<path:path>")
+def render_file(path):
+ """
+ render_file - renders an HTML document for the given `path`.
+
+ If `path` is an HTML file it is rendered within the base template,
+ otherwise, the raw file is returned. If `path` points to a directory, this
+ function instead creates an index for the directory containing it's files,
+ links, and other info.
+ """
+ if is_hidden_path(path):
+ abort(404)
+ abs_path = "./templates/site/" + path
+ context = default_context()
+ context.update(
+ {
+ 'title': path.split('.')[0].upper(),
+ 'parent_dir': '/' + '/'.join(path.split('/')[:-1])
+ }
+ )
+ if os.path.isfile(abs_path):
+ if abs_path.endswith('.html'):
+ with open(abs_path) as f:
+ content = f.read()
+ print(path.split('/')[-2] + '/')
+ return render_template_string(CONTENT_BLOCK.replace('$', content), **context)
+ else:
+ # not an html file, so don't render it
+ return send_from_directory('templates/site/', path,
+ mimetype=siteconfig.MIMETYPES.get(
+ f".{ path.split('.')[-1] }", siteconfig.DEFAULT_MIMETYPE
+ )
+ )
+ elif os.path.isdir(abs_path):
+ dirs, files, links, description = index_dir(abs_path)
+ context.update(
+ {
+ 'cur_dir': path.split('/')[-1] + '/',
+ 'dirs': dirs,
+ 'files': files,
+ 'links': links,
+ 'description': description
+ }
+ )
+ return render_template("index.html", **context)
+ else:
+ context.update({'errors': "404 File not found"})
+ return render_template("base.html", **context)
+
+
+@app.route("/raw/<path:path>")
+def send_file_from_site(path):
+ """
+ send_file - instead of rendering a file within a template as with
+ `render_file`, send the raw file to the user
+ """
+ return send_from_directory('template/site/', path,
+ mimetype=siteconfig.MIMETYPES.get(
+ f".{ path.split('.')[-1] }", siteconfig.DEFAULT_MIMETYPE
+ )
+ )
+
+
+@app.route("/static/<path:path>")
+def send_file_from_static(path):
+ """
+ send_file - instead of rendering a file within a template as with
+ `render_file`, send the raw file to the user
+ """
+ return send_from_directory('static/', path,
+ mimetype=siteconfig.MIMETYPES.get(
+ f".{ path.split('.')[-1] }", siteconfig.DEFAULT_MIMETYPE
+ )
+ )
+
+
+def setup():
+ """
+ setup - sets up the app according to the settings specified (or not
+ speified) in `siteconfig`
+ """
+ if siteconfig.SECRET_KEY:
+ app.config['SECRET_KEY'] = siteconfig.SECRET_KEY
+ else:
+ SECRET_KEY = os.urandom(32)
+ app.config['SECRET_KEY'] = SECRET_KEY
+
+ if siteconfig.MAIN_SITE_DIRS:
+ app.config.update({'MAIN_SITE_DIRS': siteconfig.MAIN_SITE_DIRS})
+ else:
+ app.config.update({'MAIN_SITE_DIRS': index_dir('./templates/site')[0]})
+
+ app.config.update({'DOMAIN': siteconfig.DOMAIN})
+ app.config.update({'HOME_TITLE': siteconfig.HOME_TITLE})
+
+
+if __name__ == '__main__':
+ setup()
+ app.run(host=siteconfig.HOST, port=siteconfig.PORT)
diff --git a/siteconfig.py b/siteconfig.py
new file mode 100644
index 0000000..28c3cbd
--- /dev/null
+++ b/siteconfig.py
@@ -0,0 +1,21 @@
+class siteconfig:
+ # REQUIRED SETTINGS#
+ DOMAIN = "example.net" # Your domain name, or site title
+ HOME_TITLE = "WELCOME" # "HELLO WORLD!"
+ LINKS_FILE = ".links" # ".lnx"
+ DESC_FILE = ".description" # ".desc`"
+ HOST = "127.0.0.1" # "192.168.1.1" "1.2.3.4"
+ PORT = 8000 # 8080, 8001, 8002
+ DEFAULT_MIMETYPE = "application/octet-stream" # "text/plain"
+
+ MIMETYPES = {}
+ with open('mimetypes.csv') as f:
+ for line in f.readlines():
+ ext, mime = line.strip().split(',')
+ MIMETYPES.update({ext: mime})
+
+ # OPTIONAL SETTINGS #
+ # Remove 'None' to add custom values
+ MAIN_SITE_DIRS = None # ["dir1", "dir2", "dir3"]
+ # b"\xca\x05\x80\xa3|\xdbh@\xec<\xcd\x19\xf4\nRN:)\x13\x917|km\xb0'>'\x1d&G\xe8"
+ SECRET_KEY = None
diff --git a/static/main.css b/static/main.css
new file mode 100644
index 0000000..f8837d0
--- /dev/null
+++ b/static/main.css
@@ -0,0 +1,48 @@
+h1,h2,h3,h4 {
+ text-align: center;
+ margin: 0.5em auto;
+}
+body {
+ margin-left: auto;
+ margin-right: auto;
+ text-align: center;
+ font-family: Courier, Monospace;
+ word-wrap: normal;
+}
+/* some Stallman trickery for making mobile text bigger */
+@media screen and (max-device-width: 480px) {
+ body {
+ font-size: 200%
+ }
+ .loicense {
+ font-size: 75%;
+ }
+}
+.navbar {
+ text-align: center;
+ max-width: 1000px;
+ display: block;
+}
+.content {
+ text-align: justify;
+ max-width: 500px;
+ display: inline-block;
+}
+.license {
+ font-size: 65%;
+ text-align: center;
+ max-width: 350px;
+ display: block;
+}
+p {
+ margin-top: 0.75em;
+ margin-bottom: 0.75em;
+}
+
+table {
+ border-spacing: 0 10px;
+}
+td {
+ text-align: left
+}
+
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..b2e8e74
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,28 @@
+<html>
+ <title>{{ domain }}/{{ title }}</title>
+ <head>
+ {% block css%}
+ <link rel="stylesheet" type="text/css" href="/static/main.css">
+ {% endblock %}
+ </head>
+ <body><center>
+ <h1><a href="/">{{ domain }}</a></h1>
+ <!-- navbar -->
+ <div class="navbar">
+ <b>|</b> <!-- This makes the bar symmetrical -->
+ {% for dir in navbar %}
+ <b> <a href="/{{ dir }}">{{ dir }}/</a> |</b>
+ {% endfor %}
+ </div>
+ <h2>{{ title }}</h2>
+ <div class="content">
+ {% block content %}
+ {% endblock %}
+ <h3>{{ errors }}</h3>
+ {% if parent_dir != '/' %}
+ <h3><a href="{{ parent_dir }}">Go up to parent folder ({{ parent_dir }})</a></h3>
+ {% endif %}
+ </div>
+ <div class="license">{% include 'site/license.html' %}</div>
+ </center></body>
+</html>
diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 0000000..7e815e2
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,26 @@
+{% extends 'base.html' %}
+{% block content %}
+
+<p>{{ description }}</p>
+
+{% if dirs %}
+<h3>Topics</h3>
+{% for d in dirs %}
+<li><a href="{{ cur_dir }}{{ d }}">{{ d }}</a></li>
+{% endfor %}
+{% endif %}
+
+{% if files %}
+<h3>File Listing</h3>
+{% for f in files %}
+<li><a href="{{ cur_dir }}{{ f }}">{{ f }}</a></li>
+{% endfor %}
+{% endif %}
+
+{% if links %}
+{% for l in links %}
+<li><a href="{{ l.split(',')[1] }}" target="_blank" rel="noopener noreferrer">{{l.split(',')[0]}}</a></li>
+{% endfor %}
+{% endif %}
+
+{% endblock %}
diff --git a/templates/site/about/.description b/templates/site/about/.description
new file mode 100644
index 0000000..eee24f1
--- /dev/null
+++ b/templates/site/about/.description
@@ -0,0 +1 @@
+Who runs this place anyway?
diff --git a/templates/site/about/faq.html b/templates/site/about/faq.html
new file mode 100644
index 0000000..0ad0c1a
--- /dev/null
+++ b/templates/site/about/faq.html
@@ -0,0 +1,3 @@
+<h2>Frequently Anticipated Questions</h2>
+<li><i>A question?</i></li>
+<p>Yes, that is a question. Nice one</p>
diff --git a/templates/site/about/whoami.html b/templates/site/about/whoami.html
new file mode 100644
index 0000000..954d8db
--- /dev/null
+++ b/templates/site/about/whoami.html
@@ -0,0 +1 @@
+<p>Person that does things</p>
diff --git a/templates/site/files/.description b/templates/site/files/.description
new file mode 100644
index 0000000..0dd21eb
--- /dev/null
+++ b/templates/site/files/.description
@@ -0,0 +1 @@
+Everything else I don't have a place for
diff --git a/templates/site/files/1.html b/templates/site/files/1.html
new file mode 100644
index 0000000..6be9b63
--- /dev/null
+++ b/templates/site/files/1.html
@@ -0,0 +1,11 @@
+<p>This is some text</p>
+<br>
+<br>
+<br>
+<p>
+This is a whole lot more text including very very very long lines, like wowza
+this is long! Fortunately, css should wrap it nicely
+</p>
+<p>
+Separate each paragraph!
+</p>
diff --git a/templates/site/fun/.description b/templates/site/fun/.description
new file mode 100644
index 0000000..37c5a36
--- /dev/null
+++ b/templates/site/fun/.description
@@ -0,0 +1 @@
+The internet is srs business these days. Here's some fun stuff instead
diff --git a/templates/site/fun/.links b/templates/site/fun/.links
new file mode 100644
index 0000000..b3ea8fb
--- /dev/null
+++ b/templates/site/fun/.links
@@ -0,0 +1 @@
+???,http://www.nyan.cat/
diff --git a/templates/site/home.html b/templates/site/home.html
new file mode 100644
index 0000000..01d676e
--- /dev/null
+++ b/templates/site/home.html
@@ -0,0 +1,4 @@
+{% extends 'base.html' %}
+{% block content %}
+<p>Home</p>
+{% endblock %}
diff --git a/templates/site/license.html b/templates/site/license.html
new file mode 100644
index 0000000..172b43f
--- /dev/null
+++ b/templates/site/license.html
@@ -0,0 +1,17 @@
+<p xmlns:dct="http://purl.org/dc/terms/" xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#">
+ <a rel="license"
+ href="http://creativecommons.org/publicdomain/zero/1.0/">
+ <img src="http://i.creativecommons.org/p/zero/1.0/88x31.png" style="border-style: none;" alt="CC0" />
+ </a>
+ <br />
+ To the extent possible under law,
+ <span property="dct:title">The author</span>
+ has waived all copyright and related or neighboring rights to
+ content on <span property="dct:title">{{ domain }}</span>.
+ All work may be cited without attribution at the reader's discretion.
+ However, if you do use the work here, or otherwise benefit from it,
+ the author would love to hear about it!
+ This work is published from:
+<span property="vcard:Country" datatype="dct:ISO3166" content="US" about="{{ domain }}">
+ United States</span>.
+</p>
diff --git a/templates/site/thoughts/.description b/templates/site/thoughts/.description
new file mode 100644
index 0000000..f9004b2
--- /dev/null
+++ b/templates/site/thoughts/.description
@@ -0,0 +1,3 @@
+My thoughts on various topics that I may or may not be qualified to write about
+When I can't figure out exactly what I'm thinking, it gets written somewhere
+here.
diff --git a/templates/site/thoughts/rants/dontread.txt b/templates/site/thoughts/rants/dontread.txt
new file mode 100644
index 0000000..e90754c
--- /dev/null
+++ b/templates/site/thoughts/rants/dontread.txt
@@ -0,0 +1,2 @@
+I hate people who try to beep me while I'm turning left... like what do you
+want me to do? Run the kids over?
diff --git a/templates/site/tutorials/.description b/templates/site/tutorials/.description
new file mode 100644
index 0000000..ec41834
--- /dev/null
+++ b/templates/site/tutorials/.description
@@ -0,0 +1,3 @@
+There's a lot of tutorials out there that can show you something if you know
+what to look for. Here's some tutorials that might help when you don't know
+what to search.
diff --git a/templates/site/tutorials/.links b/templates/site/tutorials/.links
new file mode 100644
index 0000000..a0e2e99
--- /dev/null
+++ b/templates/site/tutorials/.links
@@ -0,0 +1 @@
+Cool site,https://fsf.org
diff --git a/templates/site/tutorials/.secret/secretfile.txt b/templates/site/tutorials/.secret/secretfile.txt
new file mode 100644
index 0000000..cae75f7
--- /dev/null
+++ b/templates/site/tutorials/.secret/secretfile.txt
@@ -0,0 +1 @@
+You shouldn't be able to see this file. It is a secret
diff --git a/templates/site/tutorials/how-to-make-this-site.html b/templates/site/tutorials/how-to-make-this-site.html
new file mode 100644
index 0000000..09dc877
--- /dev/null
+++ b/templates/site/tutorials/how-to-make-this-site.html
@@ -0,0 +1 @@
+<p>Carefully</p>
diff --git a/templates/site/tutorials/linux/linuz1.html b/templates/site/tutorials/linux/linuz1.html
new file mode 100644
index 0000000..c299a06
--- /dev/null
+++ b/templates/site/tutorials/linux/linuz1.html
@@ -0,0 +1 @@
+Linux is cool
diff --git a/templates/site/tutorials/python/.description b/templates/site/tutorials/python/.description
new file mode 100644
index 0000000..d69e5c3
--- /dev/null
+++ b/templates/site/tutorials/python/.description
@@ -0,0 +1 @@
+Some basics and thoughts on Python
diff --git a/templates/site/tutorials/python/py-style.md b/templates/site/tutorials/python/py-style.md
new file mode 100644
index 0000000..2a68fac
--- /dev/null
+++ b/templates/site/tutorials/python/py-style.md
@@ -0,0 +1,93 @@
+# Coding Style Guide
+
+The purpose of this document is twofold:
+1) To ensure that anyone who might like to make my code better understands
+ why I write python the way I do
+2) to ensure *I* adhere to my own style because I'm terribly inconsistent
+
+Being terribly inconsistent, the guidelines are not set in stone and if
+you have a good argument for doing things a particular, I don't really care.
+
+*BUT* first and foremost, *code must comply with PEP8 first*. This is easy
+to automate. I like coala since it's friendly but there' plenty of advanced
+linters out there.
+
+That aside, I have the following idiosyncracies:
+
+## 1) *Strings* are *double-quoted*. *Keys* and *chars* are *single-quoted*.
+
+This is really just because I like how C does it. And Cpython's C-based so
+why not?
+
+Like so:
+ ```
+ string = "This is a phrase"
+ word = "word"
+ cur_char = 'a'
+ newline = '\n' # note, two characters, but it's still ONE char in output
+ # keys are single-quoted to avoid confusion
+ dictionary = { 'key' : "1245dqw3w431", 'return': newline }
+ ```
+
+The only exception is for strings with quotes in them (anything to avoid
+escapes, really)
+ ```
+ quoted_string = (
+ '"You miss 100% of the shots you don't take - Wayne Gretsky" - Michael Scott'
+ )
+ ```
+That brings me to my next point.
+
+## 2) Long strings belong in parentheses
+
+As in:
+ ```
+ longboi = (
+ "This is a really long string usefull when making help menus. Be\n"
+ "sure to leave s space at the end of each line, or add a new line\n"
+ "when needed.\n"
+
+ "Try your best to keep formatting accurate like this."
+ )
+ ```
+
+## 3) Tabs are four spaces and spaces are *ALWAYS* prefered to tabs
+Again, see PEP8.
+
+## 4) Always add spaces between arithmetic, but never for brackets
+It's a pain to read:
+ ```
+ 1/(2*sqrt(pi))*exp(x**2)
+ ```
+Do this
+ ```
+ 1 / (2 * sqrt(pi)) * exp(x ** 2)
+ ```
+The same goes for logic operators
+ ```
+ true & false ^ true
+ ```
+
+## 5) EVERYTHING should be snake_case
+This is python. Unless there's a compatibility thing (like a library's
+code was written that way, or it matches an API variable),
+snake_case is preferred.
+ ```
+
+ user_input = int(input()) # variable
+ MAX_INPUT = 1000 # constant
+ def judge_input(_input, _max): # function
+ if _max > _input:
+ print("Too big!")
+
+ judge_input(user_input, MAX_INPUT
+ class Input_Judger: # a class
+ # etc etc
+ ```
+Example exception
+ ```
+ # this doesn't actually work, but you get the idea
+ r = requests.get("www.debian.org")
+ pageSize = r.json()['pageSize'] # camel case ok
+ ```
+
diff --git a/templates/site/tutorials/python/test.py b/templates/site/tutorials/python/test.py
new file mode 100644
index 0000000..83b87a6
--- /dev/null
+++ b/templates/site/tutorials/python/test.py
@@ -0,0 +1,2 @@
+#!/usr/bin/python3
+print("hi")