# PR Previews (PP)

PR Previews is a self-hosted preview environment service for Gitea pull requests.

When someone opens or updates a pull request, PP starts an EC2 instance in your AWS account, checks out the PR branch, builds the project, runs it, and posts the live preview URL back to the PR. Reviewers can open the running app without cloning the branch locally.

PP is built for Gitea only. It does not support GitHub or GitLab.

## Get Started

The fastest way to try PP is with Docker Compose:

```bash
cp example.env .env
```

Edit `.env` and set at least:

```env
SESSION_SECRET=<long random string>
PP_BASE_URL=https://your-public-pp-url.example.com
ENCRYPTION_KEY=<64 hex chars>
```

Generate `ENCRYPTION_KEY` with:

```bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```

Then start PP:

```bash
docker compose up -d
```

Open `http://localhost:5000`, create the first user, and follow the setup wizard to connect Gitea, AWS, and your first repo.

## What It Does

- Watches Gitea pull requests through webhooks.
- Creates one preview environment per PR.
- Reuses the same EC2 instance when new commits are pushed.
- Streams deploy logs into the web UI.
- Edits a single PR comment with the current preview status and URL.
- Stops old previews manually, on PR close, or after inactivity.
- Lets users configure preinstalled runtimes/tools, build commands, run commands, env vars, ports, instance type, and Docker Compose usage per repo.

## How It Works

1. A Gitea webhook sends a pull request event to PP.
2. PP verifies the webhook signature and queues a deploy job.
3. The worker provisions or reuses an AWS EC2 instance.
4. PP connects over SSH, clones the PR branch, installs dependencies, builds, and starts the app.
5. PP posts or updates a Gitea PR comment with the preview URL.

Preview URLs look like:

```text
http://<ec2-public-ip>:<configured-port>
```

## Requirements

- A running Gitea instance.
- A Gitea personal access token for the account that should post preview comments.
- An AWS account with EC2 permissions.
- Docker and Docker Compose to run PP itself.
- A public `PP_BASE_URL` that Gitea can reach for webhooks.

For local-only testing, `PP_BASE_URL` still needs to be reachable by Gitea. Use a tunnel or a real public URL if your Gitea instance is not running on the same machine.

## Quick Start

1. Copy the environment template:

   ```bash
   cp example.env .env
   ```

2. Edit `.env`:

   ```env
   SESSION_SECRET=<long random string>
   PP_BASE_URL=https://pp.example.com
   ENCRYPTION_KEY=<64 hex chars>
   ```

   Generate a valid encryption key with:

   ```bash
   node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
   ```

3. Start PP:

   ```bash
   docker compose up -d
   ```

4. Open the UI:

   ```text
   http://localhost:5000
   ```

5. Create the first user. The first user becomes the founder admin.

6. Complete the setup wizard:

   - Add your Gitea instance URL and PAT.
   - Add your AWS credentials and region.
   - Enable a repo.
   - Configure how that repo should build and run.

## Environment Variables

| Variable | Required | Description |
|---|---:|---|
| `DATABASE_URL` | Yes | PostgreSQL connection string. In Docker Compose this is set automatically for the bundled database. |
| `SESSION_SECRET` | Yes | Secret used for session cookies. Use a long random value. |
| `PP_BASE_URL` | Yes | Public URL of this PP instance, with no trailing slash. Used for webhooks, CORS, and PR comment links. |
| `ENCRYPTION_KEY` | Yes | Exactly 64 hex characters. Encrypts Gitea PATs, AWS credentials, and SSH private keys at rest. |
| `PORT` | No | Backend port. Defaults to `5000`. |
| `LOG_LEVEL` | No | `trace`, `debug`, `info`, `warn`, or `error`. Defaults to `info`. |
| `NODE_ENV` | No | `development`, `production`, or `test`. Defaults to `development`. |

Important: changing `ENCRYPTION_KEY` after setup makes existing encrypted credentials unreadable.

## Gitea Setup

PP can register repo webhooks automatically when you enable a repo in the UI.

The Gitea PAT should belong to the account that should post PR comments. It needs permissions to:

- Read repositories.
- Read pull request and issue metadata.
- Write issue comments.
- Create, update, and delete repo webhooks.

PP registers webhooks named `PR Previews` with these events:

- `pull_request`
- `pull_request_sync`
- `issue_comment`

## AWS Setup

