Compare commits

...

8 commits

Author SHA1 Message Date
447e15f9e9
feat(linkding): Add linkding stack
Bookmarking software similar to shaarli but a bit more featureful. And
not written in php, thankfully.
2025-03-15 22:29:55 +01:00
83613f6d86
feat(roles): Add auto updating to some roles
Miniflux, searx, shaarli and wallabag will be automatically updated by
shepherd.
2025-03-15 22:29:55 +01:00
9f3274dae7
feat(landingpage): Automatically update 2025-03-15 22:29:54 +01:00
fecf14a5bc
feat(site): Change out diun with shepherd 2025-03-15 22:29:54 +01:00
2dfe9f9b92
feat(shepherd): Add auto update shepherd role
Deprecates diun as it provides a simpler implementation for docker
swarm. Mark any containers you want auto updated with
`shepherd.autoupdate=true` and the rest with
`shepherd.autoupdate=false`. Everything untagged will not be watched (by
default), though this can be changed by setting the ansible default
variable `shepherd_filter_services: `.
2025-03-15 22:29:53 +01:00
bc9104c3e8
chore(landingpage): Fix container image url 2025-03-15 22:29:52 +01:00
3418f85ffd
chore(landingpage): Switch to ghcr hosted docker image 2025-03-15 22:29:52 +01:00
ea077958ce fix(forgejo): Update to correct woodpecker versions 2025-02-16 21:45:14 +01:00
26 changed files with 341 additions and 10 deletions

View file

@ -86,7 +86,7 @@ services:
{% if forgejo_use_ci %}
wp-server:
image: woodpeckerci/woodpecker-server:latest
image: woodpeckerci/woodpecker-server:v3
networks:
- "{{ docker_swarm_public_network_name }}"
- backend
@ -120,11 +120,12 @@ services:
{% endif %}
wp-agent:
image: woodpeckerci/woodpecker-agent:latest
image: woodpeckerci/woodpecker-agent:v3
networks:
- backend
command: agent
volumes:
- woodpecker-agent-config:/etc/woodpecker
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=wp-server:9000
@ -135,6 +136,7 @@ volumes:
data:
db:
woodpecker:
woodpecker-agent-config:
networks:
"{{ docker_swarm_public_network_name }}":

View file

@ -5,5 +5,7 @@ landingpage_upstream_file_dir: "{{ docker_stack_files_dir }}/{{ stack_name }}"
landingpage_use_https: true
landingpage_autoupdate: true
# the subdomain link landingpage will be reachable under
subdomain_alias: www

View file

@ -12,6 +12,11 @@ services:
entrypoint: sh -c "/docker-entrypoint.sh nginx -g 'daemon off;'"
networks:
- "{{ docker_swarm_public_network_name }}"
{% if landingpage_autoupdate is defined and landingpage_autoupdate %}
deploy:
labels:
- shepherd.autoupdate=true
{% endif %}
networks:
"{{ docker_swarm_public_network_name }}":

View file

@ -1,6 +1,6 @@
---
stack_name: landingpage
stack_image: "martyo/cloudserve-landing"
stack_image: "ghcr.io/marty-oehme/page"
stack_compose: "{{ lookup('template', 'docker-stack.yml.j2') | from_yaml }}"

View file

@ -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 * * *

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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"
}
]
}
]
}

View file

@ -0,0 +1,6 @@
---
stack_name: linkding
stack_image: "sissbruecker/linkding"
stack_compose: "{{ lookup('template', 'docker-stack.yml.j2') | from_yaml }}"

View file

@ -8,6 +8,8 @@ miniflux_use_https: true
# the subdomain link miniflux will be reachable under
subdomain_alias: rss
miniflux_autoupdate: true
# Should ideally be overwritten in encrypted group/host vars
miniflux_admin_username: myadmin
miniflux_admin_password: mypassword

View file

@ -24,6 +24,11 @@ services:
{% else %}
- "BASE_URL={{ (miniflux_use_https == True) | ternary('https', 'http') }}://localhost/{{ (subdomain_alias is not undefined and not none) | ternary(subdomain_alias, stack_name) }}"
{% endif %}
{% if miniflux_autoupdate is defined and miniflux_autoupdate %}
deploy:
labels:
- shepherd.autoupdate=true
{% endif %}
db:
image: postgres:11

View file

@ -5,8 +5,11 @@ searx_upstream_file_dir: "{{ docker_stack_files_dir }}/{{ stack_name }}"
searx_use_https: true
searx_autoupdate: true
# the subdomain link searx will be reachable under
subdomain_alias: search
# searx_authentication:
# - username: mysearxusername
# password: JDJhJDE0JFdjUnQ5WWllcU8wa01xS0JBS2dlMy5zMEhRTmxqTXdIZmdjcTN6ZGFwRjJlYUdoSHAwRUhL # mysearxpassword
# - username: # mysearxusername
# password: # mysearxpassword

View file

@ -13,7 +13,7 @@ services:
start_period: 1m
environment:
- BIND_ADDRESS=0.0.0.0:8080
{% if server_domain is not undefined and not none %}
{% if server_domain is not undefined and not none %}
- "BASE_URL={{ (searx_use_https == True) | ternary('https', 'http') }}://{{ (subdomain_alias is not undefined and not none) | ternary(subdomain_alias, stack_name) }}.{{server_domain}}"
{% else %}
- "BASE_URL={{ (searx_use_https == True) | ternary('https', 'http') }}://localhost/{{ (subdomain_alias is not undefined and not none) | ternary(subdomain_alias, stack_name) }}"
@ -21,6 +21,11 @@ services:
volumes:
- /etc/localtime:/etc/localtime:ro
- data:/etc/searx:rw
{% if searx_autoupdate is defined and searx_autoupdate %}
deploy:
labels:
- shepherd.autoupdate=true
{% endif %}
volumes:
data:

View file

@ -5,6 +5,8 @@ shaarli_upstream_file_dir: "{{ docker_stack_files_dir }}/{{ stack_name }}"
shaarli_use_https: true
shaarli_autoupdate: true
# the subdomain link shaarli will be reachable under
subdomain_alias: links

View file

@ -14,6 +14,11 @@ services:
volumes:
- data:/var/www/shaarli/data
- cache:/var/www/shaarli/cache
{% if shaarli_autoupdate is defined and shaarli_autoupdate %}
deploy:
labels:
- shepherd.autoupdate=true
{% endif %}
{% if backup_enable is not undefined and not false and shaarli_backup_enable is not undefined and not false %}
backup:

6
roles/shepherd/README.md Normal file
View file

@ -0,0 +1,6 @@
# shepherd
Monitor the deployed swarm containers for updates.
Will notify you when it found any update for any container.
Can notify you through a wide variety of services using the apprise api.

View file

@ -0,0 +1,13 @@
---
shepherd_version: latest
shepherd_tz: Europe/Berlin
shepherd_ignored_services: label=shepherd.autoupdate=false
shepherd_filter_services: label=shepherd.autoupdate=true
shepherd_sleeptime: 5m
shepherd_rollback_on_failure: true
shepherd_image_autoclean_limit: 5
shepherd_notification_targets:

View file

@ -0,0 +1,10 @@
---
galaxy_info:
author: Marty Oehme
description: Apply docker swarm container updates
license: GPL-3.0-only
min_ansible_version: "2.9"
galaxy_tags: []
dependencies:
- docker-swarm

View file

@ -0,0 +1,11 @@
---
- name: Deploy shepherd stack to swarm
community.general.docker_stack:
name: "{{ stack_name }}"
state: present
prune: yes
compose:
- "{{ stack_compose }}"
become: true
tags:
- docker-swarm

View file

@ -0,0 +1,52 @@
version: '3.4'
services:
app:
image: "{{ stack_image }}:{{ shepherd_version }}"
# healthcheck:
# test: ["CMD", "wget", "--spider", "-q", "127.0.0.1"]
# interval: 1m
# timeout: 10s
# retries: 3
# start_period: 1m
command: serve
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
environment:
- "TZ={{ shepherd_tz }}"
- "SLEEP_TIME={{ shepherd_sleeptime }}"
- "IGNORELIST_SERVICES={{ shepherd_ignored_services }}"
{% if shepherd_filter_services is defined and not None %}
- "FILTER_SERVICES={{ shepherd_filter_services }}"
{% endif %}
- "ROLLBACK_ON_FAILURE={{ shepherd_rollback_on_failure }}"
- "IMAGE_AUTOCLEAN_LIMIT={{ shepherd_image_autoclean_limit }}"
- "VERBOSE=true"
{% if shepherd_notification_targets is defined and not None %}
- "APPRISE_SIDECAR_URL: notify:5000"
{% endif %}
networks:
- backend
deploy:
mode: replicated
replicas: 1
placement:
constraints:
- node.role == manager
{% if shepherd_notification_targets is defined and not None %}
notify:
image: mazzolino/apprise-microservice:latest
environment:
NOTIFICATION_URLS: {{ shepherd_notification_targets }}
networks:
- backend
{% endif %}
volumes:
data:
networks:
"{{ docker_swarm_public_network_name }}":
external: true
backend:

View file

@ -0,0 +1,6 @@
---
stack_name: shepherd
stack_image: "containrrr/shepherd"
stack_compose: "{{ lookup('template', 'docker-stack.yml.j2') | from_yaml }}"

View file

@ -5,5 +5,7 @@ wallabag_upstream_file_dir: "{{ docker_stack_files_dir }}/{{ stack_name }}"
wallabag_use_https: true
wallabag_autoupdate: true
# the subdomain link wallabag will be reachable under
subdomain_alias: read

View file

@ -15,11 +15,16 @@ services:
- data:/var/www/wallabag/data
environment:
- SYMFONY__ENV__FOSUSER_REGISTRATION=false
{% if server_domain is not undefined and not none %}
{% if server_domain is not undefined and not none %}
- "SYMFONY__ENV__DOMAIN_NAME={{ (wallabag_use_https == True) | ternary('https', 'http') }}://{{ (subdomain_alias is not undefined and not none) | ternary(subdomain_alias, stack_name) }}.{{server_domain}}"
{% else %}
- SYMFONY__ENV__DOMAIN_NAME={{ (wallabag_use_https == True) | ternary('https', 'http') }}://localhost
{% endif %}
{% if wallabag_autoupdate is defined and wallabag_autoupdate %}
deploy:
labels:
- shepherd.autoupdate=true
{% endif %}
redis:
image: redis:alpine

View file

@ -93,8 +93,8 @@
- ntfy
- never
- name: Install diun
- name: Install shepherd
import_role:
role: diun
role: shepherd
tags:
- diun
- shepherd