Skip to content

[SERVICE] [FEATURE] Shlink link shortener deployment #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
20 changes: 19 additions & 1 deletion inventory/aws_ec2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ keyed_groups:
prefix: "app"
separator: "_"

- key: tags.Environment
prefix: ""
separator: ""

- key: tags.Project
prefix: ""
separator: ""

- key: tags.Role
prefix: ""
separator: ""
Expand Down Expand Up @@ -65,4 +73,14 @@ keyed_groups:

- key: tags.TraitImmutable
prefix: ""
separator: ""
separator: ""

- key: tags.RoleService
prefix: ""
separator: ""
parent_group: "service"

- key: tags.ServiceInstance
prefix: ""
separator: ""
parent_group: "service"
115 changes: 115 additions & 0 deletions roles/cs.shlink/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# The suffix used for naming all instance resources
# Set it if deploying multiple instances on the same host
shlink_instance: ~

# Use https
shlink_short_domain_https: yes

# The domain name that the app is hosted at
shlink_short_domain: ~

# Base URL path of the app
shlink_base_path: ''

# Redirect to this URL on access to the base path
shlink_base_url_redirect_to: ~

# Redirect to this URL when shortcode is not in the DB
shlink_missing_url_redirect_to: ~

# Redirect to this URL when target URL is invalid
shlink_404_redirect_to: ~

# Redirect code - 301 or 302
shlink_redirect_status_code: 302

# How long to keep redirs in cache
shlink_redirect_cache_lifetime: 30

# Minimum amount of visits needed to keep the shortened entry
shlink_delete_short_url_threshold: 3

# Initial length of short codes
shlink_shortcode_length: 4

# Whether to validate target URLs
shlink_validate_url: no

# Disable tracking when this query parameter is present
shlink_disable_track_param: _shlnt

# Whether to anonymize remote IPs
# Warning! If disabled then you must ensure GDPR compliance somehow which
# might not be possible legally without displaying some opt-in, so better
# to keep anonymization enabled at all times.
shlink_anonymize_remote_addr: yes

# Number of workers
shlink_web_worker_num: 16
shlink_task_worker_num: 16

# Target docker container tag
shlink_version: 2.6.2

# GeoLite DB license key
shlink_geolite_license_key: ~

# If defined then an external MySQL DB will be used
shlink_mysql_db_host: ~
shlink_mysql_db_name: shlink
shlink_mysql_db_user: shlink
shlink_mysql_db_pass: ~
shlink_mysql_db_port: 3306

# Use local SQLite DB if MySQL DB hostname is not set
shlink_sqlite_db: "{{ not (shlink_mysql_db_host | default(false, true)) }}"

shlink_user: "shlink{{ shlink_instance_suffix }}"
shlink_group: "{{ shlink_user }}"
shlink_home: "/home/{{ shlink_user }}"

# Port bound to the host - not that container is ran rootless
# you cannot bind to ports < 1024! This is a backend only so
# you need a reverse-proxy in front anyway.
shlink_http_bind_port: 8080

# The IP to publish the http port to
shlink_http_bind_host: 0.0.0.0

# The internal port exposed by the container
shlink_http_container_port: 8888

# The shlink backend configuration build. See reference:
# - Env vars described: https://shlink.io/documentation/install-docker-image/#supported-env-vars
# - Available JSON params: https://shlink.io/documentation/install-docker-image/#provide-config-via-volumes
shlink_config_default:
anonymize_remote_addr: "{{ shlink_anonymize_remote_addr }}"

disable_track_param: "{{ shlink_disable_track_param }}"

short_domain_host: "{{ shlink_short_domain }}"
short_domain_schema: "{{ shlink_short_domain_https | ternary('https', 'http') }}"

validate_url: "{{ shlink_validate_url }}"

base_path: "{{ shlink_base_path }}"
base_url_redirect_to: "{{ shlink_base_url_redirect_to }}"
invalid_short_url_redirect_to: "{{ shlink_missing_url_redirect_to }}"
regular_404_redirect_to: "{{ shlink_404_redirect_to }}"

web_worker_num: "{{ shlink_web_worker_num }}"
task_worker_num: "{{ shlink_task_worker_num }}"
default_short_codes_length: "{{ shlink_shortcode_length }}"
geolite_license_key: "{{ shlink_geolite_license_key }}"

redirect_status_code: "{{ shlink_redirect_status_code }}"
redirect_cache_lifetime: "{{ shlink_redirect_cache_lifetime }}"

delete_short_url_threshold: "{{ shlink_delete_short_url_threshold }}"

port: "{{ shlink_http_container_port }}"

db_config: "{{ shlink_sqlite_db | ternary(shlink_db_config_sqlite, shlink_db_config_mysql) | dict2items | rejectattr('value', 'none') | list | items2dict }}"

# Extra parameters to override / add to the default JSON config
shlink_config_custom: {}
9 changes: 9 additions & 0 deletions roles/cs.shlink/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- name: Reload systemctl daemon
systemd:
daemon_reload: yes
daemon_reexec: yes

- name: Restart shlink
service:
name: "{{ shlink_service_name }}.service"
state: restarted
195 changes: 195 additions & 0 deletions roles/cs.shlink/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
###
# *** Installs shlink URL shortener ***
#
# The app is running as a rootless container as user `shlink_user`.
# All app data is stored in these user's home directory.
#
# No HTTPS termination is provided - this you must provide an HTTPS-terminating
# reverse proxy to this instance's `shlink_http_bind_port`.
###

- name: Install packages for running rootless podman containers
yum:
state: present
name:
- podman
- slirp4netns
- fuse
- fuse-overlayfs

- name: Enable user namespaces via sysctl
sysctl:
state: present
reload: yes
name: user.max_user_namespaces
value: "28633"

- name: Create user
user:
state: present
name: "{{ shlink_user }}"
group: "{{ shlink_group }}"
home: "{{ shlink_home }}"
move_home: yes

- name: Create group
group:
name: "{{ shlink_group }}"

- name: Create user
user:
state: present
name: "{{ shlink_user }}"
group: "{{ shlink_group }}"
home: "{{ shlink_home }}"

- name: Create basic dirs
file:
state: directory
path: "{{ item }}"
mode: "0755"
owner: "{{ shlink_user }}"
group: "{{ shlink_group }}"
loop:
- "{{ shlink_conf_volume_src }}"
- "{{ shlink_home }}/bin"

- name: Create data dirs
file:
state: directory
path: "{{ shlink_data_volume_src }}/{{ item }}"
mode: "0750"
owner: "{{ shlink_user }}"
group: "{{ shlink_group }}"
loop: "{{ shlink_data_mounts }}"

- name: Install CLI wrapper script
template:
src: shlink.sh
dest: "{{ shlink_home }}/.local/bin/shlink"
mode: "0755"
owner: "{{ shlink_user }}"
group: "{{ shlink_group }}"

- name: Add user local bindir to PATH
lineinfile:
state: present
create: yes
dest: "{{ shlink_home }}/{{ item }}"
line: "export PATH=\"{{ shlink_home }}/.local/bin/:$PATH\""
mode: "0644"
owner: "{{ shlink_user }}"
group: "{{ shlink_group }}"
loop:
- .profile
- .bash_profile
- .zsh_profile


- name: Compute effective backend config
set_fact:
shlink_json_config: >-
{{
shlink_config_default
| combine(
shlink_config_custom | default({}, true)
)
| dict2items
| rejectattr('value', 'none') | list
| items2dict
}}

- name: Print backend configuration
debug:
msg: |
============================================
= Effective Backend Configuration =
============================================

{{ shlink_json_config | to_nice_yaml }}

- name: Configure backend app
copy:
dest: "{{ shlink_conf_volume_src }}/shlink.config.json"
content: "{{ shlink_json_config | to_nice_json }}"
owner: "{{ shlink_user }}"
group: "{{ shlink_group }}"
mode: "0640"
notify: Restart shlink

- name: Configure backend systemd service
template:
src: shlink.service
dest: "/etc/systemd/system/{{ shlink_service_name }}.service"
register: shlink_service_configure
notify: Restart shlink

- name: Reload systemctl daemon
when: shlink_service_configure is changed
systemd:
daemon_reload: yes
daemon_reexec: yes

- name: Enable and start backend systemd service
systemd:
force: yes
enabled: yes
state: started
name: "{{ shlink_service_name }}.service"

- meta: flush_handlers

- name: Generate master API key
shell: >-
{{ shlink_home }}/.local/bin/shlink \
api-key:gen \
--no-ansi \
--no-interaction \
| grep "key:" \
| sed -E 's/^.*"([a-f0-9_-]+)".*$/\1/g' \
| tee {{ shlink_home }}/.shlink-api-key
args:
creates: "{{ shlink_home }}/.shlink-api-key"
register: shlink_gen_master_key
become: yes
become_user: "{{ shlink_user }}"

- name: Print master API key
when: shlink_gen_master_key is changed
debug:
msg: |
============================================
= !!! Auto-generated Master API Key !!! =
============================================

This key is generated only on initial setup
and stored in:
{{ shlink_home }}/.shlink-api-key

It will not be shown again.

Key: {{ shlink_gen_master_key.stdout }}


- name: Print installation info
debug:
msg: |
============================================
= Deployment Maintenance Information =
============================================

You can access the shlink CLI by logging in
as `{{shlink_user}}` user with `shlink` command.

You can also do this directly via SSH:
$ ssh {{shlink_user}}@{{ansible_host}} shlink list

You can directly access the podman container only
as the `{{shlink_user}}` user:
$ podman container logs {{shlink_container_name}}

Manage the service as `root` user via systemd:
$ systemctl status {{shlink_service_name}}

Tail the container logs:
$ journalctl -u {{shlink_service_name}} -f
Loading