From 6df5549161977a4d0beebc07b1dc0bcaf448c32b Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Wed, 29 Apr 2026 13:27:12 -0700 Subject: [PATCH] memos: deploy to ana-docker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- playbooks/deploy-memos.yaml | 88 +++++++++++++++++++++++++++++++++++++ stacks/memos/.env.example | 22 ++++++++++ stacks/memos/README.md | 38 ++++++++++++++++ stacks/memos/compose.yaml | 45 +++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 playbooks/deploy-memos.yaml create mode 100644 stacks/memos/.env.example create mode 100644 stacks/memos/README.md create mode 100644 stacks/memos/compose.yaml diff --git a/playbooks/deploy-memos.yaml b/playbooks/deploy-memos.yaml new file mode 100644 index 0000000..207fcac --- /dev/null +++ b/playbooks/deploy-memos.yaml @@ -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" diff --git a/stacks/memos/.env.example b/stacks/memos/.env.example new file mode 100644 index 0000000..2ae09d1 --- /dev/null +++ b/stacks/memos/.env.example @@ -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 diff --git a/stacks/memos/README.md b/stacks/memos/README.md new file mode 100644 index 0000000..ac202ee --- /dev/null +++ b/stacks/memos/README.md @@ -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 , 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. diff --git a/stacks/memos/compose.yaml b/stacks/memos/compose.yaml new file mode 100644 index 0000000..f8dc4f2 --- /dev/null +++ b/stacks/memos/compose.yaml @@ -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