Harden a Bash Log Monitor on WSL

Build a hardened Bash log monitor with systemd, SSH keys, and UFW in WSL.

Introduction

30 Second Summary

A program can keep running while warning signs pile up out of sight. Checking for problems by hand makes it easy to miss the moment a small issue becomes urgent.

In this project, you will build a local monitoring lab with Ubuntu running on WSL. It turns warning and error records into timestamped reports while keeping automated monitoring separate from secure administration.

What You'll Build

When the demo application log receives a warning, your Bash monitor adds fresh totals to a timestamped report on the next scheduled run.

By the end of this project, you'll have:

  • A strict log monitor that lets you run one command to see timestamped warning and error totals from the latest application records.
  • An automated monitoring service that systemd runs every minute under a restricted account. You can inspect each invocation in the journal. logrotate keeps the report history compressed and bounded.
  • A key-authenticated administrator session from Windows PowerShell over SSH. Password SSH access stays disabled. Root SSH access stays disabled. A visible UFW rule permits the connection inside the Linux lab.
  • Secret Mission: Add immediate path-based activation so changes to the demo log trigger the existing service without waiting for the timer.

What do I need before starting?

You need Ubuntu 26.04 LTS already installed in WSL. Windows administrator access is only needed if the OpenSSH Client capability is missing.

Before We Start

This checkpoint defines the lab before you configure it: /var/log/demo-app/application.log is scanned for level=WARN and level=ERROR records. A non-login log-monitor service account keeps the workload separate from the interactive monitoradmin administrator account to enforce least privilege.

Verify WSL and Install the Lab Tools

The monitoring lab needs Ubuntu services that continue beyond an interactive shell. That depends on WSL 2 running systemd correctly.

The timer, SSH daemon, firewall, and rotation policy also rely on tools that may be missing. This step verifies GNU Bash 5.3 before installing the pinned lab tools.

In this step, get ready to:
  • Confirm that Ubuntu uses WSL 2 with systemd active.
  • Install the pinned Ubuntu packages for secure access, firewall control, log rotation, and file editing.
  • Confirm that the Windows OpenSSH Client is available from PowerShell.
Confirm WSL and systemd

WSL provides the Linux environment beneath this lab. Its version and distribution mode determine whether systemd can manage the service and timer later.

  • Press the Windows key to open Windows search.
  • Type PowerShell into the search field.
  • Press Enter to open PowerShell.
  • Check the WSL release and Ubuntu distribution mode by running:
wsl --version
wsl --list --verbose

What do these checks prove?

  • The first command prints the installed WSL release.
  • The second command lists each Linux distribution with its WSL mode.
  • The Ubuntu row should show WSL version 2.

Choose the tab that matches your PowerShell output.

✔️ WSL 2 is current

Your WSL release supports systemd. The Ubuntu row also confirms that the distribution uses WSL version 2.

ⓧ WSL is older or uses version 1

Updating or converting WSL can take time because Windows may need to change the distribution architecture. Let the process finish without closing PowerShell.

  • Update WSL by running this command:
wsl --update

What does this command do?

This command updates WSL so the installed release can support systemd.

  • Use Microsoft’s WSL command guide to convert the listed Ubuntu distribution if its version column still shows 1.
  • Repeat the WSL checks by running:
wsl --version
wsl --list --verbose

What should you see?

The version check should now print a current WSL release. The Ubuntu row should show WSL version 2.

ⓧ The version check is unavailable

An unavailable version check indicates that the installed WSL release needs an update before systemd can be enabled.

  • Update WSL by running this command:
wsl --update

What does this command do?

This command retrieves a newer WSL release with systemd support.

  • Repeat the WSL checks by running:
wsl --version
wsl --list --verbose

What should you see?

PowerShell should now print a WSL release. The Ubuntu row should show WSL version 2.

Still unable to check WSL?

Confirm that Windows Update is available because the WSL update depends on Windows servicing.

Use the official WSL systemd guide to check the host requirements.

Help me diagnose my WSL version check.

The WSL host is ready. The next check confirms whether systemd currently controls the Ubuntu environment.

  • Switch back to your Ubuntu terminal from earlier.
  • Check systemd and the CPU architecture by running:
systemctl status --no-pager
dpkg --print-architecture

What do these checks prove?

  • The systemd status confirms whether the service manager is running.
  • The architecture output identifies the Ubuntu package architecture.
  • The package pins in this lab support the normal WSL architectures of amd64 and arm64.

Choose the tab that matches the systemd result.

✔️ systemd is running

That confirms systemd is active inside Ubuntu. Your service and timer units can use it later.

ⓧ systemd is not running

Ubuntu needs a distribution-specific WSL setting before systemd can become its service manager.

  • Open the WSL configuration file with administrator permissions by running:
sudo nano /etc/wsl.conf

What does this command do?

This command opens the Ubuntu-specific WSL configuration in Nano with permission to save changes.

  • Add this boot configuration to /etc/wsl.conf:
[boot]
systemd=true

What does this configuration do?

The boot setting makes systemd the init process when this Ubuntu distribution starts.

  • Press Ctrl+X to leave Nano.
  • Type Y to save the change.
  • Press Enter to confirm the file name.
  • Switch back to PowerShell.
  • Shut down all WSL instances by running:
wsl.exe --shutdown

Why shut down WSL?

A full WSL shutdown makes Ubuntu apply the new boot configuration during its next launch.

  • Press the Windows key to open Windows search.
  • Type Ubuntu into the search field.
  • Press Enter to start Ubuntu again.
  • Verify the new systemd state by running:
systemctl status --no-pager

What should you see?

The status should now report that the system is running. This confirms systemd started with Ubuntu.

Still unable to start systemd?

Check that /etc/wsl.conf contains the [boot] section above the systemd=true setting.

Confirm that you ran the shutdown command from PowerShell instead of the Ubuntu shell.

Help me troubleshoot systemd in WSL.

Install the pinned Ubuntu packages

The package index tells apt which versions are available for your architecture. Checking its candidates protects the lab from silently installing different package releases.

  • Refresh the Ubuntu package index by running:
sudo apt update

What does this command do?

This command downloads current package metadata from the configured Ubuntu repositories.

  • Inspect the available package versions by running:
apt-cache policy openssh-server ufw logrotate nano

What should you compare?

  • OpenSSH Server should offer 1:10.2p1-2ubuntu3.8.
  • UFW should offer 0.36.2-9build1.
  • logrotate should offer 3.22.0-1build1.
  • Nano should offer 8.7.1-1ubuntu0.1.

