Huge role refactoring/cleanup. Other commits will propably follow. Because some bugs will exist. Still important for longrun and also for auto docs/help/slideshow generation

This commit is contained in:
2025-07-08 23:43:13 +02:00
parent 6b87a049d4
commit 563d5fd528
1242 changed files with 2301 additions and 1355 deletions

View File

@@ -0,0 +1,15 @@
# Development Notes
## Get Settings
## LDAP
```bash
docker compose exec web bash -c 'cd /app && RAILS_ENV=production bundle exec rails runner "puts Setting.all.select { |s| s.name.start_with?(\"ldap\") }.map { |s| \"#{s.name} = #{s.value}\" }"'
```
### All
```bash
docker compose exec web bash -c 'cd /app && RAILS_ENV=production bundle exec rails runner "Setting.all.each { |s| puts \"#{s.name} = #{s.value}\" }"'
```

View File

@@ -0,0 +1,52 @@
# OpenProject
## Description
Transform your project management with [OpenProject](https://www.openproject.org/), a vibrant and collaborative tool that brings clarity and energy to your planning, tracking, and team communication. Experience streamlined workflows and an innovative platform that propels your projects forward.
## Overview
Designed for simplicity, this role automates everything needed to run OpenProject in a containerized environment. It configures essential services such as the application itself, a PostgreSQL database, reverse proxy, and optional LDAP integration for identity management.
## Purpose
The purpose of this role is to reduce the complexity of setting up OpenProject with modern productionready defaults. By combining Docker Compose and Ansible automation, it enables a handsoff setup for both small teams and larger internal infrastructures.
## Features
- **Work Package Management**:
Create, assign, track, and prioritize tasks and issues with customizable workflows that keep your project organized and on schedule.
- **Gantt Charts & Timelines**:
Visualize project schedules and dependencies using intuitive Gantt charts and timeline views, enabling effective planning and resource allocation.
- **Agile Boards & Scrum/Kanban Integration**:
Manage agile projects using Scrum or Kanban boards, track progress through sprints, and maintain a clear overview of work in progress.
- **Time Tracking & Cost Management**:
Record time spent on tasks to monitor productivity and generate detailed cost reports to manage budgets effectively.
- **Collaboration & Document Management**:
Facilitate team collaboration with built-in discussion forums, document sharing, and version control, ensuring all project documentation remains up to date.
- **Robust Reporting & Dashboards**:
Gain insights through comprehensive reporting features and customizable dashboards that help monitor project performance and key metrics.
- **Custom Plugins & Extensibility**:
Extend functionality with a wide variety of plugins and integrations, or create your own to tailor OpenProject to your unique workflow.
- **Role-Based Access Control & Security**:
Manage user permissions precisely to ensure that sensitive information and critical functions remain secure.
## Developer Notes
See the [Development.md](./Development.md) file for how to inspect and modify live settings inside the container, including full LDAP and SMTP configuration via the Rails console.
## Credits 📝
Developed and maintained by **Kevin Veen-Birkenbach**
Learn more at [www.veen.world](https://www.veen.world)
Part of the [CyMaIS Project](https://github.com/kevinveenbirkenbach/cymais)
License: [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl)

View File

@@ -0,0 +1,3 @@
# Todo
- Finish corporate CSS implementation
- Implement RBAC via LDAP

View File

@@ -0,0 +1,4 @@
group :opf_plugins do
# Deactivated plugin because it seems like it's already included in the basic image
#gem "openproject-gitlab_integration", git: "https://github.com/btey/openproject-gitlab-integration", branch: "master"
end

View File

@@ -0,0 +1,33 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: >
Transform your project management with OpenProject, a vibrant and collaborative tool that
brings clarity and energy to your planning, tracking, and team communication. Experience streamlined
workflows and an innovative platform that propels your projects forward.
license: "CyMaIS NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
min_ansible_version: "2.9"
platforms:
- name: Archlinux
versions:
- rolling
galaxy_tags:
- openproject
- project-management
- docker
- compose
- ldap
- sso
- automation
repository: "https://s.veen.world/cymais"
issue_tracker_url: "https://s.veen.world/cymaisissues"
documentation: "https://s.veen.world/cymais"
logo:
class: "fa-solid fa-project-diagram"
run_after:
- web-app-keycloak

View File

@@ -0,0 +1 @@
credentials:

View File

@@ -0,0 +1,111 @@
- name: Load LDAP configuration variables
include_vars:
file: "ldap.yml"
- name: Check if LDAP source exists
community.postgresql.postgresql_query:
db: "{{ database_name }}"
login_user: "{{ database_username }}"
login_password: "{{ database_password }}"
login_host: "127.0.0.1"
login_port: "{{ database_port }}"
query: "SELECT id FROM ldap_auth_sources WHERE name = '{{ openproject_ldap.name }}' LIMIT 1;"
register: ldap_check
- name: Update existing LDAP auth source
community.postgresql.postgresql_query:
db: "{{ database_name }}"
login_user: "{{ database_username }}"
login_password: "{{ database_password }}"
login_host: "127.0.0.1"
login_port: "{{ database_port }}"
query: >
UPDATE ldap_auth_sources SET
host = '{{ openproject_ldap.host }}',
port = {{ openproject_ldap.port }},
account = '{{ openproject_ldap.account }}',
account_password = '{{ openproject_ldap.account_password }}',
base_dn = '{{ openproject_ldap.base_dn }}',
attr_login = '{{ openproject_ldap.attr_login }}',
attr_firstname = '{{ openproject_ldap.attr_firstname }}',
attr_lastname = '{{ openproject_ldap.attr_lastname }}',
attr_mail = '{{ openproject_ldap.attr_mail }}',
onthefly_register = {{ openproject_ldap.onthefly_register }},
attr_admin = '{{ openproject_ldap.attr_admin }}',
updated_at = NOW(),
tls_mode = {{ openproject_ldap.tls_mode }},
filter_string = '{{ openproject_ldap.filter_string }}',
verify_peer = {{ openproject_ldap.verify_peer }},
tls_certificate_string = '{{ openproject_ldap.tls_certificate_string }}'
WHERE name = '{{ openproject_ldap.name }}';
when: ldap_check.query_result | length > 0
- name: Create new LDAP auth source
community.postgresql.postgresql_query:
db: "{{ database_name }}"
login_user: "{{ database_username }}"
login_password: "{{ database_password }}"
login_host: "127.0.0.1"
login_port: "{{ database_port }}"
query: >
INSERT INTO ldap_auth_sources
(name, host, port, account, account_password, base_dn, attr_login,
attr_firstname, attr_lastname, attr_mail, onthefly_register, attr_admin,
created_at, updated_at, tls_mode, filter_string, verify_peer, tls_certificate_string)
VALUES (
'{{ openproject_ldap.name }}',
'{{ openproject_ldap.host }}',
{{ openproject_ldap.port }},
'{{ openproject_ldap.account }}',
'{{ openproject_ldap.account_password }}',
'{{ openproject_ldap.base_dn }}',
'{{ openproject_ldap.attr_login }}',
'{{ openproject_ldap.attr_firstname }}',
'{{ openproject_ldap.attr_lastname }}',
'{{ openproject_ldap.attr_mail }}',
{{ openproject_ldap.onthefly_register }},
'{{ openproject_ldap.attr_admin }}',
NOW(),
NOW(),
{{ openproject_ldap.tls_mode }},
'{{ openproject_ldap.filter_string }}',
{{ openproject_ldap.verify_peer }},
'{{ openproject_ldap.tls_certificate_string }}'
);
when: ldap_check.query_result | length == 0
- name: Show all LDAP sources (debug)
community.postgresql.postgresql_query:
db: "{{ database_name }}"
login_user: "{{ database_username }}"
login_password: "{{ database_password }}"
login_host: "127.0.0.1"
login_port: "{{ database_port }}"
query: "SELECT id, name FROM ldap_auth_sources"
register: ldap_entries
when: enable_debug | bool
- name: Debug LDAP entries
debug:
var: ldap_entries
when: enable_debug | bool
# This works just after the first admin login
# @todo Remove and replace trough LDAP RBAC group
- name: Set LDAP user as admin via OpenProject Rails runner
shell: >
docker compose exec web bash -c "
cd /app &&
RAILS_ENV={{ CYMAIS_ENVIRONMENT | lower }} bundle exec rails runner \"
user = User.find_by(mail: '{{ users.administrator.email }}');
if user.nil?;
puts 'User with email {{ users.administrator.email }} not found.';
else;
user.admin = true;
user.save!;
puts 'User \#{user.login} is now an admin.';
end
\"
"
args:
chdir: "{{ docker_compose.directories.instance }}"

View File

@@ -0,0 +1,45 @@
---
- name: "include service-rdbms-central"
include_role:
name: service-rdbms-central
- name: "include role webserver-proxy-domain for {{application_id}}"
include_role:
name: webserver-proxy-domain
vars:
domain: "{{ domains | get_domain(application_id) }}"
http_port: "{{ ports.localhost.http[application_id] }}"
- name: "Create {{openproject_plugins_folder}}"
file:
path: "{{openproject_plugins_folder}}"
state: directory
mode: '0755'
- name: "Transfering Gemfile.plugins to {{openproject_plugins_folder}}"
copy:
src: Gemfile.plugins
dest: "{{openproject_plugins_folder}}Gemfile.plugins"
notify:
- docker compose up
- name: "create {{dummy_volume}}"
file:
path: "{{dummy_volume}}"
state: directory
mode: 0755
- name: flush docker service
meta: flush_handlers
- name: Set settings in OpenProject
shell: >
docker compose exec web bash -c "cd /app &&
RAILS_ENV={{ CYMAIS_ENVIRONMENT | lower }} bundle exec rails runner \"Setting[:{{ item.key }}] = '{{ item.value }}'\""
args:
chdir: "{{ docker_compose.directories.instance }}"
loop: "{{ openproject_rails_settings | dict2items }}"
- name: Setup LDAP
include_tasks: ldap.yml
when: applications[application_id].features.ldap | bool

View File

@@ -0,0 +1,14 @@
FROM openproject/community:{{applications.openproject.version}}
# If installing a local plugin (using `path:` in the `Gemfile.plugins` above),
# you will have to copy the plugin code into the container here and use the
# path inside of the container. Say for `/app/vendor/plugins/openproject-slack`:
# COPY /path/to/my/local/openproject-slack /app/vendor/plugins/openproject-slack
COPY volumes/plugins/Gemfile.plugins /app/
# If the plugin uses any external NPM dependencies you have to install them here.
# RUN npm add npm <package-name>*
RUN bundle config unset deployment && bundle install && bundle config set deployment 'true'
RUN ./docker/prod/setup/postinstall.sh

View File

@@ -0,0 +1,98 @@
# @todo Test which containers can be removed crom cental_database networks
x-op-app: &app
logging:
driver: journald
image: {{custom_openproject_image}}
build:
context: .
dockerfile: Dockerfile
{% include 'roles/docker-compose/templates/base.yml.j2' %}
cache:
image: memcached
container_name: openproject-memcached
{% include 'roles/docker-container/templates/base.yml.j2' %}
proxy:
{% include 'roles/docker-container/templates/base.yml.j2' %}
image: {{custom_openproject_image}}
container_name: openproject-proxy
command: "./docker/prod/proxy"
ports:
- "127.0.0.1:{{ports.localhost.http[application_id]}}:80"
environment:
APP_HOST: web
depends_on:
- web
volumes:
- "data:/var/openproject/assets"
- "{{dummy_volume}}:/var/openproject/pgdata" # This mount is unnecessary and just done to prevent anonymous volumes
web:
<<: *app
{% include 'roles/docker-container/templates/base.yml.j2' %}
command: "./docker/prod/web"
container_name: openproject-web
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{% include 'roles/docker-container/templates/depends_on/dmbs_incl.yml.j2' %}
cache:
condition: service_started
seeder:
condition: service_started
{% set container_port = 8080 %}
{% set container_healthcheck = 'health_checks/default' %}
{% include 'roles/docker-container/templates/healthcheck/curl.yml.j2' %}
volumes:
- "data:/var/openproject/assets"
- "{{dummy_volume}}:/var/openproject/pgdata" # This mount is unnecessary and just done to prevent anonymous volumes
worker:
<<: *app
{% include 'roles/docker-container/templates/base.yml.j2' %}
command: "./docker/prod/worker"
container_name: openproject-worker
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{% include 'roles/docker-container/templates/depends_on/dmbs_incl.yml.j2' %}
cache:
condition: service_started
seeder:
condition: service_started
volumes:
- "data:/var/openproject/assets"
- "{{dummy_volume}}:/var/openproject/pgdata" # This mount is unnecessary and just done to prevent anonymous volumes
cron:
<<: *app
{% include 'roles/docker-container/templates/base.yml.j2' %}
command: "./docker/prod/cron"
container_name: openproject-cron
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{% include 'roles/docker-container/templates/depends_on/dmbs_incl.yml.j2' %}
cache:
condition: service_started
seeder:
condition: service_started
volumes:
- "data:/var/openproject/assets"
- "{{dummy_volume}}:/var/openproject/pgdata" # This mount is unnecessary and just done to prevent anonymous volumes
seeder:
<<: *app
command: "./docker/prod/seeder"
container_name: openproject-seeder
env_file:
- "{{docker_compose.files.env}}"
logging:
driver: journald
restart: on-failure
{% include 'roles/docker-container/templates/networks.yml.j2' %}
volumes:
- "data:/var/openproject/assets"
- "{{dummy_volume}}:/var/openproject/pgdata" # This mount is unnecessary and just done to prevent anonymous volumes
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
data:
{% include 'roles/docker-compose/templates/networks.yml.j2' %}

View File

@@ -0,0 +1,20 @@
##
# All environment variables defined here will only apply if you pass them
# to the OpenProject container in docker-compose.yml under x-op-app -> environment.
# For the examples here this is already the case.
#
# Please refer to our documentation to see all possible variables:
# https://www.openproject.org/docs/installation-and-operations/configuration/environment/
#
OPENPROJECT_HTTPS=true
OPENPROJECT_HOST__NAME={{domains | get_domain(application_id)}}
OPENPROJECT_RAILS__RELATIVE__URL__ROOT=
IMAP_ENABLED=false
POSTGRES_PASSWORD="{{ database_password }}"
DATABASE_URL="{{ database_url_full }}?pool=20&encoding=unicode&reconnect=true"
RAILS_MIN_THREADS=4
RAILS_MAX_THREADS=16
OPENPROJECT_HSTS=true
RAILS_CACHE_STORE: "memcache"
OPENPROJECT_CACHE__MEMCACHE__SERVER: "cache:11211"
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: ""

View File

@@ -0,0 +1,34 @@
version: "13" # Update when available. Sadly no rolling release implemented
oauth2_proxy:
application: "proxy"
port: "80"
acl:
whitelist:
- "/users/me" # Necessary for Nextcloud Plugin to work
- "/api/" # Necessary for Nextcloud Plugin to work
- "/oauth/token" # Necessary for Nextcloud Plugin to work
ldap:
filters:
administrators: True # Set true to filter administrators
users: False # Set true to filter users
features:
matomo: true
css: false # Temporary deactivated. Needs to be optimized for production use.
portfolio_iframe: true
ldap: true
central_database: true
oauth2: true
csp:
flags:
script-src-elem:
unsafe-inline: true
style-src:
unsafe-inline: true
domains:
canonical:
- "project.{{ primary_domain }}"
docker:
services:
database:
enabled: true

View File

@@ -0,0 +1,17 @@
openproject_ldap:
name: "{{ primary_domain }}" # Display name for the LDAP connection in OpenProject
host: "{{ ldap.server.domain }}" # LDAP server address
port: "{{ ldap.server.port }}" # LDAP server port (typically 389 or 636)
account: "{{ ldap.dn.administrator.data }}" # Bind DN (used for authentication)
account_password: "{{ ldap.bind_credential }}" # Bind password
base_dn: "{{ ldap.dn.ou.users }}" # Base DN for user search
attr_login: "{{ ldap.user.attributes.id }}" # LDAP attribute used for login
attr_firstname: "givenName" # LDAP attribute for first name
attr_lastname: "{{ ldap.user.attributes.surname }}" # LDAP attribute for last name
attr_mail: "{{ ldap.user.attributes.mail }}" # LDAP attribute for email
attr_admin: "{{ openproject_filters.administrators }}" # Optional: LDAP attribute for admin group (leave empty if unused)
onthefly_register: true # Automatically create users on first login
tls_mode: 0 # 0 = No TLS, 1 = TLS, 2 = STARTTLS
verify_peer: false # Whether to verify the SSL certificate
filter_string: "{{ openproject_filters.users }}" # Optional: Custom filter for users (e.g., "(objectClass=person)")
tls_certificate_string: "" # Optional: Client certificate string for TLS (usually left empty)

View File

@@ -0,0 +1,28 @@
application_id: "openproject"
docker_repository_address: "https://github.com/opf/openproject-deploy"
database_type: "postgres"
docker_repository: true
openproject_plugins_folder: "{{docker_compose.directories.volumes}}plugins/"
custom_openproject_image: "custom_openproject"
# The following volume doesn't have a practcical function. It just exist to prevent the creation of unnecessary anonymous volumes
dummy_volume: "{{docker_compose.directories.volumes}}dummy_volume"
openproject_rails_settings:
email_delivery_method: "smtp"
smtp_address: "{{ system_email.host }}"
smtp_domain: "{{ system_email.domain }}"
smtp_user_name: "{{ users['no-reply'].email }}"
smtp_password: "{{ users['no-reply'].mailu_token }}"
smtp_ssl: false
openproject_filters:
administrators: >-
{{ '(memberOf=cn=openproject-admins,' ~ ldap.dn.ou.roles ~ ')'
if applications[application_id].ldap.filters.administrators else '' }}
users: >-
{{ '(memberOf=cn=openproject-users,' ~ ldap.dn.ou.roles ~ ')'
if applications[application_id].ldap.filters.users else '' }}