From 510587029174ffdaf5c14417e174503e31af7b63 Mon Sep 17 00:00:00 2001 From: Anarion Date: Wed, 9 Oct 2024 13:48:02 +0200 Subject: [PATCH] :sparkles: Add Richy --- README.md | 1 + nas.yml | 4 + roles/richy/defaults/main.yml | 72 +++++ roles/richy/molecule/default/molecule.yml | 16 ++ roles/richy/molecule/default/side_effect.yml | 10 + roles/richy/molecule/default/verify.yml | 68 +++++ .../richy/molecule/default/verify_stopped.yml | 68 +++++ roles/richy/requirements.yml | 1 + roles/richy/tasks/main.yml | 258 ++++++++++++++++++ roles/richy/templates/nginx.conf | 35 +++ website/docs/applications/other/richy.md | 27 ++ 11 files changed, 560 insertions(+) create mode 100644 roles/richy/defaults/main.yml create mode 100644 roles/richy/molecule/default/molecule.yml create mode 100644 roles/richy/molecule/default/side_effect.yml create mode 100644 roles/richy/molecule/default/verify.yml create mode 100644 roles/richy/molecule/default/verify_stopped.yml create mode 120000 roles/richy/requirements.yml create mode 100644 roles/richy/tasks/main.yml create mode 100644 roles/richy/templates/nginx.conf create mode 100644 website/docs/applications/other/richy.md diff --git a/README.md b/README.md index 0f823f2804..eca3a69746 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,7 @@ If you have a spare domain name you can configure applications to be accessible * [Readeck](https://codeberg.org/readeck/readeck) - a simple web application that lets you save the precious readable content of web pages you like and want to keep forever. * [Releasebell](https://github.com/anarion80/releasebell) - Starred GitHub repositories notifications * [Restic](https://restic.net/) - A modern backup program +* [Richy](https://gitlab.com/imn1/richy) - Application that helps you to manage your investing portfolio * [Romm](https://github.com/zurdi15/romm) - A game library manager focused on retro gaming * [Route53 DDNS](https://crazymax.dev/ddns-route53/) - Automatically update AWS Route53 with your IP address * [RSS-Bridge](https://rss-bridge.github.io/rss-bridge/) - The RSS feed for websites missing it diff --git a/nas.yml b/nas.yml index fb05607be0..9f44a0f6d7 100644 --- a/nas.yml +++ b/nas.yml @@ -793,6 +793,10 @@ tags: - restic + - role: richy + tags: + - richy + - role: romm tags: - romm diff --git a/roles/richy/defaults/main.yml b/roles/richy/defaults/main.yml new file mode 100644 index 0000000000..e020a9baa6 --- /dev/null +++ b/roles/richy/defaults/main.yml @@ -0,0 +1,72 @@ +--- +richy_enabled: false +richy_available_externally: false + +# directories +richy_data_directory: "{{ docker_home }}/richy" + +# network +richy_port: "8176" +richy_hostname: "richy" +richy_network_name: "richy" + +# specs +richy_memory: 1g +richy_db_memory: 1g +richy_nginx_memory: 1g +richy_redis_memory: 1g +richy_worker_memory: 1g +richy_worker_slow_memory: 1g +richy_worker_fast_memory: 1g +richy_beat_memory: 1g + +# docker +richy_container_name: richy +richy_image_name: "n1cz/richy" +richy_image_version: latest +richy_db_container_name: richy-db +richy_db_image_name: "postgres" +richy_db_image_version: 16-alpine +richy_nginx_container_name: richy-nginx +richy_nginx_image_name: "nginx" +richy_nginx_image_version: 1.23-alpine +richy_redis_container_name: richy-redis +richy_redis_image_name: redis +richy_redis_image_version: 7-alpine +richy_worker_container_name: richy-worker +richy_worker_image_name: "n1cz/richy" +richy_worker_image_version: latest +richy_worker_slow_container_name: richy-worker-slow +richy_worker_slow_image_name: "n1cz/richy" +richy_worker_slow_image_version: latest +richy_worker_fast_container_name: richy-worker-fast +richy_worker_fast_image_name: "n1cz/richy" +richy_worker_fast_image_version: latest +richy_beat_container_name: richy-beat +richy_beat_image_name: "n1cz/richy" +richy_beat_image_version: latest +richy_user_id: "1000" +richy_group_id: "1000" + +# richy +richy_db_user: richy +richy_db_name: richy +richy_db_pass: tralala256 +richy_sentry_url: "" +richy_sentry_enable: "false" +richy_base_url: "https://{{ richy_hostname }}.{{ ansible_nas_domain }}" +richy_domain: "{{ richy_hostname }}.{{ ansible_nas_domain }}" +richy_redis_url: "{{ richy_redis_container_name }}:6379" + +# main env dictionary for the container and for merging with optional environment variables +richy_env: + DB_PASSWORD: "{{ richy_db_pass }}" + DB_HOST: "{{ richy_db_container_name }}" + DB_NAME: "{{ richy_db_name }}" + DB_USER: "{{ richy_db_user }}" + SENTRY_URL: "{{ richy_sentry_url }}" + SENTRY_ENABLE: "{{ richy_sentry_enable }}" + TZ: "{{ ansible_nas_timezone }}" + BASE_URL: "{{ richy_base_url }}" + DOMAIN: "{{ richy_domain }}" + REDIS_URL: "{{ richy_redis_url }}" diff --git a/roles/richy/molecule/default/molecule.yml b/roles/richy/molecule/default/molecule.yml new file mode 100644 index 0000000000..3fd4c3080f --- /dev/null +++ b/roles/richy/molecule/default/molecule.yml @@ -0,0 +1,16 @@ +--- +provisioner: + inventory: + group_vars: + all: + richy_enabled: true + richy_data_directory: "/tmp/richy" +platforms: + - name: instance + image: geerlingguy/docker-ubuntu2204-ansible:latest + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + - /var/run/docker.sock:/var/run/docker.sock + - /tmp:/tmp:rw + privileged: true + pre_build_image: true diff --git a/roles/richy/molecule/default/side_effect.yml b/roles/richy/molecule/default/side_effect.yml new file mode 100644 index 0000000000..8644706e73 --- /dev/null +++ b/roles/richy/molecule/default/side_effect.yml @@ -0,0 +1,10 @@ +--- +- name: Stop + hosts: all + become: true + tasks: + - name: "Include {{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }} role" + ansible.builtin.include_role: + name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" + vars: + richy_enabled: false diff --git a/roles/richy/molecule/default/verify.yml b/roles/richy/molecule/default/verify.yml new file mode 100644 index 0000000000..c8ed1b9190 --- /dev/null +++ b/roles/richy/molecule/default/verify.yml @@ -0,0 +1,68 @@ +--- +- name: Verify + hosts: all + gather_facts: false + tasks: + - name: Include vars + ansible.builtin.include_vars: + file: ../../defaults/main.yml + + - name: Get richy db container state + community.docker.docker_container: + name: "{{ richy_db_container_name }}" + register: result_db + + - name: Get richy redis container state + community.docker.docker_container: + name: "{{ richy_redis_container_name }}" + register: result_redis + + - name: Get richy nginx container state + community.docker.docker_container: + name: "{{ richy_nginx_container_name }}" + register: result_nginx + + - name: Get richy worker container state + community.docker.docker_container: + name: "{{ richy_worker_container_name }}" + register: result_worker + + - name: Get richy worker slow container state + community.docker.docker_container: + name: "{{ richy_worker_slow_container_name }}" + register: result_worker_slow + + - name: Get richy worker fast container state + community.docker.docker_container: + name: "{{ richy_worker_fast_container_name }}" + register: result_worker_fast + + - name: Get richy beat container state + community.docker.docker_container: + name: "{{ richy_beat_container_name }}" + register: result_beat + + - name: Get richy container state + community.docker.docker_container: + name: "{{ richy_container_name }}" + register: result + + - name: Check if richy containers are running + ansible.builtin.assert: + that: + - result_db.container['State']['Status'] == "running" + - result_db.container['State']['Restarting'] == false + - result_redis.container['State']['Status'] == "running" + - result_redis.container['State']['Restarting'] == false + - result_nginx.container['State']['Status'] == "running" + - result_nginx.container['State']['Restarting'] == false + - result_worker.container['State']['Status'] == "running" + - result_worker.container['State']['Restarting'] == false + - result_worker_slow.container['State']['Status'] == "running" + - result_worker_slow.container['State']['Restarting'] == false + - result_worker_fast.container['State']['Status'] == "running" + - result_worker_fast.container['State']['Restarting'] == false + - result_beat.container['State']['Status'] == "running" + - result_beat.container['State']['Restarting'] == false + - result.container['State']['Status'] == "running" + - result.container['State']['Restarting'] == false diff --git a/roles/richy/molecule/default/verify_stopped.yml b/roles/richy/molecule/default/verify_stopped.yml new file mode 100644 index 0000000000..8f96d24894 --- /dev/null +++ b/roles/richy/molecule/default/verify_stopped.yml @@ -0,0 +1,68 @@ +--- +- name: Verify + hosts: all + gather_facts: false + tasks: + - name: Include vars + ansible.builtin.include_vars: + file: ../../defaults/main.yml + + - name: Try and stop and remove richy + community.docker.docker_container: + name: "{{ richy_container_name }}" + state: absent + register: result + + - name: Try and stop and remove richy db + community.docker.docker_container: + name: "{{ richy_db_container_name }}" + state: absent + register: result_db + + - name: Try and stop and remove richy redis + community.docker.docker_container: + name: "{{ richy_redis_container_name }}" + state: absent + register: result_redis + + - name: Try and stop and remove richy nginx + community.docker.docker_container: + name: "{{ richy_nginx_container_name }}" + state: absent + register: result_nginx + + - name: Try and stop and remove richy worker + community.docker.docker_container: + name: "{{ richy_worker_container_name }}" + state: absent + register: result_worker + + - name: Try and stop and remove richy worker slow + community.docker.docker_container: + name: "{{ richy_worker_slow_container_name }}" + state: absent + register: result_worker_slow + + - name: Try and stop and remove richy worker fast + community.docker.docker_container: + name: "{{ richy_worker_fast_container_name }}" + state: absent + register: result_worker_fast + + - name: Try and stop and remove richy beat + community.docker.docker_container: + name: "{{ richy_beat_container_name }}" + state: absent + register: result_beat + + - name: Check if richy is stopped + ansible.builtin.assert: + that: + - not result.changed + - not result_db.changed + - not result_redis.changed + - not result_nginx.changed + - not result_worker.changed + - not result_worker_slow.changed + - not result_worker_fast.changed + - not result_beat.changed diff --git a/roles/richy/requirements.yml b/roles/richy/requirements.yml new file mode 120000 index 0000000000..9a736435ab --- /dev/null +++ b/roles/richy/requirements.yml @@ -0,0 +1 @@ +../../requirements.yml \ No newline at end of file diff --git a/roles/richy/tasks/main.yml b/roles/richy/tasks/main.yml new file mode 100644 index 0000000000..1139b3be47 --- /dev/null +++ b/roles/richy/tasks/main.yml @@ -0,0 +1,258 @@ +--- +- name: Start Richy + block: + - name: Create Richy Directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ richy_user_id }}" + group: "{{ richy_group_id }}" + with_items: + - "{{ richy_data_directory }}" + + - name: Create Richy mount directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ richy_user_id }}" + group: "{{ richy_group_id }}" + with_items: + - "{{ richy_data_directory }}/static" + - "{{ richy_data_directory }}/config" + - "{{ richy_data_directory }}/media" + - "{{ richy_data_directory }}/logs" + + - name: Copy Richy Nginx config + ansible.builtin.template: + src: nginx.conf + dest: "{{ richy_data_directory }}/config/nginx.conf" + + - name: Create Richy Network + community.docker.docker_network: + name: "{{ richy_network_name }}" + + - name: Create Richy Database Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ richy_db_container_name }}" + image: "{{ richy_db_image_name }}:{{ richy_db_image_version }}" + pull: true + networks: + - name: "{{ richy_network_name }}" + network_mode: "{{ richy_network_name }}" + exposed_ports: + - 5432 + volumes: + - "{{ richy_data_directory }}/postgres-data:/var/lib/postgresql/data/:rw" + env: + POSTGRES_PASSWORD: "{{ richy_db_pass }}" + POSTGRES_USER: "{{ richy_db_user }}" + POSTGRES_DB: "{{ richy_db_name }}" + labels: + traefik.enable: "false" + restart_policy: unless-stopped + memory: "{{ richy_db_memory }}" + healthcheck: + test: "pg_isready -U {{ richy_db_user }}" + interval: 10s + timeout: 5s + retries: 5 + + - name: Create Richy Redis Docker Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ richy_redis_container_name }}" + image: "{{ richy_redis_image_name }}:{{ richy_redis_image_version }}" + pull: true + exposed_ports: + - 6379 + networks: + - name: "{{ richy_network_name }}" + network_mode: "{{ richy_network_name }}" + restart_policy: unless-stopped + memory: "{{ richy_redis_memory }}" + labels: + traefik.enable: "false" + healthcheck: + test: "redis-cli ping" + interval: 10s + timeout: 5s + retries: 5 + + - name: Create Richy Docker Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ richy_container_name }}" + image: "{{ richy_image_name }}:{{ richy_image_version }}" + pull: true + volumes: + - "{{ richy_data_directory }}/media:/var/www/app/richy/media" + - "{{ richy_data_directory }}/logs:/var/www/app/logs" + - "{{ richy_data_directory }}/static:/var/www/app/richy/static" + networks: + - name: "{{ richy_network_name }}" + network_mode: "{{ richy_network_name }}" + exposed_ports: + - 8000 + env: "{{ richy_optional_env | default({}) | combine(richy_env) }}" + restart_policy: unless-stopped + memory: "{{ richy_memory }}" + labels: + traefik.enable: "false" + + - name: Create Richy Nginx Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ richy_nginx_container_name }}" + image: "{{ richy_nginx_image_name }}:{{ richy_nginx_image_version }}" + pull: true + ports: + - "{{ richy_port }}:80" + volumes: + - "{{ richy_data_directory }}/config/nginx.conf:/etc/nginx/conf.d/default.conf:rw" + - "{{ richy_data_directory }}/static:/static:ro" + - "{{ richy_data_directory }}/media:/media:ro" + networks: + - name: "{{ richy_network_name }}" + network_mode: "{{ richy_network_name }}" + restart_policy: unless-stopped + memory: "{{ richy_nginx_memory }}" + labels: + traefik.enable: "{{ richy_available_externally | string }}" + traefik.http.routers.richy.rule: "Host(`{{ richy_hostname }}.{{ ansible_nas_domain }}`)" + traefik.http.routers.richy.tls.certresolver: "letsencrypt" + traefik.http.routers.richy.tls.domains[0].main: "{{ ansible_nas_domain }}" + traefik.http.routers.richy.tls.domains[0].sans: "*.{{ ansible_nas_domain }}" + traefik.http.services.richy.loadbalancer.server.port: "80" + healthcheck: + test: "service nginx status" + interval: 10s + timeout: 5s + retries: 5 + + - name: Create Richy Worker Docker Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ richy_worker_container_name }}" + image: "{{ richy_worker_image_name }}:{{ richy_worker_image_version }}" + pull: true + command: celery -A richy worker -c 2 -Q celery -O fair -l error --max-tasks-per-child=3 + env: "{{ richy_optional_env | default({}) | combine(richy_env) }}" + networks: + - name: "{{ richy_network_name }}" + network_mode: "{{ richy_network_name }}" + volumes: + - "{{ richy_data_directory }}/logs:/var/www/app/logs" + - "{{ richy_data_directory }}/media:/var/www/app/richy/media" + restart_policy: unless-stopped + memory: "{{ richy_worker_memory }}" + labels: + traefik.enable: "false" + healthcheck: + test: celery -A {{ richy_container_name }} inspect ping + interval: 10s + timeout: 5s + retries: 5 + + - name: Create Richy Worker Slow Docker Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ richy_worker_slow_container_name }}" + image: "{{ richy_worker_slow_image_name }}:{{ richy_worker_slow_image_version }}" + pull: true + command: celery -A richy worker -c 1 -Q slow -O fair -l error --max-tasks-per-child=3 + env: "{{ richy_optional_env | default({}) | combine(richy_env) }}" + networks: + - name: "{{ richy_network_name }}" + network_mode: "{{ richy_network_name }}" + volumes: + - "{{ richy_data_directory }}/logs:/var/www/app/logs" + - "{{ richy_data_directory }}/media:/var/www/app/richy/media" + restart_policy: unless-stopped + memory: "{{ richy_worker_slow_memory }}" + labels: + traefik.enable: "false" + healthcheck: + test: celery -A {{ richy_container_name }} inspect ping + interval: 10s + timeout: 5s + retries: 5 + + - name: Create Richy Worker Fast Docker Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ richy_worker_fast_container_name }}" + image: "{{ richy_worker_fast_image_name }}:{{ richy_worker_fast_image_version }}" + pull: true + command: celery -A richy worker -c 2 -Q fast -O fair -l error --max-tasks-per-child=3 + env: "{{ richy_optional_env | default({}) | combine(richy_env) }}" + networks: + - name: "{{ richy_network_name }}" + network_mode: "{{ richy_network_name }}" + volumes: + - "{{ richy_data_directory }}/logs:/var/www/app/logs" + - "{{ richy_data_directory }}/media:/var/www/app/richy/media" + restart_policy: unless-stopped + memory: "{{ richy_worker_fast_memory }}" + labels: + traefik.enable: "false" + healthcheck: + test: celery -A {{ richy_container_name }} inspect ping + interval: 10s + timeout: 5s + retries: 5 + + - name: Create Richy Beat Docker Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ richy_beat_container_name }}" + image: "{{ richy_beat_image_name }}:{{ richy_beat_image_version }}" + pull: true + command: /var/www/app/wait_for_it.sh -t 120 -s richy:8000 -- celery -A richy beat -l error -S django + env: "{{ richy_optional_env | default({}) | combine(richy_env) }}" + volumes: + - "{{ richy_data_directory }}/logs:/var/www/app/logs" + networks: + - name: "{{ richy_network_name }}" + network_mode: "{{ richy_network_name }}" + restart_policy: unless-stopped + memory: "{{ richy_beat_memory }}" + labels: + traefik.enable: "false" + when: richy_enabled is true + +- name: Stop Richy + block: + - name: Stop Richy + community.docker.docker_container: + name: "{{ richy_container_name }}" + state: absent + - name: Stop Richy DB + community.docker.docker_container: + name: "{{ richy_db_container_name }}" + state: absent + - name: Stop Richy Nginx + community.docker.docker_container: + name: "{{ richy_nginx_container_name }}" + state: absent + - name: Stop Richy Redis + community.docker.docker_container: + name: "{{ richy_redis_container_name }}" + state: absent + - name: Stop Richy Worker + community.docker.docker_container: + name: "{{ richy_worker_container_name }}" + state: absent + - name: Stop Richy Worker Slow + community.docker.docker_container: + name: "{{ richy_worker_slow_container_name }}" + state: absent + - name: Stop Richy Worker Fast + community.docker.docker_container: + name: "{{ richy_worker_fast_container_name }}" + state: absent + - name: Stop Richy Beat + community.docker.docker_container: + name: "{{ richy_beat_container_name }}" + state: absent + when: richy_enabled is false diff --git a/roles/richy/templates/nginx.conf b/roles/richy/templates/nginx.conf new file mode 100644 index 0000000000..c249d2c7cb --- /dev/null +++ b/roles/richy/templates/nginx.conf @@ -0,0 +1,35 @@ +server { + + listen 80; + + client_max_body_size 50M; + proxy_read_timeout 300; + keepalive_timeout 300; + + location /static/ { + alias /static/; + } + + location /media/ { + alias /media/; + } + + location ~* favicon|android-chrome|safari-pinned-tab|apple-touch-icon|ms-icon|browserconfig.xml|manifest { + root /static/images/favicon; + } + + location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + + # auth_basic "Restricted access bitch!"; + # auth_basic_user_file /etc/nginx/.htpasswd; + + if (!-f $request_filename) { + proxy_pass http://{{ richy_container_name }}:8000; + break; + } + } +} diff --git a/website/docs/applications/other/richy.md b/website/docs/applications/other/richy.md new file mode 100644 index 0000000000..925dfa61d1 --- /dev/null +++ b/website/docs/applications/other/richy.md @@ -0,0 +1,27 @@ +--- +title: "Richy" +description: "Application that helps you to manage your investing portfolio" +--- + +Homepage: [https://richy.de/](https://richy.de/) + +### What Richy is + +- a (passive) portfolio manager +- market news hub +- a tool that aggregates information that helps you form ideas +- much better than your excel sheets +- quite documented + +### What Richy is not + +- an investing platform like RobinHood +- an app that gives you investing advice +- a trading bot +- a smart app with some kind of AI that tries to predict market + +## Usage + +Set `richy_enabled: true` in your `inventories//group_vars/nas.yml` file. Set all relevant `richy_*` env variables as defined in `defaults\main.yml`. + +Richy web interface can be found at [http://ansible_nas_host_or_ip:8176](http://ansible_nas_host_or_ip:8176). Wait for the application to initialize and then log in as: `admin@test.com`, password `test`.