diff --git a/pygitweb/README.md b/pygitweb/README.md
index 7ec9d08..4662d20 100644
--- a/pygitweb/README.md
+++ b/pygitweb/README.md
@@ -62,3 +62,24 @@ See [ROUTES.md](ROUTES.md) for more detail.
 **Project-scoped actions (stub only)**
 
 These actions are accepted but return a minimal placeholder page. Add handlers in `main.py` to implement: `blame`, `blame_incremental`, `blame_data`, `blobdiff`, `blobdiff_plain`, `heads`, `patch`, `patches`, `rss`, `atom`, `search`, `search_help`, `snapshot`, `object`.
+
+## Query parameter short names (CGI mapping)
+
+- `p` → project
+- `a` → action
+- `f` → file_name
+- `fp` → file_parent
+- `h` → hash
+- `hp` → hash_parent
+- `hb` → hash_base
+- `hpb` → hash_parent_base
+- `pg` → page
+- `o` → order
+- `s` → searchtext
+- `st` → searchtype
+- `sf` → snapshot_format
+- `opt` → extra_options
+- `sr` → search_use_regexp
+- `by_tag` → ctag
+- `ds` → diff_style
+- `pf` → project_filter
diff --git a/pygitweb/ROUTES.md b/pygitweb/ROUTES.md
deleted file mode 100644
index d992e7d..0000000
--- a/pygitweb/ROUTES.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# Pygitweb Routes (Gitweb actions)
-
-Routes are implemented as FastAPI path operations. Path info is parsed from path params and query.
-
-## No project required
-
-| Action         | Method | Path / Query                          | Handler          |
-|----------------|--------|---------------------------------------|------------------|
-| project_list   | GET    | `/`                                   | git_project_list |
-| project_index  | GET    | `/?a=project_index`                   | git_project_index|
-| opml           | GET    | `/?a=opml`                            | git_opml         |
-
-## Project required
-
-| Action         | Method | Path / Query                          | Handler          |
-|----------------|--------|---------------------------------------|------------------|
-| summary        | GET    | `/{project}` or `/{project}/?a=summary` | git_summary    |
-| log            | GET    | `/{project}/log/{hash}`               | git_log          |
-| shortlog       | GET    | `/{project}/shortlog/{hash}`          | git_shortlog     |
-| history        | GET    | `/{project}/history/{hash}` or `/{hash}/path` | git_history |
-| commit         | GET    | `/{project}/commit/{hash}`            | git_commit       |
-| commitdiff     | GET    | `/{project}/commitdiff/{hash}`        | git_commitdiff   |
-| commitdiff_plain | GET  | `/{project}/commitdiff/{hash}` (Accept: patch) | git_commitdiff_plain |
-| patch          | GET    | `/{project}/patch/{hash}`             | git_patch        |
-| patches        | GET    | `/{project}/patches/{hash}`           | git_patches      |
-| tree           | GET    | `/{project}/tree/{hash}` or `/{hash}/path/` | git_tree     |
-| blob           | GET    | `/{project}/blob/{hash}/path`         | git_blob         |
-| blob_plain     | GET    | `/{project}/blob/{hash}/path` (raw)    | git_blob_plain   |
-| blobdiff       | GET    | `/{project}/blobdiff/{hash_base}..{hash}/path` | git_blobdiff |
-| blobdiff_plain | GET    | same, raw                             | git_blobdiff_plain |
-| blame          | GET    | `/{project}/blame/{hash}/path`        | git_blame        |
-| blame_incremental | GET  | (incremental)                         | git_blame_incremental |
-| blame_data     | GET    | (JSON)                                | git_blame_data   |
-| tags           | GET    | `/{project}/tags`                     | git_tags         |
-| tag            | GET    | `/{project}/tag/{hash}`               | git_tag          |
-| heads          | GET    | `/{project}/heads`                    | git_heads        |
-| remotes        | GET    | `/{project}/remotes`                  | git_remotes      |
-| search         | GET    | `/{project}/search`                   | git_search       |
-| search_help    | GET    | `/{project}/search_help`              | git_search_help  |
-| snapshot       | GET    | `/{project}/snapshot/{hash}.{ext}`    | git_snapshot     |
-| object         | GET    | (dispatch by object type when only hash) | git_object   |
-| rss            | GET    | `/{project}/rss`                      | git_rss          |
-| atom           | GET    | `/{project}/atom`                     | git_atom         |
-| feed           | GET    | (rss/atom dispatcher)                 | git_feed         |
-
-## Query parameter short names (CGI mapping)
-
-- `p` → project
-- `a` → action
-- `f` → file_name
-- `fp` → file_parent
-- `h` → hash
-- `hp` → hash_parent
-- `hb` → hash_base
-- `hpb` → hash_parent_base
-- `pg` → page
-- `o` → order
-- `s` → searchtext
-- `st` → searchtype
-- `sf` → snapshot_format
-- `opt` → extra_options
-- `sr` → search_use_regexp
-- `by_tag` → ctag
-- `ds` → diff_style
-- `pf` → project_filter
diff --git a/pygitweb/__init__.py b/pygitweb/__init__.py
index 83e184f..f4c0c68 100644
--- a/pygitweb/__init__.py
+++ b/pygitweb/__init__.py
@@ -1,6 +1,11 @@
 from pygitweb.main import app
