deployment templates for lichen
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add ansible playbooks for deploying and updating lichen

deploy.yml sets up a fresh server: installs docker via get.docker.com,
syncs the docker-compose reference files to /srv/lichen, renders an
.env from inventory vars, pulls images, and waits for the app health
check to turn green.

update.yml targets an existing deployment: re-syncs the compose files
(picking up any upstream changes), optionally uploads a locally-built
custom binary from ../docker-compose/bin/lichen-server, pulls the
latest image, and restarts the stack.

Two roles (docker + lichen) split the work. The compose/Caddyfile/
entrypoint are not duplicated — Ansible ships the existing
docker-compose/ files via copy.

notplants 5860d6e0 91e17216

+278
+79
ansible/README.md
··· 1 + # Lichen Ansible Deployment 2 + 3 + Deploy or update a lichen server with Ansible. Wraps the `docker-compose/` 4 + reference — Ansible handles Docker install, file sync, and lifecycle; the 5 + actual services (lichen + caddy) still run from the same compose stack. 6 + 7 + ## Prerequisites 8 + 9 + - Ansible 2.12+ on your workstation 10 + - A target server with SSH access, sudo, and ports 80/443 open 11 + - A domain with an A record pointing at the server 12 + 13 + ## Setup 14 + 15 + 1. Copy the inventory example and edit it: 16 + ```bash 17 + cp inventory.example.yml inventory.yml 18 + # set ansible_host, ansible_user, lichen_domain 19 + ``` 20 + 21 + 2. Supply the admin password. Easiest: via `--extra-vars` at runtime. 22 + For anything non-trivial, use `ansible-vault`: 23 + ```bash 24 + ansible-vault create group_vars/all.yml 25 + # add: lichen_admin_password: "..." 26 + ``` 27 + 28 + ## Deploy a fresh server 29 + 30 + ```bash 31 + ansible-playbook -i inventory.yml deploy.yml \ 32 + --extra-vars "lichen_admin_password=CHANGEME" 33 + ``` 34 + 35 + This will: 36 + 1. Install Docker if missing (via `get.docker.com`) 37 + 2. Copy the `docker-compose/` files to the server (`/srv/lichen` by default) 38 + 3. Render `.env` from your inventory vars 39 + 4. `docker compose pull` + `docker compose up -d` 40 + 41 + Caddy obtains a TLS cert from Let's Encrypt on the first request to your 42 + domain. Wait ~30s after DNS is live, then visit `https://<lichen_domain>`. 43 + 44 + ## Update to latest 45 + 46 + ```bash 47 + ansible-playbook -i inventory.yml update.yml 48 + ``` 49 + 50 + Pulls the latest `notplants/lichen-full` image and restarts the app service. 51 + Caddy is left alone. Optionally copies a custom binary if one exists at 52 + `../docker-compose/bin/lichen-server` on your workstation — see 53 + `/lichen-mod` for building custom binaries. 54 + 55 + ## Variables 56 + 57 + | Variable | Default | Purpose | 58 + |----------|---------|---------| 59 + | `lichen_domain` | — (required) | dashboard + Caddy TLS | 60 + | `lichen_admin_user` | `admin` | admin login | 61 + | `lichen_admin_password` | — (required on first deploy) | admin login | 62 + | `lichen_auth_providers` | `file,atproto` | enabled auth backends | 63 + | `lichen_deploy_dir` | `/srv/lichen` | target directory on the server | 64 + | `lichen_rust_log` | `info` | `RUST_LOG` for the container | 65 + 66 + ## Files 67 + 68 + ``` 69 + ansible/ 70 + ├── ansible.cfg 71 + ├── deploy.yml # install docker + deploy from scratch 72 + ├── update.yml # pull latest image + restart 73 + ├── inventory.example.yml 74 + ├── roles/ 75 + │ ├── docker/ # install docker engine + compose plugin 76 + │ └── lichen/ # sync compose files, render .env, bring up stack 77 + └── group_vars/ 78 + └── .gitkeep # put vault-encrypted vars here 79 + ```
+8
ansible/ansible.cfg
··· 1 + [defaults] 2 + host_key_checking = False 3 + stdout_callback = yaml 4 + inventory = inventory.yml 5 + roles_path = roles 6 + 7 + [ssh_connection] 8 + pipelining = True
+27
ansible/deploy.yml
··· 1 + --- 2 + - name: Deploy a lichen server from scratch 3 + hosts: all 4 + become: true 5 + vars: 6 + lichen_admin_user: admin 7 + lichen_auth_providers: "file,atproto" 8 + lichen_deploy_dir: /srv/lichen 9 + lichen_rust_log: info 10 + pre_tasks: 11 + - name: Require lichen_domain 12 + assert: 13 + that: 14 + - lichen_domain is defined 15 + - lichen_domain | length > 0 16 + fail_msg: "lichen_domain must be set in your inventory" 17 + - name: Require lichen_admin_password 18 + assert: 19 + that: 20 + - lichen_admin_password is defined 21 + - lichen_admin_password | length >= 8 22 + fail_msg: >- 23 + lichen_admin_password must be supplied (8+ chars). 24 + Pass --extra-vars 'lichen_admin_password=...' or use ansible-vault. 25 + roles: 26 + - docker 27 + - lichen
ansible/group_vars/.gitkeep

This is a binary file and will not be displayed.

+13
ansible/inventory.example.yml
··· 1 + all: 2 + hosts: 3 + lichen: 4 + ansible_host: 1.2.3.4 5 + ansible_user: root 6 + # ansible_ssh_private_key_file: ~/.ssh/id_ed25519 7 + vars: 8 + lichen_domain: mydomain.com 9 + lichen_admin_user: admin 10 + # set lichen_admin_password via --extra-vars or ansible-vault 11 + lichen_auth_providers: "file,atproto" 12 + lichen_deploy_dir: /srv/lichen 13 + lichen_rust_log: info
+28
ansible/roles/docker/tasks/main.yml
··· 1 + --- 2 + # install docker engine + compose plugin via the official convenience script. 3 + # this is the lowest-friction option across debian/ubuntu/rhel — no distro-specific 4 + # branching here. 5 + 6 + - name: Check if docker is already installed 7 + command: docker --version 8 + register: docker_installed 9 + failed_when: false 10 + changed_when: false 11 + 12 + - name: Install docker via get.docker.com 13 + shell: | 14 + set -o pipefail 15 + curl -fsSL https://get.docker.com | sh 16 + args: 17 + executable: /bin/bash 18 + when: docker_installed.rc != 0 19 + 20 + - name: Ensure docker service is running and enabled 21 + systemd: 22 + name: docker 23 + state: started 24 + enabled: true 25 + 26 + - name: Verify `docker compose` is available 27 + command: docker compose version 28 + changed_when: false
+52
ansible/roles/lichen/tasks/main.yml
··· 1 + --- 2 + - name: Create deploy directory 3 + file: 4 + path: "{{ lichen_deploy_dir }}" 5 + state: directory 6 + mode: "0755" 7 + 8 + - name: Create bin directory for custom binary overrides 9 + file: 10 + path: "{{ lichen_deploy_dir }}/bin" 11 + state: directory 12 + mode: "0755" 13 + 14 + - name: Sync compose stack files from ../docker-compose 15 + copy: 16 + src: "{{ playbook_dir }}/../docker-compose/{{ item.name }}" 17 + dest: "{{ lichen_deploy_dir }}/{{ item.name }}" 18 + mode: "{{ item.mode }}" 19 + loop: 20 + - { name: docker-compose.yml, mode: "0644" } 21 + - { name: Caddyfile, mode: "0644" } 22 + - { name: entrypoint.sh, mode: "0755" } 23 + 24 + - name: Render .env 25 + template: 26 + src: env.j2 27 + dest: "{{ lichen_deploy_dir }}/.env" 28 + mode: "0600" 29 + no_log: true 30 + 31 + - name: Pull images 32 + command: docker compose pull 33 + args: 34 + chdir: "{{ lichen_deploy_dir }}" 35 + changed_when: false 36 + 37 + - name: Start services 38 + command: docker compose up -d 39 + args: 40 + chdir: "{{ lichen_deploy_dir }}" 41 + changed_when: true 42 + 43 + - name: Wait for app healthcheck 44 + command: docker compose ps --format '{{ "{{.Health}}" }}' app 45 + args: 46 + chdir: "{{ lichen_deploy_dir }}" 47 + register: health 48 + until: "'healthy' in health.stdout" 49 + retries: 12 50 + delay: 5 51 + changed_when: false 52 + failed_when: false
+5
ansible/roles/lichen/templates/env.j2
··· 1 + DOMAIN={{ lichen_domain }} 2 + ADMIN_USER={{ lichen_admin_user }} 3 + ADMIN_PASSWORD={{ lichen_admin_password }} 4 + AUTH_PROVIDERS={{ lichen_auth_providers }} 5 + RUST_LOG={{ lichen_rust_log }}
+66
ansible/update.yml
··· 1 + --- 2 + - name: Update an existing lichen deployment 3 + hosts: all 4 + become: true 5 + vars: 6 + lichen_deploy_dir: /srv/lichen 7 + tasks: 8 + - name: Ensure the deploy directory exists 9 + stat: 10 + path: "{{ lichen_deploy_dir }}/docker-compose.yml" 11 + register: compose_stat 12 + 13 + - name: Fail if the deployment hasn't been set up yet 14 + fail: 15 + msg: >- 16 + {{ lichen_deploy_dir }}/docker-compose.yml not found. 17 + Run deploy.yml first. 18 + when: not compose_stat.stat.exists 19 + 20 + # re-sync the compose/caddy/entrypoint files in case anything changed upstream 21 + - name: Sync compose stack files 22 + copy: 23 + src: "{{ playbook_dir }}/../docker-compose/{{ item.name }}" 24 + dest: "{{ lichen_deploy_dir }}/{{ item.name }}" 25 + mode: "{{ item.mode }}" 26 + loop: 27 + - { name: docker-compose.yml, mode: "0644" } 28 + - { name: Caddyfile, mode: "0644" } 29 + - { name: entrypoint.sh, mode: "0755" } 30 + 31 + # pick up a locally-built custom binary if one is sitting in ../docker-compose/bin/ 32 + - name: Check for a local custom binary 33 + stat: 34 + path: "{{ playbook_dir }}/../docker-compose/bin/lichen-server" 35 + register: local_bin 36 + delegate_to: localhost 37 + become: false 38 + 39 + - name: Ensure bin/ exists on the server 40 + file: 41 + path: "{{ lichen_deploy_dir }}/bin" 42 + state: directory 43 + mode: "0755" 44 + 45 + - name: Upload custom binary if present 46 + copy: 47 + src: "{{ playbook_dir }}/../docker-compose/bin/lichen-server" 48 + dest: "{{ lichen_deploy_dir }}/bin/lichen-server" 49 + mode: "0755" 50 + when: local_bin.stat.exists 51 + 52 + - name: Pull latest images 53 + command: docker compose pull 54 + args: 55 + chdir: "{{ lichen_deploy_dir }}" 56 + changed_when: true 57 + 58 + - name: Restart the stack with the new image 59 + command: docker compose up -d 60 + args: 61 + chdir: "{{ lichen_deploy_dir }}" 62 + changed_when: true 63 + 64 + - name: Prune dangling images 65 + command: docker image prune -f 66 + changed_when: false