Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Build

on:
push:
branches: [main]
paths:
- "**/*.go"
- "**/Dockerfile"
- "cmd/**"
- "internal/**"
- "ui/**"
- "go.mod"
- "go.sum"
workflow_dispatch:

env:
REGISTRY: ghcr.io

jobs:
prepare:
runs-on: ubuntu-24.04
outputs:
repo_owner: ${{ steps.meta.outputs.repo_owner }}
short_sha: ${{ steps.meta.outputs.short_sha }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Extract metadata
id: meta
run: |
echo "repo_owner=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"

build-api:
runs-on: ubuntu-24.04
needs: prepare
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6

- uses: docker/setup-buildx-action@v4
with:
buildkitd-config-inline: |
[worker.oci]
compression-type = "zstd"
compression-level = 3

- name: Login to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Build and push API image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/${{ needs.prepare.outputs.repo_owner }}/appa-api:${{ needs.prepare.outputs.short_sha }}
ghcr.io/${{ needs.prepare.outputs.repo_owner }}/appa-api:main
cache-from: type=gha
cache-to: type=gha,mode=max
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}

build-ui:
runs-on: ubuntu-24.04
needs: prepare
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6

- uses: docker/setup-buildx-action@v4
with:
buildkitd-config-inline: |
[worker.oci]
compression-type = "zstd"
compression-level = 3

- name: Login to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Build and push UI image
uses: docker/build-push-action@v6
with:
context: ./ui
push: true
tags: |
ghcr.io/${{ needs.prepare.outputs.repo_owner }}/appa-ui:${{ needs.prepare.outputs.short_sha }}
ghcr.io/${{ needs.prepare.outputs.repo_owner }}/appa-ui:main
cache-from: type=gha
cache-to: type=gha,mode=max
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
20 changes: 15 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ on:

jobs:
build-api:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
with:
buildkitd-config-inline: |
[worker.oci]
compression-type = "zstd"
compression-level = 3

- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
Expand All @@ -38,13 +43,18 @@ jobs:
org.opencontainers.image.revision=${{ github.sha }}

build-ui:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v3
- uses: docker/setup-buildx-action@v4
with:
buildkitd-config-inline: |
[worker.oci]
compression-type = "zstd"
compression-level = 3

- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
Expand All @@ -69,11 +79,11 @@ jobs:
org.opencontainers.image.revision=${{ github.sha }}

release:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0

