# Linux VM

This guide shows how to self-host xlwings Lite on a plain Linux VM using the `xlwings-lite` CLI.

## Prerequisites

- A Linux VM (e.g., Ubuntu 24.04 LTS). Recommended minimal specs: 1 vCPU, 1 GB RAM, 25 GB SSD.
- A hostname pointing to the Linux VM, e.g., `xlwings-lite.mycompany.com`.
- Docker installed on the VM:
  ```shell
  curl -fsSL https://get.docker.com -o get-docker.sh
  sudo sh ./get-docker.sh
  ```

  If this doesn’t work, follow [Docker’s official installation guide](https://docs.docker.com/engine/install/).
- `bzip2` installed on the VM:
  - Ubuntu/Debian: `sudo apt install -y bzip2`
  - RHEL/Fedora/Amazon Linux: `sudo dnf install -y bzip2`
- An xlwings license key. If you don’t have one, you can request a [trial key](https://www.xlwings.org/trial).

## Install

### 1. Provide TLS certificates

xlwings Lite is an Office.js add-in, which requires TLS certificates even when hosted internally. xlwings Lite expects your cert and key under `/etc/xlwings-lite/certs/`, named `tls.crt` and `tls.key`.

If you need help with creating the certificates, see here:

- [TLS certificates via Windows CA Server]()
- [TLS certificates via Let's Encrypt (certbot)]()

### 2. Install the xlwings-lite CLI

```shell
sudo curl -fsSL https://lite.xlwings.org/cli -o /usr/local/bin/xlwings-lite
sudo chmod +x /usr/local/bin/xlwings-lite
```

### 3. Run the installer

```shell
sudo xlwings-lite install
```

Once `install` prints `Running`, visit `https://<your-hostname>` in your browser to confirm (the page shows the version of xlwings Lite).

### 4. Register the add-in with Microsoft 365 admin center

1. In your browser, go to `https://<your-hostname>/manifest`, which will download `xlwings-lite-manifest.xml`.
2. Go to [Microsoft 365 admin center](https://admin.microsoft.com/)
   - Click on [`Show all` > `Settings` > `Integrated Apps`](https://admin.microsoft.com/#/Settings/IntegratedApps).
   - If you have **xlwings Lite** installed, uninstall it first.
   - Click on `Upload custom apps` and select `Office Add-in` (App type).
   - Select `Upload manifest file (.xml) from device`. Click `Choose File`, then select the `xlwings-lite-manifest.xml` from the previous step.
   - Click `Next`, then assign the desired users.
   - Click `Next` and accept permission requests.
   - Click `Next` and `Finish deployment`.

The users will get the add-in to show up automatically although it may take a few hours.

## Maintenance

### Update xlwings Lite

To update xlwings Lite to the latest version, run:

```shell
sudo xlwings-lite update
```

### Update the xlwings-lite CLI

To update the CLI, simply re-run the install command:

```shell
sudo curl -fsSL https://lite.xlwings.org/cli -o /usr/local/bin/xlwings-lite
sudo chmod +x /usr/local/bin/xlwings-lite
```

### Edit the configuration

The config file is `/etc/xlwings-lite/xlwings-lite.conf`. After editing, run `sudo xlwings-lite restart` to apply. See [Configuration](configuration.md) for the full list of supported settings.

### Download PyPI packages locally

By default, users install Python packages from [pypi.org](https://pypi.org/) at runtime by editing their `requirements.txt` file in xlwings Lite. If you want to restrict this but don’t want to set up a proper 3rd party registry, set `XLWINGS_ENABLE_PYPI=false` and download the PyPI packages with `xlwings-lite packages add`. You’ll have to download all packages your users depend on, but at the very least the following:

```text
sudo xlwings-lite packages add xlwings==0.35.3 seaborn==0.13.2
```

You can also pass a requirements file (this assumes it’s stored under `/etc/xlwings-lite`):

```text
sudo xlwings-lite packages add -r /etc/xlwings-lite/requirements.txt
```

#### NOTE
`packages add` only downloads pure-Python wheels and does not resolve sub-dependencies automatically. This is intentional: compiled packages like numpy, pandas, and matplotlib are already built into Pyodide. If a package has sub-dependencies, add them explicitly.

To disable direct PyPI access, set the environment variable:

```shell
sudo xlwings-lite config set XLWINGS_ENABLE_PYPI false
sudo xlwings-lite restart
```

With this configuration, users can only install packages that have been downloaded via `packages add` (or that are built into Pyodide).

Wheels and index files are stored under `/var/lib/xlwings-lite/pypi/`.

### Uninstall

```shell
sudo xlwings-lite stop
sudo rm /usr/local/bin/xlwings-lite
sudo rm -rf /etc/xlwings-lite /var/lib/xlwings-lite
```

You may also want to remove Docker and the Docker images.

#### NOTE
If you want to remove the add-in again and run into issues (“Remove apps failed. No apps were successfully removed. Please try to remove them later.”), use this legacy URL: [https://admin.microsoft.com/#/Settings/AddIns](https://admin.microsoft.com/#/Settings/AddIns)

### TLS certificates via Windows CA Server

If you issue your TLS certificates from a Windows CA Server (i.e., Active Directory Certificate Services / AD CS), here’s how to obtain one for your Linux VM. Steps marked  *(Linux)* are run on the Linux VM (e.g., via PuTTY); steps marked  *(Windows CA)* are run on the Windows CA server in a command prompt with admin rights.

1.  *(Linux)* Change into the certs folder:
   ```shell
   mkdir -p /etc/xlwings-lite/certs
   cd /etc/xlwings-lite/certs
   ```
2.  *(Linux)* Create a config file. Make sure to replace `<your-vm.example.com>` in both places with your own domain name:
   ```shell
   cat > csr.cnf <<'EOF'
   [req]
   distinguished_name = req_distinguished_name
   req_extensions = v3_req
   prompt = no

   [req_distinguished_name]
   CN = <your-vm.example.com>

   [v3_req]
   subjectAltName = @alt_names

   [alt_names]
   DNS.1 = <your-vm.example.com>
   EOF
   ```
3.  *(Linux)* Create the CSR file:
   ```shell
   openssl req -new -key tls.key -out tls.csr -config csr.cnf
   ```
4. Copy `/etc/xlwings-lite/certs/tls.csr` from the Linux VM to the Windows CA server, e.g., using WinSCP.
5.  *(Windows CA)* Generate the leaf certificate:
   ```shell
   certreq -submit -attrib "CertificateTemplate:WebServer" tls.csr tls.leaf.crt
   ```
6.  *(Windows CA)* Export the issuing CA certificate:
   ```shell
   certutil -ca.cert ca.crt
   ```

   This exports the certificate of the CA that signed your leaf cert, which you’ll need in the next step to complete the chain.
7. Copy `tls.leaf.crt` and `ca.crt` back to the Linux VM (e.g., using WinSCP). Copy them to `/tmp/` first to avoid any permission errors, then move them into place:
   ```shell
   sudo mv /tmp/tls.leaf.crt /tmp/ca.crt /etc/xlwings-lite/certs
   ```
8.  *(Linux)* Build the fullchain certificate that the app will actually serve:
   ```shell
   cd /etc/xlwings-lite/certs
   cat tls.leaf.crt ca.crt > tls.crt
   ```

   #### NOTE
   In a 2-tier (or deeper) AD CS setup, the CA that signed your leaf cert is an *intermediate* CA whose own certificate is signed by an offline *root* CA. In that case `ca.crt` (the intermediate) alone is not enough as clients need the full chain from leaf to root. Concatenate all certs in order from leaf to root:
   ```shell
   cat tls.leaf.crt intermediate.crt root.crt > tls.crt
   ```

   To check what’s currently in your chain, run `openssl crl2pkcs7 -nocrl -certfile tls.crt | openssl pkcs7 -print_certs -noout` and verify each `subject` matches the next cert’s `issuer`. To obtain the root cert, ask your Windows admin or, on the CA server, open `certlm.msc` → *Trusted Root Certification Authorities* → *Certificates*, then export the relevant root as a Base-64 encoded `.cer` file.
9.  *(Linux)* Set file permissions:
   ```shell
   chmod 644 /etc/xlwings-lite/certs/tls.crt
   chmod 600 /etc/xlwings-lite/certs/tls.key
   ```
10.  *(Linux)* Restart xlwings Lite:
    ```shell
    xlwings-lite restart
    ```
11. Open `https://your-vm.example.com` in a browser. The address bar should show the padlock with no warning.

Once it works, keep the `tls.csr` and config files around for the next renewal. When the cert expires (typically 1–2 years), you’ll regenerate the CSR and request a new leaf certificate. `tls.key` can be reused indefinitely or rotated.

### TLS certificates via Let’s Encrypt (certbot)

If your VM’s hostname is a subdomain of a public domain you control (e.g. `your-vm.example.com`), you can get a free, auto-renewing certificate from Let’s Encrypt—even when the VM itself is not reachable from the public internet. Use the [DNS-01 challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge), which proves domain ownership by creating a TXT record at `_acme-challenge.your-vm.example.com` instead of requiring inbound HTTP access.

With a DNS provider plugin (e.g. `certbot-dns-cloudflare`), renewals can be fully automated:

```bash
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
  -d your-vm.example.com
```

The certificate and key will be placed under `/etc/letsencrypt/live/your-vm.example.com/` as `fullchain.pem` and `privkey.pem`. Since xlwings Lite reads the cert from `/etc/xlwings-lite/certs/`, add a deploy hook that copies the renewed files into place and restarts the service:

```shell
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/xlwings-lite.sh > /dev/null <<'EOF'
#!/bin/bash
set -e
cp /etc/letsencrypt/live/your-vm.example.com/fullchain.pem /etc/xlwings-lite/certs/tls.crt
cp /etc/letsencrypt/live/your-vm.example.com/privkey.pem   /etc/xlwings-lite/certs/tls.key
chmod 644 /etc/xlwings-lite/certs/tls.crt
chmod 600 /etc/xlwings-lite/certs/tls.key
xlwings-lite restart
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/xlwings-lite.sh
```

certbot’s systemd timer will then handle renewals automatically and run the hook each time the certificate is renewed.

## Troubleshooting

- **`cannot talk to the docker daemon`** — Start Docker: `sudo systemctl start docker`.
