Skip to content

Feat/numishare tomcat#237

Open
danyalberchtoldlf wants to merge 9 commits intomainfrom
feat/numishare-tomcat
Open

Feat/numishare tomcat#237
danyalberchtoldlf wants to merge 9 commits intomainfrom
feat/numishare-tomcat

Conversation

@danyalberchtoldlf
Copy link
Copy Markdown
Contributor

No description provided.

…urityManager, allowPaths

* Add Tomcat-9-style RHEL 10 support: pick java-21-openjdk-headless via a new
  vars/RedHat10.yml override (java-17 was dropped from EL10 AppStream). EL8/9
  are unchanged.

* Switch the Solr 9.x tarball download to dlcdn.apache.org first, fall back
  to archive.apache.org on 404 / failure. The archive server advertises
  `Vary: Slow,Glacial` and was making get_url hang for ~30 minutes; the CDN
  delivers a 371 MB tarball in seconds. Solr 8.x stays on the Lucene archive
  path (EOL, no longer mirrored by the CDN).

* Add `become: false` to both `delegate_to: localhost` get_url tasks so the
  role works under ansible-navigator EEs (sudo without a password fails
  inside the EE container).

* Expose three previously-buried-or-missing knobs as user-facing variables:
  - apache_solr__heap (default '512m', drives SOLR_HEAP)
  - apache_solr__security_manager_enabled (default true; disable for solr.solr.home outside Solr's permitted paths, e.g. Numishare)
  - apache_solr__allow_paths (default []; auto-augmented with apache_solr__dump_directory when backups are enabled, so Solr 9 can write its snapshots)

* Add a mariadb-dump-style backup pipeline:
  - /usr/local/bin/apache-solr-dump (curl + replication?command=backup,
    polls command=details until status==success, status==exception fails
    loudly, status==unknown after 10 min per core fails loudly)
  - /etc/apache-solr-dump.conf (sourced by the script)
  - apache-solr-dump.service (Type=oneshot, After=solr.service)
  - apache-solr-dump.timer (default 22:<minute-by-host-seed>:00 daily)
  Wipe-and-refresh per run; retention is the surrounding backup tool's job.
  apache_solr__dump_cores empty (default) disables the timer. The dump
  directory's parent is created as 0o755 root:root so other dump pipelines
  (existdb-dump, mariadb-dump) sharing /backup/ can still traverse into
  their own subdirs.

* Mark apache_solr as proven on RHEL 10 in COMPATIBILITY.md.

* README documents the optional backup variables and the matching restore
  via replication?command=restore + restorestatus polling.
…_xms/xmx/xx defaults

* Clone the existing 9.0 templates to a 10.1 set:
    etc/tomcat/10.1-server.xml.j2
    etc/tomcat/10.1-context.xml.j2
    etc/tomcat/10.1-logging.properties.j2
    etc/tomcat/10.1-tomcat-users.xml.j2
    etc/sysconfig/10.1-tomcat.j2
  The role's `tomcat__installed_version` lookup picks them up automatically
  on RHEL 10 (which ships tomcat-10.1.x in AppStream). The cloned configs
  are byte-compatible enough for our use case; once concrete divergences
  surface they will be patched in place rather than re-derived from the
  vendor stock files. EL8/9 deployments using 9.0 are unchanged.

* Expose the JVM heap knobs that lived as inline `| d('1024M')` fallbacks
  inside the sysconfig templates as first-class defaults:
    apache_tomcat__env_xms (default '1024M')
    apache_tomcat__env_xmx (default '1024M')
    apache_tomcat__env_xx  (default '+UseParallelGC')
  User-visible behavior is unchanged; users who want to tune the Tomcat
  heap from their inventory now have documented variables. Both the 9.0
  and 10.1 sysconfig templates drop the inline `| d(...)` and reference
  the defaults directly.

* Mark apache_tomcat as proven on RHEL 10 in COMPATIBILITY.md.
New role to install and operate eXist-db 6.x as a systemd-managed service.
Targeted at the Numishare stack but standalone-usable.

Install:
* Extract upstream exist-distribution-<version>-unix.tar.bz2 to /opt/existdb
* Create the existdb system user and group
* Create /var/lib/existdb/data and /var/log/existdb with the right ownership
* Rewrite Jetty HTTP/HTTPS ports off 8080/8443 to existdb__http_port (8888)
  / existdb__https_port (8444) so eXist-db can coexist with Tomcat or Wildfly
  on the same host
* Redirect log4j2 output to existdb__log_dir (/var/log/existdb)
* Force client.properties to 127.0.0.1 — eXist-db's Jetty binds IPv4 only,
  so on dual-stack hosts the bundled CLI tools (client.sh, backup.sh,
  restore.sh) hit the obscure "HTTP server returned unexpected status: null"
  when localhost resolves to ::1 first
* Set the admin password on first install only, gated by a marker file
  (.linuxfabrik-admin-password-set) so re-runs don't clobber a manually
  rotated password
* Drop a systemd unit, daemon-reload on changes, enable & start

Backup pipeline (mariadb-dump-style):
* /usr/local/bin/existdb-dump (calls bin/backup.sh; uses runuser instead of
  sudo because systemd's no-TTY oneshot context swallows sudo's stderr; passes
  -ouri=xmldb:exist://127.0.0.1:<port>/exist/xmlrpc explicitly because
  bin/backup.sh ignores client.properties at runtime and falls back to the
  compiled-in default port 8080)
* /etc/existdb-dump.conf (sourced by the script)
* existdb-dump.service (Type=oneshot, After=existdb.service)
* existdb-dump.timer (default 22:<minute-by-host-seed>:00 daily)
* Wipe-and-refresh per run; retention is the surrounding backup tool's job
* Toggle via existdb__dump_enabled (default true)
* Dump directory's parent is created as 0o755 root:root so other dump
  pipelines (apache-solr-dump, mariadb-dump) sharing /backup/ can still
  traverse into their own subdirs

README documents both the role and the matching bin/restore.sh invocation
for disaster recovery.

COMPATIBILITY: marked as proven on RHEL 10.
New role for Numishare (https://github.com/ewg118/numishare), the
open-source numismatic-collection platform. The role wires Numishare into
an already-deployed eXist-db / Solr / Tomcat / Orbeon stack rather than
managing those services itself.

Install:
* Install git-core and shallow-clone numishare__git_url to
  numishare__install_dir (default /opt/numishare). update: false — the
  checkout is one-time, intentional updates happen out-of-band.
* Deploy /opt/numishare/exist-config.xml so Numishare's XPL pipelines know
  how to authenticate against eXist-db. Mode 0640 (contains the eXist-db
  admin password in plaintext) and owned by the Tomcat user/group via
  numishare__app_{user,group}.
* Deploy /opt/numishare/solr-home/<version>/core.properties and create the
  /var/solr/data/<core_name> -> /opt/numishare/solr-home/<version> symlink
  so Solr's core discovery picks up the Numishare core. Both notify the
  apache_solr restart handler so Solr reloads the core (Solr only scans
  for cores at startup, so without the notify the core would never load
  on first deploy).
* chown -R numishare__solr_user:numishare__solr_group on the solr-home
  subtree.

Themes:
* mkdir -p numishare__themes_dir (default /opt/themes).
* Symlink <themes_dir>/default -> <install_dir>/ui to expose Numishare's
  bundled UI as the "default" theme via the same /orbeon/themes/<name>/
  delivery path as custom themes.
* Deploy custom themes from inventory via the numishare__themes__*
  combined-var pattern. Each entry is keyed by `name`; source is either
  git_url (with optional git_version, git_update) or tarball_url (with
  optional tarball_strip_components). state: 'absent' removes the theme.

README documents the variables and gives examples for both sources.

COMPATIBILITY: marked as proven on RHEL 10.
New role to deploy Orbeon Forms (https://www.orbeon.com/) Community Edition
into an already-deployed Tomcat for the Numishare stack. Heavily wired to
Numishare's expectations rather than a generic Orbeon installer.

Deployment:
* Install unzip, fetch orbeon_forms__zip_url, extract orbeon.war from the
  CE zip, and unarchive it exploded into orbeon_forms__home
  (/var/lib/tomcat/webapps/orbeon).
* Deploy <tomcat-conf>/Catalina/localhost/orbeon.xml with
  <Resources allowLinking="true"/>. The `path` attribute is intentionally
  omitted — Tomcat ignores it for context descriptors and emits a warning
  during startup.

Log path fix (RHEL 10 Tomcat):
* Orbeon's bundled WEB-INF/resources/config/log4j2.xml references log files
  via the relative path `../logs/orbeon.log`. With CATALINA_BASE on
  /usr/share/tomcat (RHEL 10), this resolves to /usr/share/logs/, which
  the tomcat user cannot create. Every FileAppender silently fails to
  initialize, Orbeon's diagnostics disappear, and the only surface is a
  generic "Page Not Found" with no log trail. The role rewrites the
  prefix to orbeon_forms__log_dir (default /var/log/tomcat).

Numishare wiring:
* Symlink orbeon_forms__numishare_dir -> WEB-INF/resources/apps/numishare.
* Copy <numishare>/vendor/exist-xqj-api-1.0.1/*.jar into WEB-INF/lib/.
* Place a Numishare-branded favicon at WEB-INF/resources/ops/images/
  orbeon-icon-16.{ico,png}. Numishare's xforms templates hardcode that
  path; Orbeon 2023.1 removed the file from the WAR (the entire
  WEB-INF/resources/ops/ tree is gone), so without this every Numishare
  page emits a 404 for the favicon.
* Copy properties-local.xml.template -> properties-local.xml (idempotent
  via force: false) and inject a Numishare block via blockinfile markers
  (oxf.epilogue.theme, oxf.fr.authentication.method=container, container
  roles).
* Replace the shipped <login-config> with the configured BASIC or FORM
  auth block (orbeon_forms__auth_method).
* Replace the shipped <session-config> with orbeon_forms__session_timeout
  (default 720 minutes; Numishare's admin forms benefit from a longer
  timeout).
* Inject a Numishare block before </web-app> (security-constraint for
  /numishare/admin/*, security-roles, /themes/* servlet-mapping for the
  Tomcat default servlet).

Themes:
* Symlink orbeon_forms__themes_dir (/opt/themes) into both the discovery
  path (apps/themes) and the delivery path (orbeon_home/themes), matching
  the structure the numishare role's apps/themes/default symlink expects.

Final chown -R orbeon_forms__tomcat_user:orbeon_forms__tomcat_group on the
deployment, then unconditional `systemctl restart tomcat.service`.

orbeon_forms__roles default ships only `numishare-admin` (the universal
Numishare role); per-collection container roles are added via the
inventory.

COMPATIBILITY: marked as proven on RHEL 10.
New setup_* playbook that wires up the full Numishare stack on a single
host, in the correct order:

  1. linuxfabrik.lfops.apps         (OS-level deps; injects apache_solr's
                                     bc / lsof / pwgen / tar via
                                     apache_solr__apps__apps__dependent_var)
  2. linuxfabrik.lfops.apache_solr  (numishare's solr-home is outside
                                     Solr's permitted paths, so we inject
                                     apache_solr__security_manager_enabled:
                                     false and apache_solr__dump_cores:
                                     ['numishare'] for the backup pipeline)
  3. linuxfabrik.lfops.apache_tomcat (provides the tomcat user/group
                                     numishare and orbeon_forms chown to)
  4. linuxfabrik.lfops.numishare    (writes the eXist-config and Solr
                                     core wiring before existdb/orbeon
                                     reference them)
  5. linuxfabrik.lfops.existdb      (XML database; numishare's
                                     exist-config.xml points at it)
  6. linuxfabrik.lfops.orbeon_forms (final WAR deployment + Numishare
                                     properties / web.xml / themes)

Each role can be skipped via setup_numishare__<role>__skip_role (per the
lfops setup_* convention). pre_tasks / post_tasks log start and end via
the shared role's log-start.yml / log-end.yml so a run leaves a trail in
/var/log/linuxfabrik-lfops.log.
…eon_forms

CONTRIBUTING.md requires every role to ship `meta/argument_specs.yml`
declaring all user-facing variables with types and (where applicable)
defaults, so Ansible validates them at role entry without manual
`assert + is defined` blocks in the tasks.

Each spec covers what is documented in the role README:
* mandatory variables (none of these three roles have any — the eXist-db
  admin password defaults to a non-secure placeholder and is documented
  as "must be overridden")
* simple optional variables with their static defaults
* the __host_var / __group_var halves of injection variables
  (numishare__themes__*) — internal __role_var, __dependent_var and
  __combined_var are intentionally excluded per CONTRIBUTING.

`default` is omitted on entries whose `defaults/main.yml` value is a
Jinja2 expression that argument_specs cannot evaluate
(existdb__dump_on_calendar, existdb__dump_password).

orbeon_forms__auth_method ships an explicit `choices: ['BASIC', 'FORM']`
constraint matching the README documentation; `BASIC` and `FORM` are the
only auth methods the login-config.xml.j2 template emits.
…ctions, service split, playbooks docs)

* playbooks/README.md: document setup_numishare.yml in alphabetical
  position between setup_nextcloud and setup_rocketchat (CONTRIBUTING:
  "After creating a new playbook, document it in playbooks/README.md").

* playbooks/all.yml: import setup_numishare.yml in the alphabetical slot
  between setup_nextcloud.yml and setup_rocketchat.yml (CONTRIBUTING: "and
  add it in the playbooks/all.yml").

* playbooks/setup_numishare.yml: introduce the
  setup_numishare__apache_solr__skip_injections__internal_var pattern
  exactly as CONTRIBUTING.md spells it out for setup_* playbooks. Defaults
  to the apache_solr skip_role state, can be overridden via
  setup_numishare__apache_solr__skip_injections to allow running the
  apache_solr role without injecting its OS deps into the apps role (e.g.
  when the user manages OS deps elsewhere). Apply via ternary on the
  apps role's apps__apps__dependent_var.

* roles/existdb/tasks/main.yml: split the combined enable+state systemd
  call into two separate ansible.builtin.service tasks per CONTRIBUTING
  ("Split the service `enabled` and `state` into separate tasks. This is
  relevant for handlers that would restart the service"). Same split for
  existdb-dump.timer; the timer block now also has its own
  ansible.builtin.systemd `daemon_reload: true` task gated on the
  template-deploy result rather than piggybacking on the combined call.
  Register the existdb.service state task as
  __existdb__service_state_result for any future handler that needs to
  skip a redundant restart, matching the pattern in roles/example.
… paths to numishare__install_dir

Numishare upstream ships two files with the install path hardcoded to
`/usr/local/projects/numishare` — the upstream maintainer's own layout:

* `xforms/admin.xhtml` line 101: the `<installation_path>` element in the
  XForms instance for the Add-New-Collection form. The value pre-fills the
  "Installation Directory" field and gets persisted into eXist-db per
  collection at `/db/numishare/<collection>/config.xml`.
* `script/reindex-collection.php`: the `$eXist_config_path` constant points
  at `/usr/local/projects/numishare/exist-config.xml` for the CLI reindex
  helper.

lfops installs Numishare to `/opt/numishare` by default. Without this
rewrite the admin form defaults to a non-existent path on every fresh
install (so the user has to clear and retype the field on every new
collection), and the reindex script crashes on missing `exist-config.xml`
until it is manually patched.

Add an `ansible.builtin.replace` task right after the git clone, scoped
to the two known files via a loop. We do not regex over the whole
checkout to avoid accidentally rewriting `docker/docker-compose.yml`,
which uses `/usr/local/projects/numishare` as a Docker volume mount path
that is intentionally independent of the host layout.

Re-runs are idempotent: replace finds nothing to change after the first
pass since the literal `/usr/local/projects/numishare` is gone.
@@ -0,0 +1,78 @@
- name: 'Playbook linuxfabrik.lfops.setup_numishare'
hosts: 'all'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be lfops_setup_numishare

Comment on lines +3 to +5
become: true
any_errors_fatal: true
serial: 1
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't set these unless absolutely necessary

Comment on lines +27 to +36
# OS-level helpers (java, git, unzip, ...). Reduce to whatever is missing
# by overriding `apps__apps__host_var` in the inventory. apache_solr's OS
# dependencies (bc, lsof, pwgen, tar) are injected here unless the user
# opts out via setup_numishare__apache_solr__skip_injections.
- role: 'linuxfabrik.lfops.apps'
apps__apps__dependent_var: '{{
(not setup_numishare__apache_solr__skip_injections__internal_var) | ternary(apache_solr__apps__apps__dependent_var, [])
}}'
when:
- 'not setup_numishare__apps__skip_role__internal_var'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just do this directly in the role itself

@@ -1,18 +1,29 @@
apache_solr__allow_paths: [] # extra paths Solr is allowed to read/write (-Dsolr.allowPaths). Backup directory is auto-included when apache_solr__dump_cores is non-empty.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments in the source code should be intended for the developers. user-facing info belongs into the README

dest: '/tmp/solr-{{ apache_solr__version }}.tgz'
checksum: '{{ apache_solr__checksum }}'
delegate_to: 'localhost'
become: false # writes to /tmp/ on the controller; ansible-navigator EE has no sudo
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't set this in the playbooks / roles, it should be set in the inventory for the respective host

@@ -0,0 +1,14 @@
<!-- {{ ansible_managed }} -->
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing timestamp

@@ -0,0 +1,10 @@
<!-- ANSIBLE MANAGED: Numishare login-config -->
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong header & missing timestamp

@@ -0,0 +1,25 @@
<!-- Numishare specific configuration -->
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing header

Comment on lines +34 to +36
## Mandatory Role Variables

None. All variables have defaults that match the Numishare stack layout.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove empty sections

* Direct download URL of the Orbeon CE zip. The role unpacks `orbeon.war` from inside the zip and explodes it.
* Type: String.
* Default: `'https://github.com/orbeon/orbeon-forms/releases/download/tag-release-2023.1-ce/orbeon-2023.1.202312312000-CE.zip'`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing examples section

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants