feat(podman_seafile): initial import
Some checks failed
Ansible Lint Check / lint (push) Failing after 47s

This commit is contained in:
Iain Learmonth 2025-12-01 15:09:46 +00:00
parent 7a66272ae3
commit 7891343dc5
14 changed files with 411 additions and 0 deletions

9
playbooks/seafile.yml Normal file
View file

@ -0,0 +1,9 @@
---
- name: Podman Seafile | Deploy and update Seafile instances
hosts:
- seafile
roles:
- role: sr2c.core.baseline
tags: bootstrap
- role: sr2c.apps.podman_seafile
tags: seafile

View file

@ -0,0 +1,3 @@
---
podman_seafile_podman_rootless_user: seafile
# podman_seafile_redis_password:

View file

@ -0,0 +1,9 @@
---
- name: Restart Seafile
ansible.builtin.systemd_service:
name: seafile.service
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_seafile_podman_rootless_user }}"

View file

@ -0,0 +1,122 @@
---
- name: Podman Seafile | PATCH | Install data plate
ansible.builtin.template:
src: etc/motd.d/10-data-plate.txt
dest: /etc/motd.d/10-data-plate.txt
owner: root
group: root
mode: "0444"
become: true
- name: Podman Seafile | PATCH | Install podman and verify rootless podman user
ansible.builtin.include_role:
role: sr2c.core.podman_host
vars:
podman_host_minimum_unpriv_port: 80
podman_host_rootless_users: ["{{ podman_seafile_podman_rootless_user }}"]
- name: Podman Seafile | AUDIT | Get subuid range for user
ansible.builtin.command:
cmd: "getsubids {{ podman_seafile_podman_rootless_user }}"
register: _podman_seafile_user_subuid
changed_when: false
- name: Podman Seafile | AUDIT | Get subgid range for user
ansible.builtin.command:
cmd: "getsubids -g {{ podman_seafile_podman_rootless_user }}"
register: _podman_seafile_user_subgid
changed_when: false
- name: Podman Seafile | AUDIT | Parse outputs of getsubids and store results
ansible.builtin.set_fact:
_podman_seafile_user_subuid_start: "{{ (_podman_seafile_user_subuid.stdout_lines[0].split()[2] | int) }}"
_podman_seafile_user_subgid_start: "{{ (_podman_seafile_user_subgid.stdout_lines[0].split()[2] | int) }}"
# MySQL runs with UID/GID 999 inside the container
- name: Podman Seafile | PATCH | Create data directory for MySQL
ansible.builtin.file:
path: "/home/{{ podman_seafile_podman_rootless_user }}/mysql_data"
owner: "{{ _podman_seafile_user_subuid_start + 998 }}"
group: "{{ _podman_seafile_user_subgid_start + 998 }}"
mode: "0750"
state: "directory"
become: true
# Seafile runs as root inside the container
- name: Podman Seafile | PATCH | Create data directories for Seafile
ansible.builtin.file:
path: "/home/{{ podman_seafile_podman_rootless_user }}/{{ item }}"
owner: "{{ podman_seafile_podman_rootless_user }}"
group: "{{ podman_seafile_podman_rootless_user }}"
mode: "0755"
state: "directory"
become: true
with_items:
- seafile_data
- seadoc_data
- onlyoffice/logs
- onlyoffice/data
- onlyoffice/lib
- name: Podman CDR Link | PATCH | Install container quadlets
ansible.builtin.template:
src: "home/podman/config/containers/systemd/{{ item }}"
dest: "/home/{{ podman_seafile_podman_rootless_user }}/.config/containers/systemd/{{ item }}"
owner: "{{ podman_seafile_podman_rootless_user }}"
mode: "0400"
with_items:
- mysql.container
- redis.container
- seafile.container
- seadoc.container
- onlyoffice.container
- frontend.network
- seafile.network
become: true
notify:
- Restart Seafile
- name: Podman Seafile | PATCH | Set up nginx and Let's Encrypt certificate
ansible.builtin.include_role:
name: sr2c.core.podman_nginx
vars:
podman_nginx_frontend_network: frontend
podman_nginx_podman_rootless_user: "{{ podman_seafile_podman_rootless_user }}"
podman_nginx_primary_hostname: "{{ podman_seafile_hostname }}"
- name: Podman Seafile | PATCH | Install production nginx configuration file
ansible.builtin.template:
src: home/podman/nginx/nginx.conf
dest: "/home/{{ podman_seafile_podman_rootless_user }}/nginx/nginx.conf"
owner: "{{ podman_seafile_podman_rootless_user }}"
group: "{{ podman_seafile_podman_rootless_user }}"
mode: "0644"
become: true
notify:
- Restart nginx
- name: Podman Seafile | PATCH | Ensure services are running and enabled
ansible.builtin.systemd_service:
name: seafile.service
scope: user
masked: false
state: started
enabled: true
become: true
become_user: "{{ podman_seafile_podman_rootless_user }}"
- name: Podman Seafile | AUDIT | Wait until the seahub config file is created
ansible.builtin.wait_for:
path: "/home/{{ podman_seafile_podman_rootless_user }}/seafile_data/seafile/conf/seahub_settings.py"
state: present
become: true
- name: Podman Seafile | PATCH | Append Seafile config block from template for proxy and OAuth
ansible.builtin.blockinfile:
path: "/home/{{ podman_seafile_podman_rootless_user }}/seafile_data/seafile/conf/seahub_settings.py"
block: "{{ lookup('ansible.builtin.template', 'home/podman/seafile_data/seahub_settings.py') }}"
insertafter: EOF
marker: "# {mark} ANSIBLE MANAGED BLOCK (Keycloak OAuth login)"
become: true
notify:
- Restart Seafile

