# pyeasydeploy

Deploy Python apps to Linux servers over SSH, with plain Python functions on top of [Fabric](https://www.fabfile.org/): no agents on the server, no YAML, no DSL to learn. Your deploy is a `deploy.py` in your project that reads top to bottom.

It is made for a few servers and a team that writes Python, not to compete with Ansible or Docker.

```python
from pyeasydeploy import (
    Host, Key, SupervisorService, connect, create_venv,
    deploy_supervisor_service, get_target_python_instance,
    install_local_package, supervisor_restart,
)

APP = "myapp"
USER = "deploy"

conn = connect(Host(
    address="203.0.113.10",
    user=USER,
    auth=Key(path="~/.ssh/id_ed25519"),
    sudo_password="...",   # better: os.environ["SUDO_PASSWORD"]
))

py = get_target_python_instance(conn, "3.11")
venv = create_venv(conn, py, f"/home/{USER}/venvs/{APP}")
install_local_package(conn, venv, f"./{APP}")

deploy_supervisor_service(conn, SupervisorService(
    name=APP,
    command=f"{venv.path}/bin/python -m {APP}",
    directory=f"/home/{USER}",
    user=USER,
))
supervisor_restart(conn, APP)
```

## What a deploy does

The introductory example is a complete deploy. Each call is one step, run over SSH in order:

1. `connect` opens a connection to the `Host`: its address, user, and either a `Password` or a `Key`. Operations that need sudo use `sudo_password`, or the SSH password when `auth` is a `Password`. The connection is lazy: a wrong password shows up on the first command.
2. `get_target_python_instance` picks an interpreter already installed on the server: the newest one whose version matches `"3.11"`.
3. `create_venv` builds a virtual environment with that interpreter, removing any previous one at the same path.
4. `install_local_package` uploads your local package and installs it in the venv with its dependencies.
5. `deploy_supervisor_service` writes a supervisord entry for the app and reloads supervisord, so the app runs as a service that restarts on failure and survives reboots. `supervisor_restart` then restarts it, so it runs the new code even when its configuration did not change.

The `venv` object returned by `create_venv` carries its own path, so the service command is built from it and no path is repeated by hand. Run the script again and the server ends up in the same state: see [Reproducibility](https://offerrall.github.io/doc/pyeasydeploy/reproducibility.md).

The models (`Host`, `Key`, `SupervisorService` and the rest) validate themselves when built, so a relative path or a malformed service name fails on your machine before anything reaches the server. Keep secrets out of the script: read `sudo_password` from the environment, for example `os.environ["SUDO_PASSWORD"]`.

## Requirements

- **Your machine:** Windows, macOS or Linux, with SSH access to the server.
- **The server:** Linux with SSH and a `python3` in `/usr/bin`. Tested on Arch, Debian and Ubuntu.
- **Services:** supervisord is installed and managed on Arch and on Debian-based distributions (Debian, Ubuntu), detected from `/etc/os-release`. Managing services needs sudo on the server.

## Where to go next

- [Guide](https://offerrall.github.io/doc/pyeasydeploy/guide.md): each part of a deploy in detail, with the options that change it.
- [Reference](https://offerrall.github.io/doc/pyeasydeploy/reference.md): every public name.
- [Reproducibility](https://offerrall.github.io/doc/pyeasydeploy/reproducibility.md): how far "same script, same server" reaches.
- [Design](https://offerrall.github.io/doc/pyeasydeploy/design.md): the principles, and what the library deliberately does not do.
