-
Install Homebrew.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -
Install and start PostgreSQL.
POSTGRESQL_VERSION=18 brew install postgresql@$POSTGRESQL_VERSION brew services start postgresql@$POSTGRESQL_VERSION
-
Install Rust.
brew install rustup rustup-init
-
The backend uses SQLx to interact with PostgreSQL. Install the SQLx in order to run migrations and perform other administrative operations.
cargo install sqlx-cli --no-default-features --features rustls,postgres
-
Configure SQLx with the DATABASE_URL.
export DATABASE_URL=postgresql://localhost/kosoAlso, add the environment variable to the appropriate profile file (
~/.profile,~/.bash_profile,~/.bashrc,~/.zshrc,~/.zshenv) so you don't have to run it every time. -
Create the database and run the DB migrations.
In the
backendfolder, run:sqlx database create sqlx migrate run
-
Install Node.js.
brew install node pnpm
-
Install the Stripe CLI.
brew install stripe/stripe-cli/stripe stripe login
-
Configure secrets.
In the
kosoroot folder, run:mkdir -p .secrets/koso openssl rand -hex 256 > .secrets/koso/hmac
-
Run the most recent DB migrations.
In the
backendfolder, run:sqlx migrate run
-
Install the latest frontend dependencies.
In the
frontendfolder, run:pnpm install
-
Start the backend server.
In the
backendfolder, run:cargo run
-
Start the frontend server.
In the
frontendfolder, run:pnpm dev
-
Navigate to http://localhost:5173/
The Koso Workspace is configured for development in VS Code.
The following plugins are recommended:
- Rust Analyzer
- Even Better TOML
- Svelte for VS Code
- Tailwind CSS IntelliSense
- Prettier - Code Formatter
- ESLint
- Vitest
- Playwright Test for VSCode
- httpBook - Rest Client
Once a server has been started, you can interact with it at http://localhost:3000. There are example requests in koso.http which you can run with httpBook.
This setup is similar to how the app will run in production. A single server serves the API, WebSocket, and static frontend files.
-
In the
frontendfolder, run:pnpm build
-
In the
backendfolder, run the server:cargo run
This will create a frontend/build folder. The backend/static folder is symlinked to that folder and will serve the compiled frontend directly from the backend.
Playwright tests, i.e. integration tests, flex the entire system end-to-end via the frontend testing framework Playwright. The tests run as part of CI, but you may also run them locally.
Make changes and run the tests quickly without rebuilding the world. Start a frontend and backend server in the usual manner, see above, and run the tests in VSCode using the Playwright extension or via the CLI:
pnpm exec playwright testFollow "Running a Built Frontend with the Backend" above to build the frontend and run the backend. Run the tests:
PW_SERVER_PORT=3000 pnpm exec playwright testThis is what our CI workflows do. playwright will build the frontend and run a backend for the duration of the tests:
CI=true pnpm exec playwright testBuild and run the docker image defined in Dockerfile.
- Download and install Docker: https://www.docker.com/products/docker-desktop/
-
Build the image:
DOCKER_DEFAULT_PLATFORM=linux/amd64 docker build -t ghcr.io/kosolabs/koso . -
Configure the DATABASE_URL.
export DATABASE_URL=postgresql://localhost/kosoAlso, add the environment variable to the appropriate profile file (
~/.profile,~/.bash_profile,~/.bashrc,~/.zshrc,~/.zshenv) so you don't have to run it every time. -
Run database migrations:
DOCKER_DEFAULT_PLATFORM=linux/amd64 docker run \ --env DATABASE_URL \ --network=host \ --rm -it \ ghcr.io/kosolabs/koso:latest \ "./sqlx" migrate run -
Run the server:
DOCKER_DEFAULT_PLATFORM=linux/amd64 docker run \ --env KOSO_ENV=dev \ -v $HOME/.secrets:/.secrets \ --network=host \ --rm -it \ ghcr.io/kosolabs/koso:latest
Commands for setting up a new server are in setup_server.sh
We use a Github Environment configured on the Deploy workflow which exposes a KOSO_KEY to access the server.
Add a migration:
sqlx migrate add some-meaningful-nameRun migrations
sqlx migrate runpsql_backup.sh exports backups of our Postgresql DB to cloud storage.
The script is ran by a daily cron and logs are available at koso-psql-backups/backups.log.
Backups are stored in a GCP cloud storage bucket named koso-psql-backups. The bucket has
soft deletion and object versioning configured, along with lifecycle rules to
auto-delete objects after 30 days.
Identify the backup to restore in the cloud console and update backup_name below with the target object name.
backup_name=TARGET-backup.sql.gzDownload and unzip the backup:
backup_object=gs://$koso-psql-backups/$backup_name
gcloud storage cp --print-created-message $backup_object ./
gzip -dk $backup_nameRestore the backup:
PGPASSWORD=$PSQL_PASSWORD pg_restore \
--host="$PSQL_HOST" \
--port="$PSQL_PORT" \
--db="$PSQL_DB" \
--username="$PSQL_USER" \
-f \
$backup_nameUpgrade Postgres to a new major version. In the example below, from 17 to 18.
-
Update the postgres image version from postgres:17 to postgres:18 in ci.yml and merge.
-
Install the new version of posgres:
sudo apt update sudo apt install postgresql-18 pg_lsclusters
-
Backup the cluster just in case:
pg_dumpall > ~/postgres-dump-$(date -u "+%Y-%m-%dT%H-%M-%S-%3NZ")
-
Upgrade the cluster:
sudo service postgresql stop sudo pg_renamecluster 18 main main_pristine sudo pg_upgradecluster 17 main sudo service postgresql start pg_lsclusters -
Verify the new cluster is working by visiting our app and verifying things work. Look at backend logs as well for anything suspicious.
pg_lsclusters
-
Drop the old and transition version:
sudo pg_dropcluster 17 main --stop sudo pg_dropcluster 18 main_pristine --stop
Local development of integrations requires that external services be able to talk to your locally running server. This is accomplished via a reverse proxy.
-
Install cloudflared.
brew install cloudflared
-
Login to Cloudflare.
cloudflared login
-
Authorize Cloudflare Tunnel to the koso.app domain.
-
Click Authorize.
-
Create a tunnel.
cloudflared tunnel create koso
-
Copy the tunnel ID to the clipboard.
-
Configure the DNS for the tunnel. Replace
namespacewith an appropriate name, e.g. shad-dev.cloudflared tunnel route dns koso ${namespace}.koso.app -
Write the following file to
~/.cloudflared/config.yaml. Replace${tunnel-id}with the value from the output of the tunnel create command.url: http://localhost:3000 tunnel: ${tunnel-id} credentials-file: ~/.cloudflared/${tunnel-id}.json
-
Start the reverse proxy server.
cloudflared tunnel run koso
References:
- https://docs.github.com/en/webhooks/webhook-events-and-payloads
- https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/using-webhooks-with-github-apps
- https://docs.github.com/en/webhooks/testing-and-troubleshooting-webhooks/testing-webhooks
Install Smee
npm install --global smee-clientAfter starting your local server:
- Configure your development webhook secret in:
.secrets/github/webhook_secret - Start a new Smee channel: https://smee.io/
- Start smee locally with the new channel
smee -u $CHANNEL_URL --port 3000 --path /plugins/github/app/webhook - Trigger or redeliver some events
-
Install the CLI:
brew install stripe/stripe-cli/stripe stripe login
-
Configure your sandbox secret API key in
.secrets/stripe/secret_key -
Configure your sandbox webhook secret
stripe listen --api-key $(cat .secrets/stripe/secret_key) --print-secret > .secrets/stripe/webhook_secret
We use the Koso Labs Sandbox Stripe sandbox for testing. Login to Stripe and switch to the Sandbox to find API keys and webhook details. Feel free to create a new sandbox if needed.
Start a local listener with stripe listen:
./backend/scripts/stripe_listen.shWith this in place and your local servers running, you can:
- Run playwright subscription tests
- Trigger events on demand with
stripe trigger. For example:stripe trigger checkout.session.completed - Test interactively using the
4242 4242 4242 4242card number: https://docs.stripe.com/testing#testing-interactively
-
Variables used in this section:
${title}, e.g.ShadDev${namespace}, e.g.shad-dev
-
Navigate to https://discord.com/developers/applications.
-
Click New Application.
-
Set the name of the app to
${title}Koso, e.g.ShadDevKoso. -
Copy the Application ID and save it for later (
${application-id}). -
Copy the public key into
.secrets/discord/public_key. -
Set the Interactions Endpoint URL to:
https://${namespace}.koso.app/api/notifiers/discord/interaction -
Click Save Changes.
-
Click Bot.
-
Click Reset Token.
-
Click Yes, do it!.
-
Enter your password and click Submit if necessary.
-
Copy the Token into
.secrets/discord/token. -
Install the
/tokencommand into your discord app:curl -X POST "https://discord.com/api/v10/applications/${application-id}/commands" \ -H "Authorization: Bot $(cat .secrets/discord/token)" \ -H "Content-Type: application/json" \ -d '{ "name": "token", "description": "Start the authorization flow for Koso" }'
-
Replace
${application-id}in the link below with the value from above, then follow the link to install the app. -
Start a conversation with the bot (
@${title}Koso). -
Click the Commands button.
-
Click the Send button corresponding to the
/tokencommand. -
Follow the link to authorize your local Koso to send notifications via Discord.
-
Variables used in this section:
${title}, e.g.ShadDev${namespace}, e.g.shad-dev
-
Navigate to https://api.slack.com/apps.
-
Click Create New App.
-
Click From a manifest.
-
Set your workspace to: Koso Labs.
-
Click Next.
-
Copy / paste the following manifest replacing the variables as necessary:
{ "display_information": { "name": "${title}Koso", "description": "Receive notification updates from ${title}Koso" }, "features": { "bot_user": { "display_name": "${title}Kosobot", "always_online": true }, "slash_commands": [ { "command": "/${namespace}-token", "url": "https://${namespace}.koso.app/api/notifiers/slack/command", "description": "Start the authorization flow", "should_escape": false } ] }, "oauth_config": { "scopes": { "bot": ["chat:write", "im:history", "im:read", "im:write", "commands"] } }, "settings": { "interactivity": { "is_enabled": true, "request_url": "https://${namespace}.koso.app/api/notifiers/slack/interact" }, "org_deploy_enabled": false, "socket_mode_enabled": false, "token_rotation_enabled": false } } -
Copy the Signing Secret into
.secrets/slack/signing_secret. -
Click Install App.
-
Click Install to Koso Labs.
-
Click Allow.
-
Copy the Bot User OAuth Token into
.secrets/slack/token. -
Click App Home.
-
In the Show Tabs section, check the box Allow users to send Slash commands and messages from the messages tab.
-
In Slack, click your app in the Apps section.
-
Click on About.
-
Next to the token command, click Start command.
-
Click the Send button.
-
Click Authorize Koso to authorize your local Koso to send notifications via Slack.
You can send a test message from the profile page.
Create a new bot and configure the secrets.
-
Variables used in this section:
${title}, e.g.ShadDev${namespace}, e.g.shad-dev
-
Send a Telegram message to @BotFather:
/newbot. -
Name the bot
${title}Kosobot. -
Copy the access token into
.secrets/telegram/token. -
Generate a secret token that will authorize Telegram to call the webhook and save it into
.secrets/telegram/secret_token.openssl rand -base64 256 | tr -dc 'A-Za-z0-9' | head -c 256 > .secrets/telegram/secret_token
-
Configure your Telegram bot to use the secret_token with your webhook.
curl -X POST "https://api.telegram.org/bot$(cat .secrets/telegram/token)/setWebhook" \ -d "url=https://${namespace}.koso.app/api/notifiers/telegram/webhook" \ -d "secret_token=$(cat .secrets/telegram/secret_token)"
-
Send a
/tokenmessage to your bot: ${title}Kosobot. -
Follow the link to authorize your local Koso to send notifications via Telegram.
With setup complete you can interact with tasks and generate notifications. Note though that most self notifications are suppressed. Use login_test_user.sh to login as a test user, interact with tasks and trigger notifications.
After starting your local server:
- Open the command palette (
cmd+shift+p) and runMCP: Open User Configuration - Insert the Koso server
{ "servers": { "koso-mcp": { "url": "http://localhost:3000/api/mcp/sse", "type": "http" } }, "inputs": [] } - Open a Copilot chat, click on
Configure toolsand enable Koso
- Install Claude Code:
npm install -g @anthropic-ai/claude-code - Run the setup flow:
claude - Add Koso
- Local:
claude mcp add --transport http koso-local-mcp http://localhost:3000/api/mcp/sse - Remote:
claude mcp add --transport http koso-mcp https://koso.app/api/mcp/sse
- Local:
- Run
claudeand then/mcpto authenticate
Use the MCP Inspector to test the server without a model.
Setup:
- Run the inspector:
npx @modelcontextprotocol/inspector - Enter the server URL ()
http://localhost:3000/api/mcp/sse) and click Connect