+import pygitweb.__meta__
 
 """
 Git Repo Browser built with FastAPI + Pygit2
 """
-__version__ = "1.0.0"
+__version__ = pygitweb.__meta__.__version__
+__author__ = pygitweb.__meta__.__author__
+__description__ = pygitweb.__meta__.__description__
+__url__ = pygitweb.__meta__.__url__
+__license__ = pygitweb.__meta__.__license__
diff --git a/pygitweb/__meta__.py b/pygitweb/__meta__.py
new file mode 100644
index 0000000..8e34511
--- /dev/null
+++ b/pygitweb/__meta__.py
@@ -0,0 +1,8 @@
+"""
+Metadata for PyGitWeb - this is the canonical source of all information below.
+"""
+__version__ = "1.0.0"
+__author__ = "Will Bowers"
+__license__ = "Apache 2.0" # This may change before being distributed.
+__description__ = "FastAPI + Pygit2 Repo Browser"
+__url__ = "https://github.com/willbowers/pygitweb"
diff --git a/pygitweb/config.py b/pygitweb/config.py
index 31c76ef..5602c0b 100644
--- a/pygitweb/config.py
+++ b/pygitweb/config.py
@@ -11,6 +11,50 @@ import re
 from pathlib import Path
 from typing import Any
 
+# Allowed actions (from %actions in gitweb.perl)
+ACTIONS = {
+    "blame",
+    "blame_incremental",
+    "blame_data",
+    "blobdiff",
+    "blobdiff_plain",
+    "blob",
+    "blob_plain",
+    "commitdiff",
+    "commitdiff_plain",
+    "commit",
+    "heads",
+    "history",
+    "log",
+    "patch",
+    "patches",
+    "remotes",
+    "rss",
+    "atom",
+    "search",
+    "search_help",
+    "shortlog",
+    "summary",
+    "tag",
+    "tags",
+    "tree",
+    "snapshot",
+    "object",
+    "opml",
+    "project_list",
+    "project_index",
+}
+
+# Map file extension to highlight.js language (class name)
+BLOB_LANG = {
+    "py": "python", "js": "javascript", "ts": "typescript", "jsx": "javascript", "tsx": "typescript",
+    "html": "html", "htm": "html", "css": "css", "scss": "scss", "json": "json", "md": "markdown",
+    "sh": "bash", "bash": "bash", "yml": "yaml", "yaml": "yaml", "xml": "xml", "go": "go",
+    "rs": "rust", "java": "java", "c": "c", "h": "c", "cpp": "cpp", "cc": "cpp", "cxx": "cpp",
+    "sql": "sql", "r": "r", "rb": "ruby", "php": "php", "swift": "swift", "kt": "kotlin",
+    "vue": "xml", "toml": "toml", "ini": "ini", "cfg": "ini", "dockerfile": "dockerfile",
+}
+
 # Defaults (equivalent to @GITWEB_*@ in gitweb.perl)
 PROJECTROOT = os.environ.get("GITWEB_PROJECTROOT", "/pub/scm")
 PROJECT_MAXDEPTH = int(os.environ.get("GITWEB_PROJECT_MAXDEPTH", "2"))