PP launches preview instances in your AWS account. Each preview gets its own EC2 instance, SSH key pair, and security group.

Required IAM policy:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:RunInstances",
        "ec2:TerminateInstances",
        "ec2:DescribeInstances",
        "ec2:CreateSecurityGroup",
        "ec2:DeleteSecurityGroup",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:DescribeSecurityGroups",
        "ec2:CreateKeyPair",
        "ec2:DeleteKeyPair",
        "ec2:CreateTags",
        "sts:GetCallerIdentity"
      ],
      "Resource": "*"
    }
  ]
}
```

Every EC2 instance created by PP is tagged with `pp:managed=true` plus user, repo, PR number, and preview ID metadata.

## Repo Configuration

Each enabled repo has its own preview configuration:

- EC2 instance type.
- Inactivity timeout.
- Public app port.
- Environment variables.
- Apt packages.
- Setup commands.
- Build commands.
- Post-build commands.
- Run command.
- Preinstall options for Docker + Compose, Node.js/version, Python, Go, Lua, build tools, and custom apt packages.
- Docker Compose mode and compose file path.
- Disabled `/pp` commands.
- Deny list of Gitea usernames to ignore.

Docker Compose mode runs:

```bash
docker compose -f <compose-file> up -d --build --force-recreate
```

Non-compose mode runs your configured commands and starts the app with your configured run command.

## PR Commands

PP also listens for commands in PR comments. The first line must start with `/pp `.

| Command | Action |
|---|---|
| `/pp help` | Post the available command list. |
| `/pp rebuild` | Rebuild and restart the preview. If the preview is stopped, a new instance is provisioned. |
| `/pp stop` | Stop the preview and terminate its EC2 instance. |
| `/pp start` | Start a stopped preview, or unignore and deploy an ignored preview. |
| `/pp logs` | Post the last deploy log lines as a PR comment. |
| `/pp ignore` | Ignore future events for this PR until `/pp start` is used. |

Commands can be disabled per repo, except `/pp help`.

## Operating Notes

- Webhooks return quickly. Deploy work is handled asynchronously by the job worker.
- A newer deploy for the same PR cancels the currently running deploy.
- Stopped previews release their EC2 instance, SSH key pair, and security group.
- Stopped and failed preview records are cleaned up after the configured retention period.
- On startup, PP looks for orphaned managed EC2 instances and terminates ones that no longer match an active preview.
- Preview logs are stored in PostgreSQL and capped by the admin log size setting.
- Live app logs are available from the preview detail page while a preview is running.

## Security Model

- Gitea PATs, AWS credentials, and SSH private keys are encrypted at rest.
- Webhook payloads are verified with HMAC-SHA256 before processing.
- API responses do not return raw stored secrets.
- EC2 SSH keys are generated per preview launch and deleted on stop.
- Users can manage only their own repos and previews.
- Admins can manage users, global settings, and all previews.

Keep `ENCRYPTION_KEY` backed up. Losing it means PP cannot decrypt saved credentials.

## Development

This is a pnpm workspace monorepo:

```text
backend/   Node.js API and workers
frontend/  React, Vite, and Tailwind UI
prisma/    Prisma schema and migrations
```

Use pnpm, not npm or yarn.

Start only the database:

```bash
docker compose up -d pp-db
```

Apply migrations:

```bash
cd backend
pnpm migrate
```

Run the backend on port 5000:

```bash
cd backend
pnpm install
pnpm dev
```

Run the frontend dev server on port 3000:

```bash
cd frontend
pnpm install
pnpm dev
```

Build backend:

```bash
cd backend
pnpm build
```

Build frontend:

```bash
cd frontend
pnpm build
```

Prisma schema lives at `prisma/schema.prisma` in the repo root. The backend package scripts already pass the correct schema path.

## Production Deployment

The included Compose stack runs:

- `pp-db`: PostgreSQL.
- `pp-backend`: the backend API, workers, migrations, and built frontend.

Start or update the stack with:

```bash
docker compose up -d
```

The container runs Prisma migrations on startup before starting the backend.

## Project References

- Product behavior: `SPEC.md`
- Environment template: `example.env`
- Agent and contributor implementation notes: `AGENTS.md`
- Contributing guide: `CONTRIBUTING.md`
- Security policy: `SECURITY.md`
- License: Apache License 2.0 (`LICENSE`)