Choose the tab that matches the candidate versions in your output.

✔️ I see the pinned versions

Your Ubuntu repositories contain the package versions used by this lab.

  • Install the pinned packages by running:
sudo apt install -y openssh-server=1:10.2p1-2ubuntu3.8 ufw=0.36.2-9build1 logrotate=3.22.0-1build1 nano=8.7.1-1ubuntu0.1

What does this command install?

  • OpenSSH Server provides the SSH daemon and key tools used later.
  • UFW provides the Linux host-firewall controls for the lab.
  • logrotate bounds the report history through rotation and compression.
  • Nano provides the terminal editor used for configuration files.
  • Confirm the installed package versions by running:
apt-cache policy openssh-server ufw logrotate nano

What should you see?

Each package should show its pinned release as the installed version. That result confirms the lab tool set is ready.

Package installation failed?

Check the update output for an unavailable Ubuntu repository. Confirm that the candidate versions match the pinned releases before retrying the installation.

Help me troubleshoot the pinned Ubuntu package installation.

ⓧ I see different versions

A different candidate usually means the local package index needs another refresh. Keep the verified pins unchanged so the rest of the lab matches the tested environment.

  • Refresh the package index again by running:
sudo apt update

Why refresh again?

A second refresh rules out stale metadata before you install exact versions.

  • Attempt the pinned installation by running:
sudo apt install -y openssh-server=1:10.2p1-2ubuntu3.8 ufw=0.36.2-9build1 logrotate=3.22.0-1build1 nano=8.7.1-1ubuntu0.1

What does this command enforce?

The exact version suffixes prevent apt from selecting different releases silently.

  • Check the resulting package state by running:
apt-cache policy openssh-server ufw logrotate nano

What should you see?

The installed and candidate entries should match the verified package pins.

Pinned versions still unavailable?

Confirm that this distribution is Ubuntu 26.04 before continuing. These pins target that release.

Help me diagnose different Ubuntu package candidates.

ⓧ One or more packages are unavailable

An unavailable package indicates that apt could not find it in the currently configured repository metadata.

  • Refresh the repository metadata by running:
sudo apt update

What does this retry do?

This retry gives apt another chance to retrieve package indexes from every configured Ubuntu repository.

  • Attempt the pinned installation by running:
sudo apt install -y openssh-server=1:10.2p1-2ubuntu3.8 ufw=0.36.2-9build1 logrotate=3.22.0-1build1 nano=8.7.1-1ubuntu0.1

What does this command install?

The command requests all four lab packages with their verified Ubuntu 26.04 releases.

  • Inspect the package state again by running:
apt-cache policy openssh-server ufw logrotate nano

What should you see?

Each package should now have an installed entry and a candidate entry.

Package still unavailable?

Review the package update output for a repository that could not be reached. Avoid substituting another release because later behavior depends on the verified pins.

Help me restore an unavailable Ubuntu package.

Verify the Windows OpenSSH Client

The OpenSSH server now lives inside Ubuntu. Windows needs its separate OpenSSH Client capability so PowerShell can create keys and connect to the lab later.

This check may require administrator approval if the client needs installation. Windows shows the permission prompt before any system capability changes.

  • Press the Windows key to open Windows search.
  • Type PowerShell into the search field.
  • Right-click PowerShell in the search results.
  • Choose the administrator option from the context menu.
  • Approve the Windows permission prompt if it appears.
  • Check the installed OpenSSH capabilities by running:
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'

What does this check show?

The command lists the Windows OpenSSH capabilities. Use the client entry to determine whether an installation is required.

Choose the tab that matches the OpenSSH Client entry.

✔️ The client is installed

The Windows OpenSSH Client is ready. PowerShell can use its SSH commands in the access-hardening step.

ⓧ The client is missing

Windows may spend a short while downloading the capability. Keep the elevated PowerShell window open until the operation finishes.

  • Install the Windows OpenSSH Client by running:
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0

What does this command do?

This command adds the in-box OpenSSH Client capability to Windows.

  • Confirm that the SSH client responds by running:
ssh -V

What should you see?

PowerShell should print an OpenSSH version instead of reporting that the command is unavailable.

OpenSSH Client installation failed?

Confirm that the PowerShell window has administrator privileges. Check that Windows Update access is available for optional capability downloads.

Help me troubleshoot the Windows OpenSSH Client installation.

Before you run the final checks, which tools do you expect to return versions from Ubuntu and which one should answer from PowerShell?

  • Switch back to your Ubuntu terminal from earlier.
  • Verify systemd, Bash, logrotate, and UFW by running:
systemctl status --no-pager
bash --version
logrotate --version
ufw version

What should you see?

  • The systemd command should print a running system status.
  • Bash should report version 5.3.
  • logrotate should report version 3.22.0.
  • UFW should report version 0.36.2.
  • Switch back to the elevated PowerShell window.
  • Verify the Windows SSH client by running:
ssh -V

What should you see?

PowerShell should print an OpenSSH version. Every final check now returns a status or version instead of a missing-command result.

Your foundation is ready. Ubuntu now has the service manager and pinned tools needed to build the monitor.

The lab tools are in place. Next up, you will create the controlled application log and turn it into a timestamped warning and error report.

Build and Run the Bash Monitor

Your Ubuntu lab has systemd running as its service manager. The required command-line tools are ready.

Now you will build a Bash monitor that turns controlled log records into a timestamped report. A manual run gives you a visible baseline before you test the monitor against a later log change.

In this step, get ready to:
  • Create a controlled source log with information, warning, and error records.
  • Build an executable monitor with strict Bash behavior.
  • Test how a completed manual run responds to a later error.
Create the controlled source log

A controlled log gives the monitor predictable input. Nano lets you create records for each level without relying on unrelated system activity.

  • Create the source-log directory and open /var/log/demo-app/application.log by running these commands:
sudo install -d /var/log/demo-app
sudo nano /var/log/demo-app/application.log

What do these commands do?

  • The first command creates the /var/log/demo-app directory if it is missing.
  • The second command opens application.log with administrator permissions.
  • Add the controlled records by pasting this content into Nano:
2026-10-20T10:00:00Z level=INFO message="Demo application started"
2026-10-20T10:01:00Z level=WARN message="Controlled latency warning"
2026-10-20T10:02:00Z level=ERROR message="Controlled request failure"

