Skip to content

Conversation

@printminion-co
Copy link
Contributor

@printminion-co printminion-co commented Jan 13, 2026

Summary

This PR introduces a new event InstallationCompletedEvent that is dispatched after a successful Nextcloud installation. This allows apps and integrations to hook into the installation process and perform additional actions once the system is fully set up.

Backstory/Precondition: We are using Nextcloud in k8s. Meaning all apps are already "baked" into image. Meaning Setup::install calls Installer::installShippedApps(false, $output); always and installs all apps in all apps folders.

Example: InstallationCompletedEvent Sequence Diagram - Welcome Mail Focus
sequenceDiagram
    participant NC as Nextcloud Core
    participant ED as Event Dispatcher
    participant PIEL as PostInstallEventListener
    participant UM as UserManager
    participant IConfig as IConfig
    participant AppConfig as IAppConfig
    participant JobList as IJobList
    participant PSJ as PostSetupJob
    participant HTTP as HTTP Client
    participant WMH as WelcomeMailHelper

    Note over NC,WMH: Application Bootstrap & Post-Installation Phase
    NC->>ED: dispatch(InstallationCompletedEvent)
    ED->>PIEL: handle(Event)
    
    PIEL->>AppConfig: setValueString("example_admin_app", "post_install", "INIT")
    
    Note over PIEL: Initialize Admin User
    PIEL->>PIEL: initAdminUser()
    PIEL->>UM: get(adminUserId)
    UM-->>PIEL: adminUser
    PIEL->>IConfig: setUserValue(adminUserId, 'core', 'lang', language)
    
    rect rgb(240, 240, 240)
        Note over PIEL: Configure SMTP for Welcome Mail<br/>(black box)
        PIEL->>PIEL: setSendMailAccount()
    end
    
    Note over PIEL,JobList: Schedule Welcome Mail Job
    PIEL->>JobList: add(PostSetupJob.class, adminUserId)
    PIEL-->>ED: handle complete
    
    Note over NC,WMH: Background Job Execution (Every 2 seconds)
    loop Until job completes or times out
        NC->>JobList: execute scheduled jobs
        JobList->>PSJ: run(adminUserId)
        PSJ->>AppConfig: getValueString("example_admin_app", "post_install", "UNKNOWN")
        
        alt Status is "DONE"
            PSJ->>JobList: remove(this)
            Note right of PSJ: Job already completed
        else Status is "UNKNOWN"
            Note right of PSJ: Wait for initialization
        else Status is "INIT"
            Note over PSJ,WMH: Attempt to Send Welcome Mail
            PSJ->>PSJ: sendInitialWelcomeMail(adminUserId)
            
            Note over PSJ,HTTP: Check if Nextcloud URL is ready
            PSJ->>IConfig: getSystemValue("overwrite.cli.url")
            IConfig-->>PSJ: baseUrl
            PSJ->>PSJ: isUrlAvailable(client, baseUrl)
            PSJ->>HTTP: GET baseUrl/status.php
            
            alt URL not available (status != 2xx)
                HTTP-->>PSJ: error or non-2xx status
                Note right of PSJ: Domain not ready,<br/>retry in next cron run
            else URL is available
                HTTP-->>PSJ: status 200 OK
                
                PSJ->>UM: userExists(adminUserId)
                alt User doesn't exist
                    UM-->>PSJ: false
                    Note right of PSJ: Log warning,<br/>skip welcome mail
                else User exists
                    UM-->>PSJ: true
                    PSJ->>UM: get(adminUserId)
                    UM-->>PSJ: adminUser
                    
                    rect rgb(240, 240, 240)
                        Note over PSJ,WMH: Send Welcome Mail<br/>(black box)
                        PSJ->>WMH: new WelcomeMailHelper()
                        PSJ->>WMH: sendWelcomeMail(adminUser, true)
                        WMH-->>PSJ: mail sent successfully
                    end
                end
                
                Note over PSJ,AppConfig: Mark Job as Complete (always)
                PSJ->>AppConfig: setValueString("example_admin_app", "post_install", "DONE")
                PSJ->>JobList: remove(this)
            end
        end
    end
    
    Note over NC,WMH: Welcome Mail Delivery Complete
Loading

Flow Description - Welcome Mail Execution

1. Post-Installation Event Triggered

When Nextcloud is installed, the InstallationCompletedEvent is dispatched and handled by PostInstallEventListener.

2. Initial Setup Phase (Synchronous)

2.1 Set Job Status

  • Sets example_admin_app.post_install status to "INIT" via IAppConfig::setValueString() to track the setup progress

2.2 Initialize Admin User

  • Initializes admin user configuration
  • Sets user language preferences via IConfig::setUserValue()

2.3 Configure SMTP for Welcome Mail (Black Box)

  • Configures SMTP settings for email delivery
  • Implementation details hidden for simplicity

2.4 Schedule Background Job

  • Adds PostSetupJob to the job list with adminUserId as argument
  • Job runs every 2 seconds (configured in PostSetupJob constructor)

3. Background Job Execution (Asynchronous)

The PostSetupJob runs periodically via Nextcloud's cron system:

3.1 Check Job Status

  • Reads example_admin_app.post_install from IAppConfig (default: "UNKNOWN")
  • If "DONE": Job already completed, removes itself from job list
  • If "UNKNOWN": Waits for initialization
  • If "INIT": Proceeds to send welcome mail

3.2 Verify Nextcloud URL Availability

  • Retrieves overwrite.cli.url from system config via IConfig::getSystemValue()
  • Creates HTTP client via IClientService::newClient()
  • Makes HTTP GET request to {baseUrl}/status.php
  • If URL not available: Skips mail sending, waits for next cron run (returns early)
  • If URL available: Proceeds to mail sending

3.3 Send Welcome Mail

  1. Verifies admin user exists via IUserManager::userExists()
  2. If user doesn't exist: Logs warning and skips welcome mail
  3. If user exists:
    • Retrieves user object via IUserManager::get()
    • Creates new WelcomeMailHelper() instance
    • Calls sendWelcomeMail(adminUser, true) (Black Box)
      • SMTP connection, authentication, and email delivery handled internally

3.4 Mark Job Complete

  • Always sets example_admin_app.post_install status to "DONE" via IAppConfig::setValueString()
  • Always removes itself from the job list (even if user doesn't exist or mail fails)
  • This ensures the job doesn't retry indefinitely if there's a persistent issue

Status Flow

Event Dispatched → INIT → (wait for URL) → Send Mail → DONE

Key Components

Component Purpose
PostInstallEventListener Handles the post-install event, sets up SMTP config, schedules the mail job
PostSetupJob Background job that waits for system readiness and sends the welcome mail
WelcomeMailHelper Handles the actual email composition and SMTP delivery
IConfig Stores system configuration (SMTP settings) and user preferences
IAppConfig Stores app-specific configuration (job status tracking)
IJobList Manages background job scheduling and execution

Retry Mechanism

The job runs every 2 seconds and will retry until:

  • The Nextcloud URL becomes available (status.php returns 2xx)
  • The welcome mail is successfully sent
  • The job marks itself as "DONE"

This ensures the welcome mail is sent even if the system isn't fully ready immediately after installation.

Changes

New Event Class

  • Added OCP\Install\Events\InstallationCompletedEvent - a typed event that provides:
    • Data directory path
    • Admin username (if created)
    • Admin email (if configured)
    • Helper method hasAdminUser() to check if an admin was created

Integration

  • Modified OC\Setup to dispatch the event after successful installation:
    • Added IEventDispatcher dependency
    • Event is dispatched after database setup, admin user creation, app installation, and background jobs configuration
    • Passes installation details (data directory, admin username, admin email) to event listeners

Tests

  • Comprehensive test suite for InstallationCompletedEvent (7 tests, 23 assertions)
    • Constructor parameter validation
    • Getter methods
    • Edge cases (null values, minimal parameters)
  • Extended SetupTest with integration tests (3 new tests)
    • Event dispatcher injection
    • Event parameter validation from install options
    • Handling of disabled admin user scenario

Use Cases

Apps can listen to this event to:

  • Send welcome/notification emails
  • Trigger external API integrations
  • Initialize app-specific data structures
  • Set up monitoring or logging systems
  • Configure third-party services
  • Report installation completion to management systems

Example Usage

<?php
namespace OCA\MyApp\Listeners;

use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Install\Events\InstallationCompletedEvent;

class InstallationCompletedListener implements IEventListener {
    public function handle(Event $event): void {
        if (!($event instanceof InstallationCompletedEvent)) {
            return;
        }

        $dataDir = $event->getDataDirectory();
        $adminUsername = $event->getAdminUsername();
        
        if ($event->hasAdminUser()) {
            // Perform actions with admin user info
            $this->sendWelcomeEmail($adminUsername, $event->getAdminEmail());
        }
        
        // Initialize app-specific data
        $this->initializeAppData($dataDir);
    }
}

Breaking Changes

None. This is a new event that doesn't modify any existing behavior.

API Version

@since 33.0.0

Testing

✅ All tests passing:

  • InstallationCompletedEventTest: 7/7 tests passed, 23 assertions
  • SetupTest: 21/21 tests passed, 47 assertions (including 3 new tests)
  • No regressions in existing test suite

Files Changed

  • lib/public/Install/Events/InstallationCompletedEvent.php (new, 79 lines)
  • lib/private/Setup.php (modified, +10 lines)
  • tests/lib/Install/Events/InstallationCompletedEventTest.php (new, 89 lines)
  • tests/lib/SetupTest.php (modified, +88 lines)

Total: 266 insertions, 1 deletion

Checklist

  • Event class follows Nextcloud event naming conventions
  • Comprehensive documentation in docblocks
  • Full test coverage for event class
  • Integration tests for event dispatching
  • No breaking changes to existing code
  • Event dispatched at the correct point in installation flow
  • Proper @since annotations

@printminion-co printminion-co requested a review from a team as a code owner January 13, 2026 15:07
@printminion-co printminion-co requested review from ArtificialOwl, icewind1991, salmart-dev and sorbaugh and removed request for a team January 13, 2026 15:07
@printminion-co printminion-co force-pushed the feature/add_postinstall_event branch from a4987a7 to eaea8ed Compare January 13, 2026 15:08
@printminion-co printminion-co changed the title add postinstall event Add InstallationCompletedEvent for post-installation actions Jan 13, 2026
@printminion-co printminion-co force-pushed the feature/add_postinstall_event branch 2 times, most recently from 68ec825 to bcc3cbd Compare January 13, 2026 15:51
@printminion-co printminion-co force-pushed the feature/add_postinstall_event branch 4 times, most recently from b259285 to 6cd47a7 Compare January 19, 2026 16:12
Copy link
Member

@provokateurin provokateurin left a comment

Choose a reason for hiding this comment

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

While this event might be useful in server, how would a non-default app be able to use it? The app is not enabled during the installation and needs to be enabled via occ/web interface separately, but wouldn't be able to catch the event anymore.
Please explain your use-case better.

@printminion-co
Copy link
Contributor Author

While this event might be useful in server, how would a non-default app be able to use it? The app is not enabled during the installation and needs to be enabled via occ/web interface separately, but wouldn't be able to catch the event anymore. Please explain your use-case better.

  • Changed @since version
  • Added description "Backstory/Precondition: We are using Nextcloud in k8s. Meaning all apps are already "baked" into image. Meaning Setup::install calls Installer::installShippedApps(false, $output); always and installs all apps in all apps folders."
  • Added description section "Example: InstallationCompletedEvent Sequence Diagram - Welcome Mail Focus"

@provokateurin

printminion-co added a commit to IONOS-Productivity/ncw-tools that referenced this pull request Jan 21, 2026
…ner and related stubs

refactor: use InstallationCompletedEvent properties instead of file parsing

Remove initAdminUser() method and config file reading logic in favor of using the event's built-in getAdminUsername() method. This eliminates the need for:
- Reading and parsing /vault/secrets/adminconfig
- Quote stripping logic
- ADMIN_CONFIG_PATH and ADMIN_USER_KEY constants

Updated stub to match the real implementation from nextcloud/server#57522 including:
- Constructor with dataDirectory, adminUsername, and adminEmail parameters
- Getter methods: getDataDirectory(), getAdminUsername(), getAdminEmail()
- hasAdminUser() helper method

Benefits:
- Cleaner code with no file I/O operations
- No error handling needed for missing/malformed config files
- Uses official Nextcloud API instead of custom parsing
- Better type safety with proper event typing

Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
@printminion-co printminion-co force-pushed the feature/add_postinstall_event branch from c8e03a8 to 7e53353 Compare January 22, 2026 14:13
…ooks

Add InstallationCompletedEvent class in public API (OCP namespace) that
provides installation details: data directory, admin username, and admin
email. Event will be dispatched after successful installation.

Include comprehensive unit tests covering all event scenarios.

Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
@printminion-co printminion-co force-pushed the feature/add_postinstall_event branch from 7e53353 to f40a561 Compare January 22, 2026 15:41
Integrate event dispatching into Setup class:
- Inject IEventDispatcher dependency
- Dispatch InstallationCompletedEvent after successful installation
- Add Setup tests for event integration
- Update composer autoload for new class

Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
@printminion-co printminion-co force-pushed the feature/add_postinstall_event branch from f40a561 to ffd4c5c Compare January 22, 2026 15:43
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.

3 participants