# deploy


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

This module puts a web app on a virtual private server (VPS). It follows
the pattern of lego, the project it was taken from: a Docker image
described by dockeasy, a Hetzner server provisioned by vpseasy, and a
Cloudflare tunnel opened by cfeasy, so the app is reachable without
opening a public port. The work is done by two scripts in the project,
`setup.py` for one-time setup and `deploy.py` for every deploy. This
module writes both, and
[`Deploy`](https://vedicreader.github.io/pullup/deploy.html#deploy) is
the pipeline that runs `deploy.py`.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/deploy.py#L36"
target="_blank" style="float:right; font-size:smaller">source</a>

### generator

``` python
def generator():
```

*The tool name that generated scripts say wrote them.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/deploy.py#L31"
target="_blank" style="float:right; font-size:smaller">source</a>

### use_generator

``` python
def use_generator(
    name
):
```

*Set the tool name that generated `setup.py` and `deploy.py` files say
wrote them.*

Each generated script says in its docstring which tool wrote it.
[`use_generator`](https://vedicreader.github.io/pullup/deploy.html#use_generator)
sets that name, which is `Leela` until you change it. A host application
that bundles pullup calls it with its own name. The templates read the
name each time they build their text, so a call made after import still
takes effect.

`DeployError` is another name for
[`PipelineError`](https://vedicreader.github.io/pullup/pipeline.html#pipelineerror),
so catching either one catches both.

`DEPLOY_KEYS` lists the settings a deploy needs. A value of `None` marks
a secret: it has no default and goes to GitHub as an encrypted secret. A
string marks an ordinary variable, and the string is its default.
`MODE`, `PORT`, `SERVER_USER` and `RSYNC_FORCE` have defaults; the
domain, the server name and the three tokens do not.

``` python
{k: ('secret' if v is None else f'variable, default {v!r}') for k, v in DEPLOY_KEYS.items()}
```

    {'MODE': "variable, default 'prod'",
     'PORT': "variable, default '5001'",
     'DOMAIN': 'secret',
     'SERVER_NAME': 'secret',
     'SERVER_USER': "variable, default 'deploy'",
     'HCLOUD_TOKEN': 'secret',
     'CLOUDFLARE_API_TOKEN': 'secret',
     'CF_TUNNEL_TOKEN': 'secret',
     'RSYNC_FORCE': "variable, default 'false'"}

`DEPLOY_STEPS` is the default deploy plan. Each step runs one subcommand
of the project’s `deploy.py`. `env` writes the `.env` file the container
reads.
[`compose`](https://vedicreader.github.io/pullup/wfbuild.html#compose)
writes the Dockerfile, `docker-compose.yml` and the Caddyfile. `deploy`
opens the tunnel, provisions the server if it does not exist yet and
copies the project to it with rsync. Only the last step needs tokens.

``` python
[(s.id, s.cmd, s.needs) for s in DEPLOY_STEPS]
```

    [('env', 'python deploy.py env', []),
     ('compose', 'python deploy.py compose', []),
     ('deploy',
      'python deploy.py deploy',
      ['HCLOUD_TOKEN', 'CLOUDFLARE_API_TOKEN'])]

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/deploy.py#L59"
target="_blank" style="float:right; font-size:smaller">source</a>

### setup_template

``` python
def setup_template(
    app, domain, subdomain:str='', port:int=5001, lfs:bool=True
):
```

*The text of a one-time `setup.py` that prepares a project for deploying
the lego way.*

[`setup_template`](https://vedicreader.github.io/pullup/deploy.html#setup_template)
returns the text of a `setup.py` for the project, without writing it.
Run once after scaffolding, that script records the keys in gheasy’s
config, tracks images and audio with Git LFS, writes `.env.example` with
an empty value for each secret, writes the GitHub deploy workflow, and
copies each installed library’s `SKILL.md` for coding agents. Its
subcommands push your local values to GitHub (`push`), leaving out keys
with no value so a secret is never set to the text `None`, upload the
server’s SSH key as the `DEPLOY_KEY` secret (`ssh-key`), and redo single
parts (`mkenv`, `workflow`, `skills`).

`ENV_KEYS` in the generated file lists every key the script handles,
with the domain filled in. It is `DEPLOY_KEYS` plus `SERVER_PASSWORD`:

``` python
src = setup_template('lego', 'example.com', subdomain='app')
literal(src, 'ENV_KEYS')
```

    {'MODE': 'prod',
     'PORT': 5001,
     'DOMAIN': 'app.example.com',
     'SERVER_NAME': None,
     'SERVER_USER': 'deploy',
     'SERVER_PASSWORD': None,
     'HCLOUD_TOKEN': None,
     'CLOUDFLARE_API_TOKEN': None,
     'CF_TUNNEL_TOKEN': None,
     'RSYNC_FORCE': 'false'}

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/deploy.py#L168"
target="_blank" style="float:right; font-size:smaller">source</a>

### deploy_template

``` python
def deploy_template(
    app, domain, subdomain:str='', port:int=5001, second_host:str=''
):
```

*The text of a `deploy.py` that builds, tunnels and deploys the app;
`second_host` adds another hostname.*

[`deploy_template`](https://vedicreader.github.io/pullup/deploy.html#deploy_template)
returns the text of the `deploy.py` itself. Its subcommands are `env`,
[`compose`](https://vedicreader.github.io/pullup/wfbuild.html#compose)
and `deploy`, which the pipeline’s steps call, plus `status`, which
lists the servers and tunnels your tokens can see, `rollback`, which
runs the image from before the last deploy or a kept image you name, and
`nuke`, which deletes the server once you type back a random
confirmation word. `nuke` keeps the Cloudflare tunnel unless you add
`--tunnel`, because the tunnel follows whichever server runs its
connector and survives a move to a new box. After a deploy,
`after_deploy` waits until the site answers and purges Cloudflare’s
cache for the host; vpseasy’s `deploy` has already kept the replaced
image for `rollback` and pruned dangling images. The tunnel is named
`<subdomain or app>_<domain>`. `second_host` adds another hostname
served by the same tunnel and container. The file is meant to be edited,
and the pipeline runs whatever it says.

``` python
dsrc = deploy_template('lego', 'example.com', subdomain='app')
literal(dsrc, 'host'), literal(dsrc, 'tunnel_name'), literal(dsrc, 'inc')[:4]
```

    ('app.example.com',
     'app_example.com',
     ['lego/', 'static/', 'pyproject.toml', 'uv.lock'])

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/deploy.py#L328"
target="_blank" style="float:right; font-size:smaller">source</a>

### Deploy

``` python
def Deploy(
    root, # the project the commands run in
    python:NoneType=None, # the interpreter whose virtual environment they run in; None uses this process's
    env:NoneType=None, # an `EnvStore`, or anything with `.get(key)` and `.values(keys)`
    dir:str='.pullup', # where the plan is written, relative to `root`
):
```

*The deploy pipeline, one step per subcommand of the project’s
`deploy.py`.*

[`Deploy`](https://vedicreader.github.io/pullup/deploy.html#deploy) is
the pipeline for all of this. Its default plan is `DEPLOY_STEPS`, saved
to `deploy.json` in the plan directory.

`scaffold` sets a project up for deploying. It writes `deploy.py`,
writes `setup.py` if there is none, adds the deploy keys to
`.gheasy/config.json`, and adds the deploy workflow through
[`Workflows`](https://vedicreader.github.io/pullup/workflows.html#workflows).
It needs a domain, and it refuses to replace an existing `deploy.py`
unless you pass `force=True`. It returns the paths it wrote along with
the new state:

``` python
d = Deploy(root)
[str(Path(p).relative_to(root)) for p in d.scaffold(domain='example.com', subdomain='app')['written']]
```

    ['deploy.py',
     'setup.py',
     '.gheasy/config.json',
     '.github/workflows/deploy.yml']

`target` reports where the deploy goes and what is still missing, read
from the same store the steps will use: the app, domain, server, user
and port, the secret keys with no value yet, and which of the generated
files exist. A user interface can tell from it whether a project has
been scaffolded and what to ask for before the first deploy, and `state`
includes it under `target`. Each key is also read in lower case, such as
`server_name`, for projects whose `.env` and GitHub variables predate
the upper-case names. The values come from the default store, so they
reflect the machine that ran this page.

``` python
{k: v for k, v in d.target().items() if k != 'keys'}
```

    {'app': 'lego',
     'domain': 'app.example.com',
     'server': '',
     'user': 'deploy',
     'port': '5001',
     'missing': ['SERVER_NAME',
      'HCLOUD_TOKEN',
      'CLOUDFLARE_API_TOKEN',
      'CF_TUNNEL_TOKEN'],
     'script': True,
     'setup': True,
     'compose': False,
     'dockerfile': False,
     'caddyfile': False}
