rustlift 2.0.2

A typestate-driven deployment agent for Azure Web Apps
Documentation
# Rustlift

[![Rust 2021](https://img.shields.io/badge/rust-2021-orange.svg)](https://www.rust-lang.org/)
[![CI](https://github.com/hghalebi/rustlift/actions/workflows/rust.yml/badge.svg)](https://github.com/hghalebi/rustlift/actions/workflows/rust.yml)
[![Target Azure App Service](https://img.shields.io/badge/target-Azure%20App%20Service-0078D4.svg)](https://azure.microsoft.com/en-us/products/app-service/)
[![License Rustlift NC 1.0](https://img.shields.io/badge/license-Rustlift%20NC%201.0-blue.svg)](LICENSE)

Rustlift is a typestate-driven deployment tool that builds and deploys the Axum server in this repository to Azure App Service.

This repository is distributed as a source-available project under a
non-commercial license.

```text
Init -> Authenticated -> InfraReady -> ArtifactReady -> Live
```

Each transition is encoded in the type system, so invalid stage order is rejected at compile time.

## Quickstart

```bash
cargo install cross
az login
export AZURE_SUBSCRIPTION_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
cargo run --bin rustlift
```

## What Rustlift does

Running `cargo run --bin rustlift` performs one deployment pipeline:

1. Authenticates using your Azure CLI session.
2. Ensures Azure infrastructure exists:
   Resource Group (`{APP_NAME}-rg`), Linux App Service Plan (`{APP_NAME}-plan`, SKU `B1`), and Web App (`APP_NAME`).
3. Builds `server` for `x86_64-unknown-linux-musl` using `cross`.
4. Creates `deploy.zip` containing `server` and `startup.sh`.
5. Deploys the zip with `az webapp deployment source config-zip`.
6. Verifies `https://{APP_NAME}.azurewebsites.net/health`.

## Prerequisites

| Tool | Why it is needed | Install |
|------|-------------------|---------|
| Rust (stable) | Build and run binaries | [rustup.rs]https://rustup.rs |
| Docker | Required by `cross` images | [Get Docker]https://docs.docker.com/get-docker/ |
| cross | Cross-compile Linux `musl` binary | `cargo install cross` |
| Azure CLI (`az`) | Auth and zip deployment | [Install Azure CLI]https://learn.microsoft.com/en-us/cli/azure/install-azure-cli |

First-time setup:

```bash
cargo install cross
az login
rustup target add x86_64-unknown-linux-musl # optional, useful for IDE support
```

## Configuration

Rustlift is configured only through environment variables:

| Variable | Required | Default | Used for |
|----------|----------|---------|----------|
| `AZURE_SUBSCRIPTION_ID` | yes | none | Azure subscription ID |
| `APP_NAME` | no | `rust-enterprise-api` | Web App name and DNS prefix |
| `AZURE_LOCATION` | no | `eastus` | Region for Resource Group and App Service |
| `RUST_LOG` | no | `info` | Log level for `rustlift` |
| `PORT` | no | `8080` | Local/server runtime port for `server` binary |

Derived names:

- Resource Group: `{APP_NAME}-rg`
- App Service Plan: `{APP_NAME}-plan`
- Binary inside zip: `server`

## Feature flags

This project currently defines no Cargo feature flags.

## Usage

Deploy with defaults:

```bash
export AZURE_SUBSCRIPTION_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
cargo run --bin rustlift
```

Deploy with custom app and region:

```bash
AZURE_SUBSCRIPTION_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
APP_NAME=my-api \
AZURE_LOCATION=westeurope \
RUST_LOG=debug \
cargo run --bin rustlift
```

Run the Axum server locally:

```bash
cargo run --bin server
PORT=3000 cargo run --bin server
```

Endpoints:

| Route | Method | Response | Purpose |
|-------|--------|----------|---------|
| `/` | GET | `Hello, Azure!` | Basic service endpoint |
| `/health` | GET | `OK` | Health probe used by deployment verification |

## Infrastructure details

The Web App is configured with:

- `linux_fx_version = "DOTNETCORE|8.0"`
- `app_command_line = "sh startup.sh"`
- App settings:
  - `WEBSITES_PORT=8080`
  - `WEBSITE_RUN_FROM_PACKAGE=1`
- `https_only = true`

## Repository layout

| Path | Purpose |
|------|---------|
| `src/main.rs` | Deployment CLI entrypoint (`rustlift`) |
| `src/pipeline.rs` | Typestate pipeline and Azure operations |
| `src/errors.rs` | Error taxonomy used across stages |
| `src/resilience.rs` | Retry wrapper and backoff policy |
| `src/bin/server.rs` | Axum app that is deployed |
| `Cross.toml` | Cross image config for Linux `musl` builds |
| `startup.sh` | Startup command wrapper executed on Azure |
| `examples/probe_deps.rs` | Azure SDK dependency probe example |

## Reliability model

- Errors are strongly typed via `DeployError` in `src/errors.rs`.
- Fatal errors (for example missing config or build failures) stop immediately.
- Transient errors (for example network/infra failures) are retried by `reliable_op` in `src/resilience.rs`.
- Retry policy:
  - Max elapsed time: 5 minutes
  - Max retry interval: 30 seconds
  - Backoff multiplier: 1.5x

## Testing

```bash
cargo test
cargo run --example probe_deps
```

## Community

- Contribution guide: [`CONTRIBUTING.md`]CONTRIBUTING.md
- Code of Conduct: [`CODE_OF_CONDUCT.md`]CODE_OF_CONDUCT.md
- Security policy: [`SECURITY.md`]SECURITY.md
- Support policy: [`SUPPORT.md`]SUPPORT.md
- Changelog: [`CHANGELOG.md`]CHANGELOG.md
- Release process: [`RELEASE.md`]RELEASE.md

## Troubleshooting

- `Missing AZURE_SUBSCRIPTION_ID`: export a valid subscription ID before running.
- `Azure CLI ('az') not found`: install Azure CLI and ensure it is on `PATH`.
- Auth failures: run `az login` again and retry.
- `cross` build failures: verify Docker is running and `cross` is installed.
- Health check failures: inspect Azure logs for startup or binding issues.

## Licence

This project uses a dual-licence model:

- Non-commercial use (learning, hobby, and non-commercial contributions): Rustlift Non-Commercial Licence v1.0.
- Commercial use: separate paid commercial licence.

See [`LICENSE`](LICENSE) for full terms.

Note: the current non-commercial licence is not OSI-approved.
If you plan a public source-available release, follow [`RELEASE.md`](RELEASE.md).

### Contributing

By submitting a pull request, you agree to license contributions under the same non-commercial terms and grant rights described in [`LICENSE`](LICENSE).