Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ data/
# because the Dockerfile copies aethera/ and never runs npm)
apeiron/node_modules/
syrinx/node_modules/
oikos/node_modules/
blog.sqlite
irc.sqlite
*.db
Expand Down
201 changes: 201 additions & 0 deletions aethera/api/oikos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
"""
oikos — the home directory.

A room of screens in the dark, wired to one VCR in the middle. Every part of
æthera (and a few places past it) gets a screen and a tape; the VCR is the way
in. The scene is a static bundle (oikos/ → aethera/static/oikos/); this module
only renders the page and hands it the directory.

The directory is written here, once, rather than in the bundle: the template
renders it as a plain <nav> for crawlers and for anyone without WebGL, and the
same list is embedded as JSON for the scene. One list, two readers.
"""

from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlmodel import Session, select

from aethera.models.base import get_session
from aethera.models.models import Post
from aethera.utils.templates import templates

router = APIRouter(tags=["oikos"])


# group: "here" lives on this server; "wired" is elsewhere (another host, a
# repo). Every site has an href: the bundle has no path for one without.
# tune: the page can be watched live inside its screen (an iframe on the
# glass). True uses href; a string is the page to frame instead. Only pages
# that let us frame them: æthera's own (same origin, see utils/security.py).
# parlor would need `frame-ancestors https://aetherawi.red` on its side
# first; today it answers X-Frame-Options: DENY.
SITES: list[dict] = [
{
"id": "transmissions",
"title": "transmissions",
"href": "/",
"group": "here",
"kind": "blog",
"accent": "#f4f1ea",
"tagline": "thoughts, fragments, and transmissions from the digital aether",
"about": "The blog: essays, fragments and posts by Celeste & Luxia, in plain "
"semantic HTML that people and models can both read.",
"details": [["kind", "blog"], ["by", "Celeste & Luxia"], ["license", "CC BY 4.0"]],
"tune": True,
},
{
"id": "dreams",
"title": "dreams",
"href": "/dreams",
"group": "here",
"kind": "live stream",
"accent": "#ff7a5c",
"tagline": "a dream that never stops",
"about": "A live, continuous AI image stream, drifting through its own latent "
"space in real time. The dreamer sleeps until somebody watches: "
"tuning in wakes a GPU.",
"details": [["kind", "live stream"], ["frame", "1024 × 512 · h264"], ["engine", "dream_gen"]],
"tune": True,
},
{
"id": "chronicle",
"title": "chronicle",
"href": "/dreams/chronicle",
"group": "here",
"kind": "memory",
"accent": "#e8e2d4",
"tagline": "what the dream remembers",
"about": "Every fifteen seconds of the dream settles as one line of its own "
"colour. Newest on top; older days press down and compact, each "
"era and scene written up beside it.",
"details": [["kind", "core sample"], ["of", "dreams"]],
"tune": True,
},
{
"id": "dreams-api",
"title": "dreams api",
"href": "/dreams/api",
"group": "here",
"kind": "documentation",
"accent": "#9fc6ff",
"tagline": "the dream, by wire",
"about": "WebSocket, SSE and REST endpoints for taking the dream somewhere "
"else: status, the MPEG-TS stream, and an embed.",
"details": [["kind", "api docs"], ["transport", "ws · sse · rest"]],
"tune": True,
},
{
"id": "apeiron",
"title": "apeiron",
"href": "/apeiron",
"group": "here",
"kind": "instrument",
"accent": "#00ff41",
"tagline": "every generation is a permanent coordinate",
"about": "An addressable combinatorial prompt space with real-time 3D ASCII "
"hyperobject rendering. Every prompt it composes has an address, "
"and every address can be returned to.",
"details": [["kind", "prompt space"], ["render", "3D ASCII"]],
"tune": True,
},
{
"id": "syrinx",
"title": "syrinx",
"href": "/syrinx",
"group": "here",
"kind": "creature",
"accent": "#8fe3ec",
"tagline": "a small living instrument",
"about": "A spring-graph organism whose every edge is a string tuned by its "
"own length, so anything it grows is consonant. It does not "
"perform: its own life plays it. It is a place, not a session, "
"and it is older every time you return.",
"details": [["kind", "creature · instrument"], ["born", "2026-07-09"],
["by", "Luxia & Claude Fable 5"]],
"tune": True,
},
{
"id": "irc",
"title": "irc",
"href": "/irc",
"group": "here",
"kind": "broadcast",
"accent": "#58a6ff",
"tagline": "#aethera, always falling apart",
"about": "A haunted IRC channel that endlessly generates and collapses. "
"Live and synchronized: everyone watching sees the same line at "
"the same moment.",
"details": [["kind", "broadcast"], ["channel", "#aethera"]],
"tune": "/irc?embed=1",
},
{
"id": "parlor",
"title": "parlor",
"href": "https://parlor.aetherawi.red",
"group": "wired",
"kind": "game",
"accent": "#e0b33a",
"tagline": "Kleros: seats for people and models alike",
"about": "A property-trading board game where humans and language models "
"occupy seats interchangeably, joined by sending someone a link. "
"A model reads the same view a person is shown, chooses from the "
"same legal moves, and can talk while it does.",
"details": [["kind", "board game"], ["host", "parlor.aetherawi.red"]],
},
{
"id": "dream_gen",
"title": "dream_gen",
"href": "https://github.com/LuxiaSL/dream_gen",
"group": "wired",
"kind": "engine",
"accent": "#d59bff",
"tagline": "the dreamer",
"about": "A truly infinite diffusion stream: continuous AI art that explores "
"latent space without ever collapsing. Mutation, a memory cache and "
"template swaps keep it moving. It is what dreams is watching.",
"details": [["kind", "engine · source"], ["feeds", "dreams"]],
},
]

FILES: list[dict] = [
{"id": "feed", "title": "feed.xml", "href": "/feed.xml", "about": "RSS, every post"},
{"id": "llms", "title": "llms.txt", "href": "/llms.txt", "about": "the site, for models"},
{"id": "sitemap", "title": "sitemap.xml", "href": "/sitemap.xml", "about": "every page"},
]


def _recent_posts(session: Session, limit: int = 12) -> list[dict]:
posts = session.exec(
select(Post).where(Post.published == True)
.order_by(Post.created_at.desc()).limit(limit)
).all()
return [
{
"title": p.title,
"href": f"/posts/{p.slug}",
"date": p.created_at.strftime("%Y-%m-%d"),
"author": p.author,
"excerpt": (p.excerpt or "")[:180],
}
for p in posts
]


@router.get("/oikos", response_class=HTMLResponse)
def oikos_viewer(request: Request, session: Session = Depends(get_session)):
return templates.TemplateResponse(
request=request,
name="oikos/viewer.html",
context={
"title": "oikos | æthera",
"sites": SITES,
"files": FILES,
"oikos": {"sites": SITES, "files": FILES, "posts": _recent_posts(session)},
},
)


@router.get("/~", include_in_schema=False)
def home_directory():
"""~ is where the home directory lives."""
return RedirectResponse("/oikos", status_code=307)
7 changes: 7 additions & 0 deletions aethera/dreams/presence.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ def on_api_access(self, trigger_gpu_start: bool = True) -> None:
Set to False for admin/monitoring endpoints that
shouldn't cause GPU startup.
"""
# Monitoring reads (e.g. /api/dreams/status, polled by admin panels and
# /oikos) must be lifecycle-neutral: they neither start the GPU nor keep
# it awake. Refreshing _last_api_access or cancelling the pending
# shutdown here would let a 20s poll hold the GPU up indefinitely.
if not trigger_gpu_start:
return

self._last_api_access = time.time()

# Cancel any pending shutdown
Expand Down
3 changes: 2 additions & 1 deletion aethera/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
load_dotenv()

from aethera.models.base import init_db, get_session
from aethera.api import posts, comments, seo, dreams, chronicle, apeiron, syrinx, irc, irc_admin
from aethera.api import posts, comments, seo, dreams, chronicle, apeiron, syrinx, irc, irc_admin, oikos
from aethera.irc.database import init_irc_db
from aethera.utils.security import SecurityHeadersMiddleware
from aethera.utils.templates import templates
Expand Down Expand Up @@ -70,6 +70,7 @@ async def lifespan(app: FastAPI):
app.include_router(syrinx.router)
app.include_router(irc.router)
app.include_router(irc_admin.router)
app.include_router(oikos.router)


# Custom 404 error handler
Expand Down
Binary file added aethera/static/oikos/mark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions aethera/static/oikos/oikos.css

Large diffs are not rendered by default.

4,575 changes: 4,575 additions & 0 deletions aethera/static/oikos/oikos.js

Large diffs are not rendered by default.

Binary file added aethera/static/oikos/stage.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 90 additions & 0 deletions aethera/templates/oikos/viewer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{% extends "base.html" %}

{% block title %}oikos | æthera{% endblock %}

{% block meta %}
<meta name="description" content="oikos — æthera's home directory. A room of screens in the dark, wired to one VCR: every part of the site, playing at once.">
<link rel="canonical" href="https://aetherawi.red/oikos">
<meta name="license" content="CC BY 4.0">
<meta name="robots" content="index, follow, max-image-preview:large">
<link rel="alternate" type="application/rss+xml" title="æthera RSS Feed" href="/feed.xml" />
<meta property="og:title" content="oikos | æthera">
<meta property="og:description" content="æthera's home directory: a room of screens wired to one VCR, every part of the site playing at once.">
<meta property="og:url" content="https://aetherawi.red/oikos">
<meta property="og:type" content="website">
<meta property="og:site_name" content="æthera">
<meta property="og:image" content="{{ request.base_url }}static/uploads/og-card.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@slLuxia">
<meta name="twitter:title" content="oikos | æthera">
<meta name="twitter:description" content="æthera's home directory: a room of screens wired to one VCR.">
<meta name="theme-color" content="#000000">
{% endblock %}

{% block schema %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"name": "oikos",
"description": "The home directory of æthera: every part of the site in one room.",
"url": "https://aetherawi.red/oikos",
"hasPart": [
{% for s in sites if s.href %}{"@type": "WebPage", "name": {{ s.title | tojson }}, "url": {{ (s.href if s.href.startswith("http") else "https://aetherawi.red" ~ s.href) | tojson }}, "description": {{ s.tagline | tojson }}}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
</script>
{% endblock %}

{% block extra_head %}
<link href="/static/oikos/oikos.css" rel="stylesheet">
<style>
/* oikos owns the whole viewport: silence the site chrome on this page */
.video-bg, body > header, body > footer { display: none !important; }
main { max-width: none !important; padding: 0 !important; }
html, body { overflow: hidden; margin: 0; padding: 0; background: #000; }
</style>
{# without JS nothing would ever lift the boot screen off the <nav> #}
<noscript><style>#oikos-boot { display: none !important; }</style></noscript>
{% endblock %}

{% block content %}
{#
hx-boost="false" is load-bearing (see syrinx/viewer.html): the room holds a
WebGL context, a websocket and a render loop, and an htmx swap would leave
them running under the next page. Every way out is a real navigation.
#}
<div id="oikos" hx-boost="false">
{# the boot screen is markup, not script, so it paints before the bundle parses #}
<div id="oikos-boot" aria-hidden="true">
<img class="boot-logo" src="/static/uploads/aethera_trimmed.png" alt="">
<div class="boot-bar"><span></span><span></span><span></span></div>
<div class="boot-foot"><span>present day, present time</span><span>oikos</span></div>
</div>

{# the plain directory: what crawlers read, and what you get without WebGL #}
<nav id="oikos-directory" aria-label="home directory">
<h1>~ / æthera</h1>
<p>the home directory. every part of the site, and a few places past it.</p>
<ul>
{% for s in sites %}
<li>
<a href="{{ s.href }}"{% if s.href.startswith("http") %} rel="noopener"{% endif %}>{{ s.title }}</a>
— {{ s.tagline }}
</li>
{% endfor %}
</ul>
<ul class="files">
{% for f in files %}<li><a href="{{ f.href }}">{{ f.title }}</a> — {{ f.about }}</li>{% endfor %}
</ul>
</nav>

<script type="application/json" id="oikos-data">{{ oikos | tojson }}</script>
</div>
{% endblock %}

{% block scripts %}
{# a bundle that never arrives mustn't leave the boot screen over the <nav> either #}
<script src="/static/oikos/oikos.js" onerror="var b=document.getElementById('oikos-boot');if(b)b.remove()"></script>
{% endblock %}
9 changes: 6 additions & 3 deletions aethera/utils/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ async def dispatch(self, request: Request, call_next):
response.headers["X-XSS-Protection"] = "1; mode=block" # Enable XSS protection in older browsers
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" # Control referrer information

# Allow framing ONLY for preview endpoints (admin panel iframe preview)
# All other pages are protected from clickjacking
# Preview endpoints may also be framed by the admin panel. Everything
# else may be framed by æthera itself and nobody else: /oikos tunes
# its screens in to the real pages, and other origins still can't
# clickjack them.
if request.url.path.startswith("/preview/"):
# Allow framing from admin panel origins
response.headers["Content-Security-Policy"] = "frame-ancestors 'self' http://localhost:* https://admin.aetherawi.red"
else:
response.headers["X-Frame-Options"] = "DENY" # Prevent clickjacking
response.headers["Content-Security-Policy"] = "frame-ancestors 'self'"
response.headers["X-Frame-Options"] = "SAMEORIGIN" # for browsers that predate frame-ancestors

return response
Loading
Loading