View file

@ -0,0 +1,8 @@
=========================================================
A Seafile instance is hosted on this server.
Podman user: {{ podman_seafile_podman_rootless_user }}
=========================================================
# Become the podman user
sudo -iu {{ podman_seafile_podman_rootless_user }}
=========================================================

View file

@ -0,0 +1,2 @@
[Network]
NetworkName=frontend

View file

@ -0,0 +1,16 @@
[Container]
ContainerName=mysql
Environment=MYSQL_ROOT_PASSWORD={{ podman_seafile_mysql_root_password | replace("%", "%%") }}
Environment=MYSQL_LOG_CONSOLE=true
Environment=MARIADB_AUTO_UPGRADE=1
HealthCmd=["/usr/local/bin/healthcheck.sh","--connect","--mariadbupgrade","--innodb_initialized"]
HealthInterval=20s
HealthRetries=10
HealthStartPeriod=30s
HealthTimeout=5s
Image=docker.io/mariadb:10.11
Network=seafile.network
Volume=/home/{{ podman_seafile_podman_rootless_user }}/mysql_data:/var/lib/mysql:rw,Z
[Service]
Restart=always

View file

@ -0,0 +1,12 @@
[Container]
ContainerName=onlyoffice
Environment=JWT_ENABLED=true
Environment=JWT_SECRET={{ podman_seafile_jwt_private_key | replace("%", "%%") }}
Image=docker.io/onlyoffice/documentserver:8.1.0.1
Network=frontend.network
Volume=/home/{{ podman_seafile_podman_rootless_user }}/onlyoffice/logs:/var/log/onlyoffice:rw,Z
Volume=/home/{{ podman_seafile_podman_rootless_user }}/onlyoffice/data:/var/www/onlyoffice/Data:rw,Z
Volume=/home/{{ podman_seafile_podman_rootless_user }}/onlyoffice/lib:/var/lib/onlyoffice:rw,Z
[Service]
Restart=always

View file

@ -0,0 +1,9 @@
[Container]
ContainerName=redis
Environment=REDIS_PASSWORD={{ podman_seafile_redis_password | replace("%", "%%") }}
Exec=/bin/sh -c 'redis-server --requirepass "$$REDIS_PASSWORD"'
Image=docker.io/redis
Network=seafile.network
[Service]
Restart=always

