From 18a805160059b0c807231e504fee764fe473cf90 Mon Sep 17 00:00:00 2001 From: Hexadual Date: Thu, 30 Apr 2026 21:11:07 -0500 Subject: [PATCH] initial commit --- .gitattributes | 4 ++ .gitea/workflows/docker.yml | 43 +++++++++++++++++++ .gitignore | 6 +++ Dockerfile | 12 ++++++ README.md | 84 +++++++++++++++++++++++++++++++++++++ docker-compose.yml | 12 ++++++ egg.py | 71 +++++++++++++++++++++++++++++++ requirements.txt | 1 + 8 files changed, 233 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitea/workflows/docker.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 egg.py create mode 100644 requirements.txt diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9258b3d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto +Dockerfile text eol=lf +*.sh text eol=lf +*.py text eol=lf diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml new file mode 100644 index 0000000..2423baf --- /dev/null +++ b/.gitea/workflows/docker.yml @@ -0,0 +1,43 @@ +name: Build & publish Docker images + +on: + push: + branches: [main] + release: + types: [published] + +env: + REGISTRY: git.hexadual.io + +jobs: + build: + name: Build & push all images + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + run: | + git clone --depth 1 \ + https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@git.hexadual.io/${{ github.repository }}.git \ + . + git checkout ${{ github.sha }} + + - name: Log in to registry + run: | + echo "${{ secrets.REGISTRY_TOKEN }}" | \ + docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin + + - name: Build & push egg + run: | + docker compose build egg + docker compose push egg + docker tag ${{ env.REGISTRY }}/rocobo/gcp-dot-egg:latest \ + ${{ env.REGISTRY }}/rocobo/gcp-dot-egg:${{ github.sha }} + docker push ${{ env.REGISTRY }}/rocobo/gcp-dot-egg:${{ github.sha }} + + - name: Logout + if: always() + run: docker logout ${{ env.REGISTRY }} || true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4408ec7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.pyc +*.pyo +.env +*.db +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2d0af91 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.14-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY egg.py . + +VOLUME ["/data"] + +CMD ["python", "egg.py"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..896a858 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# GCP Egg + +Distributed client for the **Global Consciousness Project** — a self-hosted network variance tracker inspired by the [Global Consciousness Project](https://global-mind.org). + +Each "egg" container draws 200 random bits per second from the OS hardware-entropy pool and submits the count of 1-bits to a central server, which runs Stouffer Z-score analysis and displays a live colored dot. + +## Architecture + +``` +Egg containers (anyone can run) + │ POST /api/data (one 200-bit trial per second) + ▼ + Server (FastAPI + SQLite) + │ Stouffer Z network variance analysis every 60 s + ▼ + Website → animated dot + history chart + embeddable iframe +``` + +The server is maintained separately. This repo contains only the egg client. + +## Run an Egg + +Download the compose file and start it — no building required: + +```bash +curl -O https://git.hexadual.io/rocobo/GCP-Dot/raw/branch/main/docker-compose.yml +docker compose up -d +``` + +Docker will pull the pre-built image from the registry automatically. The egg will immediately start sending one trial per second and persist its ID across restarts. + +## Environment Variables + +| Variable | Default | Description | +|---|---|---| +| `SERVER_URL` | `https://gcp.hexadual.io` | Server to send trials to | +| `EGG_ID` | auto-generated | Override the egg's unique identifier | + +The auto-generated ID is derived from a SHA-256 hash stored at `/data/egg_id` — mount a volume there to keep it stable across restarts. + +## Published Image + +The egg image is built and published automatically on every push to `main`: + +```bash +docker pull git.hexadual.io/rocobo/gcp-dot-egg:latest +``` + +## How the Analysis Works + +The server analyses the past hour of data every 60 seconds: + +1. **Normalise** each trial to a Z-score: `z = (trial − 100) / √50` + (Binomial(200, 0.5) has mean = 100, variance = 50) +2. **Stouffer Z** per second across all active eggs: `S_t = Σzᵢ / √N` +3. **Network variance**: `V = Σ S_t²` — follows χ²(T) under H₀ +4. **Index** = lower-tail CDF × 100 + +### Color Table + +| Color | Index | Meaning | +|---|---|---| +| Blue | > 95% | Significantly small variance — deep coherence | +| Cyan | 90–95% | Small variance — probable coherence | +| Green | 40–90% | Normal random behavior | +| Yellow | 10–40% | Slightly elevated variance | +| Orange | 5–10% | Strongly elevated variance | +| Red | < 5% | Significantly large variance | + +## Embed the Dot + +```html + +``` + +## Server API Reference + +| Endpoint | Description | +|---|---| +| `POST /api/data` | Submit a trial `{egg_id, timestamp, trial}` | +| `GET /api/status` | Latest analysis result | +| `GET /api/history?limit=60` | Last N analysis records | +| `GET /api/eggs` | Eggs active in the last 2 minutes | diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a270750 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +services: + egg: + image: git.hexadual.io/rocobo/gcp-dot-egg:latest + volumes: + - gcp_egg_data:/data + restart: unless-stopped + environment: + - SERVER_URL=https://gcp.hexadual.io + - PYTHONUNBUFFERED=1 + +volumes: + gcp_egg_data: diff --git a/egg.py b/egg.py new file mode 100644 index 0000000..b09a05b --- /dev/null +++ b/egg.py @@ -0,0 +1,71 @@ +import os +import time +import hashlib +import platform + +import requests + +ID_FILE = "/data/egg_id" + + +def generate_trial(): + """Sum of 200 random bits drawn from the OS CSPRNG (hardware entropy pool).""" + raw = os.urandom(25) # 25 bytes = 200 bits + return sum(bin(b).count("1") for b in raw) + + +def load_or_create_id(): + try: + with open(ID_FILE) as f: + egg_id = f.read().strip() + if egg_id: + return egg_id + except OSError: + pass + egg_id = hashlib.sha256(os.urandom(32)).hexdigest()[:16] + try: + os.makedirs("/data", exist_ok=True) + with open(ID_FILE, "w") as f: + f.write(egg_id) + except OSError: + pass + return egg_id + + +def main(): + server_url = os.environ.get("SERVER_URL", "http://localhost:8000").rstrip("/") + egg_id = os.environ.get("EGG_ID") or load_or_create_id() + + print(f"GCP Egg id={egg_id} server={server_url} platform={platform.system()}") + + session = requests.Session() + errors = 0 + + while True: + loop_start = time.time() + timestamp = int(loop_start) + trial = generate_trial() + + try: + resp = session.post( + f"{server_url}/api/data", + json={"egg_id": egg_id, "timestamp": timestamp, "trial": trial}, + timeout=5, + ) + if resp.status_code == 200: + errors = 0 + else: + errors += 1 + if errors % 10 == 1: + print(f"Server returned {resp.status_code} (error #{errors})") + except Exception as exc: + errors += 1 + if errors % 10 == 1: + print(f"Send error (#{errors}): {exc}") + + elapsed = time.time() - loop_start + time.sleep(max(0.0, 1.0 - elapsed)) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..98d8768 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests>=2.32.0