diff --git a/.gitea/workflows/python-package.yml b/.gitea/workflows/python-package.yml
new file mode 100644
index 0000000..a236fcf
--- /dev/null
+++ b/.gitea/workflows/python-package.yml
@@ -0,0 +1,15 @@
+name: Package Python
+on:
+ push:
+ tags:
+ - 'v[0-9]+.[0-9]+.[0-9]+'
+
+jobs:
+ Package:
+ uses: jmaa/workflows/.gitea/workflows/python-package.yaml@v6.21
+ with:
+ REGISTRY_DOMAIN: gitfub.space
+ REGISTRY_ORGANIZATION: usagi-keiretsu
+ secrets:
+ PIPY_REPO_USER: ${{ secrets.PIPY_REPO_USER }}
+ PIPY_REPO_PASS: ${{ secrets.PIPY_REPO_PASS }}
diff --git a/.gitea/workflows/python-test.yml b/.gitea/workflows/python-test.yml
new file mode 100644
index 0000000..cf6212f
--- /dev/null
+++ b/.gitea/workflows/python-test.yml
@@ -0,0 +1,6 @@
+name: Test Python
+on: [push]
+
+jobs:
+ Test:
+ uses: jmaa/workflows/.gitea/workflows/python-test.yaml@v6.21
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7d3bf4b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,3 @@
+Copyright (c) 2022-2024 Jon Michael Aanes
+
+All rights reserved.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a1c323c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+
+
+
+
+# Kobo Calendar Server
+
+Small calendar web-server pulling from ICAL files, and presenting events in
+a lightweight HMTL frontend.
+
+The motivation was to create a lightweight wall calendar to use with an old
+[Kobo Aura e-reader](https://en.wikipedia.org/wiki/Kobo_Aura), to take
+advantage of the its e-ink screen, but I had problems getting it to run 24/7.
+
+## Example usage
+
+1. Add ICAL links to any [SecretLoader
+ compatible](https://gitfub.space/Jmaa/secret_loader) location, for the key
+ `ical_links`. Simplest approach is to add them to a file under
+ `secrets/ical_links`. ([Google Calendar hidden ICAL
+ links](https://support.google.com/calendar/answer/37648) works great here.)
+2. Launch server using something like: `python -m kobo_wall_calendar`
+3. Navigate to .
+4. Enjoy
+
+
+# License
+
+```
+Copyright (c) 2022-2024 Jon Michael Aanes
+
+All rights reserved.
+```
diff --git a/ruff.toml b/ruff.toml
new file mode 100644
index 0000000..4f533b8
--- /dev/null
+++ b/ruff.toml
@@ -0,0 +1,76 @@
+#[tool.ruff]
+lint.select = [ 'ALL' ]
+lint.ignore = [
+ 'ANN', # Type annotations (lots of work.)
+ 'FBT', # Boolean trap is bullshit.
+ 'C90', 'PLR0911', 'PLR0915', 'PLR0913', # Code complexity measures are bullshit.
+ 'Q003', 'D205', # Format conflict
+
+ 'TCH', # Microoptimization at the cost of readability
+
+ 'D100', 'D101', 'D102', 'D103', 'D104', 'D105', 'D106', 'D107', 'D300', 'D401', # Missing docstrings (lots of work)
+ 'TD002', 'TD003', 'TD004', 'FIX', # Who cares about TODO standards?
+ "E501", 'PLR0912', 'PTH123', 'F405', 'E402', 'PLW0603',
+
+ 'SIM102',
+
+ 'TRY002', 'TRY003',
+]
+
+# Autofix
+lint.fixable = [
+ 'SIM', 'C', 'Q', 'D',
+ 'UP035', 'UP004', 'UP032', 'UP006', 'UP007', 'I001',
+ 'F401', 'COM812', 'B011', 'PLR1722', 'PLR0402', 'S101',
+]
+lint.unfixable = ['SIM102', 'SIM114']
+
+# Exclude a variety of commonly ignored directories.
+exclude = [
+ ".bzr",
+ ".direnv",
+ ".eggs",
+ ".git",
+ ".hg",
+ ".mypy_cache",
+ ".nox",
+ ".pants.d",
+ ".pytype",
+ ".ruff_cache",
+ ".svn",
+ ".tox",
+ ".venv",
+ "__pypackages__",
+ "_build",
+ "buck-out",
+ "build",
+ "dist",
+ "node_modules",
+ "venv",
+]
+
+# Same as Black.
+line-length = 88
+
+# Allow unused variables when underscore-prefixed.
+lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
+
+# Assume Python 3.10.
+target-version = "py310"
+
+[lint.flake8-quotes]
+inline-quotes = "single"
+multiline-quotes = "double"
+docstring-quotes = "double"
+
+[format]
+quote-style = "single"
+indent-style = "space"
+docstring-code-format = true
+
+[lint.per-file-ignores]
+"test/*" = [
+ "S101", # Test Asserts
+ "T201", # Debug prints
+ "PLR2004", # magic-value-comparison
+]
diff --git a/setup.py b/setup.py
index 3be9693..bd1c646 100644
--- a/setup.py
+++ b/setup.py
@@ -1 +1,93 @@
-# Bogus
+# WARNING
+#
+# THIS IS AN AUTOGENERATED FILE.
+#
+# MANUAL CHANGES CAN AND WILL BE OVERWRITTEN.
+
+import re
+
+from setuptools import setup
+
+PACKAGE_NAME = 'kobo_wall_calendar'
+
+PACKAGE_DESCRIPTION = """
+# Kobo Calendar Server
+
+Small calendar web-server pulling from ICAL files, and presenting events in
+a lightweight HMTL frontend.
+
+The motivation was to create a lightweight wall calendar to use with an old
+[Kobo Aura e-reader](https://en.wikipedia.org/wiki/Kobo_Aura), to take
+advantage of the its e-ink screen, but I had problems getting it to run 24/7.
+
+## Example usage
+
+1. Add ICAL links to any [SecretLoader
+ compatible](https://gitfub.space/Jmaa/secret_loader) location, for the key
+ `ical_links`. Simplest approach is to add them to a file under
+ `secrets/ical_links`. ([Google Calendar hidden ICAL
+ links](https://support.google.com/calendar/answer/37648) works great here.)
+2. Launch server using something like: `python -m kobo_wall_calendar`
+3. Navigate to .
+4. Enjoy
+
+
+# License
+
+```
+Copyright (c) 2022-2024 Jon Michael Aanes
+
+All rights reserved.
+```"""
+
+PACKAGE_DESCRIPTION_SHORT = 'Small calendar web-server pulling from ICAL files, and presenting events in
+a lightweight HMTL frontend.'
+
+
+def parse_version_file(text: str) -> str:
+ match = re.match(r'^__version__\s*=\s*(["\'])([\d\.]+)\1$', text)
+ if match is None:
+ msg = 'Malformed _version.py file!'
+ raise Exception(msg)
+ return match.group(2)
+
+
+with open(PACKAGE_NAME + '/_version.py') as f:
+ version = parse_version_file(f.read())
+
+
+def parse_requirements(text: str) -> list[str]:
+ return text.strip().split('\n')
+
+
+def read_requirements(path: str) -> list[str]:
+ with open(path) as f:
+ return parse_requirements(f.read())
+
+
+REQUIREMENTS_MAIN = """
+ical
+requests_cache
+secret_loader @ git+https://gitfub.space/Jmaa/secret_loader@main
+bottle"""
+
+REQUIREMENTS_TEST = """
+"""
+
+
+setup(
+ name=PACKAGE_NAME,
+ version=version,
+ description=PACKAGE_DESCRIPTION_SHORT,
+ long_description=PACKAGE_DESCRIPTION,
+ long_description_content_type='text/markdown',
+ author='Jon Michael Aanes',
+ author_email='jonjmaa@gmail.com',
+ url='https://gitfub.space/Jmaa/' + PACKAGE_NAME,
+ packages=[PACKAGE_NAME],
+ install_requires=parse_requirements(REQUIREMENTS_MAIN),
+ extras_require={
+ 'test': parse_requirements(REQUIREMENTS_TEST),
+ },
+ python_requires='>=3.9',
+)