View file

@ -0,0 +1,22 @@
[Unit]
Requires=mysql.service
After=mysql.service
[Container]
ContainerName=seadoc
Environment=DB_HOST=mysql
Environment=DB_PORT=3306
Environment=DB_USER=seafile
Environment=DB_PASSWORD={{ podman_seafile_mysql_user_password | replace("%", "%%") }}
Environment=DB_NAME=seahub_db
Environment=TIME_ZONE=Etc/UTC
Environment=JWT_PRIVATE_KEY={{ podman_seafile_jwt_private_key | replace("%", "%%") }}
Environment=NON_ROOT=false
Environment=SEAHUB_SERVICE_URL=http://seafile:80
Image=docker.io/seafileltd/sdoc-server:2.0-latest
Network=seafile.network
Network=frontend.network
Volume=/home/{{ podman_seafile_podman_rootless_user }}/seadoc_data:/shared:rw,Z
[Service]
Restart=always

View file

@ -0,0 +1,41 @@
[Unit]
Requires=mysql.service redis.service
After=mysql.service redis.service
[Container]
ContainerName=seafile
Environment=SEAFILE_MYSQL_DB_HOST=mysql
Environment=SEAFILE_MYSQL_DB_PORT=3306
Environment=SEAFILE_MYSQL_DB_USER=seafile
Environment=SEAFILE_MYSQL_DB_PASSWORD={{ podman_seafile_mysql_user_password | replace("%", "%%") }}
Environment=INIT_SEAFILE_MYSQL_ROOT_PASSWORD={{ podman_seafile_mysql_root_password | replace("%", "%%") }}
Environment=SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
Environment=SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
Environment=SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db
Environment=TIME_ZONE=Etc/UTC
Environment=INIT_SEAFILE_ADMIN_EMAIL={{ podman_seafile_admin_email | replace("%", "%%") }}
Environment=INIT_SEAFILE_ADMIN_PASSWORD={{ podman_seafile_admin_password | replace("%", "%%") }}
Environment=SEAFILE_SERVER_HOSTNAME={{ podman_seafile_hostname | replace("%", "%%") }}
Environment=SEAFILE_SERVER_PROTOCOL=https
Environment=SITE_ROOT=/
Environment=NON_ROOT=false
Environment=JWT_PRIVATE_KEY={{ podman_seafile_jwt_private_key | replace("%", "%%") }}
Environment=SEAFILE_LOG_TO_STDOUT=true
Environment=CACHE_PROVIDER=redis
Environment=REDIS_HOST=redis
Environment=REDIS_PORT=6379
Environment=REDIS_PASSWORD={{ podman_seafile_redis_password | replace("%", "%%") }}
Environment=MEMCACHED_HOST=memcached
Environment=MEMCACHED_PORT=11211
Environment=ENABLE_NOTIFICATION_SERVER=false
Environment=ENABLE_SEAFILE_AI=false
Environment=MD_FILE_COUNT_LIMIT=100000
Environment=ENABLE_SEADOC=true
Environment=SEADOC_SERVER_URL=https://{{ podman_seafile_hostname | replace("%", "%%") }}/sdoc-server
Image=docker.io/seafileltd/seafile-mc:13.0-latest
Network=seafile.network
Network=frontend.network
Volume=/home/{{ podman_seafile_podman_rootless_user }}/seafile_data:/shared:rw,Z
[Service]
Restart=always

View file

@ -0,0 +1,2 @@
[Network]
NetworkName=seafile

View file