What does this log contain?

  • The level=INFO record represents normal application activity.
  • The level=WARN record gives the monitor one warning to count.
  • The level=ERROR record gives the monitor one error to count.
  • Press Ctrl+O to save application.log.
  • Press Enter to confirm the file name.
  • Press Ctrl+X to exit Nano.
  • Make the source log readable and print its three records by running these commands:
sudo chmod 0644 /var/log/demo-app/application.log
tail -n 3 /var/log/demo-app/application.log

What do these commands confirm?

  • The first command gives every local account permission to read the controlled source log during this manual test.
  • The second command prints the last three lines so you can confirm the monitor has one record at each level.

You will see the level=INFO, level=WARN, and level=ERROR records in the terminal.

Can't see all three records?

  • Return to /var/log/demo-app/application.log in Nano to confirm that each record occupies one line.
  • Run the permission and display commands again if the terminal reports that the file cannot be read.

Help me fix my controlled application log.

Build the monitor script

Strict Bash behavior stops the monitor when an unset variable or failed pipeline could make its report unreliable. The script also accepts environment overrides while keeping server-ready default paths.

  • Create the script directory and open /usr/local/lib/log-monitor/log-monitor.sh by running these commands:
sudo install -d /usr/local/lib/log-monitor
sudo nano /usr/local/lib/log-monitor/log-monitor.sh

What do these commands prepare?

  • The first command creates the directory that holds the monitor implementation.
  • The second command opens the new script with permission to save under /usr/local/lib.
  • Add the complete monitor by pasting this code into Nano:
#!/usr/bin/env bash
set -euo pipefail

source_log="${SOURCE_LOG:-/var/log/demo-app/application.log}"
report_log="${REPORT_LOG:-/var/log/log-monitor/report.log}"
window_lines="${WINDOW_LINES:-200}"

if [[ ! -r "$source_log" ]]; then
  printf 'ERROR source log is not readable: %s\n' "$source_log" >&2
  exit 1
fi

recent_log="$(tail -n "$window_lines" "$source_log")"
warning_count="$(grep -c 'level=WARN' <<<"$recent_log" || true)"
error_count="$(grep -c 'level=ERROR' <<<"$recent_log" || true)"
timestamp="$(date --iso-8601=seconds)"
summary="$timestamp source=$source_log window_lines=$window_lines warnings=$warning_count errors=$error_count"

printf '%s\n' "$summary" | tee -a "$report_log"

if (( error_count > 0 )); then
  printf 'ALERT errors=%s source=%s\n' "$error_count" "$source_log" >&2
fi

What does this code do?

  • The set -euo pipefail line makes common script failures stop the run.
  • The source_log, report_log, and window_lines variables use defaults that environment variables can override.
  • The recent_log value holds the latest records so warning_count and error_count can count matching levels.
  • The summary value combines an ISO 8601 timestamp with the source path and counts before tee appends it to the report.
  • The final condition sends an ALERT message to standard error whenever the error count is greater than zero.
  • Press Ctrl+O to save log-monitor.sh.
  • Press Enter to confirm the file name.
  • Press Ctrl+X to exit Nano.
  • Make the script executable and validate its Bash syntax by running these commands:
sudo chmod +x /usr/local/lib/log-monitor/log-monitor.sh
bash -n /usr/local/lib/log-monitor/log-monitor.sh

What does the syntax check prove?

  • The permission command allows the operating system to execute log-monitor.sh as a program.
  • The bash -n check parses the script without running its monitoring logic.

A valid script returns you to the prompt without a syntax error. Your executable monitor is ready for its first real run.

Seeing a Bash syntax problem?

  • Compare every quote and closing bracket with the reference below.
  • Confirm that the final fi appears on its own line.
  • Save the corrected file before running the syntax check again.

Help me find the syntax problem in my Bash monitor.

✔️ Awesome, I've got everything!

Great. Your saved monitor has passed its Bash syntax check.

ⓧ I'd like to double check the full code

#!/usr/bin/env bash
set -euo pipefail

source_log="${SOURCE_LOG:-/var/log/demo-app/application.log}"
report_log="${REPORT_LOG:-/var/log/log-monitor/report.log}"
window_lines="${WINDOW_LINES:-200}"

if [[ ! -r "$source_log" ]]; then
  printf 'ERROR source log is not readable: %s\n' "$source_log" >&2
  exit 1
fi

recent_log="$(tail -n "$window_lines" "$source_log")"
warning_count="$(grep -c 'level=WARN' <<<"$recent_log" || true)"
error_count="$(grep -c 'level=ERROR' <<<"$recent_log" || true)"
timestamp="$(date --iso-8601=seconds)"
summary="$timestamp source=$source_log window_lines=$window_lines warnings=$warning_count errors=$error_count"

printf '%s\n' "$summary" | tee -a "$report_log"

if (( error_count > 0 )); then
  printf 'ALERT errors=%s source=%s\n' "$error_count" "$source_log" >&2
fi

How to compare this file

Check your saved file from the shebang through the closing fi. The variable names and default paths must match this reference exactly.

Run the monitor and expose the gap

The REPORT_LOG override sends this manual test to a report in your home directory. This keeps the test writable before the service creates its dedicated report directory.

Before you run the monitor, consider how many warnings and errors its first summary should count.

  • Run the monitor with the home-directory report override by using this command:
REPORT_LOG="$HOME/log-monitor-report.log" /usr/local/lib/log-monitor/log-monitor.sh

What happens during this run?

  • The environment override changes only the report destination for this invocation.
  • The monitor scans the latest 200 records from the controlled source log.
  • The summary appears in the terminal before the same line is appended to $HOME/log-monitor-report.log.
  • The existing error causes a separate ALERT errors=1 message.

You will see a timestamped summary containing warnings=1 and errors=1. Your monitor now has its first real result.

Monitor run failing?

  • Confirm that /var/log/demo-app/application.log still contains the three controlled records.
  • Run the syntax check again if Bash points to a line in log-monitor.sh.
  • Run the source-log permission command again if the monitor cannot read its input.

Help me diagnose my manual monitor run.

  • Inspect the report's newest summary by running this command:
tail -n 1 "$HOME/log-monitor-report.log"

What does this check show?

The command reads the final line already stored in your home-directory report. That line contains the same timestamped counts printed by the monitor.

You will see the source path /var/log/demo-app/application.log with window_lines=200, warnings=1, and errors=1.

  • Append another controlled error to the source log by running this command:
printf '%s\n' '2026-10-20T10:03:00Z level=ERROR message="Controlled follow-up failure"' | sudo tee -a /var/log/demo-app/application.log

What does this append?

The command adds one new level=ERROR record without replacing the existing log. tee -a also prints the appended line in the terminal.

  • Wait ten seconds after the new source record appears.

Before you inspect the report, consider whether a new source error can change a report after the script has exited.

  • Check the report again by running this command:
tail -n 1 "$HOME/log-monitor-report.log"

What does this second check test?

The command reads only the report's existing final line. The monitor process remains stopped after its earlier invocation.

You will see the same summary with errors=1. The unchanged line is the intended shortfall of a manual monitor.

The script processes input only during an invocation. A later run can scan the error that you just appended.

Before you rerun the monitor, predict how the error total will change.

  • Run the monitor again and inspect its newest report line by using these commands:
REPORT_LOG="$HOME/log-monitor-report.log" /usr/local/lib/log-monitor/log-monitor.sh
tail -n 1 "$HOME/log-monitor-report.log"

What does the rerun prove?

  • The first command starts a fresh scan of the controlled source log.
  • The second command reads the summary created by that fresh invocation.

You will see a newer ISO 8601 timestamp with warnings=1 and errors=2. The report now captures the added error because you invoked the monitor again.

Your monitor can scan controlled records and preserve each result in a report. Next up, you will run the same script under a restricted service identity with journal output.

Run the Monitor as a Hardened Service

Your manual Bash monitor already turns the controlled log into a useful report. The next goal is to give that workload its own identity inside systemd.

Running the script from your login account mixes human privileges with workload privileges. A dedicated service account applies least privilege by granting the monitor only the access it needs.

In this step, get ready to:
  • Create the log-monitor service identity.
  • Restrict the source log to group-readable permissions.
  • Run the monitor through a hardened one-shot service.
Establish the service access boundary

The monitor needs permission to read the application log. It does not need an interactive login or a home directory.

  • Create the non-login account with a matching group by running this command:
sudo adduser --system --group --no-create-home log-monitor

What does this command create?

  • The --system option creates an account intended for a workload.
  • The --group option creates the matching log-monitor group.
  • The --no-create-home option leaves the account without a home directory.
  • The account uses /usr/sbin/nologin as its shell.
  • Inspect the new account record by running this command:
getent passwd log-monitor

What should the account show?

The record starts with log-monitor. Its final field is /usr/sbin/nologin.

You should also see /nonexistent in the home-directory field. This identity can own files without becoming a human login account.

Account record missing?

Check that the creation command uses the account name log-monitor exactly. A different spelling creates a different identity.

Ask for help with the account creation output if the command reports a conflict. Help me diagnose why the log-monitor system account was not created correctly.

The source log now needs a narrow access rule. Root keeps ownership while the log-monitor group receives read access.

  • Apply the source-log ownership and permissions by running these commands:
sudo chown root:log-monitor /var/log/demo-app
sudo chmod 0750 /var/log/demo-app
sudo chown root:log-monitor /var/log/demo-app/application.log
sudo chmod 0640 /var/log/demo-app/application.log

How does this protect the source log?

  • The directory mode 0750 lets its owner manage the directory.
  • The same mode lets the group enter the directory.
  • The file mode 0640 gives the owner read and write access.
  • The same file mode gives the group read-only access.
  • Confirm the owner and mode of both paths by running this command:
stat -c '%U:%G %a' /var/log/demo-app /var/log/demo-app/application.log

What should the permissions show?

The first line should show root:log-monitor 750. The second line should show root:log-monitor 640.

That is the workload boundary in place. The service can read the controlled source without gaining permission to rewrite it.

Permissions do not match?

Run the ownership commands again if either path shows a different group. Run the mode commands again if the numeric permissions differ.

Make sure you inspected /var/log/demo-app/application.log from earlier. Help me fix the ownership or permissions on my demo application log.

Define and validate the service unit

A service unit tells systemd which identity runs the script. It also defines the filesystem boundaries around that process.

  • Create /etc/systemd/system/log-monitor.service with Nano by running this command:
sudo nano /etc/systemd/system/log-monitor.service

Why open the editor with sudo?

The service unit belongs under /etc/systemd/system. Administrator access lets Nano create the file in that protected directory.

  • Paste this complete service definition into the empty file:
[Unit]
Description=Scan the demo application log for warnings and errors
Documentation=man:systemd.service(5)
After=local-fs.target
ConditionPathExists=/var/log/demo-app/application.log

[Service]
Type=oneshot
User=log-monitor
Group=log-monitor
ExecStart=/usr/local/lib/log-monitor/log-monitor.sh
LogsDirectory=log-monitor
LogsDirectoryMode=0750
UMask=0027
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes

How does the service stay restricted?

  • The Type=oneshot directive runs the monitor to completion for each invocation.
  • The User=log-monitor directive selects the non-login workload identity.
  • The Group=log-monitor directive grants access to the group-readable source log.
  • The ProtectSystem=strict directive makes the main filesystem hierarchy read-only for the service.
  • The LogsDirectory=log-monitor directive creates the writable /var/log/log-monitor exception.
  • The remaining hardening directives isolate temporary files and devices. They also block new privileges and kernel changes.
  • Press Ctrl+O to write the service file.
  • Press Enter to confirm the existing path.
  • Press Ctrl+X to exit Nano.

✔️ Awesome, I've got everything!

Great. Double-check that you saved /etc/systemd/system/log-monitor.service before continuing.

ⓧ I'd like to double check the full code

[Unit]
Description=Scan the demo application log for warnings and errors
Documentation=man:systemd.service(5)
After=local-fs.target
ConditionPathExists=/var/log/demo-app/application.log

[Service]
Type=oneshot
User=log-monitor
Group=log-monitor
ExecStart=/usr/local/lib/log-monitor/log-monitor.sh
LogsDirectory=log-monitor
LogsDirectoryMode=0750
UMask=0027
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes

The unit must pass systemd's structural checks before the manager loads it. A clean validation protects the next run from configuration mistakes.

  • Validate the service unit and reload systemd by running these commands:
sudo systemd-analyze verify /etc/systemd/system/log-monitor.service
sudo systemctl daemon-reload

What should validation show?

A valid unit returns you to the prompt without a diagnostic. The reload makes systemd read the new service definition.

Seeing a unit validation problem?

