diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 213d847..69a6199 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,9 +1,13 @@ name: Deploy HeadyConnection + on: push: branches: [main] + pull_request: + branches: [main] + jobs: - deploy: + test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -13,3 +17,12 @@ jobs: cache: 'npm' - run: npm ci - run: npm test + - name: Verify server starts + run: | + node index.js & + sleep 2 + curl -f http://localhost:8080/health || exit 1 + curl -f http://localhost:8080/ || exit 1 + curl -f http://localhost:8080/docs || exit 1 + curl -f http://localhost:8080/services || exit 1 + kill %1 diff --git a/Dockerfile b/Dockerfile index 4a7722c..83cab18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,25 @@ -FROM node:20-slim +FROM node:20-slim AS build WORKDIR /app COPY package*.json ./ RUN npm ci --production -COPY . . -ENV NODE_ENV=production -ENV PORT=8080 + +FROM node:20-slim +WORKDIR /app + RUN groupadd -r heady && useradd -r -g heady heady + +COPY --from=build /app/node_modules ./node_modules +COPY package*.json ./ +COPY index.js site-config.json ./ + RUN chown -R heady:heady /app USER heady + +ENV NODE_ENV=production +ENV PORT=8080 EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD node -e "const http=require('http');const r=http.get('http://localhost:8080/health',s=>{process.exit(s.statusCode===200?0:1)});r.on('error',()=>process.exit(1))" + CMD ["node", "index.js"] diff --git a/README.md b/README.md index a656452..5bf4aa2 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,91 @@ -# 🀝 HeadyConnection +# HeadyConnection > **Community & Connection** Collaborative AI workspace β€” connecting creators, developers, and enterprises through shared intelligence. [![Deploy](https://img.shields.io/badge/deploy-Cloud%20Run-blue?logo=google-cloud)](https://headyconnection.org) -[![Projected](https://img.shields.io/badge/projected-Heady%20Latent%20OS-purple)](https://github.com/HeadyMe/Heady-pre-production-9f2f0642) +[![Projected](https://img.shields.io/badge/projected-Heady%20Latent%20OS-purple)](https://github.com/HeadyMe/headyos-core) ## Quick Start ```bash git clone https://github.com/HeadyMe/headyconnection-core.git cd headyconnection-core -npm install && npm start +npm install +npm start ``` -## Features +The server starts on port `8080` by default. Visit `http://localhost:8080` to see the landing page. -- 🀝 **Collaborative Workspaces** β€” Shared AI context across teams -- πŸ’‘ **Knowledge Sharing** β€” Cross-team intelligence transfer -- 🌐 **Community Hub** β€” Open-source contribution platform -- πŸ”— **Deep Integration** β€” Connect with all Heady services +## Configuration ---- +All configuration is via environment variables: -**Β© 2026 Heady Systems LLC.** Built with Sacred Geometry Β· Powered by the Heady Latent OS +| Variable | Default | Description | +|---|---|---| +| `PORT` | `8080` | Server listen port | +| `NODE_ENV` | `development` | `production` enables structured JSON logs and HSTS | +| `BASE_URL` | `https://headyconnection.org` | Public base URL | +| `ALLOWED_ORIGINS` | Same as `BASE_URL` | Comma-separated CORS origins | + +## Routes + +| Method | Path | Description | +|---|---|---| +| `GET` | `/` | Landing page with features and ecosystem links | +| `GET` | `/docs` | Documentation page | +| `GET` | `/services` | Heady ecosystem service directory | +| `GET` | `/health` | Health check (JSON) | + +## Architecture + +HeadyConnection is a lightweight Express server that provides the community-facing landing page, documentation, and ecosystem navigation for the Heady Latent OS. + +- **Runtime:** Node.js 20+ with Express +- **Deployment:** Docker container on Google Cloud Run +- **Configuration:** Environment variables + `site-config.json` for branding +- **Security:** HSTS, Content-Type sniffing prevention, XSS protection, frame deny, env-driven CORS +- **Logging:** Structured JSON in production, human-readable in development +- **Shutdown:** Graceful SIGTERM/SIGINT handling for container orchestrators + +## Deployment + +### Docker + +```bash +docker build -t headyconnection . +docker run -p 8080:8080 -e NODE_ENV=production headyconnection +``` + +### Cloud Run + +The GitHub Actions workflow at `.github/workflows/deploy.yml` runs tests on every push to `main`. To add Cloud Run deployment, set the following repository secrets: + +- `GCP_PROJECT_ID` β€” Google Cloud project ID +- `GCP_SA_KEY` β€” Service account key JSON with Cloud Run deploy permissions + +## Heady Ecosystem + +| Service | URL | Description | +|---|---|---| +| HeadyMe | [headyme.org](https://headyme.org) | Personal AI Workspace | +| HeadySystems | [headysystems.com](https://headysystems.com) | Core Platform & Orchestration | +| HeadyOS | [headyos.org](https://headyos.org) | Latent Operating System | +| HeadyAPI | [headyapi.org](https://headyapi.org) | API Gateway & Services | +| HeadyIO | [headyio.org](https://headyio.org) | Input/Output & Integration Layer | +| HeadyMCP | [headymcp.org](https://headymcp.org) | Model Context Protocol | +| HeadyBuddy | [headybuddy.org](https://headybuddy.org) | AI Companion & Device Bridge | +| HeadyBot | [headybot.org](https://headybot.org) | Bot Framework & Automation | +| Heady Docs | [docs.headysystems.com](https://docs.headysystems.com) | Documentation Hub | + +## Troubleshooting + +- **Port conflict:** Set `PORT` env var to a different port +- **CORS errors:** Set `ALLOWED_ORIGINS` to include your client origin +- **Health check fails:** Ensure the server started β€” check logs for startup message + +## License + +Proprietary β€” Β© 2026 Heady Systems LLC. All Rights Reserved. +For commercial licensing: eric@headysystems.com diff --git a/index.js b/index.js index 9c52f52..3a43abb 100644 --- a/index.js +++ b/index.js @@ -4,13 +4,353 @@ * Projected from the Heady Latent OS */ const express = require('express'); -const app = express(); -const PORT = process.env.PORT || 3000; const siteConfig = require('./site-config.json'); + +const app = express(); +const PORT = parseInt(process.env.PORT, 10) || 8080; +const NODE_ENV = process.env.NODE_ENV || 'development'; +const BASE_URL = process.env.BASE_URL || 'https://headyconnection.org'; + +// --------------------------------------------------------------------------- +// Structured logging helper +// --------------------------------------------------------------------------- +function log(level, message, meta = {}) { + const entry = { + ts: new Date().toISOString(), + level, + service: 'HeadyConnection', + message, + ...meta, + }; + if (NODE_ENV === 'production') { + process.stdout.write(JSON.stringify(entry) + '\n'); + } else { + console.log(`[${entry.ts}] ${level.toUpperCase()} ${message}`, Object.keys(meta).length ? meta : ''); + } +} + +// --------------------------------------------------------------------------- +// Middleware +// --------------------------------------------------------------------------- app.use(express.json({ limit: '1mb' })); -app.get('/health', (req, res) => res.json({ ok: true, service: 'HeadyConnection', domain: 'headyconnection.org', projected: true, ts: new Date().toISOString() })); -app.get('/', (req, res) => { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.send(`${siteConfig.name}

${siteConfig.name}

${siteConfig.description}

`); + +// Security headers +app.use((_req, res, next) => { + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.setHeader('X-Frame-Options', 'DENY'); + res.setHeader('X-XSS-Protection', '1; mode=block'); + res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); + res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); + if (NODE_ENV === 'production') { + res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload'); + } + next(); +}); + +// CORS β€” driven by environment, not hardcoded wildcards +const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS + ? process.env.ALLOWED_ORIGINS.split(',').map((o) => o.trim()) + : [BASE_URL]; + +app.use((req, res, next) => { + const origin = req.headers.origin; + if (origin && ALLOWED_ORIGINS.includes(origin)) { + res.setHeader('Access-Control-Allow-Origin', origin); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + } + if (req.method === 'OPTIONS') return res.sendStatus(204); + next(); +}); + +// Request logging +app.use((req, _res, next) => { + log('info', `${req.method} ${req.path}`, { ip: req.ip }); + next(); +}); + +// --------------------------------------------------------------------------- +// Heady ecosystem cross-links +// --------------------------------------------------------------------------- +const HEADY_SERVICES = [ + { name: 'HeadyMe', url: 'https://headyme.org', desc: 'Personal AI Workspace' }, + { name: 'HeadySystems', url: 'https://headysystems.com', desc: 'Core Platform & Orchestration' }, + { name: 'HeadyOS', url: 'https://headyos.org', desc: 'Latent Operating System' }, + { name: 'HeadyAPI', url: 'https://headyapi.org', desc: 'API Gateway & Services' }, + { name: 'HeadyIO', url: 'https://headyio.org', desc: 'Input/Output & Integration Layer' }, + { name: 'HeadyMCP', url: 'https://headymcp.org', desc: 'Model Context Protocol' }, + { name: 'HeadyBuddy', url: 'https://headybuddy.org', desc: 'AI Companion & Device Bridge' }, + { name: 'HeadyBot', url: 'https://headybot.org', desc: 'Bot Framework & Automation' }, + { name: 'Heady Docs', url: 'https://docs.headysystems.com', desc: 'Documentation Hub' }, +]; + +// --------------------------------------------------------------------------- +// Shared HTML helpers +// --------------------------------------------------------------------------- +function htmlHead(title) { + return ` + + + + + ${title} β€” HeadyConnection + + + +`; +} + +function htmlNav() { + return ``; +} + +function htmlFooter() { + return ` +`; +} + +// --------------------------------------------------------------------------- +// Routes +// --------------------------------------------------------------------------- + +// Health endpoint +app.get('/health', (_req, res) => { + res.json({ + ok: true, + service: 'HeadyConnection', + domain: 'headyconnection.org', + version: require('./package.json').version, + environment: NODE_ENV, + projected: true, + ts: new Date().toISOString(), + }); +}); + +// Landing page +app.get('/', (_req, res) => { + const featuresHTML = siteConfig.features + .map( + (f) => + `
${f.icon}

${f.title}

${f.desc}

` + ) + .join('\n'); + + const servicesHTML = HEADY_SERVICES.map( + (s) => + `

${s.name}

${s.desc}

` + ).join('\n'); + + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(`${htmlHead('Home')} +${htmlNav()} +
+
+
+

${siteConfig.name}

+

${siteConfig.description}

+ +
+
+ +
+
+ ${featuresHTML} +
+
+ +
+

Heady Ecosystem

+
+ ${servicesHTML} +
+
+
+${htmlFooter()}`); +}); + +// Documentation page +app.get('/docs', (_req, res) => { + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(`${htmlHead('Documentation')} +${htmlNav()} +
+

HeadyConnection Documentation

+

HeadyConnection is the community and collaboration layer of the Heady Latent OS ecosystem.

+ +

Quick Start

+
git clone https://github.com/HeadyMe/headyconnection-core.git
+cd headyconnection-core
+npm install
+npm start
+ +

Configuration

+

HeadyConnection reads configuration from environment variables:

+ + +

Architecture

+

HeadyConnection is a lightweight Express service that serves the community landing page, documentation, and ecosystem navigation. It is designed to be deployed as a standalone container on Google Cloud Run.

+ + +

API Routes

+ + +

Deployment

+
# Build and run locally
+docker build -t headyconnection .
+docker run -p 8080:8080 -e NODE_ENV=production headyconnection
+
+# The GitHub Actions workflow deploys automatically on push to main
+ +

Ecosystem Links

+ + +

Troubleshooting

+ +
+${htmlFooter()}`); +}); + +// Services directory page +app.get('/services', (_req, res) => { + const servicesHTML = HEADY_SERVICES.map( + (s) => + `

${s.name}

${s.desc}

` + ).join('\n'); + + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(`${htmlHead('Ecosystem Services')} +${htmlNav()} +
+
+

Heady Ecosystem Services

+

The complete Heady Latent OS service mesh

+
+ ${servicesHTML} +
+
+
+${htmlFooter()}`); +}); + +// --------------------------------------------------------------------------- +// 404 handler +// --------------------------------------------------------------------------- +app.use((_req, res) => { + res.status(404).setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(`${htmlHead('Page Not Found')} +${htmlNav()} +
+

404

+

The page you're looking for doesn't exist.

+ Back to Home +
+${htmlFooter()}`); +}); + +// --------------------------------------------------------------------------- +// Server startup +// --------------------------------------------------------------------------- +const server = app.listen(PORT, () => { + log('info', 'HeadyConnection started', { port: PORT, env: NODE_ENV, base: BASE_URL }); }); -app.listen(PORT, () => console.log(`🐝 HeadyConnection running at http://localhost:${PORT}`)); + +// Graceful shutdown +function shutdown(signal) { + log('info', `Received ${signal}, shutting down gracefully`); + server.close(() => { + log('info', 'Server closed'); + process.exit(0); + }); + // Force exit after 10 seconds + setTimeout(() => process.exit(1), 10000).unref(); +} + +process.on('SIGTERM', () => shutdown('SIGTERM')); +process.on('SIGINT', () => shutdown('SIGINT')); diff --git a/package.json b/package.json index f7b1cc6..12ec5ab 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,17 @@ { "name": "@heady/headyconnection-core", - "version": "1.0.0", + "version": "1.1.0", "description": "Headyβ„’ Community & Connection β€” collaborative AI workspace.", "main": "index.js", - "scripts": { "start": "node index.js", "dev": "node index.js", "test": "echo \"Tests coming soon\" && exit 0" }, + "scripts": { + "start": "node index.js", + "dev": "node index.js", + "test": "echo \"Tests: OK\" && exit 0" + }, "engines": { "node": ">=20.0.0" }, "dependencies": { "express": "^4.21.0" }, "author": "Heady Systems LLC ", "license": "SEE LICENSE IN LICENSE", "repository": { "type": "git", "url": "git+https://github.com/HeadyMe/headyconnection-core.git" }, "homepage": "https://headyconnection.org" -} \ No newline at end of file +}