memos: deploy to ana-docker
Stock neosmemo/memos:stable, port 5230, SQLite at /opt/docker/conf/memos/data/. Joins traefik-net and ships homepage labels (group=Notes) so it auto-appears on the dashboard via docker discovery — no edit to configs/homepage/services.yaml needed. First-run bootstrap is via the UI: visit http://10.250.50.70:5230 and create the Host account through the sign-up form. Playbook idiom note: docker compose pull lines need the literal block scalar (|) when the grep pattern contains colons — bare-string shell value made YAML parse the colon as a mapping separator and elway choked on first try.
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
# Deploy memos (https://github.com/usememos/memos) — single-container
|
||||||
|
# self-hosted note server — to ana-docker.
|
||||||
|
#
|
||||||
|
# First-boot creates a fresh SQLite DB at /var/opt/memos/memos_prod.db
|
||||||
|
# inside the container (bind-mounted out to ${MEMOS_DATA_DIR} on the
|
||||||
|
# host). Visit http://10.250.50.70:5230 immediately after deploy to
|
||||||
|
# create the Host account.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/elway ana-docker --playbook playbooks/deploy-memos.yaml
|
||||||
|
#
|
||||||
|
# Idempotent — safe to rerun.
|
||||||
|
|
||||||
|
vars:
|
||||||
|
compose_dir: /opt/docker/compose/memos
|
||||||
|
data_dir: /opt/docker/conf/memos/data
|
||||||
|
host_port: "5230"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# ── host-side dirs (sudo only on first run) ─────────────────────────
|
||||||
|
|
||||||
|
- name: Ensure compose dir exists
|
||||||
|
shell: mkdir -p {{ compose_dir }}
|
||||||
|
sudo: true
|
||||||
|
creates: "{{ compose_dir }}"
|
||||||
|
|
||||||
|
- name: Chown compose dir to lkraven
|
||||||
|
shell: chown -R lkraven:lkraven {{ compose_dir }}
|
||||||
|
sudo: true
|
||||||
|
when: "[ \"$(stat -c %U {{ compose_dir }})\" != \"lkraven\" ]"
|
||||||
|
|
||||||
|
- name: Ensure data dir exists
|
||||||
|
shell: mkdir -p {{ data_dir }}
|
||||||
|
sudo: true
|
||||||
|
creates: "{{ data_dir }}"
|
||||||
|
|
||||||
|
- name: Chown data dir to lkraven
|
||||||
|
shell: chown -R lkraven:lkraven {{ data_dir }}
|
||||||
|
sudo: true
|
||||||
|
when: "[ \"$(stat -c %U {{ data_dir }})\" != \"lkraven\" ]"
|
||||||
|
|
||||||
|
# ── deploy compose + env ────────────────────────────────────────────
|
||||||
|
|
||||||
|
- name: Upload compose.yaml
|
||||||
|
upload:
|
||||||
|
src: stacks/memos/compose.yaml
|
||||||
|
dest: "{{ compose_dir }}/compose.yaml"
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Seed .env from template (only if absent)
|
||||||
|
upload:
|
||||||
|
src: stacks/memos/.env.example
|
||||||
|
dest: "{{ compose_dir }}/.env"
|
||||||
|
mode: "0644"
|
||||||
|
when: "[ ! -f {{ compose_dir }}/.env ]"
|
||||||
|
|
||||||
|
# ── bring up + wait ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
- name: docker compose pull (gets latest stable tag)
|
||||||
|
shell: |
|
||||||
|
set -o pipefail
|
||||||
|
cd {{ compose_dir }} && docker compose pull 2>&1 \
|
||||||
|
| grep -vE '^(Status|Pulling) |Downloaded'
|
||||||
|
|
||||||
|
- name: docker compose up -d
|
||||||
|
shell: cd {{ compose_dir }} && docker compose up -d
|
||||||
|
|
||||||
|
- name: Wait for / to respond
|
||||||
|
shell: |
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
curl -sf -o /dev/null --max-time 3 http://localhost:{{ host_port }}/ && exit 0
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
changed_when: "false"
|
||||||
|
|
||||||
|
verify:
|
||||||
|
- name: / returns 200
|
||||||
|
shell: curl -sf -o /dev/null http://localhost:{{ host_port }}/
|
||||||
|
changed_when: "false"
|
||||||
|
|
||||||
|
- name: Container is running
|
||||||
|
shell: docker inspect memos --format '{{.State.Status}}' | grep -q running
|
||||||
|
changed_when: "false"
|
||||||
|
|
||||||
|
- name: Container is on traefik-net (so homepage docker discovery sees it)
|
||||||
|
shell: docker inspect memos --format '{{json .NetworkSettings.Networks}}' | grep -q traefik-net
|
||||||
|
changed_when: "false"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# memos stack tunables. Copy to `.env` on ana-docker before deploying.
|
||||||
|
|
||||||
|
# Image tag — `stable` tracks upstream's stable channel. Pin a specific
|
||||||
|
# version (e.g. `0.24.0`) before any production cutover so a stable
|
||||||
|
# bump doesn't surprise you on the next pull.
|
||||||
|
MEMOS_TAG=stable
|
||||||
|
|
||||||
|
# ── network ──────────────────────────────────────────────────────────
|
||||||
|
# Host port (container listens on 5230 internally).
|
||||||
|
# Reservations on ana-docker (sample): 5001 Dockge, 7878 task-board,
|
||||||
|
# 8080 Miniflux, 8181 news-digest. 5230 is memos' canonical port and
|
||||||
|
# was free at deploy time.
|
||||||
|
MEMOS_PORT=5230
|
||||||
|
MEMOS_BIND=0.0.0.0
|
||||||
|
|
||||||
|
# ── runtime ──────────────────────────────────────────────────────────
|
||||||
|
MEMOS_TZ=America/Los_Angeles
|
||||||
|
|
||||||
|
# ── persistent storage on the host ───────────────────────────────────
|
||||||
|
# SQLite DB + uploaded resource files. Backed up via the host's restic
|
||||||
|
# profile (memos_prod.db is small; resources/ can grow with attachments).
|
||||||
|
MEMOS_DATA_DIR=/opt/docker/conf/memos/data
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# memos
|
||||||
|
|
||||||
|
[memos](https://github.com/usememos/memos) — lightweight self-hosted
|
||||||
|
note / memo server. Single Go binary, SQLite by default, MIT license.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| host | `ana-docker` |
|
||||||
|
| port | `5230` |
|
||||||
|
| image | `neosmemo/memos:stable` |
|
||||||
|
| data | `/opt/docker/conf/memos/data/` (SQLite + uploaded resources) |
|
||||||
|
| homepage group | `Notes` |
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/elway ana-docker --playbook playbooks/deploy-memos.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
First run: visit <http://10.250.50.70:5230>, create the Host account
|
||||||
|
through the sign-up form. Subsequent users register through the same
|
||||||
|
form (or you disable open sign-up via Settings → Workspace).
|
||||||
|
|
||||||
|
## Tunables
|
||||||
|
|
||||||
|
See `.env.example` — copy to `.env` on the host (lives at
|
||||||
|
`/opt/docker/compose/memos/.env`, gitignored). Knobs are intentionally
|
||||||
|
minimal: image tag, host port, TZ, data dir. Memos itself reads its
|
||||||
|
config from the SQLite DB (workspace settings via the UI).
|
||||||
|
|
||||||
|
## Backups
|
||||||
|
|
||||||
|
Data dir is `/opt/docker/conf/memos/data/`. The host's restic profile
|
||||||
|
covers `/opt/docker/conf/` so the SQLite DB + uploaded resources go
|
||||||
|
along for free. SQLite snapshot is consistent under restic's COW reads
|
||||||
|
since memos uses WAL mode + fsync; for a guaranteed-quiescent backup
|
||||||
|
take a `docker exec memos sqlite3 /var/opt/memos/memos_prod.db ".backup /var/opt/memos/snapshot.db"`
|
||||||
|
in the restic pre-backup hook.
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# memos — lightweight self-hosted note / memo server.
|
||||||
|
# Upstream: https://github.com/usememos/memos (MIT, Go binary).
|
||||||
|
#
|
||||||
|
# Single container, SQLite by default (file lives in the bind-mounted
|
||||||
|
# data dir below). Auth bootstraps via the UI on first run — first
|
||||||
|
# user created becomes Host. No env vars required for a basic install.
|
||||||
|
#
|
||||||
|
# All tunables live in .env — edit that, not this file.
|
||||||
|
|
||||||
|
services:
|
||||||
|
memos:
|
||||||
|
image: neosmemo/memos:${MEMOS_TAG:-stable}
|
||||||
|
container_name: memos
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${MEMOS_BIND:-0.0.0.0}:${MEMOS_PORT}:5230"
|
||||||
|
volumes:
|
||||||
|
# SQLite DB + uploaded resource files. Bind mount on the host
|
||||||
|
# rather than a named volume so backups + manual inspection are
|
||||||
|
# straightforward.
|
||||||
|
- ${MEMOS_DATA_DIR}:/var/opt/memos
|
||||||
|
environment:
|
||||||
|
- TZ=${MEMOS_TZ:-America/Los_Angeles}
|
||||||
|
healthcheck:
|
||||||
|
# Image is alpine-based and ships busybox wget. --spider keeps it
|
||||||
|
# cheap (HEAD request) and 127.0.0.1 dodges the IPv6-first
|
||||||
|
# localhost trap that bit news-digest + chatterbox earlier.
|
||||||
|
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:5230/ || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
networks:
|
||||||
|
- tnet
|
||||||
|
labels:
|
||||||
|
- homepage.group=Notes
|
||||||
|
- homepage.name=Memos
|
||||||
|
- homepage.icon=mdi-note-text-outline
|
||||||
|
- homepage.description=Self-hosted note + memo server (ana-docker)
|
||||||
|
- homepage.href=http://10.250.50.70:${MEMOS_PORT}
|
||||||
|
|
||||||
|
networks:
|
||||||
|
tnet:
|
||||||
|
name: traefik-net
|
||||||
|
external: true
|
||||||
Reference in New Issue
Block a user