From a0a495d804319c3f7ad179baba7b87b41f1dc1f3 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Tue, 4 Aug 2026 21:11:25 +0200 Subject: Rework the README/INSTALL documents and provide a bootstrap script (untested ;) --- INSTALL.md | 333 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 30 +++++ README.rdoc | 4 - doc/CUTOVER_2026.md | 348 ++++++++++++++++++++++++++++++++++++++++++++++++++++ doc/INSTALL.md | 341 -------------------------------------------------- lib/tasks/init.rake | 75 +++++++++++ 6 files changed, 786 insertions(+), 345 deletions(-) create mode 100644 INSTALL.md create mode 100644 README.md delete mode 100644 README.rdoc create mode 100644 doc/CUTOVER_2026.md delete mode 100644 doc/INSTALL.md create mode 100644 lib/tasks/init.rake diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..cad9c9e1 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,333 @@ +# Installing CCCMS + +A Rails 8 application on PostgreSQL. ImageMagick 7 and Ghostscript are +hard runtime dependencies: image variants, PDF thumbnails and social +cards are shelled out to them. Production runs on FreeBSD behind nginx +with Unicorn; development works anywhere the stack below does. + +For the historical record of the June 2026 migration from Rails 2, see +`doc/CUTOVER_2026.md` — it is not an installation guide and is not +maintained. + +## 1. Dependencies + +| What | Why | FreeBSD 14/15 | Debian/Ubuntu | macOS (brew) | +|---|---|---|---|---| +| PostgreSQL 16 | database | `postgresql16-server postgresql16-client` | `postgresql postgresql-client libpq-dev` | `postgresql@16` | +| ImageMagick **7** | image variants, social cards | `ImageMagick7-nox11` | see trap below | `imagemagick` | +| Ghostscript | PDF rasterisation | `ghostscript10` | `ghostscript` | `ghostscript` | +| libyaml | psych | `libyaml` | `libyaml-dev` | `libyaml` | +| libffi, readline, gdbm | Ruby build | `libffi readline gdbm` | `libffi-dev libreadline-dev libgdbm-dev` | (in base) | +| libxml2, libxslt | libxml-ruby | `libxml2 libxslt` | `libxml2-dev libxslt1-dev` | `libxml2 libxslt` | +| libical | recurrence expansion via the chaos_calendar gem | libical | libical-dev | libical | +| GNU make | native gems | `gmake` | (default) | (default) | +| Node | asset pipeline | `node` | `nodejs` | `node` | +| git, curl, gnupg | fetching and verifying | `git curl gnupg` | `git curl gnupg` | (in base) | + +Debian trap: the `imagemagick` package is version 6 on Debian 12 and +earlier, which has no `magick` binary, only the deprecated `convert`. +The code calls `magick` at four sites in +`app/models/concerns/file_attachment.rb`. Check with `magick -version` +before going further; if it is absent, install from a backport or build +ImageMagick 7. + +FreeBSD jail: PostgreSQL needs System V shared memory. On the host, +in `/etc/jail.conf`: + + allow.sysvipc = 1; + +Restart the jail. Without it PostgreSQL fails to start with a cryptic +shared-memory error. + +On 14.x with libical 3.0.20+ the include path for libical is +``, not ``, should the chaos_calendar Gem act +up. + +## 2. Ruby and the gemset + +rvm is used for its gemsets, which work like Python venvs. Version +3.4.10. + + curl -L https://github.com/rvm/rvm/releases/download/1.29.12/1.29.12.tar.gz \ + -o /tmp/rvm.tar.gz + curl -L https://github.com/rvm/rvm/releases/download/1.29.12/1.29.12.tar.gz.asc \ + -o /tmp/rvm.tar.gz.asc + gpg --keyserver hkps://keys.openpgp.org \ + --recv-keys 7D2BAF1CF37B13E2069D6956105BD0E739499BDB + gpg --verify /tmp/rvm.tar.gz.asc /tmp/rvm.tar.gz + tar -xzf /tmp/rvm.tar.gz -C /tmp + bash /tmp/rvm-1.29.12/install --auto-dotfiles + source /usr/local/rvm/scripts/rvm + +**rvm 1.29.12 is the current stable release and is years old. Its +version list does not know about Ruby 3.4.** Replace it: + + curl -L https://raw.githubusercontent.com/rvm/rvm/master/config/known \ + -o /usr/local/rvm/config/known + rvm list known | sed -n '/# MRI/,/^$/p' + rvm install 3.4.10 --autolibs=read-only --with-opt-dir=/usr/local + +`--autolibs=read-only` stops rvm running the package manager on your +behalf. `--with-opt-dir=/usr/local` is the libyaml fix: ports and brew +install there, Ruby's configure does not look there, and without it +psych fails to build **silently** and surfaces much later as YAML errors +when Rails loads `database.yml`. Verify the build before continuing: + + ruby -ryaml -ropenssl -rzlib -e 'puts "ok #{Psych::LIBYAML_VERSION}"' + +Then the gemset: + + cd /path/to/cccms + rvm use 3.4.10@rails8-upgrade --create + +`.ruby-version` and `.ruby-gemset` in the project root make rvm switch +automatically on entering the directory. `.ruby-version` must keep the +`ruby-` prefix, `ruby-3.4.10`, not `3.4.10`, because the rc.d script +concatenates it into a gemset path and a bare version yields a path that +does not exist. + +## 3. Gems + + gem install bundler + MAKE=gmake bundle install + +`MAKE=gmake` on FreeBSD only, and it is not optional: several native +extensions fail against BSD make. + +## 4. Database + + # FreeBSD + sysrc 'postgresql_enable="YES"' + service postgresql initdb + service postgresql start + + psql -U postgres postgres + +```sql +CREATE ROLE rails WITH LOGIN PASSWORD 'choose-one'; +ALTER ROLE rails CREATEDB; + +CREATE DATABASE cccms_dev OWNER rails ENCODING 'UTF8' + LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; +CREATE DATABASE psql_test OWNER rails ENCODING 'UTF8' + LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; +``` + +`CREATEDB` is needed because the test suite creates and drops its own +database. `TEMPLATE template0` is required whenever a non-default locale +is given. + +Two config files are gitignored and must be created. `config/database.yml`: + +```yaml +development: + adapter: postgresql + encoding: unicode + database: cccms_dev + pool: 5 + username: rails + password: choose-one + +test: + adapter: postgresql + encoding: UTF8 + database: psql_test + username: rails + password: + +production: + adapter: postgresql + encoding: unicode + database: cccms_production + pool: 5 + username: rails + password: choose-one +``` + +`config/initializers/secret_token.rb`, one line: + +```ruby +Cccms::Application.config.secret_key_base = "<64 hex chars, e.g. from `rails secret`>" +``` + +### 4a. Migrate. Never load the schema. + + bundle exec rails db:migrate + +Do not run `db:setup` or `db:schema:load`. + +`db/schema.rb` is gitignored, and it could not be used even if it were +present: the full-text `search_vector` column is maintained by a PostgreSQL +trigger, and Ruby's schema format cannot express triggers. A schema-loaded +database gets the column and its GIN index with nothing populating them, +and site search then silently returns no results. Migrations are the only +complete record of the structure. + +## 5. First start + +Compile the admin assets. The TinyMCE bundle lives in gitignored +`public/assets/`: + + bundle exec rails assets:precompile + +Bootstrap the content tree and one account: + + ADMIN_PASS=choose-one bundle exec rake cccms:init + +`ADMIN_LOGIN` (default `admin`) and `ADMIN_EMAIL` are optional. A missing +`ADMIN_PASS` aborts. The task creates root, the Trash, `home`, +`/updates`, `/disclosure`, `/club/erfas`, `/club/chaostreffs` with +placeholder titles, and is idempotent. + +Start the server: + + bundle exec rails server -p 3000 -b 0.0.0.0 + +`-b 0.0.0.0` is required inside a FreeBSD jail, where `localhost` does +not resolve. + +`public/system/uploads/` starts empty. It is gitignored; on a fresh +install there is nothing to copy. + +### The first admin needs two logins + +The bootstrap account is an administrator without a second factor, so +it cannot yet create users, reset factors or deactivate accounts: +administrative actions need a code entered within the last thirty +minutes, and there is no password-only path. This is deliberate. To +finish: + +1. sign in as the bootstrap account +2. **Mein Konto** -> enable second factor, scan the QR code, confirm +3. sign out, sign in again, entering the code + +Elevation is granted at that login and user management unlocks. + +## 6. Production on FreeBSD + +Unicorn, started by an rc.d script. Templates in `doc/`: + + doc/unicorn.rb -> /usr/local/etc/unicorn.rb + doc/rc.d_cccms -> /usr/local/etc/rc.d/cccms + +The rc.d script reads `.ruby-version` and `.ruby-gemset` from the project +directory to find the gemset — see the prefix note in §2. + +nginx proxies everything to Unicorn. Uploads need their own block: + + location /system/uploads/ { + add_header Content-Security-Policy "sandbox" always; + add_header X-Content-Type-Options "nosniff" always; + + proxy_pass http://127.0.0.1:9090; + proxy_set_header Host $host; + proxy_buffering off; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + proxy_pass http://127.0.0.1:9090/; + proxy_set_header Host $host; + proxy_buffering off; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + } + +- Note: No trailing slash on its `proxy_pass`. With one, nginx strips the + matched prefix and the backend 404s. The `location /` block gets away + with a trailing slash only because replacing `/` with `/` is a no-op. +- The CSP is not optional. Uploaded files are served by Rails' static + file server, which bypasses the middleware that sets the application's + security headers. Without `sandbox`, an uploaded SVG opened directly is + a document that runs its own script, on the same origin as the site + and its admin sessions. +- `add_header` in a location replaces inherited headers, so anything + set at server level must be repeated here. + +## 7. Maintenance + +### Deploy + + service cccms stop && git pull && bundle exec rails db:migrate && service cccms start + +`bundle install` too when `Gemfile.lock` changed. Use `install over` +`update`: the lockfile names exact versions and checksums, so the server +gets what was tested. In development, `touch tmp/restart.txt` restarts a +running server in place. + +Occurrences are regenerated yearly at service start. Recurring +events are expanded into finite `occurrences` rows rather than computed +per request. Range queries over 200+ recurring events would otherwise +mean full RRULE expansion on every page load. The window is five years, +which is chaos_calendar's expansion limit. + +The rc.d script's `start_postcmd` regenerates when +`/var/db/cccms_occurrences_regenerated` is missing or older than 365 +days. Run at post-start, since it must not block the server coming up +or run when startup failed. + + service cccms regenerate_occurrences + +The yearly cadence is chosen to coincide with the reboot that follows an +operating-system upgrade. Regeneration is expensive, and that is the +natural point to pay for it. + +### Security updates + + gem install bundler-audit # once, outside the Gemfile + bundle-audit check --update + +Worth running monthly. Vulnerabilities in the HTML sanitizer matter most +here: every page body passes through it. + +Ruby upgrades: a new gemset rather than a replacement, so the old one +remains as the way back. Install and populate the new gemset before +pulling a commit that changes `.ruby-version`, or every `rake` and +`runner` invocation breaks while the running server carries on under the +old Ruby. + +### One-shot tasks + +- `users:clear_otp` is the lockout escape hatch: it clears one account's + second factor from the shell when every administrator is locked out. + Deliberately unwitnessed — there is no actor to attribute a shell + command to. + +Logs are in `log/`, gitignored. The action log inside the application at +`/admin/log` records who changed what; `log/production.log` records +everything else. + +## 8. Traps + +- ImageMagick's policy travels with the project. + `config/imagemagick/policy.xml` is loaded via `MAGICK_CONFIGURE_PATH`, + set per invocation. Nothing to install, and do not patch the system + `policy.xml` or a port upgrade would revert it and a fresh checkout + would not have it. ImageMagick prepends the project path, so the + system file is still read. +- Two independent allowlists govern editor HTML. TinyMCE's + `extended_valid_elements` in `public/javascripts/admin_interface.js` + and the server's sanitizer in `ContentHelper#aggregate?`. An attribute + permitted by one and not the other is either offered and discarded, or + stripped from markup the application itself emits. They must be + changed together. +- `otp_required` is `false` on every account. Second factors are + effectively opt-in until that is flipped, and flipping it locks out + anyone who has not enrolled. +- Uploads are not in the repository. `public/system/` is gitignored + and is not covered by a database dump either. Back it up separately or + the site loses every image. +- The test database is not sandboxed against `rails runner`. A `runner` + invocation that writes will leave rows behind. Wrap writes in a + transaction with `raise ActiveRecord::Rollback`, or run + `RAILS_ENV=test bundle exec rails db:test:prepare` afterwards. +- Ruby 3.4 bundled gems are fatal under bundler. A `require` of a + gem that is bundled-but-not-default warns outside bundler and raises + `LoadError` under `bundle exec`. `csv` is already declared for this + reason; the same applies to `base64`, `bigdecimal` and friends if a + future `require` reaches for one. + +## Tests + + bundle exec rake test diff --git a/README.md b/README.md new file mode 100644 index 00000000..9dc81727 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# CCCMS + +The content management system behind [www.ccc.de](https://www.ccc.de). + +A Rails application with a nested-tree content model, per-node revision +history, translated content via Globalize, and a witnessed action log. +Editing is deliberately open: any editor may draft anywhere, and only +changes that reach the RSS feeds are gated on a role. + +## Stack + +Ruby 3.4, Rails 8.1, PostgreSQL 16, ImageMagick 7 with Ghostscript. +Production runs on FreeBSD behind nginx with Unicorn; development runs +anywhere the above are available. + +## Documentation + +- `INSTALL.md` — setting up from scratch, and maintaining an existing + installation +- `CONTRIBUTING.md` — conventions this codebase follows, and why +- `doc/CUTOVER_2026.md` — historical record of the June 2026 migration + from Rails 2 to Rails 8. Not maintained. + +## Repositories + +- https://codeberg.org/erdgeist/cccms +- git://erdgeist.org/cccms + +Public content is CC-licensed per page; see the site itself. The code is +beerware. Original code credits to https://github.com/hukl/cccms diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index 5adf6893..00000000 --- a/README.rdoc +++ /dev/null @@ -1,4 +0,0 @@ -=CCCMS - -This is the repository for the CCCMS. Its a simple content management system -inspired all the good parts of different other simple content management systems. diff --git a/doc/CUTOVER_2026.md b/doc/CUTOVER_2026.md new file mode 100644 index 00000000..2a6cb22a --- /dev/null +++ b/doc/CUTOVER_2026.md @@ -0,0 +1,348 @@ +# Rails 2 to Rails 8 cutover, June 2026 + +A historical record of one migration onto a fresh FreeBSD jail. NOT an +installation guide and no longer maintained: branch names, Ruby and +gemset versions, migration stamps and expected test counts are all +stale. See INSTALL.md for setting the project up. + +## CCCMS Installation Guide + +This document covers the non-obvious steps required to install the CCCMS +stack on a fresh FreeBSD jail. It assumes a FreeBSD 14.x base jail with +network access and a working pkg repository. + +## 1. Install packages + +```sh +pkg install gmake pkgconf curl gnupg git autoconf automake libtool bash \ + readline libyaml libffi gdbm libxml2 libxslt libical \ + postgresql16-server postgresql16-client \ + ImageMagick7-nox11 node vim +``` + +Note: the package is `ImageMagick7-nox11`, not `ImageMagick-nox11`. The +nox11 variant avoids pulling in the entire X11 dependency chain. + +## 2. Enable sysvipc for the jail + +PostgreSQL uses System V shared memory for inter-process communication. +On the host, the jail must have sysvipc enabled. In `/etc/jail.conf` or +the jail's ezjail configuration: + + `allow.sysvipc = 1;` + +Restart the jail after making this change. Without it, PostgreSQL will +fail to start with a shared memory error. + +## 3. Enable and initialise PostgreSQL + +```sh +# Enable PostgreSQL in rc.conf +echo 'postgresql_enable="YES"' >> /etc/rc.conf + +# Initialise the database cluster +service postgresql initdb + +# Start PostgreSQL +service postgresql start +``` + +## 4. Create database roles and set permissions + +```sh +psql -U postgres postgres +``` + +```sql +CREATE ROLE rails WITH LOGIN PASSWORD 'your-password-here'; +ALTER ROLE rails CREATEDB; +``` + +`CREATEDB` is required for the Rails test suite to create and drop the +test database between runs. + +## 5. Create databases + +```sql +CREATE DATABASE cccms_production OWNER rails ENCODING 'UTF8' + LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; +CREATE DATABASE cccms_dev OWNER rails ENCODING 'UTF8' + LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; +CREATE DATABASE psql_test OWNER rails ENCODING 'UTF8' + LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; +``` + +`TEMPLATE template0` is required when specifying a non-default locale. + +## 6. Restore the database dump (or start empty) + +If restoring from a pg_dump: + +```sh +pg_restore -U postgres -d cccms_production \ + --no-owner --no-acl /path/to/cccms_production.dump + +pg_restore -U postgres -d cccms_dev \ + --no-owner --no-acl /path/to/cccms_production.dump +``` + +Expected harmless warnings: +- `schema "public" already exists` — benign, ignore +- `array_accum aggregate` failure — dead code, ignore + +Transfer ownership to the rails user (REASSIGN OWNED does not work on +PostgreSQL 15+ due to system object protection): + +```sh +psql -U postgres cccms_production +``` + +```sql +DO $$ +DECLARE + obj RECORD; +BEGIN + FOR obj IN + SELECT tablename FROM pg_tables WHERE schemaname = 'public' + LOOP + EXECUTE 'ALTER TABLE public.' || quote_ident(obj.tablename) || + ' OWNER TO rails'; + END LOOP; + FOR obj IN + SELECT sequence_name FROM information_schema.sequences + WHERE sequence_schema = 'public' + LOOP + EXECUTE 'ALTER SEQUENCE public.' || quote_ident(obj.sequence_name) || + ' OWNER TO rails'; + END LOOP; + FOR obj IN + SELECT viewname FROM pg_views WHERE schemaname = 'public' + LOOP + EXECUTE 'ALTER VIEW public.' || quote_ident(obj.viewname) || + ' OWNER TO rails'; + END LOOP; +END $$; + +ALTER SCHEMA public OWNER TO rails; +``` + +Repeat for `cccms_dev`. + +## 7. Clone the repository + +```sh +cd /usr/local/www +git clone https://github.com/erdgeist/cccms.git +cd cccms +git checkout rails-upgrade +``` + +## 8. Copy assets (optional but recommended) + +The `public/system/uploads/` directory contains all uploaded files +referenced by the database. Without it, images and attachments will be +missing throughout the site. + +```sh +# On the source system: +tar -czf /tmp/cccms_uploads.tar.gz -C /usr/local/www cccms/public/system + +# On the new system: +tar -xzf /path/to/cccms_uploads.tar.gz -C /usr/local/www +``` + +## 9. Copy gitignored config files + +These files are not in the repository and must be copied or created: + +```sh +# Required: +config/database.yml +config/initializers/secret_token.rb + +# If used: +config/initializers/exception_notification.rb +/usr/local/etc/unicorn.rb +``` + +`database.yml` template: + +```yaml +development: + adapter: postgresql + encoding: unicode + database: cccms_dev + pool: 5 + username: rails + password: your-password-here + +test: + adapter: postgresql + encoding: UTF8 + database: psql_test + username: rails + password: + +production: + adapter: postgresql + encoding: unicode + database: cccms_production + pool: 5 + username: rails + password: your-password-here +``` + +## 10. Install rvm + +rvm 1.29.12 is the latest formal release as of mid-2026. Download and +verify before installing: + +```sh +curl -L https://github.com/rvm/rvm/releases/download/1.29.12/1.29.12.tar.gz \ + -o /tmp/rvm-1.29.12.tar.gz +curl -L https://github.com/rvm/rvm/releases/download/1.29.12/1.29.12.tar.gz.asc \ + -o /tmp/rvm-1.29.12.tar.gz.asc + +gpg --keyserver hkps://keys.openpgp.org \ + --recv-keys 7D2BAF1CF37B13E2069D6956105BD0E739499BDB + +gpg --verify /tmp/rvm-1.29.12.tar.gz.asc /tmp/rvm-1.29.12.tar.gz +``` + +If verification passes: + +```sh +tar -xzf /tmp/rvm-1.29.12.tar.gz -C /tmp +bash /tmp/rvm-1.29.12/install --auto-dotfiles +source /usr/local/rvm/scripts/rvm +``` + +The installed rvm ships with a stale known-versions list that only goes +to Ruby 3.0.0. Update it immediately: + +```sh +curl -L https://raw.githubusercontent.com/rvm/rvm/master/config/known \ + -o /usr/local/rvm/config/known +rvm list known | grep '^\[ruby-\]3\.' +``` + +Should now show Ruby 3.2.x and later. + +## 11. Install Ruby and create gemset + +```sh +source /usr/local/rvm/scripts/rvm +rvm install 3.2.11 --autolibs=read-only --with-opt-dir=/usr/local +rvm use 3.2.11 +rvm gemset create rails7-upgrade +rvm use 3.2.11@rails7-upgrade +``` + +The `.ruby-version` and `.ruby-gemset` files in the project root will +cause rvm to switch automatically when entering the project directory. + +## 12. Install bundler and gems + +```sh +gem install bundler +cd /usr/local/www/cccms +export MAKE=gmake +bundle install 2>&1 | tee /tmp/bundle_install.log +``` + +`MAKE=gmake` is required because FreeBSD's native make (BSD make) uses +different `-j` syntax than native gems expect. Without it, several native +gem compilations will fail. + +## 13. Run migrations + +```sh +bundle exec rails db:migrate +``` + +If restoring an existing database, first insert fake migration versions +to prevent re-running migrations that were applied to the old schema: + +```sql +INSERT INTO schema_migrations (version) VALUES + ('20260624035149'), ('20260624035150'), ('20260624035151'), + ('20260624035152'), ('20260624035153'), + ('20260625031409') +ON CONFLICT DO NOTHING; +``` + +Then run `db:migrate` to apply only new migrations. + +To enable full-text search (requires PostgreSQL 10+ with plpgsql): + +```sh +mv doc/20260626025705_add_search_vector_to_page_translations.rb.pending \ + db/migrate/20260626025705_add_search_vector_to_page_translations.rb +bundle exec rails db:migrate +``` + +## 14. Compile admin assets + +```sh +bundle exec rails assets:precompile +``` + +This compiles the admin JavaScript bundle (jQuery, jQuery UI, hotkeys) +into `public/assets/`. Required for the admin interface to work. Must be +re-run after any changes to `app/assets/javascripts/admin_bundle.js`. + +## 15. Run tests (optional but recommended) + +```sh +bundle exec rake test +``` + +Expected result: 129 runs, ~339 assertions, 3 failures, 0 errors. +The 3 failures are pre-existing and documented in the handover document. + +## 16. Start the server + +Development: + +```sh +bundle exec rails server -p 3000 -b 0.0.0.0 -e development +``` + +Note: `-b 0.0.0.0` is required — `localhost` does not resolve inside +a FreeBSD jail. + +Production (unicorn): + +```sh +/usr/local/rvm/gems/ruby-3.2.11@rails7-upgrade/wrappers/unicorn \ + -c /usr/local/etc/unicorn.rb -E production -D +``` + +The rc.d script at `/etc/rc.d/cccms` needs updating from `unicorn_rails` +to `unicorn` before use — see the handover document for details. + +## Known Gotchas + +**sysvipc:** PostgreSQL will fail silently or with a cryptic error if +sysvipc is not enabled for the jail. Enable it on the host before starting +PostgreSQL. + +**MAKE=gmake:** Native gem compilation fails without this. Set it before +every `bundle install` or add to your shell profile. + +**rvm known versions:** rvm 1.29.12 ships with a stale `config/known` that +only lists Ruby up to 3.0.0. Always update from master after installing rvm. + +**ImageMagick 7:** The `convert` command is deprecated; use `magick convert`. +The `file_attachment.rb` concern needs updating before production use. + +**pg_hba.conf:** The default FreeBSD PostgreSQL configuration uses `trust` +for local Unix socket connections, which is sufficient for the application. +No changes needed unless TCP connections are required. + +**assets:precompile:** Must be run after checkout and after any changes to +admin JavaScript. The compiled files in `public/assets/` are gitignored. + +**chaos_calendar include path:** On FreeBSD 14.x with libical 3.0.20+, +the include path is `` not ``. This is already +fixed in the `erdgeist-ruby1.9` branch. diff --git a/doc/INSTALL.md b/doc/INSTALL.md deleted file mode 100644 index 8056f7cf..00000000 --- a/doc/INSTALL.md +++ /dev/null @@ -1,341 +0,0 @@ -# CCCMS Installation Guide - -This document covers the non-obvious steps required to install the CCCMS -stack on a fresh FreeBSD jail. It assumes a FreeBSD 14.x base jail with -network access and a working pkg repository. - -## 1. Install packages - -```sh -pkg install gmake pkgconf curl gnupg git autoconf automake libtool bash \ - readline libyaml libffi gdbm libxml2 libxslt libical \ - postgresql16-server postgresql16-client \ - ImageMagick7-nox11 node vim -``` - -Note: the package is `ImageMagick7-nox11`, not `ImageMagick-nox11`. The -nox11 variant avoids pulling in the entire X11 dependency chain. - -## 2. Enable sysvipc for the jail - -PostgreSQL uses System V shared memory for inter-process communication. -On the host, the jail must have sysvipc enabled. In `/etc/jail.conf` or -the jail's ezjail configuration: - - `allow.sysvipc = 1;` - -Restart the jail after making this change. Without it, PostgreSQL will -fail to start with a shared memory error. - -## 3. Enable and initialise PostgreSQL - -```sh -# Enable PostgreSQL in rc.conf -echo 'postgresql_enable="YES"' >> /etc/rc.conf - -# Initialise the database cluster -service postgresql initdb - -# Start PostgreSQL -service postgresql start -``` - -## 4. Create database roles and set permissions - -```sh -psql -U postgres postgres -``` - -```sql -CREATE ROLE rails WITH LOGIN PASSWORD 'your-password-here'; -ALTER ROLE rails CREATEDB; -``` - -`CREATEDB` is required for the Rails test suite to create and drop the -test database between runs. - -## 5. Create databases - -```sql -CREATE DATABASE cccms_production OWNER rails ENCODING 'UTF8' - LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; -CREATE DATABASE cccms_dev OWNER rails ENCODING 'UTF8' - LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; -CREATE DATABASE psql_test OWNER rails ENCODING 'UTF8' - LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; -``` - -`TEMPLATE template0` is required when specifying a non-default locale. - -## 6. Restore the database dump (or start empty) - -If restoring from a pg_dump: - -```sh -pg_restore -U postgres -d cccms_production \ - --no-owner --no-acl /path/to/cccms_production.dump - -pg_restore -U postgres -d cccms_dev \ - --no-owner --no-acl /path/to/cccms_production.dump -``` - -Expected harmless warnings: -- `schema "public" already exists` — benign, ignore -- `array_accum aggregate` failure — dead code, ignore - -Transfer ownership to the rails user (REASSIGN OWNED does not work on -PostgreSQL 15+ due to system object protection): - -```sh -psql -U postgres cccms_production -``` - -```sql -DO $$ -DECLARE - obj RECORD; -BEGIN - FOR obj IN - SELECT tablename FROM pg_tables WHERE schemaname = 'public' - LOOP - EXECUTE 'ALTER TABLE public.' || quote_ident(obj.tablename) || - ' OWNER TO rails'; - END LOOP; - FOR obj IN - SELECT sequence_name FROM information_schema.sequences - WHERE sequence_schema = 'public' - LOOP - EXECUTE 'ALTER SEQUENCE public.' || quote_ident(obj.sequence_name) || - ' OWNER TO rails'; - END LOOP; - FOR obj IN - SELECT viewname FROM pg_views WHERE schemaname = 'public' - LOOP - EXECUTE 'ALTER VIEW public.' || quote_ident(obj.viewname) || - ' OWNER TO rails'; - END LOOP; -END $$; - -ALTER SCHEMA public OWNER TO rails; -``` - -Repeat for `cccms_dev`. - -## 7. Clone the repository - -```sh -cd /usr/local/www -git clone https://github.com/erdgeist/cccms.git -cd cccms -git checkout rails-upgrade -``` - -## 8. Copy assets (optional but recommended) - -The `public/system/uploads/` directory contains all uploaded files -referenced by the database. Without it, images and attachments will be -missing throughout the site. - -```sh -# On the source system: -tar -czf /tmp/cccms_uploads.tar.gz -C /usr/local/www cccms/public/system - -# On the new system: -tar -xzf /path/to/cccms_uploads.tar.gz -C /usr/local/www -``` - -## 9. Copy gitignored config files - -These files are not in the repository and must be copied or created: - -```sh -# Required: -config/database.yml -config/initializers/secret_token.rb - -# If used: -config/initializers/exception_notification.rb -/usr/local/etc/unicorn.rb -``` - -`database.yml` template: - -```yaml -development: - adapter: postgresql - encoding: unicode - database: cccms_dev - pool: 5 - username: rails - password: your-password-here - -test: - adapter: postgresql - encoding: UTF8 - database: psql_test - username: rails - password: - -production: - adapter: postgresql - encoding: unicode - database: cccms_production - pool: 5 - username: rails - password: your-password-here -``` - -## 10. Install rvm - -rvm 1.29.12 is the latest formal release as of mid-2026. Download and -verify before installing: - -```sh -curl -L https://github.com/rvm/rvm/releases/download/1.29.12/1.29.12.tar.gz \ - -o /tmp/rvm-1.29.12.tar.gz -curl -L https://github.com/rvm/rvm/releases/download/1.29.12/1.29.12.tar.gz.asc \ - -o /tmp/rvm-1.29.12.tar.gz.asc - -gpg --keyserver hkps://keys.openpgp.org \ - --recv-keys 7D2BAF1CF37B13E2069D6956105BD0E739499BDB - -gpg --verify /tmp/rvm-1.29.12.tar.gz.asc /tmp/rvm-1.29.12.tar.gz -``` - -If verification passes: - -```sh -tar -xzf /tmp/rvm-1.29.12.tar.gz -C /tmp -bash /tmp/rvm-1.29.12/install --auto-dotfiles -source /usr/local/rvm/scripts/rvm -``` - -The installed rvm ships with a stale known-versions list that only goes -to Ruby 3.0.0. Update it immediately: - -```sh -curl -L https://raw.githubusercontent.com/rvm/rvm/master/config/known \ - -o /usr/local/rvm/config/known -rvm list known | grep '^\[ruby-\]3\.' -``` - -Should now show Ruby 3.2.x and later. - -## 11. Install Ruby and create gemset - -```sh -source /usr/local/rvm/scripts/rvm -rvm install 3.2.11 --autolibs=read-only --with-opt-dir=/usr/local -rvm use 3.2.11 -rvm gemset create rails7-upgrade -rvm use 3.2.11@rails7-upgrade -``` - -The `.ruby-version` and `.ruby-gemset` files in the project root will -cause rvm to switch automatically when entering the project directory. - -## 12. Install bundler and gems - -```sh -gem install bundler -cd /usr/local/www/cccms -export MAKE=gmake -bundle install 2>&1 | tee /tmp/bundle_install.log -``` - -`MAKE=gmake` is required because FreeBSD's native make (BSD make) uses -different `-j` syntax than native gems expect. Without it, several native -gem compilations will fail. - -## 13. Run migrations - -```sh -bundle exec rails db:migrate -``` - -If restoring an existing database, first insert fake migration versions -to prevent re-running migrations that were applied to the old schema: - -```sql -INSERT INTO schema_migrations (version) VALUES - ('20260624035149'), ('20260624035150'), ('20260624035151'), - ('20260624035152'), ('20260624035153'), - ('20260625031409') -ON CONFLICT DO NOTHING; -``` - -Then run `db:migrate` to apply only new migrations. - -To enable full-text search (requires PostgreSQL 10+ with plpgsql): - -```sh -mv doc/20260626025705_add_search_vector_to_page_translations.rb.pending \ - db/migrate/20260626025705_add_search_vector_to_page_translations.rb -bundle exec rails db:migrate -``` - -## 14. Compile admin assets - -```sh -bundle exec rails assets:precompile -``` - -This compiles the admin JavaScript bundle (jQuery, jQuery UI, hotkeys) -into `public/assets/`. Required for the admin interface to work. Must be -re-run after any changes to `app/assets/javascripts/admin_bundle.js`. - -## 15. Run tests (optional but recommended) - -```sh -bundle exec rake test -``` - -Expected result: 129 runs, ~339 assertions, 3 failures, 0 errors. -The 3 failures are pre-existing and documented in the handover document. - -## 16. Start the server - -Development: - -```sh -bundle exec rails server -p 3000 -b 0.0.0.0 -e development -``` - -Note: `-b 0.0.0.0` is required — `localhost` does not resolve inside -a FreeBSD jail. - -Production (unicorn): - -```sh -/usr/local/rvm/gems/ruby-3.2.11@rails7-upgrade/wrappers/unicorn \ - -c /usr/local/etc/unicorn.rb -E production -D -``` - -The rc.d script at `/etc/rc.d/cccms` needs updating from `unicorn_rails` -to `unicorn` before use — see the handover document for details. - -## Known Gotchas - -**sysvipc:** PostgreSQL will fail silently or with a cryptic error if -sysvipc is not enabled for the jail. Enable it on the host before starting -PostgreSQL. - -**MAKE=gmake:** Native gem compilation fails without this. Set it before -every `bundle install` or add to your shell profile. - -**rvm known versions:** rvm 1.29.12 ships with a stale `config/known` that -only lists Ruby up to 3.0.0. Always update from master after installing rvm. - -**ImageMagick 7:** The `convert` command is deprecated; use `magick convert`. -The `file_attachment.rb` concern needs updating before production use. - -**pg_hba.conf:** The default FreeBSD PostgreSQL configuration uses `trust` -for local Unix socket connections, which is sufficient for the application. -No changes needed unless TCP connections are required. - -**assets:precompile:** Must be run after checkout and after any changes to -admin JavaScript. The compiled files in `public/assets/` are gitignored. - -**chaos_calendar include path:** On FreeBSD 14.x with libical 3.0.20+, -the include path is `` not ``. This is already -fixed in the `erdgeist-ruby1.9` branch. diff --git a/lib/tasks/init.rake b/lib/tasks/init.rake new file mode 100644 index 00000000..7e3d8dcc --- /dev/null +++ b/lib/tasks/init.rake @@ -0,0 +1,75 @@ +namespace :cccms do + desc "Bootstrap a fresh installation: the node skeleton and one admin " \ + "account. Idempotent -- every step finds before it creates, so " \ + "re-running after a new step is added is safe. " \ + "Requires ADMIN_PASS. ADMIN_LOGIN and ADMIN_EMAIL are optional. " \ + "The admin is created without the role and promoted with " \ + "update_column, because admin_needs_second_factor refuses a NEW " \ + "admin without an enrolled factor -- it exempts retention, not " \ + "creation. The account therefore cannot do user management until " \ + "it enrols a second factor and signs in again; see INSTALL.md." + task :init => :environment do + password = ENV["ADMIN_PASS"].to_s + abort "usage: ADMIN_PASS=secret bundle exec rake cccms:init" if password.empty? + abort "ADMIN_PASS must be at least 6 characters" if password.length < 6 + + login = ENV.fetch("ADMIN_LOGIN", "admin") + email = ENV.fetch("ADMIN_EMAIL", "admin@example.org") + + # publish_draft! is called with no user, which guard_live_change! treats + # as a trusted system context -- the documented nil-user path, and the + # reason a rake task can publish into /updates and /disclosure at all. + ensure_node = lambda do |parent, slug, title, body| + existing = parent ? parent.children.find_by(:slug => slug) : Node.root + if existing + puts format(" %-14s exists (%d)", slug || "root", existing.id) + next existing + end + + node = parent ? parent.children.create!(:slug => slug) : Node.create! + Globalize.with_locale(I18n.default_locale) do + node.draft.update!(:title => title, :body => body.to_s) + end + node.publish_draft! + puts format(" %-14s created (%d)", slug || "root", node.id) + node + end + + puts "Node skeleton:" + root = ensure_node.(nil, nil, "CCC", "") + + # Referencing it is enough: Node.trash self-creates on first call. + puts format(" %-14s ready (%d)", "trash", Node.trash.id) + + ensure_node.(root, "home", "Startseite", "") + + ensure_node.(root, "updates", "Updates", + '[aggregate tags="update" limit="30" order_by="published_at" order_direction="DESC"]') + + ensure_node.(root, "disclosure", "Disclosure", "") + + club = ensure_node.(root, "club", "Chaos Computer Club", "") + ensure_node.(club, "erfas", "Erfa-Kreise", + '[aggregate children="direct" order_by="slug" partial="chapter"]') + ensure_node.(club, "chaostreffs", "Chaostreffs", + '[aggregate children="direct" order_by="slug" partial="chapter"]') + + puts + if User.any? + puts "Accounts exist already; skipping admin creation." + else + user = User.create!(:login => login, :email => email, + :password => password, + :password_confirmation => password) + user.update_column(:roles, %w[admin redaktion]) + puts "Created #{user.login} <#{user.email}> as admin + redaktion." + puts + puts "This account has no second factor, so it cannot yet create" + puts "users, reset factors or deactivate accounts. To finish:" + puts " 1. sign in as #{user.login}" + puts " 2. Mein Konto -> enable second factor, scan the QR, confirm" + puts " 3. sign out and sign in again, entering the code" + puts "Elevation is granted at that login and user management unlocks." + end + end +end -- cgit v1.3