Public/New-FirebirdService.ps1

function New-FirebirdService {
    <#
    .SYNOPSIS
        Registers a Firebird environment as a system service.
    .DESCRIPTION
        Installs a Firebird server as a system service (Windows Service or systemd unit on Linux).
        Configures the listening port in firebird.conf and optionally starts the service.
    .PARAMETER Environment
        The Firebird environment to register as a service.
    .PARAMETER Port
        The TCP port for the Firebird service to listen on. Defaults to 3050.
    .PARAMETER Name
        The service name. Defaults to 'Firebird-{MajorVersion}' (e.g., 'Firebird-5').
    .PARAMETER NoStart
        If specified, the service is registered but not started.
    .EXAMPLE
        $fb5 = New-FirebirdEnvironment -Version '5.0.3'
        New-FirebirdService -Environment $fb5 -Port 3055
    .EXAMPLE
        New-FirebirdService -Environment $fb5 -Port 3055 -Name 'MyFirebird' -NoStart
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [FirebirdEnvironment]$Environment,

        [int]$Port = 3050,

        [string]$Name,

        [switch]$NoStart
    )

    if (-not $Name) {
        $Name = "Firebird-$($Environment.Version.Major)"
        Write-VerboseMark -Message "No name specified. Using default: $Name"
    }

    if (-not (Test-Path $Environment.Path)) {
        throw "Environment path does not exist: $($Environment.Path)"
    }

    $firebirdConfPath = Join-Path $Environment.Path 'firebird.conf'
    if (-not (Test-Path $firebirdConfPath)) {
        throw "firebird.conf not found at: $firebirdConfPath"
    }

    if ($IsWindows) {
        Write-VerboseMark -Message 'Registering Windows service'
        $serverPath = $Environment.GetServerPath()

        # Check for existing service with same name before modifying any config.
        # firebird.exe registers with StartServiceCtrlDispatcher as 'FirebirdServer{instance}'
        # where instance is set via the -S flag. So the SCM service name must match.
        $existingService = Get-Service -Name "FirebirdServer$Name" -ErrorAction SilentlyContinue
        if ($existingService) {
            throw "A service named 'FirebirdServer$Name' already exists. Remove it first with Remove-FirebirdService."
        }

        # Configure the listening port in firebird.conf
        Write-VerboseMark -Message "Setting RemoteServicePort to $Port in $firebirdConfPath"
        Write-FirebirdConfiguration -Path $firebirdConfPath -Configuration @{ RemoteServicePort = $Port }

        if ($PSCmdlet.ShouldProcess($Name, 'Install Windows service')) {
            # Register firebird.exe directly as a Windows service.
            # The -S flag sets the instance name that firebird.exe passes to StartServiceCtrlDispatcher,
            # which must match the registered service name 'FirebirdServer{Name}'.
            $binPath = "`"$serverPath`" -S $Name -p $Port"
            Write-VerboseMark -Message "Registering service 'FirebirdServer$Name' with binary: $binPath"
            New-Service -Name "FirebirdServer$Name" `
                -DisplayName "Firebird Server - $Name" `
                -Description "Firebird Database Server on port $Port" `
                -BinaryPathName $binPath `
                -StartupType Automatic | Out-Null

            if (-not $NoStart) {
                Write-VerboseMark -Message "Starting service: Start-Service 'FirebirdServer$Name'"
                Start-Service -Name "FirebirdServer$Name"
            } else {
                Write-VerboseMark -Message 'Skipping service start (-NoStart specified).'
            }
        }

        $svc = Get-Service -Name "FirebirdServer$Name" -ErrorAction SilentlyContinue
        $status = if ($svc) { $svc.Status.ToString() } else { 'Unknown' }
    } elseif ($IsLinux) {
        Write-VerboseMark -Message 'Registering systemd service'
        $unitName = $Name.ToLower()
        $unitPath = "/etc/systemd/system/$($unitName).service"

        if (Test-Path $unitPath) {
            throw "A systemd unit file already exists at '$unitPath'. Remove it first with Remove-FirebirdService."
        }

        # Configure the listening port in firebird.conf
        Write-VerboseMark -Message "Setting RemoteServicePort to $Port in $firebirdConfPath"
        Write-FirebirdConfiguration -Path $firebirdConfPath -Configuration @{ RemoteServicePort = $Port }

        $serverPath = $Environment.GetServerPath()
        $envPath = (Resolve-Path $Environment.Path).Path

        $unitContent = @"
[Unit]
Description=Firebird $($Environment.Version) Database Server ($Name)
After=network.target
 
[Service]
Type=simple
ExecStart=$serverPath -p $Port
WorkingDirectory=$envPath
Environment="FIREBIRD=$envPath" "LD_LIBRARY_PATH=$envPath/lib"
Restart=on-failure
 
[Install]
WantedBy=multi-user.target
"@


        if ($PSCmdlet.ShouldProcess($unitPath, 'Create systemd unit file')) {
            Write-VerboseMark -Message "Writing systemd unit file: $unitPath"
            Set-Content -Path $unitPath -Value $unitContent

            Write-VerboseMark -Message 'Running: systemctl daemon-reload'
            Invoke-ExternalCommand { & systemctl daemon-reload } `
                -ErrorMessage 'Failed to reload systemd daemon'

            Write-VerboseMark -Message "Running: systemctl enable $unitName"
            Invoke-ExternalCommand { & systemctl enable $unitName } `
                -ErrorMessage "Failed to enable service '$unitName'"

            if (-not $NoStart) {
                Write-VerboseMark -Message "Running: systemctl start $unitName"
                Invoke-ExternalCommand { & systemctl start $unitName } `
                    -ErrorMessage "Failed to start service '$unitName'"
            } else {
                Write-VerboseMark -Message 'Skipping service start (-NoStart specified).'
            }
        }

        $null = & systemctl is-active $unitName 2>&1
        $status = if ($LASTEXITCODE -eq 0) { 'Running' } else { 'Stopped' }
    } else {
        throw 'Unsupported platform. Only Windows and Linux are supported.'
    }

    [PSCustomObject]@{
        Name            = $Name
        Port            = $Port
        EnvironmentPath = $Environment.Path
        Status          = $status
    }
}