Compare every directive with the full-file reference above. Pay close attention to section headers and exact capitalization.

Confirm that the unit path is /etc/systemd/system/log-monitor.service. Help me understand the systemd service verification problem.

Start the service and inspect the result

The one-shot invocation now runs the same script under the restricted identity. Its standard output and error stream flow into the service journal.

Before you start it, do you expect the hardened monitor to read the source log and create its report successfully?

  • Trigger the one-shot service invocation by running this command:
sudo systemctl start log-monitor.service

What does starting the service do?

Systemd launches /usr/local/lib/log-monitor/log-monitor.sh as log-monitor. The LogsDirectory=log-monitor directive creates the report directory before the script runs.

The command returns after the script finishes. That completion matches the behavior of a one-shot workload.

  • Inspect the journal output and report result by running these commands:
sudo journalctl -u log-monitor.service -n 20 --no-pager
sudo tail -n 1 /var/log/log-monitor/report.log
sudo stat -c '%U:%G %a' /var/log/log-monitor /var/log/log-monitor/report.log

What proves the hardened run worked?

  • The journal shows an ISO 8601 timestamped summary for /var/log/demo-app/application.log.
  • The journal also shows an ALERT because the controlled log contains an error.
  • The report's final line contains the same timestamped warning and error totals.
  • The directory is owned by log-monitor:log-monitor with mode 750.
  • The report file is owned by log-monitor:log-monitor with mode 640.

Service did not produce a report?

Check the journal for a message about source-log readability. Confirm that the source file still shows the root:log-monitor ownership and 640 mode.

Confirm that ExecStart=/usr/local/lib/log-monitor/log-monitor.sh matches the executable script from earlier. Help me diagnose why log-monitor.service did not create its report.

That is the privilege split working: your monitor now runs with a dedicated identity and leaves an observable record. Next up, you will schedule automatic runs and keep the report size bounded.

Automate Runs and Rotate the Report

Your hardened Bash monitor now runs safely under its dedicated account. However, the report changes only when something starts the service.

A persistent systemd timer closes that automation gap. logrotate keeps the growing report within a fixed storage limit.

In this step, get ready to:
  • Schedule the monitor to run every minute.
  • Verify a timer-generated report after appending an error.
  • Rotate the report into compressed archives.
Schedule automatic monitor runs

A calendar timer activates your existing one-shot service at each minute boundary. The persistent setting also provides one catch-up activation when the timer becomes active after a missed calendar event.

  • Create /etc/systemd/system/log-monitor.timer with Nano by running this command:
sudo nano /etc/systemd/system/log-monitor.timer

What does this command do?

Nano opens a new system timer file with the permissions required to save it under /etc/systemd/system.

  • Paste this timer definition into Nano:
[Unit]
Description=Run the Bash log monitor every minute

[Timer]
OnCalendar=minutely
Persistent=true
Unit=log-monitor.service

[Install]
WantedBy=timers.target

How does this timer work?

  • The OnCalendar=minutely setting schedules an activation at every minute boundary.
  • The Persistent=true setting records the last trigger time for catch-up behavior.
  • The Unit=log-monitor.service setting activates the hardened service you already tested.
  • The WantedBy=timers.target setting allows the timer to be enabled through its install section.

Cron can schedule commands. This timer activates your existing service with its restricted identity plus its journal output.

  • Press Ctrl+O to write the timer file.
  • Press Enter to confirm the file name.
  • Press Ctrl+X to exit Nano.

✔️ Awesome, I've got everything!

Great. Your timer definition is saved with a one-minute calendar schedule.

ⓧ I'd like to double check the full code

[Unit]
Description=Run the Bash log monitor every minute

[Timer]
OnCalendar=minutely
Persistent=true
Unit=log-monitor.service

[Install]
WantedBy=timers.target

Compare every line with your saved timer file. Pay close attention to the section headings plus each directive value.

  • Validate the calendar expression by running this command:
systemd-analyze calendar minutely

What does this validation show?

The command normalizes minutely to *-*-* *:*:00. It also calculates the next activation time.

  • Load the new timer definition by running these commands:
sudo systemctl daemon-reload
sudo systemctl enable --now log-monitor.timer

What do these commands do?

  • The first command makes systemd reload unit definitions from disk.
  • The second command enables log-monitor.timer through its install section.
  • The --now option activates the timer immediately.
  • Confirm the timer has a scheduled activation by running this command:
sudo systemctl list-timers log-monitor.timer --all

What should you see?

You should see a row for log-monitor.timer with an upcoming activation. That row proves the scheduler recognizes your timer.

That is the automation layer running. Your monitor now has a schedule that survives terminal closure.

Timer missing from the list?

Check that the file name ends with .timer. Confirm that each section heading matches the reference exactly.

Reload the unit definitions after correcting the file. Enable the timer again once the reload completes.

Help me troubleshoot why log-monitor.timer is missing or inactive.

Prove the timer catches new errors

The manual monitor stopped watching after its process exited. This test changes the source log without manually starting the service.

  • Use Nano to edit /var/log/demo-app/application.log with administrator permissions.
  • Duplicate one existing level=ERROR record onto a new line.
  • Save /var/log/demo-app/application.log.
  • Wait for the next minute boundary.

Before you inspect the report, do you expect the latest summary to include the additional error without a manual service start?

  • Inspect the latest timer-generated summaries by running this command:
sudo tail -n 3 /var/log/log-monitor/report.log

What should the report show?

The newest line should have a later ISO 8601 timestamp. Its error total should include the additional controlled record.

That new line proves the timer fixed the manual-run shortfall from Step 2.

  • Inspect the matching service invocation by running this command:
sudo journalctl -u log-monitor.service -n 20 --no-pager

What does the journal prove?

You should see a recent summary from log-monitor.service. You may also see its alert because the controlled log contains errors.

The matching timestamp connects the scheduled service invocation to the new report entry.

Report still has the old timestamp?

Confirm that the timer row shows an upcoming activation. Wait through a complete minute boundary before checking again.

Check the service journal for a recent invocation. A permissions problem may prevent the service from reading the source log or writing the report.

Help me find why my timer did not add a new report line.

Rotate the report safely

A report that grows every minute needs a retention limit. This policy rotates the file at 1k while keeping three compressed archives.

  • Create /etc/logrotate.d/log-monitor with Nano by running this command:
sudo nano /etc/logrotate.d/log-monitor

What does this command do?