@@ -34,8 +78,6 @@ MatrixProvider: TODO - Will authenticate against the Matrix network.
 LDAPProvider: TODO - Will authenticate against a local LDAP server.
 """
 DISTGIT_AUTH: str = os.environ.get("DISTGIT_AUTH", "distgit.auth.RootProvider")
-if DISTGIT_AUTH == "None":
-    print("WARNING: Authentication is disabled. PROCEED WITH CAUTION.")
 DISTGIT_ADMIN_USER: str = os.environ.get("DISTGIT_ADMIN_USER", None)
 DISTGIT_ADMIN_PASSWORD: str = os.environ.get("DISTGIT_ADMIN_PASSWORD", None)
 DISTGIT_SESSION_TIMEOUT: str = os.environ.get("DISTGIT_SESSION_TIMEOUT", 3600 * 24 * 7)
@@ -89,6 +131,7 @@ KNOWN_SNAPSHOT_FORMAT_ALIASES: dict[str, str | None] = {
     "": None,
 }
 
+
 # Feature defaults; override in config or per-repo
 _feature_snapshot_default = ["tgz"]
 _snapshot_fmts: list[str] = []
diff --git a/pygitweb/main.py b/pygitweb/main.py
index 0306bb6..ba76f4e 100644
--- a/pygitweb/main.py
+++ b/pygitweb/main.py
@@ -21,9 +21,14 @@ from fastapi.staticfiles import StaticFiles
 
 from jinja2 import Template, Environment, PackageLoader
 
-from pygitweb import config
+from pygitweb import config, __meta__
 from pygitweb.config import (
+    ACTIONS,
+    BLOB_LANG,
     DISTGIT_AUTH,
+    DISTGIT_ADMIN_USER,
+    DISTGIT_ADMIN_PASSWORD,
+    DISTGIT_SESSION_TIMEOUT,
     EXPORT_OK,
     PROJECTROOT,
     PROJECTS_LIST,
@@ -60,41 +65,13 @@ from pygitweb.validation import (
 )
 from urllib.parse import quote, urlencode
 
-# Allowed actions (from %actions in gitweb.perl)
-ACTIONS = {
-    "blame",
-    "blame_incremental",
-    "blame_data",
-    "blobdiff",
-    "blobdiff_plain",
-    "blob",
-    "blob_plain",
-    "commitdiff",
-    "commitdiff_plain",
-    "commit",
-    "heads",
-    "history",
-    "log",
-    "patch",
-    "patches",
-    "remotes",
-    "rss",
-    "atom",
-    "search",
-    "search_help",
-    "shortlog",
-    "summary",
-    "tag",
-    "tags",
-    "tree",
-    "snapshot",
-    "object",
-    "opml",
-    "project_list",
-    "project_index",
-}
-
-app = FastAPI(title="pygitweb", description="FastAPI + Pygit2 Repo Browser")
+app = FastAPI(
+    debug=(DISTGIT_AUTH == "None"),
+    title="PyGitWeb",
+    summary="FastAPI + Pygit2 Repo Browser",
+    description=open("pygitweb/README.md", "r", encoding="utf-8").read(),
+    version = __meta__.__version__
+)
 
 env = Environment(
     loader=PackageLoader("pygitweb", "templates"),
@@ -103,17 +80,7 @@ env = Environment(
     lstrip_blocks=True,
 )
 PREAMBLE = env.get_template("preamble.html")
-POSTAMBLE = """</div></div></div><script src="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/js/tabler.min.js"></script></body></html>"""
-
-# Map file extension to highlight.js language (class name)
-BLOB_LANG = {
-    "py": "python", "js": "javascript", "ts": "typescript", "jsx": "javascript", "tsx": "typescript",
-    "html": "html", "htm": "html", "css": "css", "scss": "scss", "json": "json", "md": "markdown",
-    "sh": "bash", "bash": "bash", "yml": "yaml", "yaml": "yaml", "xml": "xml", "go": "go",
-    "rs": "rust", "java": "java", "c": "c", "h": "c", "cpp": "cpp", "cc": "cpp", "cxx": "cpp",
-    "sql": "sql", "r": "r", "rb": "ruby", "php": "php", "swift": "swift", "kt": "kotlin",
-    "vue": "xml", "toml": "toml", "ini": "ini", "cfg": "ini", "dockerfile": "dockerfile",
-}
+POSTAMBLE = """</div></div></div></body></html>"""
 
 # Todo handle with nginx route
 _static_dir = Path(__file__).parent / "static"
diff --git a/pygitweb/templates/preamble.html b/pygitweb/templates/preamble.html
index ef7bc7f..571d540 100644
--- a/pygitweb/templates/preamble.html
+++ b/pygitweb/templates/preamble.html
@@ -7,6 +7,7 @@
 <script id="GraphViz-script" async src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/1.8.2/viz.js"></script>
 <script id="diff2html-script" async src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
 <script id="Showdown-script" async src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.js"></script>
+<script id="tabler-script" async src="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/js/tabler.min.js"></script>
 <link href=" https://cdn.jsdelivr.net/npm/diff2html@3.4.56/bundles/css/diff2html.min.css " rel="stylesheet">
 <link rel="preconnect" href="https://fonts.googleapis.com">
 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
