1
0

Initial commit

This commit is contained in:
Ryan Fox 2020-07-13 02:35:49 +00:00
commit 9656f3b061
Signed by: flewkey
GPG Key ID: 94F56ADFD848851E
33 changed files with 1140 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
ftp_url.txt
music/*
dist/
scripts/__pycache__/

22
LICENSE.txt Normal file
View File

@ -0,0 +1,22 @@
The code & design of this website are under the MIT license.
Do not assume anything about the contents of the website itself.
Copyright (c) 2020 Ryan Fox
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

53
Makefile Normal file
View File

@ -0,0 +1,53 @@
PAGES_IN = $(wildcard pages/*)
POSTS_IN = $(wildcard posts/*.md)
MUSIC_IN = $(wildcard music/*.mp3)
PAGES_OUT = $(addprefix dist/,$(notdir $(PAGES_IN:.md=.html)))
POSTS_OUT = $(addprefix dist/blog/,$(notdir $(POSTS_IN:.md=.html)))
MUSIC_OUT = $(addprefix dist/music/,$(notdir $(MUSIC_IN)))
POST_LISTS = dist/blog/index.html dist/blog/feed.xml
MUSIC_LISTS = dist/music/index.html dist/music/feed.xml
all: $(PAGES_OUT) $(POSTS_OUT) $(MUSIC_OUT) $(POST_LISTS) $(MUSIC_LISTS)
cp -urv static/. dist
dist/%.html: pages/%.md | dist
scripts/template_md.py $< $@
dist/%.html: pages/%.html | dist
cp $< $@
dist/blog/%.html: posts/%.md | dist/blog
scripts/template_md.py $< $@
dist/music/%.mp3: music/%.mp3 | dist/music
cp $< $@
$(POST_LISTS): $(POSTS_OUT)
scripts/gen_blog.py posts $@
$(MUSIC_LISTS): music.json | $(OUTPUT_MUSIC)
scripts/gen_music.py $< $@
dist/blog:
mkdir -p dist/blog
dist/music:
mkdir -p dist/music
dist:
mkdir dist
.PHONY: clean
upload: dist
lftp -c "set ftp:list-options -a;\
set ssl:verify-certificate false;\
open '$(shell cat ftp_url.txt)';\
lcd dist;\
cd public_html;\
mirror --reverse"
clean:
rm -rf dist

6
README.txt Normal file
View File

@ -0,0 +1,6 @@
flewkey.com
This repository contains flewkey.com, my personal website.
Jekyll was too complicated for me, so I cobbled together some Python scripts.
Send questions, issues, and patches to flewkey@2a03.party.

210
music.json Normal file
View File

@ -0,0 +1,210 @@
[
{
"id": "calm_down",
"name": "Calm Down",
"description": "This is a bit of a mess, but I like some of the ideas here.",
"timestamp": 1588820400
},
{
"id": "larry_the_cow",
"name": "Larry the Cow",
"description": "I haven't posted any music since May, so here's something I never uploaded.",
"timestamp": 1588462200
},
{
"id": "microwave",
"name": "Microwave",
"description": "This isn't a coherent song, my goal was to have fun.",
"timestamp": 1583470800
},
{
"id": "scrap_5",
"name": "Scrap #5",
"description": "I tried Ardour for the first time. It's fun.",
"group": "Scraps",
"timestamp": 1582677000
},
{
"id": "scrap_4",
"name": "Scrap #4",
"description": "I tried publishing this without Windows this time.",
"group": "Scraps",
"timestamp": 1582509600
},
{
"id": "scrap_3",
"name": "Scrap #3",
"description": "I thought this sounded neat.",
"group": "Scraps",
"timestamp": 1580418000
},
{
"id": "scrap_2",
"name": "Scrap #2",
"description": "Another scrapped idea.",
"group": "Scraps",
"timestamp": 1578792600
},
{
"id": "baby_steps",
"name": "Baby Steps",
"description": "I tried using Ableton to edit a song, and I think it went well.",
"timestamp": 1575594000
},
{
"id": "cold_spoon",
"name": "Cold Spoon",
"description": "I pulled this out of my unfinished folder. Very loopy.",
"timestamp": 1574973000
},
{
"id": "feeling_chaotic",
"name": "Feeling Chaotic",
"description": "I felt like using FamiTracker again, so I did this quickly.",
"timestamp": 1573686000
},
{
"id": "scrap_1",
"name": "Scrap #1",
"description": "I'm going to post \"scraps\" now. These happen when I don't want to finish something I make.",
"group": "Scraps",
"timestamp": 1565319600
},
{
"id": "seriously_what_the_hell_is_this",
"name": "Seriously What The Hell Is This",
"description": "Still figuring stuff out. Why do all Ableton instruments sounds weird? Yeah screw this, I'm not making any more.",
"timestamp": 1562025600
},
{
"id": "learning_new_software",
"name": "Learning New Software",
"description": "My first attempt making music outside of a tracker. I spent nearly an hour fighting MuseScore and I have no clue what happened.",
"timestamp": 1561939200
},
{
"id": "screw_this",
"name": "Screw This!",
"description": "FamiSpam failed due to my complete laziness. This is the last song I will upload to the series. Afterwards, I'm probably going to learn proper composition and stop making tracker music for a while.",
"group": "FamiSpam",
"timestamp": 1560384000
},
{
"id": "puzzle_house",
"name": "Puzzle House",
"description": "Happy birthday DevEd!",
"group": "FamiSpam",
"timestamp": 1558396800
},
{
"id": "shaking_off_rust",
"name": "Shaking Off Rust",
"description": "I haven't made music for a while. Most of this is terrible, but that won't stop me from publishing it.",
"group": "FamiSpam",
"timestamp": 1557360000
},
{
"id": "back_into_it",
"name": "Back Into It",
"description": "Sorry this one sounds awkward, I feel rusty with this. I quit after 12 days last time, so I'll try to beat my previous streak.",
"group": "FamiSpam",
"timestamp": 1555804800
},
{
"id": "it_gets_better",
"name": "It Gets Better",
"description": "I was frustrated with this at first.",
"group": "FamiSpam",
"timestamp": 1554595200
},
{
"id": "boring_and_loopy",
"name": "Boring and Loopy",
"description": "I tried to make a loopy song, and it turned out terribly. I'll try to do better next time.",
"group": "FamiSpam",
"timestamp": 1554422400
},
{
"id": "racing_or_something",
"name": "Racing or Something",
"description": "This is one of my favourite ones so far.",
"group": "FamiSpam",
"timestamp": 1554249600
},
{
"id": "the_confusing_one",
"name": "The Confusing One",
"description": "Made in confusion.",
"group": "FamiSpam",
"timestamp": 1554076800
},
{
"id": "the_first_half_sucks",
"name": "The First Half Sucks",
"description": "I have no clue how to use arps properly.",
"group": "FamiSpam",
"timestamp": 1553904000
},
{
"id": "music_box",
"name": "Music Box",
"description": "I'm not happy with my music, so I've resolved to make 30 seconds of music a day. My goal is to write 20 minutes of music in 2 months.",
"group": "FamiSpam",
"timestamp": 1553731200
},
{
"id": "laziness",
"name": "Laziness.mp3",
"description": "I made this for an OHB on Battle of the Bits.",
"group": "FamiSpam",
"timestamp": 1551207600
},
{
"id": "filthy_foe",
"name": "Filthy Foe.nsf",
"description": "I made this for an OHB on Battle of the Bits.",
"timestamp": 1551193200
},
{
"id": "ohc03815_famitracker_pack_pack_0cc",
"name": "OHC03815 famitracker pack pack.0cc",
"description": "I made this for an OHB on Battle of the Bits.",
"timestamp": 1551171600
},
{
"id": "unsanitary_snow",
"name": "Unsanitary Snow",
"description": "A track I made for Winter Chip XIV on Battle of the Bits. Hopefully I'll have the time to try participating in OHBs this year.",
"timestamp": 1550016000
},
{
"id": "we_give_up",
"name": "We Give Up",
"description": "Some short FamiTracker songs.",
"timestamp": 1547942400
},
{
"id": "running_away",
"name": "Running Away",
"description": "Some short FamiTracker songs.",
"timestamp": 1547942400
},
{
"id": "begin_quest",
"name": "Begin Quest",
"description": "Some short FamiTracker songs.",
"timestamp": 1547942400
},
{
"id": "spooky_sunset",
"name": "Spooky Sunset",
"description": "Messing around in FamiTracker again.",
"timestamp": 1546905600
},
{
"id": "spaceship",
"name": "Spaceship",
"description": "An old-ish track I wanted to consider finished.",
"timestamp": 1532822400
}
]

50
pages/about.md Normal file
View File

@ -0,0 +1,50 @@
template: page
title: Info
license: CC-BY
# About Me
Ahoy, my name is Ryan Fox. I am a Canadian guy who likes programming and music.
I have not done anything to earn online credibility, and you are most likely
here because I sent you a link. Despite this, you are spending time reading my
website, and I am very grateful for that.
---
## Encryption
These are my PGP and SSH keys. If you would like to encrypt messages, or allow
me to access your server, these are the ones to use. I am somewhat confident
that they will not get compromised.
* PGP: [D910C0CAC5BF5739861A4ABD94F56ADFD848851E]({root}pgp.txt)
* SSH: [SHA256:IggQMESZIgwIP4/zfYI8ZA1xnMd9g9huOUtgfaKjmCo]({root}ssh.txt)
---
## Profiles
Here is a list of my main profiles, the ones worth listing.
* GitHub: [@flewkey](https://github.com/flewkey)
* Twitter: [@flewkey](https://twitter.com/flewkey)
* YouTube: [@flewkey](https://www.youtube.com/channel/UCRUfdaM8EiXu7SCfGl8FUpQ)
* PeerTube: [@flewkey_channel@toobnix.org](https://toobnix.org/video-channels/flewkey_channel/)
* Mastodon: [@flewkey@layer8.space](https://layer8.space/@flewkey)
* Newgrounds: [@flewkey](https://flewkey.newgrounds.com/)
* Soundcloud: [@flewkey](https://soundcloud.com/flewkey)
* SDF GIT Society [@flewkey](https://git.sdf.org/flewkey)
---
## Contact
If you would like to contact me privately, please send me an e-mail if possible.
Even a long, drawn out e-mail will take less time out of my day than the average
text exchange. Thanks! <flewkey@2a03.party>
I can also be found at [##dystopia]({root}dystopia{ext}) on Freenode. While I
encourage people to use the IRC channel, there is also a
[Discord server]({root}dystopia{ext}) linked to the chatroom for those who find
IRC inconvenient for some reason.

30
pages/dystopia.md Normal file
View File

@ -0,0 +1,30 @@
template: page
title: Dystopia
license: CC-BY
# Dystopia
Dystopia is a personal Discord server and IRC channel which I run for myself,
and various other people I know. It is also one of the easiest places to contact
me.
All messages on the IRC channel will be mirrored to Discord and logged.
---
## Links
* \#\#dystopia on Freenode
([webchat](https://webchat.freenode.net/?channels=##dystopia))
* <https://discord.gg/vQ8VEAU> on Discord
---
## Rules
* Use common sense.
* Do not annoy others.
* Absolutely no flame wars.
* Conduct yourself as a guest.
* Never post slurs, I won't allow it.
* Avoid posting personal information.

19
pages/index.md Normal file
View File

@ -0,0 +1,19 @@
template: page
title: Homepage
license: CC-BY
# Homepage
Ahoy, my name is Ryan Fox. I am a Canadian guy who likes programming and music.
Have you come to [read about me]({root}about{ext})?
If not, feel free to [read some blog posts]({root}blog/) or
[listen to my music]({root}music/).
---
## Projects
* [flewkey-overlay](https://git.sdf.org/flewkey/flewkey-overlay) - My personal Gentoo overlay.
* [minecraft-tweaks-2a03](https://git.sdf.org/flewkey/minecraft-tweaks-2a03) - A basic Minecraft mod.

21
posts/2020-06-30-blog.md Normal file
View File

@ -0,0 +1,21 @@
template: post
title: Website update
author: flewkey
timestamp: 1593548958
license: CC-BY
I like blogs. Blogs are one of the only suitable platforms for people to
properly present ideas. There are no character limits, the author can embed
whatever they want, and &mdash; most importantly &mdash; they can disable the
comments section. This caught my attention, so I decided to start my own blog.
Unfortunately, blogging on my plain HTML website was a pain. It needed an
update.
Multiple people had recommended Jekyll to me, and I had seen many others use it
for their websites. Making a simple blog with it seemed simple, but extending
it to handle a custom page for my music was a pain. It didn't help that I am far
too lazy to learn about Ruby.
The solution was ridiculously simple: a Makefile and some Python scripts. I had
to fiddle with it for a couple of hours, but I am quite happy with the result.
There should be less typos in the RSS feeds now!

View File

@ -0,0 +1,97 @@
template: post
title: nds-constrain't
author: flewkey
timestamp: 1594525504
license: CC-BY
Once upon a time, shutterbug2000 discovered nds-constrain't: a bug in Nintendo's
NTR SSL library that allowed it's connections to be easily intercepted. This
made it possible to connect to alternative online services without ROM patching.
The bug itself is simple: the NDS SSL library does not care whether or not a
certificate is authorized to act as a certificate authority. This means that
&mdash; with any valid certificate &mdash; we can sign whatever we want, even a
certificate under a false hostname.
A guide to using this bug for fun and profit is now available on the
[official page](https://github.com/KaeruTeam/nds-constraint), which is much
better written than mine. However, you are free to keep reading this one.
---
### Getting the Wii client certificate
As explained in the [official page](https://github.com/KaeruTeam/nds-constraint)
for nds-constrain't, the Wii client certificate is signed by Nintendo and
considered valid. Therefore, we can use it's key to sign whatever we want. You
_could_ grab it from a Wii, but it is much easier to download it from
[Larsenv's page](https://larsenv.github.io/NintendoCerts/index.html). You will
want to use the link labelled &quot;Wii NWC Prod 1&quot;, by the way.
### Converting it to a useable format
The file is a PKCS12, and we can't do anything useful with it until we extract
the certificate and the private key. Thankfully, this is pretty simple.
openssl pkcs12 -in WII_NWC_1_CERT.p12 -passin pass:alpine -passout pass:alpine -out keys.txt
That command will export the X.509 certificate and private key from the archive,
and store the output in keys.txt. They can then be copied into their appropriate
files, which I will name NWC.crt and NWC.key.
### Signing your certificate
Instructions for this are listed on the official GitHub page, but I have copied
them for reference. If I remember correctly, the DS can only handle the SHA-1
and MD5 hash formats, so pay attention to the `-sha1` flag.
openssl genrsa -out server.key 1024
openssl req -new -key server.key -out server.csr
openssl x509 -req -in server.csr -CA NWC.crt -CAkey NWC.key -CAcreateserial -out server.crt -days 3650 -sha1
Your webserver probably wants the certificate chain as well, so let's generate
that as well.
cat server.crt NWC.crt > server-chain.crt
We are ready to rock and roll!
### Using your phony certificate
Once the SSL certificate is installed, you may run into issues connecting with
your DS. This because your NDS only knows how to use SSLv3, with the SHA-1 or
MD5 cipher sets. Enabling SSLv3 and SHA-1 isn't always possible with webservers,
so I recommend using NGINX as a reverse-proxy. To enable DS compatibility for
NGINX, add the following lines to your NGINX configuration.
ssl_protocols SSLv3;
ssl_ciphers ECDHE-RSA-AES128-SHA;
Most services on Nintendo consoles make liberal use of headers. Because of this,
some extra options need to be enabled.
underscores_in_headers on;
proxy_pass_request_headers on;
Because we have enabled insecure SSL settings on NGINX, you probably don't want
to use it for any mission-critical web applications. If you continue having
issues with NDS connectivity, please contact me.
### Having fun
The possibilities are infinite. Want to run services through a debugging proxy?
Implement WFC protocols? Make a Flipnote Studio server? All of this is possible
without ROM patches!
---
### Update
After receiving some e-mails, I have learned that the NDS-supported ciphers have
been disabled in OpenSSL versions past 1.0.2g, unless configured with
"enable-weak-ssl-ciphers". This means that you may have to re-build NGINX (or
mod_ssl for Apache) to get it working.
If you have the means, I suggest taking Wireshark captures to find the cause of
any SSL issues. Enabling debug logging in NGINX can also help you pinpoint
handshake errors. If all else fails, you can find my e-mail on the about page.

27
scripts/gen_blog.py Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import markdown
from template import template
def gen_list(files, ext):
md = markdown.Markdown(extensions = ["meta", "extra"])
items = ""
for post in files:
with open("posts/"+post, "r", encoding="utf-8") as file_in:
html = md.convert(file_in.read())
md.Meta["template"] = "post_preview"
items += template(html, "blog/"+post[:-3]+ext, ext, md.Meta)
return items
def main():
meta = {"template": "post_list"}
ext = "."+sys.argv[2].split(".")[-1]
files = sorted(os.listdir(sys.argv[1]), reverse=True)
output = template(gen_list(files, ext), sys.argv[2], ext, meta)
with open(sys.argv[2], "w+", encoding="utf-8") as file_out:
file_out.write(output)
if __name__ == "__main__":
main()

28
scripts/gen_music.py Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import json
from template import template
def gen_list(music, ext):
items = ""
for track in music:
track["template"] = "track_preview"
track["file_size"] = str(os.path.getsize("music/"+track["id"]+".mp3"))
if "group" in track:
track["group_text"] = "<span>Part of "+track["group"]+"</span><br/>"
items += template("", sys.argv[2], ext, track)
return items
def main():
meta = {"template": "music_list"}
ext = "."+sys.argv[2].split(".")[-1]
with open(sys.argv[1], "r", encoding="utf-8") as file_in:
music = json.loads(file_in.read())
output = template(gen_list(music, ext), sys.argv[2], ext, meta)
with open(sys.argv[2], "w+", encoding="utf-8") as file_out:
file_out.write(output)
if __name__ == "__main__":
main()

90
scripts/template.py Normal file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import markdown
from datetime import datetime
from email import utils
def get_path(path, ext_in, ext_out):
path = path[len("pages"):]
if path[-len("index"+ext_in):] == "index"+ext_in:
return path[:-len("index"+ext_in)]
return path[:-len(ext_in)] + ext_out
def get_dir(path):
return "/".join(path.split("/")[1:-1])+"/"
def get_root(path):
root = ""
for i in range(len(path.split("/"))-2):
root += "../"
return root
licenses = {
"CC-BY": ["cc-by", "CC BY 4.0", "https://creativecommons.org/licenses/by/4.0/"]
}
def get_badge(license, root):
if license not in licenses:
return ""
data = licenses[license]
return "<a class=\"badge\" title=\""+data[1]+"\" href=\""+data[2]+"\"><img src=\""+root+"badges/"+data[0]+".png\" alt=\""+data[1]+"\"/></a>"
def read_template(template, meta_in):
if template[:10] != "meta: yes\n":
return [template, {"meta": "no"}]
data = template[:-1].split("\n\n") # Strip trailing newline
md = markdown.Markdown(extensions = ["meta"])
md.convert(data[0])
content = "\n\n".join(data[1:])
content = content.replace("{", "{{").replace("}", "}}")
meta = meta_in.copy()
meta.update(md.Meta)
return [content, MetaDict(meta)]
defaults = {
"base": "https://flewkey.com/",
"title": "Untitled",
"author": "flewkey",
"ext": ".html",
"head": ""
}
class MetaDict(dict):
def __missing__(self, key):
return defaults[key] if key in defaults else ""
def __getitem__(self, key):
value = dict.__getitem__(self, key)
if isinstance(value, list):
return "\n".join(value)
else:
return value
def template(content, path, ext, meta):
global base
meta = MetaDict(meta)
if meta["template"] == None:
return content.format_map(meta)
with open("templates/"+meta["template"]+ext, "r", encoding="utf-8") as file:
t_content = file.read()
t_info = read_template(t_content, meta)
if t_info[1]["meta"] != "no" and "template" in t_info[1]:
t_content = template(t_info[0], path, ext, t_info[1])
if "root" not in meta:
meta["root"] = get_root(path)
if "home" not in meta:
meta["home"] = "." if meta["root"] == "" else meta["root"]
if "full_path" not in meta:
meta["full_path"] = get_path(path, ext, meta["ext"])
if "full_dir" not in meta:
meta["full_dir"] = get_dir(path)
if "badge" not in meta and ext == ".html":
meta["badge"] = get_badge(meta["license"], meta["root"])
if "date" not in meta and "timestamp" in meta:
meta["date"] = datetime.fromtimestamp(int(meta["timestamp"])).isoformat().split("T")[0]
if "pub_date" not in meta and "timestamp" in meta:
meta["pub_date"] = utils.format_datetime(datetime.fromtimestamp(int(meta["timestamp"])))
meta["head"] += "\n<link rel=\"canonical\" href=\""+meta["base"]+meta["full_path"]+"\"/>"
meta["content"] = content.format_map(meta)
if "description" not in meta and ext == ".html":
meta["description"] = meta["content"].split("</p>")[0]+"</p>" # Disgusting!
return t_content.format_map(meta)

16
scripts/template_md.py Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import markdown
from template import template
def main():
md = markdown.Markdown(extensions = ["meta", "extra"])
with open(sys.argv[1], "r", encoding="utf-8") as file_in:
html = md.convert(file_in.read())
output = template(html, sys.argv[2], ".html", md.Meta) if "template" in md.Meta else html
with open(sys.argv[2], "w+", encoding="utf-8") as file_out:
file_out.write(output)
if __name__ == "__main__":
main()

9
static/.htaccess Normal file
View File

@ -0,0 +1,9 @@
ErrorDocument 404 /404.html
Redirect 301 /nds-constraint.html /blog/2020-07-12-nds-constraint.html
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,NE,R=301]

BIN
static/assets/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
static/badges/cc-by.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

14
static/music/player.js Normal file
View File

@ -0,0 +1,14 @@
function getAudio(player) {
return player.getElementsByTagName("audio")[0];
}
window.onload = function() {
var players = document.getElementsByClassName("player");
for (var i = 0; i < players.length; i++) {
getAudio(players[i])["queue"] = i;
getAudio(players[i]).addEventListener('ended', function(event) {
const next = event.target["queue"] + 1;
if (next < players.length) getAudio(players[next]).play();
});
}
}

52
static/pgp.txt Normal file
View File

@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
mCHt
=BBAA
-----END PGP PUBLIC KEY BLOCK-----

1
static/ssh.txt Normal file
View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnRLK2qyF44Fzcy6uyhZQwT1V0o8yTaybpXCMIYGXbOAAdWvdNYkg+smrebTgOq6BQsbiIl35x+lTjUI3vUnIRiaD7NQ6jKFW0sjhtxj5m48fT3lv2hNtd1G+zAokw9/K0//l/Ud93AijCgVP10UQi9Fi4a4Poim1B/DtUa5Bs0yp0PCSh8ZAimEhVpNby0GiI14iSnO09x/jHUC1o8hrq6zzZcsdo1Ar0Gf0NLBG9CnDG4W+wBryEzkkwpBEXT1j+cmY7Y/vs8x/UWULF+keIynb5WMCqzJAye1FdiMvx45hQiClKAJO7Qp3E1Nb/LzLiic8/s5zBSjatWSibnpF1yEuDUiBDKI8ImWJgIG5odenLgqqIw2VN5MQ6L/STseMvFN0zvqDGtGR9XCOvP0hOprP89eG0vtJLl7pUoZ3hDzdrp4tZDJczgkRTTWxkQ5yPkDaF6+B42l1FmwYJUFF9JiD+cEBC9/499yKqH++7DeynFZb5uzxAtwgu0duvrurrt2jzF0JQJ6zqzPV63lM/WrwlXPOE40rRBhShwmpLlFDB0cwchO0FPFmxfdL+mp3YXWVRnyg7QniitEtMmMmWZQVMN2/5TN9h/gziyux23BVaYsnYt+vFDmtXOztgbR/lVhgKZUIfR+CTr7qiHTkaDD8Ae7+KwruFJq3LjOkwsw== flewkey@2a03.party

266
static/style.css Normal file
View File

@ -0,0 +1,266 @@
body {
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
font-size: 18px;
line-height: 2;
margin: 0 0 0 0;
padding: 0 0 0 0;
background-color: #F8F8F8;
color: #111111;
}
header {
margin-left: auto;
margin-right: auto;
max-width: 840px;
height: 128px;
border-bottom: solid 2px #AAAAAA;
padding: 32px 0 32px 0;
margin-bottom: 48px;
}
header img {
display: inline-block;
height: 128px;
width: 128px;
border-radius: 32px;
margin-right: 16px;
float: left;
}
header div.info div {
display: inline-block;
line-height: 1;
padding-top: 12px;
padding-bottom: 12px;
margin-left: 16px;
vertical-align: top;
text-align: left;
float: left;
}
header div.info span.title {
display: block;
font-size: 48px;
font-weight: bold;
margin-bottom: 24px;
}
header div.info span.subtext {
display: block;
font-size: 32px;
margin-top: 24px;
}
nav {
text-align: center;
height: 42px;
line-height: 32px;
float: right;
text-align: right;
}
nav a {
color: #666666;
display: block;
}
nav a:hover {
color: #333333;
}
main {
margin-left: auto;
margin-right: auto;
max-width: 840px;
padding-left: 32px;
padding-right: 32px;
}
footer {
text-align: center;
border-top: solid 2px #AAAAAA;
padding-top: 16px;
padding-bottom: 16px;
margin-top: 48px;
font-size: 16px;
background-color: #DDDDDD;
}
footer span {
display: block;
}
span, h1, h2, h3, h4 {
padding: 0 0 0 0;
margin: 0 0 0 0;
}
p {
margin-top: 18px;
margin-bottom: 18px;
}
h1, h2 {
line-height: 1;
margin-bottom: 32px;
}
h1 {
font-size: 40px;
}
h2 {
font-size: 32px;
}
h3 {
font-size: 24px;
}
h4 {
font-size: 20px;
}
div.title-subtext {
color: #555555;
font-weight: normal;
font-size: 20px;
margin-top: 18px;
}
a {
color: #0074D9;
text-decoration: none;
}
hr {
height: 0;
border: 0;
border-top: 2px solid #AAAAAA;
margin-top: 48px;
margin-bottom: 48px;
}
div.block {
padding-top: 48px;
padding-bottom: 48px;
border-top: solid 2px #AAAAAA;
}
div.block:first-of-type {
padding-top: 0;
border-top: none;
}
@media only screen and (max-width: 950px) {
header {
height: 172px;
}
header div.info {
text-align: center;
}
header div.info img {
float: none;
}
header div.info div {
padding-top: 19px;
padding-bottom: 19px;
float: none;
}
header div.info span.title {
font-size: 50px;
margin-bottom: 14px;
}
header div.info span.subtext {
font-size: 26px;
margin-top: 14px;
}
nav {
float: none;
line-height: 48px;
text-align: center;
}
nav a {
display: inline;
margin-right: 32px;
margin-left: 32px;
}
}
@media only screen and (max-width: 550px) {
body {
font-size: 17px;
}
header {
height: 220px;
min-width: 230px;
padding: 16px 16px 16px 16px;
margin-bottom: 24px;
}
header img {
display: block;
margin-left: auto;
margin-right: auto;
margin-bottom: 6px;
}
header div.info div {
margin: 0 0 0 0;
padding: 0 0 0 0;
}
header div.info span.title {
display: block;
text-align: center;
font-size: 24px;
margin-top: 6px;
margin-bottom: 6px;
}
header div.info span.subtext {
display: block;
text-align: center;
font-size: 20px;
margin-top: 6px;
}
nav a {
display: inline;
margin-right: 8px;
margin-left: 8px;
}
hr {
margin-top: 24px;
margin-bottom: 24px;
}
h1, h2 {
margin-bottom: 16px;
}
h1 {
font-size: 36px;
}
h2 {
font-size: 30px;
}
div.block {
padding-top: 24px;
padding-bottom: 24px;
}
div.title-subtext {
font-size: 18px;
}
}

21
templates/music_list.html Normal file
View File

@ -0,0 +1,21 @@
meta: yes
title: Music
description: Listen to all of the music I've made!
head: <script src="player.js"></script>
<link rel="alternate" type="application/rss+xml" title="Ryan's Music" href="feed.xml"/>
template: page
<style>
audio {{
width: 100%;
}}
div.player audio {{
margin-top: 12px;
}}
</style>
<h1>Music</h1>
<p>On this page, you can find all of my attempts at music.<br/>If you would like to listen to it elsewhere, you can find my other profiles on my <a href="{root}about{ext}">about page</a>.</p>
<p>Subscribe to my <a href="feed.xml">RSS feed</a> to see my posts first.</p>
<hr/>
<!--Using a <ul> would be more structural, but that's too much voodoo.-->
{content}

5
templates/music_list.xml Normal file
View File

@ -0,0 +1,5 @@
meta: yes
title: Music
template: rss
{content}

34
templates/page.html Normal file
View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<link rel="stylesheet" type="text/css" href="{root}style.css"/>
<link rel="icon" type="image/x-icon" href="{root}favicon.ico"/>
<title>{title} - flewkey</title>
{head}
</head>
<body>
<header>
<div class="info">
<img src="{root}assets/avatar.png" alt="My avatar"/>
<div>
<span class="title">flewkey</span>
<span class="subtext">Level 10 Computer Mage</span>
</div>
</div>
<nav>
<a href="{home}">Home</a>
<a href="{root}about{ext}">About</a>
<a href="{root}blog/">Blog</a>
<a href="{root}music/">Music</a>
</nav>
</header>
<main>
{content}
</main>
<footer>
<span>Copyright &copy; 2020 flewkey</span>
{badge}
</footer>
</body>
</html>

8
templates/post.html Normal file
View File

@ -0,0 +1,8 @@
meta: yes
head: <meta name="description" content="{description}"/>
<meta name="author" content="{author}"/>
template: page
<h1>{title}
<div class="title-subtext"><span>Published on {date} by {author}</span></div></h1>
{content}

11
templates/post_list.html Normal file
View File

@ -0,0 +1,11 @@
meta: yes
title: Blog
description: View a post, then view more posts.
head: <link rel="alternate" type="application/rss+xml" title="Ryan's Blog" href="feed.xml"/>
template: page
<h1>Blog</h1>
<p>The chances of seeing a new blog post are the same as winning the lottery while getting struck by lightning twice on a leap day. If you still come across a post, I promise to make it disappointing.</p>
<p>Subscribe to my <a href="feed.xml">RSS feed</a> to see my posts first.</p>
<hr/>
{content}

5
templates/post_list.xml Normal file
View File

@ -0,0 +1,5 @@
meta: yes
title: Blog
template: rss
{content}

View File

@ -0,0 +1,5 @@
<div class="block">
<a href="{full_path}"><h3>{title}</h3></a>
<p>{description}</p>
<a href="{full_path}">Read more</a>
</div>

View File

@ -0,0 +1,9 @@
<item>
<title>{title}</title>
<link>{{base}}{{full_dir}}{full_path}</link>
<guid isPermaLink="true">{{base}}{{full_dir}}{full_path}</guid>
<pubDate>{pub_date}</pubDate>
<description>{description}</description>
<content:encoded><![CDATA[{content}]]></content:encoded>
<author>{author}</author>
</item>

12
templates/rss.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Ryan's {title}</title>
<link>{base}{full_dir}</link>
<atom:link href="{base}{full_path}" rel="self" type="application/rss+xml"/>
<description>{description}</description>
<language>en-ca</language>
<managingEditor>{author}</managingEditor>
{content}
</channel>
</rss>

View File

@ -0,0 +1,6 @@
<div id="audio_{id}" class="block player">
<h4>{name}</h4>
{group_text}
<span>Published on {date}</span>
<audio controls><source src="{id}.mp3" type="audio/mpeg"/></audio>
</div>

View File

@ -0,0 +1,9 @@
<item>
<title>{name}</title>
<link>{{base}}{{full_dir}}#audio_{id}</link>
<guid isPermaLink="true">{{base}}{{full_dir}}#audio_{id}</guid>
<enclosure length="{file_size}" url="{{base}}{{full_dir}}{id}.mp3" type="audio/mpeg"/>
<pubDate>{pub_date}</pubDate>
<description>{description}</description>
<author>{{author}}</author>
</item>