Open-source self-hosted dashboard for safely running and monitoring GitHub Actions across an organization
TypeScript
2
6 commits
updated Sep 27, 2026
Run GitHub Actions across a whole organization — safely, in bulk, and live.
Features · How it works · Getting started · Configuration · Security · Development · Architecture
EasyActions is a web dashboard for GitHub Actions. You pick a GitHub organization and see every
repository with its branches and workflows, plus the live status of each one. You can start
workflow_dispatch runs on many repositories at once, then watch them finish, all from one screen.
EasyActions is the product name. Pipliner is the codename: you will still see it in the code, the cookies, the
/healthanswer and the logs.
bun run desktop opens EasyActions in its own window. You can also install it
from Brave, Chrome or Edge.One Bun process serves both the web app and its API from the same origin.
Browser ── same origin ──▶ Bun + Hono
├─ /health, /auth/*, /api/* → server/ (GitHub adapters: OAuth, repos, Actions)
└─ any other path → app/ (TiniJS + Lit web app)
| Folder | Contents |
|---|---|
app/ | The web app: TiniJS on Lit, Tailwind CSS v4, MASKAI design tokens with an EasyActions brand layer |
server/ | The Hono server: auth, sessions, API routes, GitHub adapters, SQLite database (schema, repositories), config, logging |
domain/ | Framework-free logic and types shared by both sides (API contract, dispatch plan, polling policy, dashboard statistics) |
scripts/ | Production build, database, settings and user commands, and the desktop launcher |
tests/ | Unit and integration tests. GitHub is replaced by a real local fake HTTP server |
Every design choice, the full list of routes, and the behaviour on each kind of failure are described in docs/ARCHITECTURE.md; what is stored and how it is protected, in docs/SECURITY.md.
On GitHub, go to Settings › Developer settings › GitHub Apps › New GitHub App (or do the same in your organization's settings), then set:
| Setting | Value |
|---|---|
| Homepage URL | http://127.0.0.1:8094 |
| Callback URL | http://127.0.0.1:8094/auth/callback |
| Expire user authorization tokens | Checked (required: without it, sign-in is refused) |
| Request user authorization (OAuth) during installation | Unchecked |
| Webhook → Active | Unchecked |
| Repository permissions | Actions: Read and write · Contents: Read-only · Metadata: Read-only |
Create the app. Note its Client ID, generate a client secret, and install the app on your organization.
bun install --frozen-lockfile
cp .env.example .env
openssl rand -base64 32
openssl rand -base64 32
Open .env and replace every <TO_PROVIDE>:
SESSION_SECRET: the first output of the openssl commandDATA_ENCRYPTION_KEY: the second output (a different value; keep it safe — losing it signs
everybody out).env is git-ignored. Never commit it. The GitHub App's client ID and secret never go in it: you
type them on the website (step 4).
Then create the database (run it again after every upgrade, with the server stopped):
bun run db:migrate
Development mode, with hot reload, on http://127.0.0.1:8094:
bun run dev
Production mode (stop the dev server first, because both use the same port):
bun run build
bun run start
Desktop window. This reuses a running server, or builds and starts one in the background:
bun run desktop
The first time, the server starts in setup mode: open http://127.0.0.1:8094 and it shows the setup page. In the repository folder, get a one-time setup code (it works for 30 minutes):
bun run settings:setup-code
On the setup page, type that code, the GitHub address (the API address fills itself in), your app's Client ID and client secret, then Test connection and Save and finish. The secret is stored encrypted in the database and never shown again.
Sign in with GitHub (and set up your authenticator app), then run this in the repository folder, with
your GitHub login instead of your-github-login:
bun run users:promote your-github-login
Reload the page: an Administration section (Settings, Users, History) appears in the sidebar. Other admins can then be made from the Users page.
Configuration has two places:
.env, for the server and security. Every variable in the table below is required. If one
is missing, still set to <TO_PROVIDE>, or invalid, the server does not start and lists the names
of the problem variables (never their values). .env.example documents each one.| Variable | Purpose | Default in template |
|---|---|---|
HOST | Interface to bind (never 0.0.0.0 on a host) | 127.0.0.1 |
PORT | Port for the app and its API | 8094 |
APP_ORIGIN | Exact public origin: builds the callback URL and is checked on every POST | http://127.0.0.1:8094 |
SESSION_SECRET | Encrypts the short sign-in cookie, at least 32 characters | — |
DATA_ENCRYPTION_KEY | Encrypts the GitHub tokens in the database, at least 32 characters | — |
DATABASE_PATH | SQLite file, created by bun run db:migrate | ./data/pipliner.sqlite |
HTTP_IDLE_TIMEOUT_SECONDS | Seconds a connection may stay silent (30–255) | 240 |
SESSION_MAX_DAYS | Days a browser stays signed in (1–180) | 30 |
SESSIONS_PER_USER_MAX | Browsers one person can be signed in on (1–10) | 5 |
AUDIT_RETENTION_DAYS | Days the sign-in and security history is kept (30–3650) | 365 |
TWO_FACTOR_EVERY_HOURS | Hours an accepted 6-digit code stays valid for a browser (1–168) | 24 |
TWO_FACTOR_MAX_ATTEMPTS | Wrong codes before a lock (3–20) | 5 |
TWO_FACTOR_LOCK_MINUTES | First lock, doubled at each next one, 24 h at most (1–1440) | 15 |
Website settings (setup page, then Settings page):
| Setting | Purpose | Default |
|---|---|---|
| GitHub web and API addresses | github.com, GHE.com or your GitHub Enterprise Server; the API address must match the web address | — |
| GitHub App client ID and secret | The app EasyActions signs in with; the secret is stored encrypted and never shown | — |
| GitHub timeout | Timeout for every GitHub call (1000–60000 ms) | 10000 |
| Repositories read per organization | Above it, the list says it is truncated | 1000 |
| Branches read per repository | Max branches listed | 300 |
| Days before a branch is stale | Older branches are listed as "Stale" | 90 |
| Pipelines per bulk run | Max pipelines one bulk run may start | 50 |
| Dispatches sent at the same time | Parallel requests to GitHub | 3 |
| Live status: minimum seconds between checks | Polling floor | 10 |
| Live status: minutes runs are followed | Then "Status unknown" | 30 |
| Dashboard: repositories read | The most recently pushed first (1–500) | 50 |
| Dashboard: runs read per repository and period | GitHub lists 1,000 at most (100–1000) | 500 |
| Dashboard: commits read per repository | All branches, both periods (100–10000) | 2000 |
| Dashboard: seconds a result is reused | 0 reads GitHub at every visit (0–3600) | 300 |
| Dashboard: seconds allowed to read GitHub | Repositories not read in time are named (10–200) | 60 |
Changing the GitHub address or client ID signs everybody out. If a wrong value locks everybody out,
run bun run settings:setup-code --reset: it clears the connection (signing everybody out), puts the
server back in setup mode and prints a new setup code.
403 to other users. Changing the
GitHub connection, a role, someone's authenticator app or deleting someone needs a current 6-digit
code, and the history records who did it.bun run settings:setup-code on the server. Once set up, the setup
page is closed for good (--reset reopens it, from the server only).Origin, and cookies are SameSite=Lax.frame-ancestors 'none', so the "Run" button
cannot be embedded in another site (clickjacking).Warning: a
workflow_dispatchrun usually deploys something. Test with a sandbox repository that has a no-op workflow, never with production workflows.
Check gate. All of these must pass before a change is considered done:
bun install --frozen-lockfile && bun run typecheck && bun test && bun run build && bun audit --audit-level=high
| Script | What it does |
|---|---|
bun run dev | Dev server with hot reload (server/dev.ts) |
bun run build | Builds the web app into dist/app (scripts/buildApp.ts) |
bun run start | Production server (server/index.ts) |
bun run desktop | Opens EasyActions in its own window (scripts/desktop.ts) |
bun run db:migrate | Creates or upgrades the SQLite database (server stopped) |
bun run db:status | Tells whether the database is ready |
bun run db:rollback | Removes the last migration (--yes when it deletes data) |
bun run settings:setup-code | Prints a one-time code for the setup page (30 minutes); --reset first clears the GitHub connection |
bun run users:promote <login> | Makes someone an admin (they must have signed in once) |
bun run users:demote <login> | Removes the admin role (never from the last admin) |
bun run users:reset-two-factor <login> | Removes someone's authenticator app (lost phone); --all for everybody |
bun run typecheck | Strict TypeScript for the server and the app |
bun test | Unit and integration tests |
Project conventions:
app/ never imports from server/.{ detail: { code, message } }.server/config/logger.ts.The tests check the design rules. The full rules are in docs/ARCHITECTURE.md.
The planned milestones (M0–M8) are delivered. How the dashboard counts is described in docs/DASHBOARD.md.
The logos, app icons and favicons are in EasyActions-Logo-Pack-v2/. Its
README covers colours, typeface, sizes and clear-space rules.
EasyActions is released under the Apache License 2.0 (SPDX-License-Identifier: Apache-2.0). The
full text is in LICENSE, and every package ships a copy. You may use, modify and
redistribute it, including commercially, as long as you keep the copyright and licence notices and
state the changes you made to any file you redistribute.
The licence covers the code only. The EasyActions name and the logos, app icons and favicons in
EasyActions-Logo-Pack-v2/ are not granted with it: a fork may use the
code, not the brand.
3 commits
3 commits
TypeScript
86.9%
CSS
10.0%
Shell
2.7%
Open-source self-hosted dashboard for safely running and monitoring GitHub Actions across an organization
TypeScript
2
6 commits
updated Sep 27, 2026
Run GitHub Actions across a whole organization — safely, in bulk, and live.
Features · How it works · Getting started · Configuration · Security · Development · Architecture
EasyActions is a web dashboard for GitHub Actions. You pick a GitHub organization and see every
repository with its branches and workflows, plus the live status of each one. You can start
workflow_dispatch runs on many repositories at once, then watch them finish, all from one screen.
EasyActions is the product name. Pipliner is the codename: you will still see it in the code, the cookies, the
/healthanswer and the logs.
bun run desktop opens EasyActions in its own window. You can also install it
from Brave, Chrome or Edge.One Bun process serves both the web app and its API from the same origin.
Browser ── same origin ──▶ Bun + Hono
├─ /health, /auth/*, /api/* → server/ (GitHub adapters: OAuth, repos, Actions)
└─ any other path → app/ (TiniJS + Lit web app)
| Folder | Contents |
|---|---|
app/ | The web app: TiniJS on Lit, Tailwind CSS v4, MASKAI design tokens with an EasyActions brand layer |
server/ | The Hono server: auth, sessions, API routes, GitHub adapters, SQLite database (schema, repositories), config, logging |
domain/ | Framework-free logic and types shared by both sides (API contract, dispatch plan, polling policy, dashboard statistics) |
scripts/ | Production build, database, settings and user commands, and the desktop launcher |
tests/ | Unit and integration tests. GitHub is replaced by a real local fake HTTP server |
Every design choice, the full list of routes, and the behaviour on each kind of failure are described in docs/ARCHITECTURE.md; what is stored and how it is protected, in docs/SECURITY.md.
On GitHub, go to Settings › Developer settings › GitHub Apps › New GitHub App (or do the same in your organization's settings), then set:
| Setting | Value |
|---|---|
| Homepage URL | http://127.0.0.1:8094 |
| Callback URL | http://127.0.0.1:8094/auth/callback |
| Expire user authorization tokens | Checked (required: without it, sign-in is refused) |
| Request user authorization (OAuth) during installation | Unchecked |
| Webhook → Active | Unchecked |
| Repository permissions | Actions: Read and write · Contents: Read-only · Metadata: Read-only |
Create the app. Note its Client ID, generate a client secret, and install the app on your organization.
bun install --frozen-lockfile
cp .env.example .env
openssl rand -base64 32
openssl rand -base64 32
Open .env and replace every <TO_PROVIDE>:
SESSION_SECRET: the first output of the openssl commandDATA_ENCRYPTION_KEY: the second output (a different value; keep it safe — losing it signs
everybody out).env is git-ignored. Never commit it. The GitHub App's client ID and secret never go in it: you
type them on the website (step 4).
Then create the database (run it again after every upgrade, with the server stopped):
bun run db:migrate
Development mode, with hot reload, on http://127.0.0.1:8094:
bun run dev
Production mode (stop the dev server first, because both use the same port):
bun run build
bun run start
Desktop window. This reuses a running server, or builds and starts one in the background:
bun run desktop
The first time, the server starts in setup mode: open http://127.0.0.1:8094 and it shows the setup page. In the repository folder, get a one-time setup code (it works for 30 minutes):
bun run settings:setup-code
On the setup page, type that code, the GitHub address (the API address fills itself in), your app's Client ID and client secret, then Test connection and Save and finish. The secret is stored encrypted in the database and never shown again.
Sign in with GitHub (and set up your authenticator app), then run this in the repository folder, with
your GitHub login instead of your-github-login:
bun run users:promote your-github-login
Reload the page: an Administration section (Settings, Users, History) appears in the sidebar. Other admins can then be made from the Users page.
Configuration has two places:
.env, for the server and security. Every variable in the table below is required. If one
is missing, still set to <TO_PROVIDE>, or invalid, the server does not start and lists the names
of the problem variables (never their values). .env.example documents each one.| Variable | Purpose | Default in template |
|---|---|---|
HOST | Interface to bind (never 0.0.0.0 on a host) | 127.0.0.1 |
PORT | Port for the app and its API | 8094 |
APP_ORIGIN | Exact public origin: builds the callback URL and is checked on every POST | http://127.0.0.1:8094 |
SESSION_SECRET | Encrypts the short sign-in cookie, at least 32 characters | — |
DATA_ENCRYPTION_KEY | Encrypts the GitHub tokens in the database, at least 32 characters | — |
DATABASE_PATH | SQLite file, created by bun run db:migrate | ./data/pipliner.sqlite |
HTTP_IDLE_TIMEOUT_SECONDS | Seconds a connection may stay silent (30–255) | 240 |
SESSION_MAX_DAYS | Days a browser stays signed in (1–180) | 30 |
SESSIONS_PER_USER_MAX | Browsers one person can be signed in on (1–10) | 5 |
AUDIT_RETENTION_DAYS | Days the sign-in and security history is kept (30–3650) | 365 |
TWO_FACTOR_EVERY_HOURS | Hours an accepted 6-digit code stays valid for a browser (1–168) | 24 |
TWO_FACTOR_MAX_ATTEMPTS | Wrong codes before a lock (3–20) | 5 |
TWO_FACTOR_LOCK_MINUTES | First lock, doubled at each next one, 24 h at most (1–1440) | 15 |
Website settings (setup page, then Settings page):
| Setting | Purpose | Default |
|---|---|---|
| GitHub web and API addresses | github.com, GHE.com or your GitHub Enterprise Server; the API address must match the web address | — |
| GitHub App client ID and secret | The app EasyActions signs in with; the secret is stored encrypted and never shown | — |
| GitHub timeout | Timeout for every GitHub call (1000–60000 ms) | 10000 |
| Repositories read per organization | Above it, the list says it is truncated | 1000 |
| Branches read per repository | Max branches listed | 300 |
| Days before a branch is stale | Older branches are listed as "Stale" | 90 |
| Pipelines per bulk run | Max pipelines one bulk run may start | 50 |
| Dispatches sent at the same time | Parallel requests to GitHub | 3 |
| Live status: minimum seconds between checks | Polling floor | 10 |
| Live status: minutes runs are followed | Then "Status unknown" | 30 |
| Dashboard: repositories read | The most recently pushed first (1–500) | 50 |
| Dashboard: runs read per repository and period | GitHub lists 1,000 at most (100–1000) | 500 |
| Dashboard: commits read per repository | All branches, both periods (100–10000) | 2000 |
| Dashboard: seconds a result is reused | 0 reads GitHub at every visit (0–3600) | 300 |
| Dashboard: seconds allowed to read GitHub | Repositories not read in time are named (10–200) | 60 |
Changing the GitHub address or client ID signs everybody out. If a wrong value locks everybody out,
run bun run settings:setup-code --reset: it clears the connection (signing everybody out), puts the
server back in setup mode and prints a new setup code.
403 to other users. Changing the
GitHub connection, a role, someone's authenticator app or deleting someone needs a current 6-digit
code, and the history records who did it.bun run settings:setup-code on the server. Once set up, the setup
page is closed for good (--reset reopens it, from the server only).Origin, and cookies are SameSite=Lax.frame-ancestors 'none', so the "Run" button
cannot be embedded in another site (clickjacking).Warning: a
workflow_dispatchrun usually deploys something. Test with a sandbox repository that has a no-op workflow, never with production workflows.
Check gate. All of these must pass before a change is considered done:
bun install --frozen-lockfile && bun run typecheck && bun test && bun run build && bun audit --audit-level=high
| Script | What it does |
|---|---|
bun run dev | Dev server with hot reload (server/dev.ts) |
bun run build | Builds the web app into dist/app (scripts/buildApp.ts) |
bun run start | Production server (server/index.ts) |
bun run desktop | Opens EasyActions in its own window (scripts/desktop.ts) |
bun run db:migrate | Creates or upgrades the SQLite database (server stopped) |
bun run db:status | Tells whether the database is ready |
bun run db:rollback | Removes the last migration (--yes when it deletes data) |
bun run settings:setup-code | Prints a one-time code for the setup page (30 minutes); --reset first clears the GitHub connection |
bun run users:promote <login> | Makes someone an admin (they must have signed in once) |
bun run users:demote <login> | Removes the admin role (never from the last admin) |
bun run users:reset-two-factor <login> | Removes someone's authenticator app (lost phone); --all for everybody |
bun run typecheck | Strict TypeScript for the server and the app |
bun test | Unit and integration tests |
Project conventions:
app/ never imports from server/.{ detail: { code, message } }.server/config/logger.ts.The tests check the design rules. The full rules are in docs/ARCHITECTURE.md.
The planned milestones (M0–M8) are delivered. How the dashboard counts is described in docs/DASHBOARD.md.
The logos, app icons and favicons are in EasyActions-Logo-Pack-v2/. Its
README covers colours, typeface, sizes and clear-space rules.
EasyActions is released under the Apache License 2.0 (SPDX-License-Identifier: Apache-2.0). The
full text is in LICENSE, and every package ships a copy. You may use, modify and
redistribute it, including commercially, as long as you keep the copyright and licence notices and
state the changes you made to any file you redistribute.
The licence covers the code only. The EasyActions name and the logos, app icons and favicons in
EasyActions-Logo-Pack-v2/ are not granted with it: a fork may use the
code, not the brand.
3 commits
3 commits
TypeScript
86.9%
CSS
10.0%
Shell
2.7%