{"slug": "easyactions-run-github-actions-across-an-organization", "title": "EasyActions – run GitHub Actions across an organization", "summary": "EasyActions launched as a web dashboard that lets users select a GitHub organization and trigger workflow_dispatch runs across many repositories at once, with live status tracking via adaptive polling. The tool, codenamed Pipliner, authenticates through a GitHub App with a PKCE flow, keeps GitHub tokens encrypted server-side for up to 30 days, and requires a daily 6-digit authenticator code plus ten one-time recovery codes. A guard locks the Run button on default-branch targets until the user types the organization's name, and a statistics dashboard reports success rate, average duration and top failing workflows over 7, 30 or 90 days.", "body_md": "**Run GitHub Actions across a whole organization — safely, in bulk, and live.**\n\n[Features](#features) ·\n  [How it works](#how-it-works) ·\n  [Getting started](#getting-started) ·\n  [Configuration](#configuration) ·\n  [Security](#security) ·\n  [Development](#development) ·\n  [Architecture](https://github.com/Mask-AI-FR/EasyAction/blob/main/docs/ARCHITECTURE.md)\n\n**EasyActions** is a web dashboard for GitHub Actions. You pick a GitHub organization and see every\nrepository with its branches and workflows, plus the live status of each one. You can start\n`workflow_dispatch` runs on many repositories at once, then watch them finish, all from one screen.\n\nEasyActions is the product name. **Pipliner** is the codename: you will still see it in the code, the\ncookies, the `/health` answer and the logs.\n\n- **Sign in with GitHub, stay signed in.** It uses a GitHub App with a PKCE flow. You stay signed in\nfor up to 30 days (configurable): your GitHub tokens are kept encrypted on the server and renewed\nautomatically. The browser never sees them.\n- **Daily code.** Once a day, each browser asks for the 6-digit code of an authenticator app (Google\nAuthenticator, Authy, 2FAS, 1Password…), set up with a QR code at your first sign-in. Ten one-time\nrecovery codes cover a lost phone.\n- **Your account.** See every browser where you are signed in, sign the others out, make new recovery\ncodes, change your authenticator app, download your data or delete it. Sign-ins and security actions\nare kept in a history.\n- **Settings in the website, for admins.** Enter the GitHub connection (addresses, GitHub App client\nID and secret) on a setup page the first time, then change it and the limits without editing files.\nManage users: roles, authenticator reset, sign-out, deletion. Read the security history. Sensitive\nchanges ask for a current 6-digit code.\n- **Statistics dashboard.** Each organization opens on its dashboard: people who committed on any\nbranch, successful and failed runs, success rate, average duration and branches, compared with the\nprevious period; runs over time, rankings by repository, top failing workflows and recent failures.\n7, 30 or 90 days. Every chart has a table view, and the page names anything it could not read.\n- **Sidebar, organization already chosen.** The navigation sits on the left (a drawer on a phone): your\norganization with its dashboard and repositories, the admin pages, and your account card with its\nsign-out button. The organization you opened last in this browser is selected for you (otherwise the\nfirst of your list); switch to another from the card at the top. While anything loads, the\nEasyActions gear turns.\n- **Organization overview.** See every repository the app can access. Search and filter them by\nvisibility, language and archived state, and sort them. The filters are saved in the address, so\nyou can share a link to a filtered view.\n- **Branches and workflows per repository.** Branches are listed in this order: the default branch,\nthen active branches, then stale ones. Each workflow shows the status of its latest run on the\nbranch you chose.\n- **Bulk runs.** Select repositories and workflows, look over the full list of targets in a\nconfirmation dialog, and start them all with one request.\n- **Guard for production branches.** If any target is on a repository's default branch, the Run button\nstays locked until you type the organization's name.\n- **Live tracking.** Started runs are checked with an adaptive polling interval. If their state can't be\nconfirmed, they show \"Status unknown\" and a link to GitHub. The app never guesses a result.\n- **Desktop window.**`bun run desktop` opens EasyActions in its own window. You can also install it\nfrom Brave, Chrome or Edge.\n- **GitHub Enterprise Server** and GHE.com are supported through two settings.\n\nOne Bun process serves both the web app and its API from the same origin.\n\n```\nBrowser ── same origin ──▶ Bun + Hono\n                            ├─ /health, /auth/*, /api/*  → server/  (GitHub adapters: OAuth, repos, Actions)\n                            └─ any other path            → app/     (TiniJS + Lit web app)\n```\n\n| Folder | Contents | \n|---|---|\n| `app/` | The web app: TiniJS on Lit, Tailwind CSS v4, MASKAI design tokens with an EasyActions brand layer | \n| `server/` | The Hono server: auth, sessions, API routes, GitHub adapters, SQLite database (schema, repositories), config, logging | \n| `domain/` | Framework-free logic and types shared by both sides (API contract, dispatch plan, polling policy, dashboard statistics) | \n| `scripts/` | Production build, database, settings and user commands, and the desktop launcher | \n| `tests/` | Unit and integration tests. GitHub is replaced by a real local fake HTTP server | \n\nEvery design choice, the full list of routes, and the behaviour on each kind of failure are described\nin **[docs/ARCHITECTURE.md](https://github.com/Mask-AI-FR/EasyAction/blob/main/docs/ARCHITECTURE.md)**; what is stored and how it is protected, in\n**[docs/SECURITY.md](https://github.com/Mask-AI-FR/EasyAction/blob/main/docs/SECURITY.md)**.\n\n- [Bun](https://bun.sh) 1.3.11\n- A GitHub organization where you can install a GitHub App\n- Optional: a Chromium-based browser (Brave, Chromium, Chrome, Edge) for the desktop window\n\nOn GitHub, go to **Settings › Developer settings › GitHub Apps › New GitHub App** (or do the same in\nyour organization's settings), then set:\n\n| Setting | Value | \n|---|---|\n| Homepage URL | `http://127.0.0.1:8094` | \n| Callback URL | `http://127.0.0.1:8094/auth/callback` | \n| Expire user authorization tokens | **Checked** (required: without it, sign-in is refused) | \n| Request user authorization (OAuth) during installation | Unchecked | \n| Webhook → Active | Unchecked | \n| Repository permissions | **Actions:** Read and write ·**Contents:** Read-only ·**Metadata:** Read-only | \n\nCreate the app. Note its **Client ID**, generate a **client secret**, and install the app on your\norganization.\n\n```\nbun install --frozen-lockfile\ncp .env.example .env\nopenssl rand -base64 32\nopenssl rand -base64 32\n```\n\nOpen `.env` and replace every `<TO_PROVIDE>`:\n\n- `SESSION_SECRET` : the first output of the`openssl` command\n- `DATA_ENCRYPTION_KEY` : the second output (a different value; keep it safe — losing it signs\neverybody out)\n\n`.env` is git-ignored. Never commit it. The GitHub App's client ID and secret never go in it: you\ntype them on the website (step 4).\n\nThen create the database (run it again after every upgrade, with the server stopped):\n\n```\nbun run db:migrate\n```\n\nDevelopment mode, with hot reload, on [http://127.0.0.1:8094](http://127.0.0.1:8094):\n\n```\nbun run dev\n```\n\nProduction mode (stop the dev server first, because both use the same port):\n\n```\nbun run build\nbun run start\n```\n\nDesktop window. This reuses a running server, or builds and starts one in the background:\n\n```\nbun run desktop\n```\n\nThe first time, the server starts in **setup mode**: open [http://127.0.0.1:8094](http://127.0.0.1:8094) and it shows the\nsetup page. In the repository folder, get a one-time setup code (it works for 30 minutes):\n\n```\nbun run settings:setup-code\n```\n\nOn the setup page, type that code, the GitHub address (the API address fills itself in), your app's\n**Client ID** and **client secret**, then **Test connection** and **Save and finish**. The secret is\nstored encrypted in the database and never shown again.\n\nSign in with GitHub (and set up your authenticator app), then run this in the repository folder, with\nyour GitHub login instead of `your-github-login`:\n\n```\nbun run users:promote your-github-login\n```\n\nReload the page: an **Administration** section (Settings, Users, History) appears in the sidebar.\nOther admins can then be made from the Users page.\n\nConfiguration has two places:\n\n- **`.env`, for the server and security.** Every variable in the table below is**required** . If one\nis missing, still set to`<TO_PROVIDE>` , or invalid, the server does not start and lists the names\nof the problem variables (never their values).`.env.example` documents each one.\n- **The website, for the website settings** (GitHub connection and limits, second table). They are\nstored in the database: the GitHub connection is entered once on the setup page, then admins change\neverything on the Settings page. Limits start at the defaults below.\n\n| Variable | Purpose | Default in template | \n|---|---|---|\n| `HOST` | Interface to bind (never `0.0.0.0` on a host) | `127.0.0.1` | \n| `PORT` | Port for the app and its API | `8094` | \n| `APP_ORIGIN` | Exact public origin: builds the callback URL and is checked on every POST | `http://127.0.0.1:8094` | \n| `SESSION_SECRET` | Encrypts the short sign-in cookie, at least 32 characters | — | \n| `DATA_ENCRYPTION_KEY` | Encrypts the GitHub tokens in the database, at least 32 characters | — | \n| `DATABASE_PATH` | SQLite file, created by `bun run db:migrate` | `./data/pipliner.sqlite` | \n| `HTTP_IDLE_TIMEOUT_SECONDS` | Seconds a connection may stay silent (30–255) | `240` | \n| `SESSION_MAX_DAYS` | Days a browser stays signed in (1–180) | `30` | \n| `SESSIONS_PER_USER_MAX` | Browsers one person can be signed in on (1–10) | `5` | \n| `AUDIT_RETENTION_DAYS` | Days the sign-in and security history is kept (30–3650) | `365` | \n| `TWO_FACTOR_EVERY_HOURS` | Hours an accepted 6-digit code stays valid for a browser (1–168) | `24` | \n| `TWO_FACTOR_MAX_ATTEMPTS` | Wrong codes before a lock (3–20) | `5` | \n| `TWO_FACTOR_LOCK_MINUTES` | First lock, doubled at each next one, 24 h at most (1–1440) | `15` | \n\nWebsite settings (setup page, then Settings page):\n\n| Setting | Purpose | Default | \n|---|---|---|\n| GitHub web and API addresses | github.com, GHE.com or your GitHub Enterprise Server; the API address must match the web address | — | \n| GitHub App client ID and secret | The app EasyActions signs in with; the secret is stored encrypted and never shown | — | \n| GitHub timeout | Timeout for every GitHub call (1000–60000 ms) | `10000` | \n| Repositories read per organization | Above it, the list says it is truncated | `1000` | \n| Branches read per repository | Max branches listed | `300` | \n| Days before a branch is stale | Older branches are listed as \"Stale\" | `90` | \n| Pipelines per bulk run | Max pipelines one bulk run may start | `50` | \n| Dispatches sent at the same time | Parallel requests to GitHub | `3` | \n| Live status: minimum seconds between checks | Polling floor | `10` | \n| Live status: minutes runs are followed | Then \"Status unknown\" | `30` | \n| Dashboard: repositories read | The most recently pushed first (1–500) | `50` | \n| Dashboard: runs read per repository and period | GitHub lists 1,000 at most (100–1000) | `500` | \n| Dashboard: commits read per repository | All branches, both periods (100–10000) | `2000` | \n| Dashboard: seconds a result is reused | `0` reads GitHub at every visit (0–3600) | `300` | \n| Dashboard: seconds allowed to read GitHub | Repositories not read in time are named (10–200) | `60` | \n\nChanging the GitHub address or client ID signs everybody out. If a wrong value locks everybody out,\nrun `bun run settings:setup-code --reset`: it clears the connection (signing everybody out), puts the\nserver back in setup mode and prints a new setup code.\n\n- **Sessions in a local database.** The cookie holds only a random id; the session and its GitHub\ntokens live in the SQLite file, the tokens encrypted (AES-256-GCM). Signing out closes the session\nand revokes the token; the Account page signs out other browsers, exports or deletes your data.\nDetails:[docs/SECURITY.md](https://github.com/Mask-AI-FR/EasyAction/blob/main/docs/SECURITY.md) .\n- **Admins are checked by the server.** Every admin route answers`403` to other users. Changing the\nGitHub connection, a role, someone's authenticator app or deleting someone needs a current 6-digit\ncode, and the history records who did it.\n- **Setup needs the server.** Until a GitHub connection is saved, only the setup page answers, and only\nwith a one-time code printed by`bun run settings:setup-code` on the server. Once set up, the setup\npage is closed for good (`--reset` reopens it, from the server only).\n- **CSRF protection.** Every POST must carry the app's exact`Origin` , and cookies are`SameSite=Lax` .\n- **Strict headers.** Every response sets a CSP and`frame-ancestors 'none'` , so the \"Run\" button\ncannot be embedded in another site (clickjacking).\n- **No duplicate deploys.** A dispatch is**never retried** . If a dispatch times out or GitHub answers\nwith a server error, the result is reported as \"unknown\" and you are asked to check GitHub. A retry\ncould start the same deployment twice.\n- **Privacy-safe logs.** Logs are JSON lines built from a fixed list of allowed fields. They never\ncontain tokens, request bodies, headers or personal data.\n\n**Warning:** a `workflow_dispatch` run usually deploys something. Test with a sandbox repository\nthat has a no-op workflow, never with production workflows.\n\nCheck gate. All of these must pass before a change is considered done:\n\n```\nbun install --frozen-lockfile && bun run typecheck && bun test && bun run build && bun audit --audit-level=high\n```\n\n| Script | What it does | \n|---|---|\n| `bun run dev` | Dev server with hot reload ( `server/dev.ts` ) | \n| `bun run build` | Builds the web app into `dist/app` (`scripts/buildApp.ts` ) | \n| `bun run start` | Production server ( `server/index.ts` ) | \n| `bun run desktop` | Opens EasyActions in its own window ( `scripts/desktop.ts` ) | \n| `bun run db:migrate` | Creates or upgrades the SQLite database (server stopped) | \n| `bun run db:status` | Tells whether the database is ready | \n| `bun run db:rollback` | Removes the last migration ( `--yes` when it deletes data) | \n| `bun run settings:setup-code` | Prints a one-time code for the setup page (30 minutes); `--reset` first clears the GitHub connection | \n| `bun run users:promote <login>` | Makes someone an admin (they must have signed in once) | \n| `bun run users:demote <login>` | Removes the admin role (never from the last admin) | \n| `bun run users:reset-two-factor <login>` | Removes someone's authenticator app (lost phone); `--all` for everybody | \n| `bun run typecheck` | Strict TypeScript for the server and the app | \n| `bun test` | Unit and integration tests | \n\nProject conventions:\n\n- Every dependency is pinned to an exact version.\n- `app/` never imports from`server/` .\n- Every error body has the shape `{ detail: { code, message } }` .\n- Logs go only through `server/config/logger.ts` .\n- Components use shadow DOM and only MASKAI semantic classes.\n\nThe tests check the design rules. The full rules are in [docs/ARCHITECTURE.md](https://github.com/Mask-AI-FR/EasyAction/blob/main/docs/ARCHITECTURE.md).\n\nThe planned milestones (M0–M8) are delivered. How the dashboard counts is described in\n**[docs/DASHBOARD.md](https://github.com/Mask-AI-FR/EasyAction/blob/main/docs/DASHBOARD.md)**.\n\nThe logos, app icons and favicons are in [`EasyActions-Logo-Pack-v2/`](https://github.com/Mask-AI-FR/EasyAction/blob/main/EasyActions-Logo-Pack-v2). Its\n[README](https://github.com/Mask-AI-FR/EasyAction/blob/main/EasyActions-Logo-Pack-v2/README.txt) covers colours, typeface, sizes and clear-space rules.\n\nEasyActions is released under the **Apache License 2.0** (`SPDX-License-Identifier: Apache-2.0`). The\nfull text is in [LICENSE](https://github.com/Mask-AI-FR/EasyAction/blob/main/LICENSE), and every package ships a copy. You may use, modify and\nredistribute it, including commercially, as long as you keep the copyright and licence notices and\nstate the changes you made to any file you redistribute.\n\nThe licence covers the **code only**. The EasyActions name and the logos, app icons and favicons in\n[`EasyActions-Logo-Pack-v2/`](https://github.com/Mask-AI-FR/EasyAction/blob/main/EasyActions-Logo-Pack-v2) are not granted with it: a fork may use the\ncode, not the brand.", "url": "https://wpnews.pro/news/easyactions-run-github-actions-across-an-organization", "canonical_source": "https://github.com/Mask-AI-FR/EasyAction", "published_at": "2026-09-27 13:32:27+00:00", "updated_at": "2026-09-27 14:01:43.617012+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["EasyActions", "Pipliner", "GitHub", "GitHub Actions", "GitHub Enterprise Server", "GHE.com", "Bun", "Hono"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/easyactions-run-github-actions-across-an-organization", "markdown": "https://wpnews.pro/news/easyactions-run-github-actions-across-an-organization.md", "text": "https://wpnews.pro/news/easyactions-run-github-actions-across-an-organization.txt", "jsonld": "https://wpnews.pro/news/easyactions-run-github-actions-across-an-organization.jsonld"}}