Expand Down
59 changes: 39 additions & 20 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Open [http://localhost](http://localhost) in your browser.
| `ui` | Vite dev server |

This Compose flow is the local development path. Production operations use the
Appa CLI: the operator installs the CLI locally, creates an instance profile,
Appa CLI: the operator installs the CLI locally, creates a server profile,
and uses Ansible-backed commands to provision Appa Server on a remote VPS.

## Product Surfaces
Expand All @@ -35,9 +35,9 @@ Use these names consistently when discussing or documenting Appa:
|---|---|
| Appa | The whole product. |
| Appa CLI | Local operator/developer command-line tool, binary `appa`. |
| Appa Server | Remote API, dashboard, and deployment runtime. |
| Appa Instance | One remote Appa Server installation managed by the CLI. |
| Appa Server | Remote API, dashboard, and deployment runtime on a VPS. |
| Appa Stack | Server-side services: API, UI, PostgreSQL, BuildKit, Caddy. |
| Server config | Local CLI configuration for one server (`~/.appa/servers/<name>/config.toml`). |

The install script (`scripts/install.sh`) installs the CLI binary. It should not be used
as a script that is run directly on the VPS to install the server stack.
Expand All @@ -46,8 +46,8 @@ The first-time production flow is:

```bash
curl -fsSL https://raw.githubusercontent.com/theolujay/appa/main/scripts/install.sh | sh
appa instance init personal
appa instance set-host -i ~/.ssh/id_ed25519 personal root@203.0.113.10
appa server init personal
appa server set-host -i ~/.ssh/id_ed25519 personal root@203.0.113.10
appa preflight personal
appa setup personal
```
Expand Down Expand Up @@ -85,8 +85,8 @@ make build/cli # Build the Appa CLI binary
### Run CLI

```bash
make run/cli ARGS="instance init my-server"
make run/cli ARGS="instance list"
make run/cli ARGS="server init my-server"
make run/cli ARGS="server ls"
./bin/appa --help # Built binary
go run ./cmd/cli --help # Direct without build
```
Expand Down Expand Up @@ -116,8 +116,8 @@ go run ./cmd/cli --help # Direct without build
├── internal/
│ ├── cli/ # CLI implementation
│ │ ├── app.go # Root command
│ │ ├── commands/ # instance, preflight, setup, apply, status, logs, restart, upgrade
│ │ ├── config/ # TOML instance profiles
│ │ ├── commands/ # server, project, deploy, preflight, setup, apply, status, logs, restart, upgrade
│ │ ├── config/ # TOML server and project profiles
│ │ ├── ansible/ # Inventory generation + ansible-playbook runner
│ │ ├── ssh/ # SSH connectivity and command execution
│ │ └── output/ # Tables, checkmarks, progress output
Expand Down Expand Up @@ -159,11 +159,13 @@ cmd/
internal/cli/
├── app.go # Root command construction
├── commands/
│ ├── instance.go # appa instance init|set-host|list
│ ├── server.go # appa server init|edit|set-host|ls
│ ├── project.go # appa project init|edit|logs|stop|restart|env
│ ├── deploy.go # appa deploy <project-name>
│ ├── preflight.go # appa preflight <name>
│ ├── setup.go # appa setup <name> and appa apply <name>
│ └── operations.go # appa status|logs|restart|upgrade <name>
├── config/ # TOML instance profiles (~/.appa/instances/<name>/config.toml)
├── config/ # TOML server and project profiles (~/.appa/servers/<name>, ~/.appa/projects/<name>)
├── ansible/ # Inventory generation and ansible-playbook runner
├── ssh/ # SSH connectivity, command execution, identity file support
└── output/ # Tables, checkmarks, section headers
Expand Down Expand Up @@ -218,16 +220,18 @@ All routes are prefixed with `/v1/` and proxied through Caddy.

```bash
appa --version # Show build version (git tag or "(devel)")
appa instance init <name> # Create a local instance profile
appa instance edit <name> # Open profile in $EDITOR, validates on save
appa instance set-host <name> <target> # Set SSH target, e.g. root@203.0.113.10
-i, --identity-file <path> # SSH private key for this instance
appa instance list # List known Appa instances
appa server init <name> # Create a local server profile
appa server edit <name> # Open profile in $EDITOR, validates on save
appa server set-host <name> <target> # Set SSH target, e.g. root@203.0.113.10
-i, --identity-file <path> # SSH private key for this server
--port <port> # API port (e.g. 8080 for Vagrant forwarded port)
appa server ls # List known Appa servers
appa preflight <name> # Validate SSH, OS, ports, DNS, and inputs
--no-tty # Non-interactive mode (plain text output)
appa setup <name> # First-time remote Appa Server setup
--force # Skip preflight checks
--tags, --skip-tags # Pass Ansible tag filters
appa apply <name> # Re-apply instance config idempotently
appa apply <name> # Re-apply server config idempotently
--tags, --skip-tags # Pass Ansible tag filters
appa status <name> # Show remote Appa Stack health
appa logs <name> # Tail Appa Stack logs
Expand All @@ -237,9 +241,21 @@ appa restart <name> # Restart the Appa Stack
-s, --service <name> # Restart only one service
appa upgrade <name> # Upgrade remote Appa Stack images
--version <tag> # Pin to a specific version tag
appa project init <source> # Create a project profile
-t, --target <name> # Target server name
-n, --name <name> # Project name (default: source dir name)
appa project edit <name> # Open project config in $EDITOR
appa project logs <name> # Stream deployment logs (WebSocket TUI)
appa project stop <name> # Stop running or cancel pending deployment
appa project restart <name> # Stop + trigger new deployment
appa project env set <name> <key=val> # Set a project environment variable
appa project env get <name> # List project environment variables
appa project env unset <name> <key> # Remove a project environment variable
appa deploy <name> # Deploy an initialized project
--quiet # Suppress rsync progress output
```

**Profile config** (`~/.appa/instances/<name>/config.toml`, 0600 perms):
**Server config** (`~/.appa/servers/<name>/config.toml`, 0600 perms):
```toml
name = "personal"
ssh_host = ""
Expand All @@ -251,12 +267,15 @@ smtp_host = ""
smtp_port = 587
smtp_username = ""
smtp_password = ""
api_base_url = ""
api_port = 0
```

**Target format** for `set-host`: `user@host` or `user@host:port` (e.g. `root@203.0.113.10` or `root@203.0.113.10:2222`).

Longer term, project-level commands (`deploy`, `logs`, `env`, rollbacks) will
use the Appa Server API instead of SSH/Ansible.
Project-level commands (`deploy`, `logs`, `stop`, `restart`, `env`) use the
Appa Server API for all server-side operations. Only `server init`, `setup`,
and `apply` still use SSH and Ansible for host-level provisioning.

## Coding Conventions

Expand Down
66 changes: 51 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,39 @@
![GitHub License](https://img.shields.io/github/license/theolujay/appa)


Turn any VPS into your own zero-config deployment platform, effortlessly 🦬
Turn any VPS into your own zero-config deployment platform, effortlessly!

Just **connect** your domain and **push** your code. **Deploy** apps instantly without Dockerfiles or playing sysadmin on hard mode. Even better: **manage** your **entire fleet** of VPSs from **one terminal**, anywhere!

A single person could wear both hats: operator + developer.

Just **connect** your domain and **push** your code. **Deploy** apps instantly without Dockerfiles or playing sysadmin on hard mode. Even better: **manage** your **entire fleet** of VPS instances from **one terminal**, anywhere!
```
Operator Machine Remote Server (VPS)
└─ Appa CLI ├─ Caddy Gateway
│ ├─ Appa API + Dashboard
SSH + Ansible + rsync ├─ BuildKit + Railpack
└──────────────────────────┼─ PostgreSQL
└─ Your containers
╭─────── Operator ──────╮ ╭────── Developer ──────╮
│ │ │ │
│ ┌──────────┐ │ │ · git push │
│ │ Appa CLI │ │ |· appa deploy <project>│
│ └────┬─────┘ │ │ │ │
│ │ │ │ │ │
│ SSH · rsync │ │ API │
╰──────────│────────────╯ ╰───────────│───────────╯
│ │
manages deploys
▼ ▼
╭── Fleet of Servers (VPS) ──┬──────────────────────────────────╮
│ │ │
│ ┌─ nyc-prod ──────────┐ │ ┌─ lon-staging ─────────────┐ │
│ │ Blog API · Admin UI│ │ │ Client Dashboard (stg) │ │
│ └─────────────────────┘ │ └───────────────────────────┘ │
│ ◄───────┼───────► │
│ ┌─ fra-gateway ───────┐ │ ┌─ sfo-preview ─────────────┐ │
│ │ Auth · Webhook │ │ │ PR previews · E2E tests │ │
│ └─────────────────────┘ │ └───────────────────────────┘ │
│ ◄───────┼───────► │
│ ┌─ ams-worker ────────┐ │ ┌─ (more)...────────────────┐ │
│ │ Queue · Cron jobs │ │ │ Any VPS, anywhere │ │
│ └─────────────────────┘ │ └───────────────────────────┘ │
╰────────────────────────────┴──────────────────────────────────╯
Each server runs: Caddy · Appa API · BuildKit · PostgreSQL · Containers
```

## Prerequisites
Expand All @@ -26,23 +49,36 @@ Operator Machine Remote Server (VPS)

```bash
curl -fsSL https://appa.theolujay.dev/install.sh | sh
appa instance init my-server
appa instance set-host my-server root@203.0.113.10 -i ~/.ssh/id_ed25519
appa server init my-server
appa server set-host my-server root@203.0.113.10 -i ~/.ssh/id_ed25519
appa preflight my-server
appa setup my-server
```

Once setup completes, open the dashboard URL to register your admin account.
<!-- Once setup completes, open the dashboard URL to register your admin account.

The install script also automatically provisions **uv** and **Ansible** in an isolated venv under `~/.appa/ansible/`, so no system-wide Python dependencies required. -->

## Features

| Area | Capabilities |
|---|---|
| **Server management** | Initialize, provision, configure, and monitor any number of VPS servers from one CLI. |
| **Auto-deploy** | One command ships source via rsync and triggers the build pipeline. Projects are auto-created on the server. |
| **Env vars** | Manage per-project environment variables through `appa project env set/get/unset`. |
| **Project lifecycle** | View deployment logs, stop running deployments, or restart with `appa project logs/stop/restart`. |
| **Zero-config builds** | Railpack auto-detects runtimes — no Dockerfiles needed. |
| **Web dashboard** | React UI for deployment history, project management, and monitoring. |

## User Guide

See [docs/user-guide.md](docs/user-guide.md) for the full walkthrough — installation, instance management, project deployment, and CLI reference.
See [docs/user-guide.md](docs/user-guide.md) for the full walkthrough — installation, server management, project deployment, environment variables, and CLI reference.

## Documentation

| Doc | For |
|---|---|
| [User Guide](docs/user-guide.md) | Setting up and using Appa day-to-day |
| [Architecture](docs/architecture.md) | Design decisions, invariants, and data paths |
| [Contributing](CONTRIBUTING.md) | Development setup, API routes, coding conventions |
| [User Guide](docs/user-guide.md) | Installing the CLI, provisioning servers, deploying projects, managing env vars |
| [Architecture](docs/architecture.md) | Design decisions, invariants, data paths, and glossary |
| [Contributing](CONTRIBUTING.md) | Development setup, API routes, project structure, coding conventions |
| [Roadmap](docs/roadmap.md) | Completed milestones and planned features |
Loading
Loading