- name: Deploy Memos on memos-box hosts: memos_box_local become: yes vars_files: - ../../infra_vars.yml - ../../services_config.yml - ../../infra_secrets.yml - ./memos_vars.yml vars: memos_subdomain: "{{ subdomains.memos }}" memos_domain: "{{ memos_subdomain }}.{{ root_domain }}" tasks: - name: Ensure required packages are installed apt: name: - wget - tar state: present update_cache: true - name: Create memos system user user: name: "{{ memos_user }}" system: yes shell: /bin/false home: "{{ memos_data_dir }}" create_home: no comment: "Memos Service" - name: Create memos data directory file: path: "{{ memos_data_dir }}" state: directory owner: "{{ memos_user }}" group: "{{ memos_user }}" mode: '0750' - name: Create memos config directory file: path: "{{ memos_config_dir }}" state: directory owner: root group: root mode: '0755' - name: Download memos binary archive get_url: url: "{{ memos_url }}" dest: "/tmp/memos.tar.gz" mode: '0644' - name: Extract memos binary unarchive: src: "/tmp/memos.tar.gz" dest: "/tmp" remote_src: yes - name: Move memos binary to /usr/local/bin copy: src: "/tmp/memos" dest: "{{ memos_bin_path }}" remote_src: yes mode: '0755' owner: root group: root - name: Clean up temporary files file: path: "{{ item }}" state: absent loop: - /tmp/memos.tar.gz - /tmp/memos - name: Create memos environment file copy: dest: "{{ memos_config_dir }}/memos.env" content: | MEMOS_MODE=prod MEMOS_ADDR=0.0.0.0 MEMOS_PORT={{ memos_port }} MEMOS_DATA={{ memos_data_dir }} MEMOS_DRIVER=sqlite owner: root group: root mode: '0644' notify: Restart memos - name: Create memos systemd service copy: dest: /etc/systemd/system/memos.service content: | [Unit] Description=Memos - A privacy-first, lightweight note-taking service After=network.target [Service] Type=simple User={{ memos_user }} Group={{ memos_user }} WorkingDirectory={{ memos_data_dir }} EnvironmentFile={{ memos_config_dir }}/memos.env ExecStart={{ memos_bin_path }} Restart=always RestartSec=3 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target owner: root group: root mode: '0644' notify: Restart memos - name: Reload systemd daemon systemd: daemon_reload: yes - name: Enable and start memos service systemd: name: memos enabled: yes state: started - name: Wait for memos to be ready uri: url: "http://127.0.0.1:{{ memos_port }}/healthz" method: GET status_code: 200 register: memos_health retries: 10 delay: 3 until: memos_health.status == 200 - name: Display memos status debug: msg: "Memos is running on port {{ memos_port }}. Access via Tailscale at http://{{ memos_tailscale_hostname }}:{{ memos_port }}" handlers: - name: Restart memos systemd: name: memos state: restarted - name: Configure Caddy reverse proxy for Memos on vipy (proxying via Tailscale) hosts: vipy become: yes vars_files: - ../../infra_vars.yml - ../../services_config.yml - ../../infra_secrets.yml - ./memos_vars.yml vars: memos_subdomain: "{{ subdomains.memos }}" caddy_sites_dir: "{{ caddy_sites_dir }}" memos_domain: "{{ memos_subdomain }}.{{ root_domain }}" uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" tasks: - name: Ensure Caddy sites-enabled directory exists file: path: "{{ caddy_sites_dir }}" state: directory owner: root group: root mode: '0755' - name: Ensure Caddyfile includes import directive for sites-enabled lineinfile: path: /etc/caddy/Caddyfile line: 'import sites-enabled/*' insertafter: EOF state: present backup: yes - name: Create Caddy reverse proxy configuration for memos (via Tailscale) copy: dest: "{{ caddy_sites_dir }}/memos.conf" content: | {{ memos_domain }} { reverse_proxy {{ memos_tailscale_hostname }}:{{ memos_port }} { # Use Tailscale MagicDNS to resolve the upstream hostname transport http { resolvers 100.100.100.100 } } } owner: root group: root mode: '0644' - name: Reload Caddy to apply new config command: systemctl reload caddy - name: Create Uptime Kuma monitor setup script for Memos delegate_to: localhost become: no copy: dest: /tmp/setup_memos_monitor.py content: | #!/usr/bin/env python3 import sys import traceback import yaml from uptime_kuma_api import UptimeKumaApi, MonitorType try: # Load configs with open('/tmp/ansible_memos_config.yml', 'r') as f: config = yaml.safe_load(f) url = config['uptime_kuma_url'] username = config['username'] password = config['password'] monitor_url = config['monitor_url'] monitor_name = config['monitor_name'] # Connect to Uptime Kuma api = UptimeKumaApi(url, timeout=30) api.login(username, password) # Get all monitors monitors = api.get_monitors() # Find or create "services" group group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None) if not group: group_result = api.add_monitor(type='group', name='services') # Refresh to get the group with id monitors = api.get_monitors() group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None) # Check if monitor already exists existing_monitor = None for monitor in monitors: if monitor.get('name') == monitor_name: existing_monitor = monitor break # Get ntfy notification ID notifications = api.get_notifications() ntfy_notification_id = None for notif in notifications: if notif.get('type') == 'ntfy': ntfy_notification_id = notif.get('id') break if existing_monitor: print(f"Monitor '{monitor_name}' already exists (ID: {existing_monitor['id']})") print("Skipping - monitor already configured") else: print(f"Creating monitor '{monitor_name}'...") api.add_monitor( type=MonitorType.HTTP, name=monitor_name, url=monitor_url, parent=group['id'], interval=60, maxretries=3, retryInterval=60, notificationIDList={ntfy_notification_id: True} if ntfy_notification_id else {} ) api.disconnect() print("SUCCESS") except Exception as e: error_msg = str(e) if str(e) else repr(e) print(f"ERROR: {error_msg}", file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1) mode: '0755' - name: Create temporary config for monitor setup delegate_to: localhost become: no copy: dest: /tmp/ansible_memos_config.yml content: | uptime_kuma_url: "{{ uptime_kuma_api_url }}" username: "{{ uptime_kuma_username }}" password: "{{ uptime_kuma_password }}" monitor_url: "https://{{ memos_domain }}/healthz" monitor_name: "Memos" mode: '0644' - name: Run Uptime Kuma monitor setup command: python3 /tmp/setup_memos_monitor.py delegate_to: localhost become: no register: monitor_setup changed_when: "'SUCCESS' in monitor_setup.stdout" ignore_errors: yes - name: Clean up temporary files delegate_to: localhost become: no file: path: "{{ item }}" state: absent loop: - /tmp/setup_memos_monitor.py - /tmp/ansible_memos_config.yml