From aa5ada96c8ec3b2f3da6f79bd33a975692a1c946 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Fri, 7 Mar 2025 21:42:45 +0100 Subject: [PATCH] feat(linkding): Add linkding stack Bookmarking software similar to shaarli but a bit more featureful. And not written in php, thankfully. --- roles/linkding/defaults/main.yml | 19 +++++++ roles/linkding/handlers/main.yml | 52 ++++++++++++++++++++ roles/linkding/meta/main.yml | 11 +++++ roles/linkding/tasks/main.yml | 23 +++++++++ roles/linkding/templates/docker-stack.yml.j2 | 46 +++++++++++++++++ roles/linkding/templates/upstream.json.j2 | 38 ++++++++++++++ roles/linkding/vars/main.yml | 6 +++ site.yml | 5 ++ 8 files changed, 200 insertions(+) create mode 100644 roles/linkding/defaults/main.yml create mode 100644 roles/linkding/handlers/main.yml create mode 100644 roles/linkding/meta/main.yml create mode 100644 roles/linkding/tasks/main.yml create mode 100644 roles/linkding/templates/docker-stack.yml.j2 create mode 100644 roles/linkding/templates/upstream.json.j2 create mode 100644 roles/linkding/vars/main.yml diff --git a/roles/linkding/defaults/main.yml b/roles/linkding/defaults/main.yml new file mode 100644 index 0000000..b675b38 --- /dev/null +++ b/roles/linkding/defaults/main.yml @@ -0,0 +1,19 @@ +--- +linkding_version: latest-plus # plus contains self-archiving possibilities with singlefile + +linkding_upstream_file_dir: "{{ docker_stack_files_dir }}/{{ stack_name }}" + +linkding_use_https: true + +linkding_autoupdate: true + +# the subdomain link linkding will be reachable under +subdomain_alias: ld + +# initial superuser creation +linkding_username: linkdinger +linkding_password: linkdingerpass123 + +# should we back up the data? +linkding_backup_enable: true +linkding_backup_cron: 0 45 3 * * * diff --git a/roles/linkding/handlers/main.yml b/roles/linkding/handlers/main.yml new file mode 100644 index 0000000..081f2df --- /dev/null +++ b/roles/linkding/handlers/main.yml @@ -0,0 +1,52 @@ +## Register reverse proxy +- name: Ensure upstream directory exists + ansible.builtin.file: + path: "{{ linkding_upstream_file_dir }}" + state: directory + mode: "0755" + become: true + listen: "update linkding upstream" + +- name: Update upstream template + ansible.builtin.template: + src: upstream.json.j2 + dest: "{{ linkding_upstream_file_dir }}/upstream.json" + become: true + listen: "update linkding upstream" + +# figure out if upstream id exists +- name: check {{ stack_name }} upstream + community.docker.docker_container_exec: + container: "{{ caddy_container_id }}" + command: > + curl localhost:2019/id/{{ stack_name }}_upstream/ + changed_when: False + register: result + become: true + listen: "update linkding upstream" + +# upstream already exists, patch it +- name: remove old {{ stack_name }} upstream + community.docker.docker_container_exec: + container: "{{ caddy_container_id }}" + command: > + curl -X DELETE localhost:2019/id/{{ stack_name }}_upstream/ + become: true + when: (result.stdout | from_json)['error'] is not defined + listen: "update linkding upstream" + +# upstream has to be created +- name: add {{ stack_name }} upstream + community.docker.docker_container_exec: + container: "{{ caddy_container_id }}" + command: > + curl -X POST -H "Content-Type: application/json" -d @{{ linkding_upstream_file_dir }}/upstream.json localhost:2019/config/apps/http/servers/{{ (linkding_use_https == True) | ternary(caddy_https_server_name, caddy_http_server_name) }}/routes/0/ + become: true + listen: "update linkding upstream" + +- name: Ensure upstream directory is gone again + ansible.builtin.file: + path: "{{ linkding_upstream_file_dir }}" + state: absent + become: true + listen: "update linkding upstream" diff --git a/roles/linkding/meta/main.yml b/roles/linkding/meta/main.yml new file mode 100644 index 0000000..1c14785 --- /dev/null +++ b/roles/linkding/meta/main.yml @@ -0,0 +1,11 @@ +--- +galaxy_info: + author: Marty Oehme + description: Installs linkding as a docker stack service + license: GPL-3.0-only + min_ansible_version: "2.9" + galaxy_tags: [] + +dependencies: + - docker-swarm + - caddy_id diff --git a/roles/linkding/tasks/main.yml b/roles/linkding/tasks/main.yml new file mode 100644 index 0000000..e514b26 --- /dev/null +++ b/roles/linkding/tasks/main.yml @@ -0,0 +1,23 @@ +--- +## install linkding container +- name: Check upstream status + community.docker.docker_container_exec: + container: "{{ caddy_container_id }}" + command: > + curl localhost:2019/id/{{ stack_name }}_upstream/ + register: result + changed_when: (result.stdout | from_json) != (lookup('template', 'upstream.json.j2') | from_yaml) + become: true + notify: "update linkding upstream" + +- name: Deploy linkding to swarm + community.general.docker_stack: + name: "{{ stack_name }}" + state: present + prune: yes + compose: + - "{{ stack_compose }}" + become: true + tags: + - docker-swarm + notify: "update linkding upstream" diff --git a/roles/linkding/templates/docker-stack.yml.j2 b/roles/linkding/templates/docker-stack.yml.j2 new file mode 100644 index 0000000..dad26fc --- /dev/null +++ b/roles/linkding/templates/docker-stack.yml.j2 @@ -0,0 +1,46 @@ +services: + app: + image: "{{ stack_image }}:{{ linkding_version }}" + healthcheck: + test: ["CMD", "curl", "--fail", "http://127.0.0.1:9090/health"] + interval: 1m + timeout: 10s + retries: 3 + start_period: 1m + networks: + - "{{ docker_swarm_public_network_name }}" + volumes: + - data:/etc/linkding/data + environment: + - "LD_SUPERUSER_NAME={{ linkding_username }}" + - "LD_SUPERUSER_PASSWORD={{ linkding_password }}" +{% if linkding_autoupdate is defined and linkding_autoupdate %} + deploy: + labels: + - shepherd.autoupdate=true +{% endif %} + +{% if backup_enable is not undefined and not false and linkding_backup_enable is not undefined and not false %} + backup: + image: mazzolino/restic + environment: + - "TZ={{ restic_timezone }}" + # go-cron starts w seconds + - "BACKUP_CRON={{ linkding_backup_cron }}" + - "RESTIC_REPOSITORY={{ restic_repo }}" + - "AWS_ACCESS_KEY_ID={{ restic_s3_key }}" + - "AWS_SECRET_ACCESS_KEY={{ restic_s3_secret }}" + - "RESTIC_PASSWORD={{ restic_pass }}" + - "RESTIC_BACKUP_TAGS=linkding" + - "RESTIC_BACKUP_SOURCES=/volumes" + volumes: + - data:/volumes/linkding_data:ro +{% endif %} + +volumes: + data: + +networks: + "{{ docker_swarm_public_network_name }}": + external: true + diff --git a/roles/linkding/templates/upstream.json.j2 b/roles/linkding/templates/upstream.json.j2 new file mode 100644 index 0000000..a20061f --- /dev/null +++ b/roles/linkding/templates/upstream.json.j2 @@ -0,0 +1,38 @@ +{ + "@id": "{{ stack_name }}_upstream", +{% if server_domain is not undefined and not none %} + "match": [ + { + "host": [ +{% if subdomain_alias is not undefined and not none %} + "{{ subdomain_alias }}.{{ server_domain }}" +{% else %} + "{{ stack_name }}.{{ server_domain }}" +{% endif %} + ] + } + ], +{% else %} + "match": [ + { + "path": [ +{% if subdomain_alias is not undefined and not none %} + "/{{ subdomain_alias }}*" +{% else %} + "/{{ stack_name }}*" +{% endif %} + ] + } + ], +{% endif %} + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "{{ stack_name }}_app:80" + } + ] + } + ] +} diff --git a/roles/linkding/vars/main.yml b/roles/linkding/vars/main.yml new file mode 100644 index 0000000..5e74731 --- /dev/null +++ b/roles/linkding/vars/main.yml @@ -0,0 +1,6 @@ +--- +stack_name: linkding + +stack_image: "sissbruecker/linkding" + +stack_compose: "{{ lookup('template', 'docker-stack.yml.j2') | from_yaml }}" diff --git a/site.yml b/site.yml index 2e9a2be..6fe2fed 100644 --- a/site.yml +++ b/site.yml @@ -76,6 +76,11 @@ role: shaarli tags: shaarli + - name: Install linkding + import_role: + role: linkding + tags: linkding + - name: Install landingpage import_role: role: landingpage