# AWS

`splam` controls `systemctl`/`journalctl` on the host it runs on, so it has to run directly on the machine it's managing (not in a container or behind Lambda). On AWS that means an EC2 instance, run as a `systemd` service with a reverse proxy in front for TLS.

This page assumes no prior AWS experience. If you already know EC2, VPCs, and security groups, skip to [step 2](#2-install-splam).


# 1. Launch the instance


## Where the instance lives

An EC2 instance is a virtual machine that runs inside a **VPC** (a private network you own within AWS) and one of its **subnets** (a smaller address range carved out of that VPC, tied to one Availability Zone). Every new AWS account already has a default VPC with a public subnet in each Availability Zone, and that default is fine for a single `splam` instance. Launch into it unless your organization has its own VPC design you're expected to follow.

A **public subnet** is one whose route table sends `0.0.0.0/0` traffic to an internet gateway, which is what gives an instance in it a reachable public IP. `splam` itself should never be reachable directly (see security groups below), so if your account has a private subnet with a NAT gateway or a VPN/Direct Connect path back to your network, prefer that instead. You'll SSH in through a bastion host or VPN rather than the public internet.


## Choosing an instance type and AMI

Any recent Amazon Linux 2023 or Ubuntu 22.04+ AMI works; `splam` needs Python 3.11 or newer, which both ship with. A `t3.micro` or `t3.small` is plenty. The app itself is lightweight, and the audit trail/incident files are small text and JSON.


## Storage

The default 8 GB root EBS volume (`gp3`) is enough for the OS, the `splam` install, and normal audit-trail growth. If you're also using the shell-audit feature (see [How-To: Shell Audit Setup](01.13-how-to-shell-audit-setup.md)), `auditd`'s own logs in `/var/log/audit/` can grow faster under heavy `sudo` use, and 20 GB gives more headroom before you need to think about log rotation.


## Security group

A security group is a stateful firewall attached to the instance (stateful meaning a reply to an allowed outbound or inbound request doesn't need its own separate rule). Lock it down to two inbound rules:

- `22/tcp` (SSH) from your admin CIDR/VPN only, never `0.0.0.0/0`
- `443/tcp` (HTTPS) from wherever operators connect, ideally also restricted to a known CIDR range rather than the whole internet

Don't open `8000/tcp` (or whatever port `splam` binds) to the internet. It stays behind the reverse proxy in step 5, reachable only from `localhost` or from the load balancer's own security group. Outbound rules can stay at the default "allow all", since the instance needs outbound access to pull the `splam` package and any OS updates.


*\[Rich HTML output -- view on the documentation site\]*


## DNS

If operators will reach `splam` at a real hostname rather than an IP, create an `A` (or `ALIAS`) record in Route 53 (or whatever DNS provider you use) pointing at the instance's Elastic IP, or at the load balancer if you're using one (step 5). An Elastic IP is a static public IP you allocate and associate with the instance, so it survives a stop/start. Without one, a restarted instance gets a new IP and DNS breaks.


# 2. Install splam

Same steps as the [Tutorial](tutorial-getting-started.md), run as whatever user will own the service. SSH in first:

``` bash
ssh -i your-key.pem ec2-user@<instance-public-ip>
```

(`ec2-user` on Amazon Linux, `ubuntu` on Ubuntu AMIs.)

Install system dependencies. `splam` itself only needs Python and `git`, but both AMI families ship Python without `venv`/`pip` fully wired up by default on minimal images, so install explicitly:

``` bash
# Amazon Linux 2023
sudo dnf install -y python3 python3-pip git

# Ubuntu
sudo apt update && sudo apt install -y python3 python3-venv python3-pip git
```

Clone the repo:

``` bash
sudo git clone https://github.com/mjfrigaard/splam.git /opt/splam
```

Change directories:

``` bash
cd /opt/splam
```

Create the Python virtual environment. `splam` and its dependencies (Shiny for Python and its transitive packages, pulled from PyPI) are installed into this venv rather than the system Python, which is what `pip` would otherwise refuse to touch under [PEP 668](https://peps.python.org/pep-0668/):

``` bash
sudo python3 -m venv .venv
```

Install dependencies:

``` bash
sudo .venv/bin/pip install -e .
```

This needs outbound internet access to reach PyPI (`pypi.org`), so if the instance is in a private subnet, confirm the NAT gateway or VPC endpoint path allows it before this step.

Add admin:

``` bash
sudo .venv/bin/splam-users add admin
```


# 3. Grant systemctl privilege

The service account needs rights to control the units it manages. See [How-To: Sudo Privileges](01.02-how-to-sudo-privileges.md). Running as `root` is simplest for a first deploy, and a scoped `polkit`/`sudoers` rule is the tighter option once you know which units operators need.


# 4. Run splam as a systemd unit

`systemd` is the service manager already running on both Amazon Linux and Ubuntu; a unit file tells it how to start, restart, and supervise a process, which is what keeps `splam` running across reboots and crashes without a separate process manager.

Bind to `127.0.0.1` (not `0.0.0.0`) and let the reverse proxy handle external traffic. Create `/etc/systemd/system/splam.service`:

``` ini
[Unit]
Description=splam
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/splam
Environment=SPLAM_DATA_DIR=/var/lib/splam
ExecStart=/opt/splam/.venv/bin/shiny run splam.app:app --host 127.0.0.1 --port 8000
Restart=on-failure

[Install]
WantedBy=multi-user.target
```

`SPLAM_DATA_DIR=/var/lib/splam` moves credentials and audit logs out of `/opt/splam` and into the conventional system-deployment location (see [How-To: Manage Logins](01.01-how-to-logins.md)). Add `SPLAM_MAX_FAILED_LOGINS`, `SPLAM_LOCKOUT_SECONDS`, `SPLAM_IDLE_TIMEOUT`, or `SPLAM_AUDIT_LOG` as further `Environment=` lines if the defaults don't fit.

``` bash
sudo mkdir -p /var/lib/splam
```

``` bash
sudo systemctl daemon-reload
```

``` bash
sudo systemctl enable --now splam
```

`enable` makes the unit start on boot; `--now` also starts it immediately. Confirm it's actually up before moving on:

``` bash
sudo systemctl status splam
curl -I http://127.0.0.1:8000
```

The `curl` should return an HTTP response (even a redirect or login page), confirming `splam` is listening on `127.0.0.1:8000` as expected. If `systemctl status` shows `failed`, check `journalctl -u splam -n 50` for the reason before continuing. A common cause at this stage is the venv path in `ExecStart` not matching where step 2 actually installed it.


# 5. Put a reverse proxy in front

`splam` only listens on `localhost:8000`; something else has to accept the public `443/tcp` connection, terminate TLS, and forward the request. Two options:


*\[Rich HTML output -- view on the documentation site\]*


**Option A: nginx on the instance itself.** Install it, request a certificate (e.g. via [Certbot](https://certbot.eff.org/)/Let's Encrypt, which needs `80/tcp` briefly open during issuance), and proxy to `splam`:

``` bash
sudo dnf install -y nginx   # or: sudo apt install -y nginx
```

`/etc/nginx/conf.d/splam.conf`:

``` nginx
server {
    listen 443 ssl;
    server_name splam.example.com;

    ssl_certificate     /etc/letsencrypt/live/splam.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/splam.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}
```

The `Upgrade`/`Connection` headers matter because Shiny for Python holds its UI connection open over a WebSocket, not plain request/response HTTP; without them the proxy will accept the initial page load but the app will never become interactive.

**Option B: an Application Load Balancer (ALB).** AWS terminates TLS for you using a free certificate from ACM (AWS Certificate Manager), and forwards plain HTTP to the instance. This is less to maintain (no certificate renewal on the box, no nginx config) but costs more than a bare instance and is one more AWS resource to understand:

1.  Request a certificate for your domain in ACM and validate it (DNS validation via Route 53 is the easiest path).
2.  Create a target group of type "instance", protocol HTTP, port `8000`, and register the EC2 instance in it. Enable WebSocket support is the ALB default, so no extra flag is needed, but confirm the target group's health check path returns a non-error response from `splam` (the login page at `/` works).
3.  Create the ALB itself in the public subnet(s), listener on `443` using the ACM certificate, forwarding to that target group.
4.  Point DNS at the ALB's own DNS name (a `CNAME`/`ALIAS`), not at the instance.

With an ALB, the instance's security group should only accept `8000/tcp` from the ALB's security group (reference it by security-group ID, not by CIDR), and `443/tcp` is opened on the ALB's security group instead of the instance's.