@ -0,0 +1,124 @@
# {{ ansible_managed }}
error_log /dev/stdout info;
access_log /dev/stdout;
resolver 10.89.0.1 ipv6=off valid=10s;
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# Mitigate httpoxy attack
proxy_set_header Proxy "";
server {
listen 80;
listen [::]:80;
server_name {{ podman_seafile_hostname }};
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://{{ podman_seafile_hostname }}$request_uri;
}
}
upstream seafile {
zone seafile_upstream 64k;
server seafile:80 resolve;
}
upstream seadoc {
zone seadoc_upstream 64k;
server seadoc:80 resolve;
}
upstream onlyoffice {
zone onlyoffice_upstream 64k;
server onlyoffice:80 resolve;
}
map $http_upgrade $proxy_connection {
default upgrade;
"" close;
}
server {
server_name {{ podman_seafile_hostname }};
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/{{ podman_seafile_hostname }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ podman_seafile_hostname }}/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header Referrer-Policy origin always; # make sure outgoing links don't show the URL to the instance
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
location ~ ^(/accounts/login)(.*)$ {
return 301 /oauth/login$2;
}
location / {
proxy_pass http://seafile;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 443;
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 2048 8k;
client_max_body_size 100m;
}
location /sdoc-server/ {
proxy_pass http://seadoc/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
client_max_body_size 100m;
}
location /socket.io {
proxy_pass http://seadoc;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_redirect off;
proxy_buffers 8 32k;
proxy_buffer_size 64k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
}
location /onlyofficeds/ {
proxy_pass http://onlyoffice/;
proxy_http_version 1.1;
client_max_body_size 100M;
proxy_read_timeout 3600s;
proxy_connect_timeout 3600s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Forwarded-Host $host/onlyofficeds;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View file

@ -0,0 +1,32 @@
SEAFILE_SERVER_HOSTNAME = "{{ podman_seafile_hostname }}"
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
CSRF_TRUSTED_ORIGINS = ["https://{{ podman_seafile_hostname }}"]
FORCE_HTTPS_IN_CONF = True
USE_X_FORWARDED_HOST = True
ENABLE_OAUTH = True
OAUTH_CREATE_UNKNOWN_USER = True
OAUTH_ACTIVATE_USER_AFTER_CREATION = True
OAUTH_CLIENT_ID = "{{ podman_seafile_keycloak_client_id }}"
OAUTH_CLIENT_SECRET = "{{ podman_seafile_keycloak_client_secret }}"
OAUTH_REDIRECT_URL = "https://{{ podman_seafile_hostname }}/oauth/callback/"
OAUTH_PROVIDER_DOMAIN = '{{ podman_seafile_hostname }}'
OAUTH_AUTHORIZATION_URL = 'https://{{ podman_seafile_keycloak_hostname }}/realms/{{ podman_seafile_keycloak_realm }}/protocol/openid-connect/auth'
OAUTH_TOKEN_URL = 'https://{{ podman_seafile_keycloak_hostname }}/realms/{{ podman_seafile_keycloak_realm }}/protocol/openid-connect/token'
OAUTH_USER_INFO_URL = 'https://{{ podman_seafile_keycloak_hostname }}/realms/{{ podman_seafile_keycloak_realm }}/protocol/openid-connect/userinfo'
OAUTH_SCOPE = ["openid", "profile", "email"]
OAUTH_ATTRIBUTE_MAP = {
"sub": (True, "uid"),
"email": (False, "contact_email"),
"name": (False, "name")
}
ENABLE_ONLYOFFICE = True
ONLYOFFICE_APIJS_URL = 'https://{{ podman_seafile_hostname }}/onlyofficeds/web-apps/apps/api/documents/api.js'
ONLYOFFICE_JWT_SECRET = '{{ podman_seafile_jwt_private_key }}'
ONLYOFFICE_FILE_EXTENSION = ('doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'odt', 'fodt', 'odp', 'fodp', 'ods', 'fods', 'ppsx', 'pps', 'csv')
ONLYOFFICE_EDIT_FILE_EXTENSION = ('docx', 'pptx', 'xlsx', 'csv')
OFFICE_PREVIEW_MAX_SIZE = 30 * 1024 * 1024 # preview size, 30 MB