Nano opens a new system-wide rotation policy with the permissions required to save it under /etc/logrotate.d.

  • Paste this rotation policy into Nano:
/var/log/log-monitor/report.log {
    size 1k
    rotate 3
    compress
    missingok
    notifempty
    create 0640 log-monitor log-monitor
    su log-monitor log-monitor
}

How does this policy protect the report?

  • The size 1k directive makes file size the rotation trigger.
  • The rotate 3 directive retains three older reports.
  • The compress directive stores rotated reports as compressed archives.
  • The missingok plus notifempty directives skip missing or empty reports safely.
  • The create 0640 log-monitor log-monitor directive creates a replacement file with service-compatible ownership.
  • The su log-monitor log-monitor directive performs rotation under the report owner's identity.
  • Press Ctrl+O to write the rotation policy.
  • Press Enter to confirm the file name.
  • Press Ctrl+X to exit Nano.

✔️ Awesome, I've got everything!

Your rotation policy is saved with bounded compressed retention.

ⓧ I'd like to double check the full code

/var/log/log-monitor/report.log {
    size 1k
    rotate 3
    compress
    missingok
    notifempty
    create 0640 log-monitor log-monitor
    su log-monitor log-monitor
}

Compare the file path plus every directive with your saved policy. Confirm that the owner appears twice on the create line.

  • Test the policy without changing any files by running this command:
sudo logrotate --debug /etc/logrotate.d/log-monitor

What does the debug test do?

The debug mode reads the policy plus prints its decisions. It does not rotate the report or update logrotate state.

A completed debug run proves the configuration can be parsed.

Debug test reports a policy problem?

Compare the braces plus indentation with the reference policy. Check each directive for a missing value or typing mistake.

Help me fix my logrotate policy without changing the report files.

Before you force the rotation, what do you expect to happen to the current report file?

  • Create the first compressed archive by running this command:
sudo logrotate --force /etc/logrotate.d/log-monitor

What does forced rotation do?

The force option rotates the nonempty report even when its normal threshold has not been reached. The policy compresses the previous contents plus creates a fresh report file.

  • Inspect the archive plus the replacement report by running this command:
sudo ls -l /var/log/log-monitor/report.log /var/log/log-monitor/report.log.1.gz

What should the file listing show?

You should see /var/log/log-monitor/report.log.1.gz beside a fresh /var/log/log-monitor/report.log.

The fresh report should show mode 0640 with owner plus group set to log-monitor.

  • Start the monitor service once by running this command:
sudo systemctl start log-monitor.service

Why start the service now?

The one-shot service opens the replacement report during this run. A successful start proves rotation did not break the monitor's write access.

The service finishes after writing one new summary.

  • Confirm the fresh report contains a post-rotation summary by running this command:
sudo tail -n 3 /var/log/log-monitor/report.log

What should the fresh report contain?

You should see at least one ISO 8601 timestamped summary in the replacement file. Its warning plus error totals come from the current controlled source log.

That summary proves the service can continue writing after rotation.

You have closed both operational gaps. The monitor runs on schedule while its report stays bounded through compressed retention.

Fresh report still empty?

Inspect the service journal for the latest invocation. Confirm that the replacement report belongs to the log-monitor user plus group.

Compare its permissions with mode 0640. Recheck the create plus su directives if ownership differs.

Help me diagnose why log-monitor.service cannot write after rotation.

Your monitoring workload now runs without an open terminal. Next, you will create a separate administrator account plus secure its remote access with SSH keys.

Secure Administrative Access

Your systemd timer now runs the monitor automatically under a restricted workload identity. Human administration needs its own account so the non-login log-monitor identity stays limited to the service.

OpenSSH gives Windows PowerShell an encrypted path to that separate administrator. You'll prove the SSH key works before hardening access with UFW.

In this step, get ready to:
  • Create the monitoradmin account with administrator privileges.
  • Prove key-authenticated SSH access before disabling password and root SSH.
  • Permit port 22 through the Linux lab firewall.
Create the administrator and SSH key

The administrator account represents a person who can use sudo. Its Windows private key proves that person's identity without exposing the account password to SSH.

  • Prepare a strong local password for monitoradmin.
  • Complete the administrator setup by running these commands in Ubuntu:
sudo adduser monitoradmin
sudo adduser monitoradmin sudo
sudo systemctl enable --now ssh.service

What Do These Commands Set Up?

  • The first command creates the interactive account. It prompts for the password you prepared plus optional profile details.
  • The second command adds the account to the sudo group for administrative work.
  • The third command enables the SSH server at startup. It also starts the server now.

You'll see confirmation that monitoradmin was created and added to the group. The SSH service command completes without a not-found error.

Account or SSH Setup Failed?

If monitoradmin already exists, stop before changing that account. Confirm that it belongs to this lab.

If the SSH service does not start, check that the OpenSSH Server package from Step 1 is still installed.

Help me diagnose the administrator or SSH service setup.

Your private key has the same security value as a password. Keep it inside your Windows profile and never paste its contents into Ubuntu.

  • Press the Windows key to open Windows search.
  • Type Windows PowerShell and press Enter.
  • Choose a passphrase for the key when PowerShell prompts you.
  • Generate the Windows key pair by running:
ssh-keygen -t ed25519 -f "$env:USERPROFILE\.ssh\wsl-monitoradmin"

What Does This Command Create?

The command creates the private key at $env:USERPROFILE\.ssh\wsl-monitoradmin. It creates the matching shareable public key with a .pub suffix.

The private key stays on Windows. Only the public key belongs in the Ubuntu account.

PowerShell confirms where both files were saved and prints a key fingerprint. Your Windows side of the key login is ready.

Key Generation Failed?

If PowerShell reports that the destination already exists, do not overwrite it until you confirm whether it belongs to this lab.

If the command is unavailable, return to the Windows OpenSSH Client check from Step 1.

Help me troubleshoot the Windows SSH key generation command.

  • Display the public key in PowerShell by running:
Get-Content "$env:USERPROFILE\.ssh\wsl-monitoradmin.pub"

Which Key Is Safe to Copy?

This command reads the file ending in .pub. That single public key line is the value you install in Ubuntu.

Never run the same command against the file without the .pub suffix. That file is your private key.

  • Record the displayed public key here: your ed25519 public key.
  • Switch back to the Ubuntu terminal from earlier.
  • Create the key directory with restricted ownership by running:
sudo mkdir /home/monitoradmin/.ssh
sudo chown monitoradmin:monitoradmin /home/monitoradmin/.ssh
sudo chmod 0700 /home/monitoradmin/.ssh

Why Restrict the Key Directory?

The 0700 mode gives monitoradmin full access to the directory. Other users receive no access through these permission bits.

  • Confirm the directory owner and mode by running:
ls -ld /home/monitoradmin/.ssh

What Should the Directory Show?

The listing identifies monitoradmin as both owner and group. Its permission string represents mode 0700.

  • Open the authorized key file in Nano by running:
sudo nano /home/monitoradmin/.ssh/authorized_keys

What Is This File For?

OpenSSH checks authorized_keys when someone requests key authentication as monitoradmin. A matching public key allows the Windows private key to prove the requester's identity.

  • Replace the placeholder below with your ed25519 public key.
your-ed25519-public-key-here

Keep the Public Key on One Line

The file contains the complete public key as one line. Extra line breaks inside that key prevent authentication.

  • Press Ctrl+O to write the file.
  • Press Enter to confirm the filename.
  • Press Ctrl+X to close Nano.
  • Apply the required file ownership and mode by running:
sudo chown monitoradmin:monitoradmin /home/monitoradmin/.ssh/authorized_keys
sudo chmod 0600 /home/monitoradmin/.ssh/authorized_keys

Why Use Mode 0600?

Mode 0600 allows monitoradmin to read and write the file. Other users receive no permission through these bits.

✔️ Awesome, I've got everything!

Your authorized key file contains one public key line. Its owner and group are both monitoradmin.

ⓧ I'd like to double check the full code

your-ed25519-public-key-here

Before you connect, do you expect PowerShell to request the Ubuntu account password or use the private key?

  • Test the key-authenticated session from PowerShell by running:
ssh -i "$env:USERPROFILE\.ssh\wsl-monitoradmin" monitoradmin@localhost

What Does the Identity Option Do?

The -i option selects the private identity file created on Windows. The public half installed in Ubuntu verifies it.

PowerShell opens a shell prompt for monitoradmin after any key passphrase prompt. That first remote prompt proves the key path works.

  • Keep this successful SSH session open as your recovery path.

Key Login Did Not Open a Shell?

Confirm that you copied the public .pub file into authorized_keys as one line.

Confirm that the directory uses mode 0700 and the file uses mode 0600.

Help me diagnose the monitoradmin key login failure.

Harden SSH without losing access

Changing authentication rules can lock out an administrator. Your first successful SSH session stays open until a second session proves the hardened configuration works.

  • Switch to the Ubuntu terminal from earlier.
  • Open the SSH configuration snippet in Nano by running:
sudo nano /etc/ssh/sshd_config.d/00-wsl-monitor.conf

Why Use a Configuration Snippet?

The snippet keeps this lab's access rules separate from the packaged SSH configuration. Its filename places the rules early in Ubuntu's snippet order.

  • Paste this configuration into the file:
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
AllowUsers monitoradmin

What Do These Rules Enforce?

  • Public key authentication remains available.
  • Password and keyboard-interactive authentication are disabled.
  • Root cannot connect through SSH.
  • Only monitoradmin is allowed to request an SSH login.
  • Press Ctrl+O to write the snippet.
  • Press Enter to confirm the filename.
  • Press Ctrl+X to close Nano.

✔️ Awesome, I've got everything!

The SSH snippet is saved. Keep the existing key-authenticated session open for the next checks.

ⓧ I'd like to double check the full code

PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
AllowUsers monitoradmin

Before you validate the file, do you expect a correct SSH configuration to print output?

  • Validate the complete SSH configuration by running:
sudo sshd -t

How Does the Validation Behave?

The validation command checks the server configuration without restarting SSH. A successful check returns silently to the prompt.

No output confirms the syntax is valid. Any reported problem must be fixed before the service is restarted.

SSH Validation Reported a Problem?

Keep the SSH service running in its current state. Reopen /etc/ssh/sshd_config.d/00-wsl-monitor.conf and compare every directive with the full-code tab.

Help me fix the SSH configuration validation error.

  • Restart the validated SSH service by running:
sudo systemctl restart ssh.service

What Does the Restart Apply?

The restart loads the validated authentication rules. Your existing session remains the recovery path while you test a fresh connection.

  • Press the Windows key to open Windows search.
  • Type Windows PowerShell and press Enter to open a second window.
  • Test the hardened server from the second PowerShell window by running:
ssh -i "$env:USERPROFILE\.ssh\wsl-monitoradmin" monitoradmin@localhost

What Does the Second Session Prove?

A new monitoradmin prompt proves key authentication still works after the restart. It also proves the new SSH configuration admits the intended administrator.

You now have a tested key-only administrator path. The workload continues under the separate non-login log-monitor account.

Second Session Rejected?

Keep the original successful session open. Use it to inspect the snippet and rerun the validation command.

Confirm that AllowUsers monitoradmin matches the account name exactly.

Help me recover the hardened SSH connection.

Enable UFW and verify remote access

The firewall change affects inbound access inside the WSL Linux environment. Adding the SSH rule first keeps port 22 available when UFW becomes active.

Where Does UFW Fit?

UFW demonstrates a Linux host-firewall rule around the lab. Windows and Hyper-V firewall policy remain the outer WSL network boundary.

  • Return to the successful monitoradmin SSH session.
  • Permit SSH before enabling UFW by running:
sudo ufw allow 22

What Does This Rule Permit?

The rule permits inbound traffic to port 22 inside Ubuntu. That is the default port used by the SSH server in this lab.

Enabling a firewall can interrupt remote access when its administration port is missing. Your SSH rule is already present and your earlier session remains open.

  • Enable UFW by running:
  • Approve the confirmation prompt when it appears.
sudo ufw enable

What Changes When UFW Starts?

UFW activates the Linux host firewall and applies the allow rule you created. The existing SSH session provides a safe place to correct the rule if a later connection fails.

Before you inspect the firewall, do you expect its status to show port 22 as permitted?

  • Inspect the active firewall policy by running:
sudo ufw status verbose

What Should the Firewall Report?

The output reports that UFW is active. It includes an allow rule for port 22.

  • Press the Windows key to open Windows search.
  • Type Windows PowerShell and press Enter to open one more window.

Before you reconnect, do you expect the active UFW rule to preserve the key-only SSH path?

  • Run the final key-authenticated connection check from the new PowerShell window:
ssh -i "$env:USERPROFILE\.ssh\wsl-monitoradmin" monitoradmin@localhost

What Does the Final Session Confirm?

A new monitoradmin shell prompt proves SSH still works after hardening and firewall enablement. Your timer-driven monitor continues running under log-monitor.

That's the access boundary complete. Human administration now uses a tested key-only account while the monitoring workload retains its restricted service identity.

Firewall Active but SSH Rejected?

Use the successful session you kept open to confirm the port 22 allow rule still appears in the verbose UFW status.

Confirm that the SSH service remains active before changing any firewall rule.

Help me diagnose SSH access after enabling UFW.

Secret mission

Trigger Monitoring When the Log Changes

A one-minute schedule leaves a small gap between a log change and its next scan. Create a systemd path unit that watches the demo log. Then prove a controlled warning triggers the existing service immediately while the timer remains your recovery backstop.

Clean Up Your Resources

Clean Up Your Resources

Your monitoring lab can stay active for more practice. Everything runs on your local Windows and WSL device. Keeping it costs $0.

Resources you used:

  • The custom systemd service at /etc/systemd/system/log-monitor.service.
  • The active timer at /etc/systemd/system/log-monitor.timer.
  • The active path unit at /etc/systemd/system/log-monitor.path.
  • The Bash monitor in /usr/local/lib/log-monitor.
  • The controlled source log in /var/log/demo-app.
  • The service report files in /var/log/log-monitor.
  • The manual report at $HOME/log-monitor-report.log.
  • The logrotate policy at /etc/logrotate.d/log-monitor.
  • The OpenSSH hardening snippet at /etc/ssh/sshd_config.d/00-wsl-monitor.conf.
  • The project key authorization file at /home/monitoradmin/.ssh/authorized_keys.
  • The active UFW rule allowing TCP port 22.
  • The Windows key pair named wsl-monitoradmin and wsl-monitoradmin.pub.

Keep everything running

Everything can stay as it is. Choose this option if you want scheduled monitoring and immediate path activation available for more practice.

  • Your log-monitor.timer continues to schedule a run every minute while WSL is active.
  • Your log-monitor.path continues to trigger the service when the demo log changes.
  • Your report remains available in /var/log/log-monitor.
  • Your key-only monitoradmin access remains available through ssh.service.
  • Your log-monitor service account remains separate from the interactive monitoradmin account.
  • Your WSL instance can still stop when unused because systemd services do not keep it alive.

Pause - I'll come back to this later

Pause both automated triggers to stop new monitoring runs. Your scripts and reports stay in place for your return.

  • Disable the timer and path unit by running these commands:
sudo systemctl disable --now log-monitor.timer
sudo systemctl disable --now log-monitor.path

What Does This Pause?

The first command stops calendar-based runs. The second command stops immediate runs when the source log changes.

The service definition and report history remain on disk. Your SSH access also remains active.

  • Confirm both automated triggers are inactive by running these checks:
systemctl list-timers log-monitor.timer --all
systemctl list-paths log-monitor.path

What Should You See?

The listings should show no active trigger for either named unit. This confirms that monitoring is paused without deleting the lab.

Delete - I don't want to use this again

Remove the project-specific automation and local files if you have finished with the lab. The installed Ubuntu packages and local accounts remain available for other work.

Close Remote Sessions First

This cleanup removes the authorized key used by monitoradmin. Finish any work in your remote shell before continuing.

  • Close every PowerShell SSH session connected as monitoradmin.
  • Stop both automated triggers by running these commands:
sudo systemctl disable --now log-monitor.timer
sudo systemctl disable --now log-monitor.path

What Does This Stop?

These commands stop calendar activation and path activation before their unit files disappear. This prevents a trigger from starting the service during cleanup.

  • Remove the project-specific Linux files by running these commands:
sudo rm -f /etc/systemd/system/log-monitor.service
sudo rm -f /etc/systemd/system/log-monitor.timer
sudo rm -f /etc/systemd/system/log-monitor.path
sudo rm -rf /usr/local/lib/log-monitor
sudo rm -rf /var/log/demo-app
sudo rm -rf /var/log/log-monitor
rm -f "$HOME/log-monitor-report.log"
sudo rm -f /etc/logrotate.d/log-monitor
sudo rm -f /etc/ssh/sshd_config.d/00-wsl-monitor.conf
sudo rm -f /home/monitoradmin/.ssh/authorized_keys

What Gets Removed?

These commands remove the custom units and monitor script. They also remove the controlled logs and report history.

The final commands remove the rotation policy and project-specific SSH configuration. They also remove the authorized public key from Ubuntu.

  • Refresh systemd and apply the removed SSH snippet by running these commands:
sudo systemctl daemon-reload
sudo systemctl restart ssh.service

Why Reload and Restart?

The daemon reload clears the deleted custom units from systemd. Restarting the SSH service applies the remaining server configuration.

UFW may ask for confirmation before it removes the SSH rule.

  • Remove the Linux firewall rule by running these commands:
sudo ufw delete allow 22
sudo ufw status verbose

What Should You See?

The verbose status should no longer list the project rule allowing port 22. The Windows or Hyper-V firewall remains the outer WSL boundary.

Finding the Windows key pair can be fiddly because it sits inside the hidden .ssh folder.

  • Open the .ssh folder inside your Windows user profile with the Windows file browser.
  • Delete wsl-monitoradmin.
  • Delete wsl-monitoradmin.pub.

Cleanup complete. The project-specific files, automation units, key material, and firewall rule are now gone.

Nice Work!

Nice Work!

You did it! Your Ubuntu 26.04 WSL monitoring lab now detects important application records through a least-privilege service.

You've learned how to:

  • Build a strict Bash monitor that reads a controlled source log. Write ISO 8601 summaries with separate warning totals. Include separate error totals in the same report.
  • Run the monitor under a least-privilege systemd one-shot service. Schedule recurring executions with a persistent calendar timer. Inspect each invocation in the journal. Bound report storage with logrotate.
  • Separate the non-login log-monitor workload from the sudo-enabled monitoradmin account through least-privilege identities. Use OpenSSH key authentication from Windows PowerShell. Apply a UFW rule to permit SSH within WSL. Treat the Windows firewall as the outer boundary.
  • Secret Mission: Add immediate path-based activation with log-monitor.path. A source-log change now triggers the existing service without waiting for the next minute. Keep the timer active as a recovery backstop.

Ready to quiz yourself?