Skip to content

fakechris/Involute

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Involute

npm version CI Docker Publish

一人团队的 epic / issue / team / workspace 项目管理系统开源实现。

Involute bundles a GraphQL API, a kanban web app, and a CLI that can export one team snapshot, import it into Involute, verify the result, and then let you visually accept it in the board UI.

Current status

  • M0 single-team migration acceptance is done.
  • M2 Google OAuth, session auth, admin bootstrap, and team RBAC are done.
  • M1 deployable self-hosting is in progress.
  • The latest main is already live on the VPS, backup/restore has been rehearsed once, Google OAuth is configured on the public domain, and the canonical SON team snapshot has been refreshed into the VPS stack.
  • The current highest-priority gap is operator confidence on the VPS: a tighter runbook for deploy, rollback, logs, and restore.

See docs/current-status.md, docs/milestones.md, docs/vision.md, and docs/api.md for the current product state and API surface.

Workspace layout

  • packages/server — GraphQL API, Prisma-backed data model, import pipeline, validation helpers
  • packages/web — React + Vite kanban UI
  • packages/cliinvolute CLI for config, import/export, teams, issues, labels, and comments
  • packages/shared — shared TypeScript utilities
  • docs/api.md — HTTP and GraphQL API reference
  • docs/vision.md — current product vision
  • docs/milestones.md — active milestones and sequencing

Install the CLI

The published CLI is the fastest way to use Involute against an existing server:

npm install -g @turnkeyai/involute
involute --help

Or install it project-locally:

npm install @turnkeyai/involute
npx involute --help

Point the CLI at your Involute API:

involute config set server-url https://involute.example.com/graphql
involute config set token YOUR_AUTH_TOKEN
involute teams list

Import and verify one team snapshot:

export SOURCE_API_TOKEN='src_api_xxx'
involute import team --token "$SOURCE_API_TOKEN" --team SON --keep-export --output ./son-export

Environment

Create a repo-root .env file based on .env.example:

DATABASE_URL=postgresql://involute:involute@127.0.0.1:5434/involute?schema=public
AUTH_TOKEN=changeme-set-your-token
VIEWER_ASSERTION_SECRET=compose-viewer-secret
APP_ORIGIN=http://localhost:4201
GOOGLE_OAUTH_CLIENT_ID=...
GOOGLE_OAUTH_CLIENT_SECRET=...
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:4200/auth/google/callback
ADMIN_EMAIL_ALLOWLIST=you@example.com
PORT=4200

Required server variables:

  • DATABASE_URL — PostgreSQL connection string
  • APP_ORIGIN — browser origin used for cookie/CORS handling and post-login redirects
  • PORT — API port (defaults to 4200)

Optional but recommended server variables:

  • AUTH_TOKEN — trusted bearer token used by the CLI and local/dev bootstrap flows
  • VIEWER_ASSERTION_SECRET — HMAC secret used to verify signed viewer assertions for trusted impersonation
  • GOOGLE_OAUTH_CLIENT_ID — Google OAuth client id for browser sign-in
  • GOOGLE_OAUTH_CLIENT_SECRET — Google OAuth client secret
  • GOOGLE_OAUTH_REDIRECT_URI — Google callback URL handled by the API server
  • ADMIN_EMAIL_ALLOWLIST — comma-separated allowlist of emails that should become ADMIN
  • SESSION_TTL_SECONDS — browser session lifetime in seconds
  • SEED_DEFAULT_ADMIN — dev/test-only switch to seed admin@involute.local; keep this false outside local acceptance flows
  • PRISMA_BASELINE_EXISTING_SCHEMA — one-time upgrade switch for pre-migration databases that already have the schema but no _prisma_migrations history

Compatibility note:

  • GOOGLE_OAUTH_ADMIN_EMAILS is still accepted as a legacy alias, but new deployments should use ADMIN_EMAIL_ALLOWLIST

Optional web runtime variables:

  • VITE_INVOLUTE_GRAPHQL_URL — override the web app GraphQL endpoint (default: http://localhost:4200/graphql)
  • VITE_INVOLUTE_AUTH_TOKEN — trusted local/dev bearer token for bypassing browser login
  • VITE_INVOLUTE_VIEWER_ASSERTION — signed viewer assertion to act as a specific user without exposing the server secret

Local stack quick start

Use this path when you want to run the API, web app, and Postgres locally.

pnpm install
cp .env.example .env
pnpm compose:up

Smoke check:

curl http://localhost:4200/health
curl http://localhost:4201

Then open http://localhost:4201 in your browser.

If Google OAuth is configured, the web nav will expose Sign in with Google and use session cookies. If it is not configured, the browser can still talk to the API with VITE_INVOLUTE_AUTH_TOKEN for trusted local development.

Compose defaults:

  • API: http://localhost:4200
  • Web: http://localhost:4201
  • Postgres: 127.0.0.1:5434
  • CLI export mount: tracked .tmp/ on the host is available as /exports in the cli container
  • Compose uses the web-dev Docker target for the live Vite UI; the published involute-web image uses the production web target
  • server-init now applies Prisma migrations with prisma migrate deploy before seeding

Stop the stack with:

pnpm compose:down

If you want to run the published Docker Hub images instead of building from source, use:

INVOLUTE_IMAGE_NAMESPACE=fakechris INVOLUTE_IMAGE_TAG=latest pnpm compose:pull
INVOLUTE_IMAGE_NAMESPACE=fakechris INVOLUTE_IMAGE_TAG=latest pnpm compose:pull:up

VPS deployment (fresh install)

This is the recommended first production path: one VPS, Docker Compose, Postgres, the Node API, the static web container, and Caddy terminating HTTPS on a single domain.

Status:

  • the deployment files and automation are in place
  • the Tailscale-only path has already been exercised
  • the remaining production work is to validate the same stack on a public domain with real Google OAuth callback and one backup/restore drill

Files involved:

Assumptions:

  • a fresh host with Docker and Docker Compose installed
  • a DNS record for APP_DOMAIN already points at the VPS
  • a fresh Postgres volume; no legacy schema upgrade path is needed
  1. Copy the repo to the VPS and create the production env file:
cp .env.production.example .env.production
  1. Fill at least these values in .env.production:
APP_DOMAIN=involute.example.com
APP_ORIGIN=https://involute.example.com
POSTGRES_PASSWORD=...
AUTH_TOKEN=...
VIEWER_ASSERTION_SECRET=...
ADMIN_EMAIL_ALLOWLIST=you@example.com
GOOGLE_OAUTH_CLIENT_ID=...
GOOGLE_OAUTH_CLIENT_SECRET=...
GOOGLE_OAUTH_REDIRECT_URI=https://involute.example.com/auth/google/callback
  1. Bring the stack up:
pnpm compose:prod:up
  1. Smoke check it:
docker compose --env-file .env.production -f docker-compose.prod.yml ps
curl -I https://involute.example.com
curl https://involute.example.com/health
  1. If you need to re-assert the first admin explicitly:
docker compose --env-file .env.production -f docker-compose.prod.yml run --rm \
  --entrypoint /bin/sh server -lc \
  'pnpm --filter @turnkeyai/involute-server run admin:bootstrap you@example.com'

Operational notes:

  • production compose keeps Postgres internal; only Caddy exposes 80/443
  • server-init runs prisma migrate deploy before the API starts
  • SEED_DATABASE defaults to false in production; turn it on only for a fresh demo seed
  • the web container is the static production build, not the Vite dev server

Backups:

sh scripts/postgres-backup.sh

This writes a gzipped SQL dump to .backups/.

Automated deployment with Ansible

Manual SSH deployment is no longer the intended path. The repo now includes an Ansible workflow under ops/ansible.

Available playbooks:

Tailscale-specific deployment reuses docker-compose.yml and drives bind addresses through the rendered env file. Only 4200 and 4201 bind to the Tailscale IP; Postgres stays on 127.0.0.1.

Typical flow:

  1. Copy the example inventory:
cp ops/ansible/inventory/hosts.yml.example ops/ansible/inventory/hosts.yml
  1. Fill the target host, bind address, and secrets.

  2. Prepare the host:

pnpm deploy:bootstrap
  1. Deploy the Tailscale stack:
pnpm deploy:tailscale

For the current Tailscale-only test phase, use:

  • involute_stack_profile: tailscale
  • involute_bind_address: <tailscale-ip>
  • involute_app_origin: http://<tailscale-ip>:4201

When the public domain and OAuth are ready, switch the inventory to production and use docker-compose.prod.yml.

GitHub Actions can run the same deployment path from .github/workflows/deploy.yml. Configure these repository secrets before enabling it:

  • DEPLOY_HOST
  • DEPLOY_KNOWN_HOSTS
  • DEPLOY_USER
  • DEPLOY_SSH_PRIVATE_KEY
  • INVOLUTE_APP_ORIGIN
  • INVOLUTE_AUTH_TOKEN
  • INVOLUTE_VIEWER_ASSERTION_SECRET
  • INVOLUTE_BIND_ADDRESS for tailscale
  • INVOLUTE_APP_DOMAIN and INVOLUTE_POSTGRES_PASSWORD for production
  • optional: INVOLUTE_ADMIN_EMAIL_ALLOWLIST, INVOLUTE_GOOGLE_OAUTH_CLIENT_ID, INVOLUTE_GOOGLE_OAUTH_CLIENT_SECRET, INVOLUTE_GOOGLE_OAUTH_REDIRECT_URI

Recommended repository variables:

  • INVOLUTE_DEPLOY_ON_MAIN=false to keep deploy manual by default
  • INVOLUTE_DEPLOY_PROFILE=tailscale for the current private test phase

Manual single-team import

Set your source-system API token in the shell:

export SOURCE_API_TOKEN='src_api_xxx'

If you installed the published CLI:

involute import team --token "$SOURCE_API_TOKEN" --team SON --keep-export --output ./son-export

If you are using the local compose stack, you can also run the import inside the compose CLI container:

docker compose run --rm cli import team --token "$SOURCE_API_TOKEN" --team SON --keep-export --output /exports/son-export

What this does:

  • exports one team snapshot into .tmp/son-export
  • imports the exported data into Involute
  • runs import verify
  • writes .tmp/son-export/involute-import-summary.json

After it completes, open http://localhost:4201 and visually check the imported team in the board.

Recommended acceptance checks:

  • the target team appears in the board
  • issue count looks complete for that team
  • a few issues have the expected state, labels, assignee, and comments
  • the latest imported issues are visible in the board, not hidden behind the first page

Local development without Docker

Start the API:

DATABASE_URL="postgresql://involute:involute@127.0.0.1:5434/involute?schema=public" AUTH_TOKEN="changeme-set-your-token" VIEWER_ASSERTION_SECRET="compose-viewer-secret" APP_ORIGIN="http://127.0.0.1:4201" GOOGLE_OAUTH_REDIRECT_URI="http://127.0.0.1:4200/auth/google/callback" pnpm --filter @turnkeyai/involute-server exec tsx src/index.ts

Start the web app:

VITE_INVOLUTE_AUTH_TOKEN="changeme-set-your-token" VITE_INVOLUTE_GRAPHQL_URL="http://127.0.0.1:4200/graphql" pnpm --filter @turnkeyai/involute-web exec vite --host 127.0.0.1 --port 4201

Run the CLI against that local API:

pnpm --filter @turnkeyai/involute exec node dist/index.js import team --token "$SOURCE_API_TOKEN" --team SON --keep-export --output .tmp/son-export

If you need the CLI or web UI to act as a specific user, mint a short-lived viewer assertion with a trusted secret and persist it:

export INVOLUTE_VIEWER_ASSERTION_SECRET=compose-viewer-secret
pnpm --filter @turnkeyai/involute exec node dist/index.js auth viewer-assertion create user@example.com --ttl 3600
pnpm --filter @turnkeyai/involute exec node dist/index.js config set viewer-assertion SIGNED_ASSERTION_HERE

The web UI can use the same signed assertion via VITE_INVOLUTE_VIEWER_ASSERTION or localStorage key involute.viewerAssertion.

Auth and permissions

  • Browser auth now supports Google OAuth plus session cookies.
  • AUTH_TOKEN and viewer assertions remain available for trusted CLI/dev flows.
  • System admins can be bootstrapped through ADMIN_EMAIL_ALLOWLIST or pnpm --filter @turnkeyai/involute-server admin:bootstrap user@example.com.
  • Teams now have PUBLIC / PRIVATE visibility.
  • Team edits are gated by membership role: EDITOR or OWNER.
  • Team access management is available in the web UI at /settings/access and through GraphQL mutations: teamUpdateAccess, teamMembershipUpsert, and teamMembershipRemove.

Database migrations

Use Prisma migrations as the default schema workflow:

pnpm --filter @turnkeyai/involute-server prisma:migrate:dev -- --name your_change
pnpm --filter @turnkeyai/involute-server prisma:migrate:deploy

API reference

The current HTTP and GraphQL surface is documented in docs/api.md.

npm release flow

Tag-driven npm publishing is wired through .github/workflows/npm-publish.yml.

Release tags use this format:

git tag npm-v1.0.0
git push origin npm-v1.0.0

That workflow publishes the current package set in version lockstep:

  • @turnkeyai/involute-shared
  • @turnkeyai/involute-server
  • @turnkeyai/involute

Repository setup required before enabling it:

  • add NPM_TOKEN as a GitHub Actions secret
  • ensure the npm account behind that token can publish the current @turnkeyai/* scope
  • after first publish, grant package access to the npm developers team in your org settings

Team access page for the current npm org setup:

Useful admin/database commands:

pnpm --filter @turnkeyai/involute-server admin:bootstrap you@example.com
pnpm --filter @turnkeyai/involute-server prisma:migrate:baseline
pnpm --filter @turnkeyai/involute-server prisma:migrate:reset
pnpm --filter @turnkeyai/involute-server prisma:db:push

Guidance:

  • prefer prisma:migrate:dev while changing the schema locally
  • use prisma:migrate:deploy in compose, CI, and production
  • keep prisma:db:push as an explicit development-only escape hatch, not the default deployment path
  • if you are upgrading an older database that predates prisma/migrations, run prisma:migrate:baseline once before the first prisma:migrate:deploy, or set PRISMA_BASELINE_EXISTING_SCHEMA=true for a one-time compose bootstrap

Quality gates

Unit and integration checks:

pnpm typecheck
pnpm lint
pnpm test
pnpm build

Browser E2E:

pnpm e2e

The Playwright suite verifies the core board lifecycle: create, update, comment, delete comment, and delete issue.

Docker images

This repo ships one multi-target Dockerfile with server, web-dev, web, and cli targets.

Published images:

docker pull fakechris/involute-server:latest
docker pull fakechris/involute-web:latest
docker pull fakechris/involute-cli:latest

Run the compose stack from published images:

INVOLUTE_IMAGE_NAMESPACE=fakechris INVOLUTE_IMAGE_TAG=latest \
  docker compose -f docker-compose.images.yml up -d db server web

Production compose can use the same published images:

INVOLUTE_IMAGE_NAMESPACE=fakechris INVOLUTE_IMAGE_TAG=latest \
  docker compose --env-file .env.production \
  -f docker-compose.prod.images.yml up -d

Image tags:

  • latest — latest successful push from main
  • sha-<short-sha> — immutable commit image
  • <version> — pushed from docker-v<version> tags or workflow_dispatch input

The Docker Hub publish workflow expects these secrets:

  • DOCKERHUB_USERNAME
  • DOCKERHUB_TOKEN
  • DOCKERHUB_NAMESPACE — optional; defaults to DOCKERHUB_USERNAME

When they are set, .github/workflows/docker-publish.yml pushes:

  • ${DOCKERHUB_NAMESPACE}/involute-server
  • ${DOCKERHUB_NAMESPACE}/involute-web
  • ${DOCKERHUB_NAMESPACE}/involute-cli

The published involute-web image is a static production build. It bakes VITE_INVOLUTE_GRAPHQL_URL at build time, but it does not bake an auth token into the image. For local development and acceptance, the compose stack remains the reference runtime path and should stay green before publishing.

Common CLI commands

pnpm --filter @turnkeyai/involute exec node dist/index.js teams list
pnpm --filter @turnkeyai/involute exec node dist/index.js issues list --team SON
pnpm --filter @turnkeyai/involute exec node dist/index.js issues create --team SON --title "My issue"
pnpm --filter @turnkeyai/involute exec node dist/index.js comments add SON-1 --body "Hello from Involute"
pnpm --filter @turnkeyai/involute exec node dist/index.js export --token "$SOURCE_API_TOKEN" --team SON --output .tmp/son-export
pnpm --filter @turnkeyai/involute exec node dist/index.js import --file .tmp/son-export
pnpm --filter @turnkeyai/involute exec node dist/index.js import verify --file .tmp/son-export
pnpm --filter @turnkeyai/involute exec node dist/index.js import team --token "$SOURCE_API_TOKEN" --team SON

Current focus

  • Finish public-domain VPS deployment and validate Google OAuth callback plus session flow
  • Run one real Postgres backup and restore drill
  • Keep Google OAuth, admin bootstrap, and team RBAC stable while deployment hardens
  • Move database changes through Prisma migrations instead of schema push shortcuts
  • Keep the compose stack, CI, and deploy automation reproducible while the product boundary hardens

Railway remains a possible later hosting path, but it is not the current blocking milestone. See docs/current-status.md, docs/vision.md, and docs/milestones.md for the current direction.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors