Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(daemon): add daemon service for Windows #152

Merged
merged 10 commits into from
Sep 8, 2024

Conversation

ivangabriele
Copy link
Owner

@ivangabriele ivangabriele commented Sep 8, 2024

Description

Adding dev service installation for clamav-desktop-daemon for Windows.

Checklist

  • I updated the documentation accordingly. Or I don't need to.
  • I updated the tests accordingly. Or I don't need to.

@ivangabriele ivangabriele added os:windows feature:approved Approved new feature or feature enhancement. labels Sep 8, 2024
@ivangabriele ivangabriele added this to the v0.4 milestone Sep 8, 2024
@codecov-commenter
Copy link

codecov-commenter commented Sep 8, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 36.94%. Comparing base (8d52924) to head (f0479bd).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #152   +/-   ##
=======================================
  Coverage   36.94%   36.94%           
=======================================
  Files          23       23           
  Lines         720      720           
  Branches        2        2           
=======================================
  Hits          266      266           
  Misses        454      454           
Flag Coverage Δ
core 37.35% <ø> (ø)
webview 25.00% <ø> (ø)

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ivangabriele ivangabriele marked this pull request as ready for review September 8, 2024 15:28
Copy link

coderabbitai bot commented Sep 8, 2024

Walkthrough

The pull request introduces significant architectural changes to the server, adding OS-specific modules for managing service lifecycles on Windows, Linux, and macOS. It includes a new installation script for Debian packages, PowerShell scripts for installing, logging, and uninstalling the ClamAV Desktop service on Windows, and modifies the WebSocket connection URL in the dashboard commands for enhanced security.

Changes

Files Change Summary
daemon/src/main.rs Added OS-specific modules (unix_service_handler and windows_service_handler) and modified the main function for platform compatibility.
scripts/dev/install_deb_bundle.sh Changed package check from twps-desktop to clamav-desktop for installation management.
scripts/dev/install_windows_service.ps1 Introduced a script to automate the installation of the ClamAV Desktop Daemon as a Windows service.
scripts/dev/log_windows_service.ps1 Introduced a script for monitoring the "clamav-desktop-daemon" Windows service and its logs.
scripts/dev/uninstall_windows_service.ps1 Introduced a script to uninstall the ClamAV Desktop service and clean up associated files.
src-tauri/src/dashboard/commands.rs Modified the WebSocket connection URL from ws://0.0.0.0:7878 to ws://127.0.0.1:7878.

Recent review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

Commits

Files that changed from the base of the PR and between 8d52924 and f0479bd.

Files ignored due to path filters (2)
  • daemon/Cargo.toml is excluded by !**/*.toml
  • package.json is excluded by !**/*.json
Files selected for processing (6)
  • daemon/src/main.rs (2 hunks)
  • scripts/dev/install_deb_bundle.sh (1 hunks)
  • scripts/dev/install_windows_service.ps1 (1 hunks)
  • scripts/dev/log_windows_service.ps1 (1 hunks)
  • scripts/dev/uninstall_windows_service.ps1 (1 hunks)
  • src-tauri/src/dashboard/commands.rs (1 hunks)
Additional comments not posted (4)
scripts/dev/log_windows_service.ps1 (1)

1-9: Administrator check and relaunch logic is correctly implemented.

The script correctly checks for administrator privileges and relaunches itself with elevated privileges if necessary. This is a standard and secure approach for scripts that require administrative rights.

src-tauri/src/dashboard/commands.rs (1)

22-22: WebSocket URL change enhances security.

The change from ws://0.0.0.0:7878 to ws://127.0.0.1:7878 is a good security practice as it restricts access to the WebSocket server to local clients only. This change should help prevent unauthorized external access.

Ensure that this change does not negatively impact any remote clients that were intended to connect to the server. If remote access is required, consider implementing secure access controls or VPN usage.

daemon/src/main.rs (2)

143-150: Unix service handler module is well-implemented.

The introduction of a separate module for Unix service handling using tokio::main is a good practice. It ensures that the server can run asynchronously, which is crucial for performance and scalability.


272-284: Main function modifications for OS-specific handling are correctly implemented.

The use of conditional compilation to call the appropriate service handler based on the operating system is a good practice. It enhances the portability and maintainability of the application.


Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

@@ -4,7 +4,7 @@ set -e
yarn release:deb
# Allow `_apt` user to access the `deb` file
mv ./src-tauri/target/release/bundle/deb/clamav-desktop_0.0.0_amd64.deb /tmp
if dpkg -l | grep -qw twps-desktop; then
if dpkg -l | grep -qw clamav-desktop; then
Copy link

Choose a reason for hiding this comment

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

Improve error handling and verification after package removal.

The script continues execution even if the package removal fails due to the || true usage. Consider adding a check after the removal command to ensure that the package was indeed removed before proceeding with the installation. This can prevent potential conflicts or issues during the installation of the new package.

Comment on lines +1 to +33
# Check if the script is run as administrator, otherwise relaunch it with elevated privileges
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Host "[uninstall_windows_service] Must be run as an administrator. Relaunching with elevated privileges..."

# Relaunch script with elevated privileges using 'RunAs'
Start-Process powershell "-ExecutionPolicy Bypass -File $PSCommandPath" -Verb RunAs

exit
}

$serviceName = "clamav-desktop-daemon"
$daemonBinaryPath = "C:\Program Files\ClamAV Desktop\clamav-desktop-daemon.exe"
$daemonFolderPath = "C:\Program Files\ClamAV Desktop"

$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if ($null -ne $service) {
if ($service.Status -eq 'Running') {
Stop-Service -Name $serviceName
Write-Host "[uninstall_windows_service] Service '$serviceName' stopped successfully."
}

sc.exe delete $serviceName
Write-Host "[uninstall_windows_service] Service '$serviceName' uninstalled successfully."
} else {
Write-Host "[uninstall_windows_service] Service '$serviceName' does not exist."
}

if (Test-Path $daemonFolderPath) {
Remove-Item -Path $daemonFolderPath -Recurse -Force
Write-Host "[uninstall_windows_service] Daemon binary and folder removed successfully."
} else {
Write-Host "[uninstall_windows_service] Daemon folder '$daemonFolderPath' does not exist."
}
Copy link

Choose a reason for hiding this comment

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

Well-structured script with comprehensive handling of service uninstallation.

The script effectively checks and handles various states of the service and associated files. Consider enhancing the user messages to include more details about what the script is doing at each step, which could improve the user's understanding and troubleshooting capabilities.

Comment on lines +1 to +37
# Check if the script is run as administrator, otherwise relaunch it with elevated privileges
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Host "[install_windows_service] Must be run as an administrator. Relaunching with elevated privileges..."

# Relaunch script with elevated privileges using 'RunAs'
Start-Process powershell "-ExecutionPolicy Bypass -File $PSCommandPath" -Verb RunAs

exit
}

$daemonBinaryPath = "C:\Program Files\ClamAV Desktop\clamav-desktop-daemon.exe"
$serviceName = "clamav-desktop-daemon"

if (-Not (Test-Path ".\daemon\target\debug\clamav-desktop-daemon.exe")) {
throw "[install_windows_service] Error: Daemon binary not found. Make sure it is built and available in ./daemon/target/debug/."
}

if (-Not (Test-Path "C:\Program Files\ClamAV Desktop")) {
New-Item -Path "C:\Program Files\ClamAV Desktop" -ItemType Directory
}

Copy-Item -Path ".\daemon\target\debug\clamav-desktop-daemon.exe" -Destination $daemonBinaryPath -Force

$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if ($null -ne $service) {
throw "[install_windows_service] Error: Service '$serviceName' already exists. Installation aborted."
}

sc.exe create $serviceName binPath= $daemonBinaryPath start= auto DisplayName= "ClamAV Desktop Daemon"
Write-Host "[install_windows_service] Service '$serviceName' installed successfully."

Start-Service -Name $serviceName
if ($?) {
Write-Host "[install_windows_service] Service '$serviceName' started successfully."
} else {
Write-Host "[install_windows_service] Failed to start the service '$serviceName'."
}
Copy link

Choose a reason for hiding this comment

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

Well-structured script with comprehensive handling of service installation.

The script effectively checks and handles various states of the service and associated files. Consider adding retry mechanisms or more detailed error handling for scenarios where the service fails to start, which could improve robustness and troubleshooting capabilities.

Comment on lines +11 to +25
# Service name
$serviceName = "clamav-desktop-daemon"

# Display the status of the service
Write-Host "Fetching status of service '$serviceName'..."
$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue

if ($null -ne $service) {
$serviceStatus = Get-Service -Name $serviceName
Write-Host "[log_windows_service] Service '$serviceName' is currently $($serviceStatus.Status)."
} else {
Write-Host "[log_windows_service] Service '$serviceName' does not exist."

exit
}
Copy link

Choose a reason for hiding this comment

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

Service status fetching is robust.

The script robustly fetches and displays the status of the service, handling cases where the service might not exist. This ensures that the script can be used reliably in various states of the service lifecycle.

Consider adding more detailed logging for different service states or errors to enhance troubleshooting capabilities.

Comment on lines +27 to +45
# Fetch logs from Windows Event Viewer and continuously monitor new ones
Write-Host "Tailing logs for service '$serviceName'... (Press Ctrl+C to stop)"
$lastTime = (Get-Date).AddMinutes(-5) # Start by fetching logs from the last 5 minutes

while ($true) {
# Fetch the most recent logs
$eventLogs = Get-WinEvent -LogName System -FilterXPath "*[System[Provider[@Name='$serviceName'] and TimeCreated[timediff(@SystemTime) <= 300000]]]" -ErrorAction SilentlyContinue

# Display new logs if any
if ($eventLogs) {
$eventLogs | Format-Table TimeCreated, Message -AutoSize
} else {
Write-Host "[log_windows_service] No new logs..."
}

# Update the time to only fetch new logs in the next loop
$lastTime = (Get-Date)
Start-Sleep -Seconds 5 # Poll every 5 seconds
}
Copy link

Choose a reason for hiding this comment

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

Log fetching and monitoring implemented effectively.

The script effectively fetches and displays logs from the Windows Event Viewer, continuously monitoring for new logs. This functionality is crucial for real-time monitoring and troubleshooting.

To improve performance, consider adjusting the polling interval based on the frequency of log updates or implementing a more event-driven approach if supported by the system.

Comment on lines +152 to +269

fn my_service_main(_arguments: Vec<OsString>) {
if let Err(e) = run_service() {
eprintln!("Service failed: {}", e);
}
}

fn run_service() -> windows_service::Result<()> {
let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>();

let event_handler = {
let shutdown_tx = shutdown_tx.clone();
move |control_event| -> service_control_handler::ServiceControlHandlerResult {
match control_event {
ServiceControl::Stop => {
// Report that the service is stopping
shutdown_tx.send(()).unwrap();
service_control_handler::ServiceControlHandlerResult::NoError
}
ServiceControl::Interrogate => {
// Service is running, report no error
service_control_handler::ServiceControlHandlerResult::NoError
}
_ => service_control_handler::ServiceControlHandlerResult::NotImplemented,
}
}
};

// Register the control handler with Windows SCM
let status_handle =
service_control_handler::register("clamav-desktop-daemon", event_handler)?;

// Report service is starting
let start_pending_status = ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::StartPending,
controls_accepted: ServiceControlAccept::STOP,
process_id: None,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(30), // Hinting SCM that it will take time to start
};
status_handle.set_service_status(start_pending_status)?;

// Start the Tokio runtime
let rt = Runtime::new().unwrap();
rt.block_on(async {
// Start the server asynchronously
tokio::spawn(async {
start_server().await;
});
});

// Report service is now running
let running_status = ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP,
process_id: None,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(0),
};
status_handle.set_service_status(running_status)?;

// Block until we receive a shutdown signal
shutdown_rx.recv().unwrap();

// Report service is stopping
let stop_pending_status = ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::StopPending,
controls_accepted: ServiceControlAccept::empty(),
process_id: None,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(30),
};
status_handle.set_service_status(stop_pending_status)?;

// Properly clean up and stop the service
let stop_status = ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
process_id: None,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(0),
};
status_handle.set_service_status(stop_status)?;

Ok(())
}

pub fn run() -> Result<()> {
service_dispatcher::start("clamav-desktop-daemon", ffi_service_main)?;

Ok(())
}
Copy link

Choose a reason for hiding this comment

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

Windows service handler module is robustly implemented.

The use of the windows_service crate to define and manage the Windows service is appropriate. The module handles service control events effectively and uses a Tokio runtime to ensure the server runs asynchronously.

Consider adding more detailed error handling and logging, especially around service control events, to enhance troubleshooting and maintainability.

@ivangabriele ivangabriele merged commit a2dbe5c into main Sep 8, 2024
18 checks passed
@ivangabriele ivangabriele deleted the ivan/feat-daemon-add-daemon-service-for-Windows branch September 8, 2024 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature:approved Approved new feature or feature enhancement. os:windows
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants