diff --git a/roles/send/README.md b/roles/send/README.md new file mode 100644 index 0000000..339628b --- /dev/null +++ b/roles/send/README.md @@ -0,0 +1,60 @@ +Send +==== + +Deploys a self-hosted [Send](https://github.com/timvisee/send) instance +(timvisee fork of the discontinued Mozilla Send) with a Redis backend +behind Traefik, using Docker Compose. + +Requirements +------------ + +- Docker + `docker compose` plugin on the target host +- Traefik (role `digitalboard.core.traefik`) reachable via an external + Docker network named `proxy` (default) +- DNS for each entry in `send_domains` pointing at the reverse proxy +- Optional: a Garage S3 bucket if `send_storage_backend: s3` + +Role Variables +-------------- + +Important defaults (see `defaults/main.yml` for the full list): + +| Variable | Default | Description | +|---|---|---| +| `send_domains` | `["send.local.test"]` | FQDNs the router accepts; first entry is the canonical BASE_URL | +| `send_image` | `registry.gitlab.com/timvisee/send:latest` | Send container image | +| `send_max_file_size` | `1073741824` | Max upload size in bytes (1 GiB) | +| `send_max_expire_seconds` | `604800` | Max share lifetime (7 d) | +| `send_storage_backend` | `local` | `local` (volume) or `s3` | +| `send_s3_*` | `""` | S3 endpoint/bucket/key/secret (when backend is `s3`) | +| `send_use_ssl` | `true` | Issue Traefik labels for the `websecure` entrypoint | + +Dependencies +------------ + +None. + +Example Playbook +---------------- + +```yaml +- hosts: send_servers + become: true + roles: + - digitalboard.core.send +``` + +With S3 (Garage) backend: + +```yaml +send_storage_backend: s3 +send_s3_endpoint: "http://{{ hostvars['backend']['garage_s3_domain'] }}" +send_s3_bucket: "send" +send_s3_access_key: "{{ lookup('digitalboard.core.garage_credentials', 'send', host='backend')['key_id'] }}" +send_s3_secret_key: "{{ lookup('digitalboard.core.garage_credentials', 'send', host='backend')['secret_key'] }}" +``` + +License +------- + +MIT diff --git a/roles/send/defaults/main.yml b/roles/send/defaults/main.yml new file mode 100644 index 0000000..ba3aecc --- /dev/null +++ b/roles/send/defaults/main.yml @@ -0,0 +1,53 @@ +#SPDX-License-Identifier: MIT-0 +--- +# defaults file for send + +# Base directory configuration (inherited from base role or defined here) +docker_compose_base_dir: /etc/docker/compose +docker_volume_base_dir: /srv/data + +# Send-specific configuration +send_service_name: send +send_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ send_service_name }}" +send_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ send_service_name }}" + +# Service configuration +# FQDNs the send router accepts. The first entry is the canonical +# domain (used as BASE_URL); further entries cover internal *.int.* +# names so backend uploads can hit us without hairpinning via DMZ. +send_domains: + - "send.local.test" +send_image: "registry.gitlab.com/timvisee/send:latest" +send_port: 1443 +send_extra_hosts: [] + +# Redis backend +send_redis_image: "redis:7-alpine" +send_redis_service_name: "send-redis" + +# Send application configuration +# https://github.com/timvisee/send/blob/master/server/config.js +send_max_file_size: 1073741824 # 1 GiB in bytes +send_default_downloads: 1 +send_max_downloads: 100 +send_default_expire_seconds: 86400 # 24h +send_max_expire_seconds: 604800 # 7d +send_max_files_per_archive: 64 +send_download_counts: "1,2,3,4,5,20,50,100" +send_expire_times_seconds: "300,3600,86400,604800" + +# Storage backend: "local" (volume) or "s3" +send_storage_backend: "local" + +# S3 backend (only used when send_storage_backend == "s3") +send_s3_endpoint: "" +send_s3_bucket: "" +send_s3_region: "us-east-1" +send_s3_access_key: "" +send_s3_secret_key: "" +send_s3_use_path_style: true + +# Traefik configuration +send_traefik_network: "proxy" +send_internal_network: "send_internal" +send_use_ssl: true diff --git a/roles/send/handlers/main.yml b/roles/send/handlers/main.yml new file mode 100644 index 0000000..cb83189 --- /dev/null +++ b/roles/send/handlers/main.yml @@ -0,0 +1,9 @@ +#SPDX-License-Identifier: MIT-0 +--- +# handlers file for send + +- name: restart send + community.docker.docker_compose_v2: + project_src: "{{ send_docker_compose_dir }}" + state: present + recreate: always diff --git a/roles/send/meta/argument_specs.yml b/roles/send/meta/argument_specs.yml new file mode 100644 index 0000000..2e9797e --- /dev/null +++ b/roles/send/meta/argument_specs.yml @@ -0,0 +1,122 @@ +--- +argument_specs: + main: + short_description: Deploy timvisee/send (file-sharing) with a Redis backend via Docker Compose. + description: + - Renders a Compose stack with the C(timvisee/send) container and a + Redis companion behind Traefik. Storage can be local-disk or any + S3-compatible backend (e.g. the C(garage) role). + - Uses the shared C(*_domains) list convention so the router can + accept internal C(*.int.*) hostnames alongside the canonical + BASE_URL host. + options: + docker_compose_base_dir: + type: path + default: /etc/docker/compose + docker_volume_base_dir: + type: path + default: /srv/data + send_service_name: + type: str + default: send + send_docker_compose_dir: + type: path + send_docker_volume_dir: + type: path + + send_domains: + type: list + elements: str + default: ['send.local.test'] + description: + - FQDNs the router accepts. First entry is the canonical hostname + and is used as C(BASE_URL). Further entries cover internal + C(*.int.*) names so backend uploads can hit Send without + hairpinning via the DMZ. + send_image: + type: str + default: "registry.gitlab.com/timvisee/send:latest" + send_port: + type: int + default: 1443 + send_extra_hosts: + type: list + elements: str + default: [] + description: C(extra_hosts) entries injected into the send container (Docker C(host:ip) syntax). + + send_redis_image: + type: str + default: "redis:7-alpine" + send_redis_service_name: + type: str + default: send-redis + + send_max_file_size: + type: int + default: 1073741824 + description: Max upload size in bytes. Default is 1 GiB. + send_default_downloads: + type: int + default: 1 + send_max_downloads: + type: int + default: 100 + send_default_expire_seconds: + type: int + default: 86400 + description: Default share lifetime in seconds (24 h). + send_max_expire_seconds: + type: int + default: 604800 + description: Maximum share lifetime in seconds (7 d). + send_max_files_per_archive: + type: int + default: 64 + send_download_counts: + type: str + default: "1,2,3,4,5,20,50,100" + description: Comma-separated list of download-count options shown in the UI. + send_expire_times_seconds: + type: str + default: "300,3600,86400,604800" + description: Comma-separated list of expire-time options (seconds) shown in the UI. + + send_storage_backend: + type: str + choices: [local, s3] + default: local + description: + - C(local) keeps uploads in a host volume. C(s3) uses an + S3-compatible backend (any of the C(send_s3_*) variables is + required when this is set). + + send_s3_endpoint: + type: str + default: '' + send_s3_bucket: + type: str + default: '' + send_s3_region: + type: str + default: us-east-1 + send_s3_access_key: + type: str + default: '' + send_s3_secret_key: + type: str + default: '' + send_s3_use_path_style: + type: bool + default: true + description: Required for most non-AWS S3-compatible backends (Garage, MinIO). + + send_traefik_network: + type: str + default: proxy + send_internal_network: + type: str + default: send_internal + send_use_ssl: + type: bool + default: true diff --git a/roles/send/meta/main.yml b/roles/send/meta/main.yml new file mode 100644 index 0000000..20f9e67 --- /dev/null +++ b/roles/send/meta/main.yml @@ -0,0 +1,14 @@ +#SPDX-License-Identifier: MIT-0 +galaxy_info: + author: digitalboard + description: Deploy a self-hosted Send (timvisee fork) instance with Redis via Docker Compose + license: MIT + + min_ansible_version: "2.14" + + galaxy_tags: + - send + - filesharing + - docker + +dependencies: [] diff --git a/roles/send/tasks/main.yml b/roles/send/tasks/main.yml new file mode 100644 index 0000000..9ed8dd8 --- /dev/null +++ b/roles/send/tasks/main.yml @@ -0,0 +1,28 @@ +#SPDX-License-Identifier: MIT-0 +--- +# tasks file for send + +- name: Create docker compose directory + ansible.builtin.file: + path: "{{ send_docker_compose_dir }}" + state: directory + mode: '0755' + +- name: Create local upload directory + ansible.builtin.file: + path: "{{ send_docker_volume_dir }}/uploads" + state: directory + mode: '0755' + when: send_storage_backend == "local" + +- name: Create docker-compose file for send + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: "{{ send_docker_compose_dir }}/docker-compose.yml" + mode: '0644' + notify: restart send + +- name: Start send container + community.docker.docker_compose_v2: + project_src: "{{ send_docker_compose_dir }}" + state: present diff --git a/roles/send/templates/docker-compose.yml.j2 b/roles/send/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..69a43ab --- /dev/null +++ b/roles/send/templates/docker-compose.yml.j2 @@ -0,0 +1,69 @@ +services: + {{ send_service_name }}: + image: {{ send_image }} + container_name: {{ send_service_name }} + restart: unless-stopped + depends_on: + - {{ send_redis_service_name }} + networks: + - {{ send_traefik_network }} + - {{ send_internal_network }} +{% if send_extra_hosts is defined and send_extra_hosts | length > 0 %} + extra_hosts: +{% for host in send_extra_hosts %} + - "{{ host }}" +{% endfor %} +{% endif %} + environment: +{% if send_use_ssl %} + BASE_URL: "https://{{ send_domains[0] }}" +{% else %} + BASE_URL: "http://{{ send_domains[0] }}" +{% endif %} + REDIS_HOST: "{{ send_redis_service_name }}" + REDIS_PORT: "6379" + MAX_FILE_SIZE: "{{ send_max_file_size }}" + DEFAULT_DOWNLOADS: "{{ send_default_downloads }}" + MAX_DOWNLOADS: "{{ send_max_downloads }}" + DEFAULT_EXPIRE_SECONDS: "{{ send_default_expire_seconds }}" + MAX_EXPIRE_SECONDS: "{{ send_max_expire_seconds }}" + MAX_FILES_PER_ARCHIVE: "{{ send_max_files_per_archive }}" + DOWNLOAD_COUNTS: "{{ send_download_counts }}" + EXPIRE_TIMES_SECONDS: "{{ send_expire_times_seconds }}" +{% if send_storage_backend == "s3" %} + S3_BUCKET: "{{ send_s3_bucket }}" + S3_ENDPOINT: "{{ send_s3_endpoint }}" + S3_USE_PATH_STYLE_ENDPOINT: "{{ 'true' if send_s3_use_path_style else 'false' }}" + AWS_ACCESSKEYID: "{{ send_s3_access_key }}" + AWS_SECRETACCESSKEY: "{{ send_s3_secret_key }}" + AWS_REGION: "{{ send_s3_region }}" +{% else %} + FILE_DIR: "/uploads" + volumes: + - {{ send_docker_volume_dir }}/uploads:/uploads +{% endif %} + labels: + - traefik.enable=true + - traefik.docker.network={{ send_traefik_network }} + - traefik.http.routers.{{ send_service_name }}.rule={% for d in send_domains %}Host(`{{ d }}`){% if not loop.last %} || {% endif %}{% endfor +%} + - traefik.http.services.{{ send_service_name }}.loadbalancer.server.port={{ send_port }} +{% if send_use_ssl %} + - traefik.http.routers.{{ send_service_name }}.entrypoints=websecure + - traefik.http.routers.{{ send_service_name }}.tls=true +{% else %} + - traefik.http.routers.{{ send_service_name }}.entrypoints=web +{% endif %} + + {{ send_redis_service_name }}: + image: {{ send_redis_image }} + container_name: {{ send_redis_service_name }} + restart: unless-stopped + networks: + - {{ send_internal_network }} + volumes: + - {{ send_docker_volume_dir }}/redis:/data + +networks: + {{ send_internal_network }}: + {{ send_traefik_network }}: + external: true diff --git a/roles/send/vars/main.yml b/roles/send/vars/main.yml new file mode 100644 index 0000000..b2a6b30 --- /dev/null +++ b/roles/send/vars/main.yml @@ -0,0 +1,3 @@ +#SPDX-License-Identifier: MIT-0 +--- +# vars file for send