
- 0 Comments
- 788 views
💻 Where tech meets community.
Hello, Guest! 👋
You're just a few clicks away from joining an exclusive space for tech enthusiasts, problem-solvers, and lifelong learners like you.
🔐 Why Join?
By becoming a member of CodeNameJessica, you’ll get access to:
✅ In-depth discussions on Linux, Security, Server Administration, Programming, and more
✅ Exclusive resources, tools, and scripts for IT professionals
✅ A supportive community of like-minded individuals to share ideas, solve problems, and learn together
✅ Project showcases, guides, and tutorials from our members
✅ Personalized profiles and direct messaging to collaborate with other techies
🌐 Sign Up Now and Unlock Full Access!
As a guest, you're seeing just a glimpse of what we offer. Don't miss out on the complete experience! Create a free account today and start exploring everything CodeNameJessica has to offer.
Latest entry by Blogger,
A lot of people want Linux but do not want to go either remove Windows or take up the overwhelming task of dual booting. For those people, WSL (Windows Subsystem for Linux) came as a blessing. WSL lets you run Linux on your Windows device without the overhead of a Virtual Machine (VM). But in some cases where you want to fix a problem or simply do not want WSL anymore, you may have to uninstall WSL from your Windows system.
Here is step-by-step guide to remove WSL from your Windows system, remove any Linux distribution, delete all related files, and clear up some disk space. Ready? Get. Set. Learn!
You probably knew by now that we will always start with the basics i.e., what WSL does. Think of WSL as a compatibility layer for running Linux binaries on Microsoft Windows systems. It comes in two versions:
All around the world, WSL is a favourite among developers, system administrators, and students for running Linux tools like bash, ssh, grep, awk, and even Docker. But if you have moved to a proper Linux system or just want to do a clean reinstall, here are the instructions to remove WSL completely without any errors.
The first step to uninstall WSL completely is to remove all installed Linux distributions.
To check for the installed Linux distributions, open PowerShell or Command Prompt and run the command:
wsl --list --all
After executing this command, you will see a list of installed distros, such as:
To uninstall a distro like Ubuntu, follow these instructions:
Repeat for all distros you no longer need. If you plan to uninstall WSL completely, we recommend removing all distros.
if you prefer PowerShell, run these commands
wsl --unregister <DistroName>
For example, if you want to remove Ubuntu, execute the command:
wsl --unregister Ubuntu
This removes the Linux distro and all its associated files.
Once we have removed the unwanted distros, let us uninstall the WSL platform itself.
Even after uninstalling WSL and Linux distributions, some data might remain. Here are the instructions to delete WSL’s cached files and reclaim disk space.
To delete the WSL Folder, open File Explorer and go to:
%USERPROFILE%\AppData\Local\Packages
Look for folders like:
Delete any folders related to WSL distros you removed.
If you installed WSL using the Microsoft Store (i.e., “wsl.exe” package), you can also uninstall it directly from the Installed Apps section:
Finally, use the built-in Disk Cleanup utility to clear any temporary files.
If you are removing WSL due to issues or conflicts, you can always do a fresh reinstall.
Here is how you can install latest version of WSL via PowerShell
wsl --install
This installs WSL 2 by default, along with Ubuntu.
Uninstalling WSL may sound tricky, but by following these steps, you can completely remove Linux distributions, WSL components, and unwanted files from your system. Whether you are making space for something new or just doing some digital spring cleaning, this guide ensures that WSL is uninstalled safely and cleanly.
If you ever want to come back to the Linux world, WSL can be reinstalled with a single command, which we have covered as a precaution. Let us know if you face any errors. Happy learning!
The post Uninstall WSL: Step-by-Step Simple Guide appeared first on Unixmen.
Latest entry by Blogger,
Ready for the second part? We are still exploring the shape()
function, and more precisely, the arc command. I hope you took the time to digest the first part because we will jump straight into creating more shapes!
As a reminder, the shape()
function is only supported in Chrome 137+ and Safari 18.4+ as I’m writing this in May 2025.
Another classic shape that can also be used in pie-like charts.
It’s already clear that we have one arc. As for the points, we have two points that don’t move and one that moves depending on how much the sector is filled.
The code will look like this:
.sector {
--v: 35; /* [0 100]*/
aspect-ratio: 1;
clip-path: shape(from top, arc to X Y of R, line to center);
}
We define a variable that will control the filling of the sector. It has a value between 0
and 100
. To draw the shape, we start from the top
, create an arc until the point (X, Y), and then we move to the center
.
Are we allowed to use keyword values like
top
andcenter
?
Yes! Unlike the polygon()
function, we have keywords for the particular cases such as top
, bottom
, left
, etc. It’s exactly like background-position
that way. I don’t think I need to detail this part as it’s trivial, but it’s good to know because it can make your shape a bit easier to read.
The radius of the arc should be equal to 50%
. We are working with a square element and the sector, which is a portion of a circle, need to fill the whole element so the radius is equal to half the width (or height).1
As for the point, it’s placed within that circle, and its position depends on the V value. You don’t want a boring math explanation, right? No need for it, here is the formula of X and Y:
X = 50% + 50% * sin(V * 3.6deg)
Y = 50% - 50% * cos(V * 3.6deg)
Our code becomes:
.sector {
--v: 35; /* [0 100] */
aspect-ratio: 1;
clip-path: shape(from top,
arc to calc(50% + 50% * sin(var(--v) * 3.6deg))
calc(50% - 50% * cos(var(--v) * 3.6deg)) of 50%,
line to center);
}
Hmm, the result is not good, but there are no mistakes in the code. Can you figure out what we are missing?
It’s the size and direction of the arc!
Remember what I told you in the last article? You will always have trouble with them, but if we try the different combinations, we can easily fix the issue. In our case, we need to use: small cw
.
Better! Let’s try it with more values and see how the shape behaves:
Oops, some values are good, but others not so much. The direction needs to be clockwise, but maybe we should use large
instead of small
? Let’s try:
Still not working. The issue here is that we are moving one point of the arc based on the V value, and this movement creates a different configuration for the arc
command.
Here is an interactive demo to better visualize what is happening:
When you update the value, notice how large cw
always tries to follow the largest arc between the points, while small cw
tries to follow the smallest one. When the value is smaller than 50
, small cw
gives us a good result. But when it’s bigger than 50
, the large cw
combination is the good one.
I know, it’s a bit tricky and I wanted to study this particular example to emphasize the fact that we can have a lot of headaches working with arcs. But the more issues we face, the better we get at fixing them.
The solution in this case is pretty simple. We keep the use of large cw
and add a border-radius
to the element. If you check the previous demo, you will notice that even if large cw
is not producing a good result, it’s filling the area we want. All we need to do is clip the extra space and a simple border-radius: 50%
will do the job!
I am keeping the box-shadow
in there so we can see the arc, but we can clearly see how border-radius
is making a difference on the main shape.
There is still one edge case we need to consider. When the value is equal to 100
, both points of the arc will have the same coordinates, which is logical since the sector is full and we have a circle. But when it’s the case, the arc will do nothing by definition and we won’t get a full circle.
To fix this, we can limit the value to, for example, 99.99
to avoid reaching 100
. It’s kind of hacky, but it does the job.
.sector {
--v: 35; /* [0 100]*/
--_v: min(99.99, var(--v));
aspect-ratio: 1;
clip-path: shape(from top,
arc to calc(50% + 50% * sin(var(--_v) * 3.6deg))
calc(50% - 50% * cos(var(--_v) * 3.6deg)) of 50% large cw,
line to center);
border-radius: 50%;
}
Now our shape is perfect! And don’t forget that you can apply it to image elements:
Similar to the sector shape, we can also create an arc shape. After all, we are working with the arc
command, so we have to do it.
We already have half the code since it’s basically a sector shape without the inner part. We simply need to add more commands to cut the inner part.
.arc {
--v: 35;
--b: 30px;
--_v: min(99.99, var(--v));
aspect-ratio: 1;
clip-path: shape(from top,
arc to calc(50% + 50% * sin(var(--_v) * 3.6deg))
calc(50% - 50% * cos(var(--_v) * 3.6deg)) of 50% cw large,
line to calc(50% + (50% - var(--b)) * sin(var(--_v) * 3.6deg))
calc(50% - (50% - var(--b)) * cos(var(--_v) * 3.6deg)),
arc to 50% var(--b) of calc(50% - var(--b)) large
);
border-radius: 50%;
}
From the sector shape, we remove the line to center
piece and replace it with another line
command that moves to a point placed on the inner circle. If you compare its coordinates with the previous point, you will see an offset equal to --b
, which is a variable that defines the arc’s thickness. Then we draw an arc in the opposite direction (ccw
) until the point 50% var(--b)
, which is also a point with an offset equal to --b
from the top.
I am not defining the direction of the second arc since, by default, the browser will use ccw
.
Ah, the same issue we hit with the sector shape is striking again! Not all the values are giving a good result due to the same logic we saw earlier, and, as you can see, border-radius
is not fixing it. This time, we need to find a way to conditionally change the size of the arc based on the value. It should be large
when V is bigger than 50
, and small
otherwise.
Conditions in CSS? Yes, it’s possible! First, let’s convert the V value like this:
--_f: round(down, var(--_v), 50)
The value is within the range [0 99.99]
(don’t forget that we don’t want to reach the value 100). We use round()
to make sure it’s always equal to a multiple of a specific value, which is 50
in our case. If the value is smaller than 50
, the result is 0
, otherwise it’s 50
.
There are only two possible values, so we can easily add a condition. If --_f
is equal to 0
we use small; otherwise, we use large:
.arc {
--v: 35;
--b: 30px;
--_v: min(99.99, var(--v));
--_f: round(down,var(--_v), 50);
--_c: if(style(--_f: 0): small; else: large);
clip-path: shape(from top,
arc to calc(50% + 50% * sin(var(--_v) * 3.6deg))
calc(50% - 50% * cos(var(--_v) * 3.6deg)) of 50% cw var(--_c),
line to calc(50% + (50% - var(--b)) * sin(var(--_v) * 3.6deg))
calc(50% - (50% - var(--b)) * cos(var(--_v) * 3.6deg)),
arc to 50% var(--b) of calc(50% - var(--b)) var(--_c)
);
}
I know what you are thinking, but let me tell you that the above code is valid. You probably don’t know it yet, but CSS has recently introduced inline conditionals using an if()
syntax. It’s still early to play with it, but we have found a perfect use case for it. Here is a demo that you can test using Chrome Canary:
Another way to express conditions is to rely on style queries that have better support:
.arc {
--v: 35;
--b: 30px;
--_v: min(99.99, var(--v));
--_f: round(down, var(--_v), 50);
aspect-ratio: 1;
container-name: arc;
}
.arc:before {
content: "";
clip-path: shape(from top,
arc to calc(50% + 50% * sin(var(--_v) * 3.6deg))
calc(50% - 50% * cos(var(--_v) * 3.6deg)) of 50% cw var(--_c, large),
line to calc(50% + (50% - var(--b)) * sin(var(--_v) * 3.6deg))
calc(50% - (50% - var(--b)) * cos(var(--_v) * 3.6deg)),
arc to 50% var(--b) of calc(50% - var(--b)) var(--_c, large)
);
@container style(--_f: 0) { --_c: small }
}
The logic is the same but, this feature requires a parent-child relation, which is why I am using a pseudo-element. By default, the size will be large
, and if the value of --_f
is equal to 0
, we switch to small
.
Note that we have to register the variable --_f
using @property
to be able to either use the if()
function or style queries.
Did you notice another subtle change I have made to the shape? I removed border-radius
and I applied the conditional logic to the first arc. Both have the same issue, but border-radius
can fix only one of them while the conditional logic can fix both, so we can optimize the code a little.
What about adding rounded edges to our arc? It’s better, right?
Can you see how it’s done? Take it as a small exercise and update the code from the previous examples to add those rounded edges. I hope you are able to find it by yourself because the changes are pretty straightforward — we update one line
command with an arc
command and we add another arc
command at the end.
clip-path: shape(from top,
arc to calc(50% + 50% * sin(var(--_v) * 3.6deg))
calc(50% - 50% * cos(var(--_v) * 3.6deg)) of 50% cw var(--_c, large),
arc to calc(50% + (50% - var(--b)) * sin(var(--_v) * 3.6deg))
calc(50% - (50% - var(--b)) * cos(var(--_v) * 3.6deg)) of 1% cw,
arc to 50% var(--b) of calc(50% - var(--b)) var(--_c, large),
arc to top of 1% cw
);
If you do not understand the changes, get out a pen and paper, then draw the shape to better see the four arcs we are drawing. Previously, we had two arcs and two lines, but now we are working with arcs instead of lines.
And did you remember the trick of using a 1%
value for the radius? The new arcs are half circles, so we can rely on that trick where you specify a tiny radius and the browser will do the job for you and find the correct value!
We are done — enough about the arc
command! I had to write two articles that focus on this command because it’s the trickiest one, but I hope it’s now clear how to use it and how to handle the direction and size thing, as that is probably the source of most headaches.
By the way, I have only studied the case of circular arcs because, in reality, we can specify two radii and draw elliptical ones, which is even more complex. Unless you want to become a shape()
master, you will rarely need elliptical arcs, so don’t bother yourself with them.
Until the next article, I wrote an article for Frontend Masters where you can create more fancy shapes using the arc
command that is a good follow-up to this one.
(1) The arc
command is defined to draw elliptical arcs by taking two radii, but if we define one radius value, it means that the vertical and horizontal radius will use that same value and we have circular arcs. When it’s a length, it’s trivial, but when we use percentages, the value will resolve against the direction-agnostic size, which is equal to the length of the diagonal of the box, divided by sqrt(2)
.
In our case, we have a square element so 50% of the direction-agnostic size will be equal to 50% of sqrt(Width² + Height²)/sqrt(2)
. And since both width and height are equal, we end with 50% of the width (or the height). ⮑
Better CSS Shapes Using shape() — Part 2: More on Arcs originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Important thing first. Ubuntu 20.04 LTS version will be reaching its end of life on 31st May. It was released in April 2020 and had a standard support of five years.
Please check your Ubuntu version and if you are using 20.04, you can:
Time to plan your update.
💬 Let's see what else you get in this edition
PikaPods allows you to quickly deploy your favorite open source software. All future updates are handled automatically by PikaPods while you enjoy using the software. PikaPods also share revenue with the original developers of the software.
You get a $5 free credit to try it out and see if you can rely on PikaPods.
Rhino Linux's new UBXI KDE Desktop doesn't disappoint.
Carmen from Mission Libre has started a petition to get Qualcomm to release fully-free drivers for their in-production chipsets. If the petition is signed by 5,000 people, a hardcopy of the petition and signatures will be mailed to Qualcomm's head office. We can get 5,000 signatures, can't we?
Looking for some note taking apps suggestion? We have an extensive list.
Why should you opt for It's FOSS Plus membership:
✅ Ad-free reading experience
✅ Badges in the comment section and forum
✅ Supporting creation of educational Linux materials
While it is a proprietary piece of hardware, Flexbar can be a nice addition to your Linux setup.
Also, learn a thing or two about MCP servers, the latest buzzword in the (AI) tech world.
If you ever wanted to run an operating system inside your browser, then Puter is the solution for you. It is open source and can be self-hosted as well.
An It's FOSS reader created an FFmpeg AAC Audio Encoder Plugin for DaVinci Resolve. This will help you get effortless AAC audio encoding on Linux if you use DaVinci Resolve video editor.
I tried Microsoft's new terminal editor on Linux! I hate to admit it but I liked what I saw here. This is an excellent approach. I wonder why Linux didn't have something like this before. See it in action 👇
Can you identify all the GitHub alternatives in this puzzle?
In Xfce, you can use the panel item "Directory Menu" to get quick access to files from anywhere. This is like the Places extension in GNOME, but better.
In the configuration menu for it, provide the file extension in the following format *.txt;*.jsonc
as shown in the screenshot above to access the files quickly. Clicking on those files opens it in the default app.
The ricing never stops! 👨💻
On May 27, 1959, MIT retired the Whirlwind computer, a groundbreaking machine famous for pioneering real-time computing and magnetic core memory.
ProFOSSer Sheila is having an issue with MX Linux, can you help?
Share it with your Linux-using friends and encourage them to subscribe (hint: it's here).
Share the articles in Linux Subreddits and community forums.
Follow us on Google News and stay updated in your News feed.
Opt for It's FOSS Plus membership and support us 🙏
Enjoy FOSS 😄
SaltStack, commonly referred to as SALT, is a powerful open-source infrastructure management platform designed for scalability. Leveraging event-driven workflows, SALT provides an adaptable solution for automating configuration management, remote execution, and orchestration across diverse infrastructures.
This document offers an in-depth guide to SALT for both technical teams and business stakeholders, demystifying its features and applications.
SALT is a versatile tool that serves multiple purposes in infrastructure management:
Configuration Management Tool (like Ansible, Puppet, Chef): Automates the setup and maintenance of servers and applications.
Remote Execution Engine (similar to Fabric or SSH): Executes commands on systems remotely, whether targeting a single node or thousands.
State Enforcement System: Ensures systems maintain desired configurations over time.
Event-Driven Automation Platform: Detects system changes and triggers actions in real-time.
Key Technologies:
YAML: Used for defining states and configurations in a human-readable format.
Jinja: Enables dynamic templating for YAML files.
Python: Provides extensibility through custom modules and scripts.
SALT accommodates various architectures to suit organizational needs:
Master/Minion: A centralized control model where a Salt Master manages Salt Minions to send commands and execute tasks.
Masterless: A decentralized approach using salt-ssh
to execute tasks locally without requiring a master node.
Component | Description |
---|---|
Salt Master | Central control node that manages minions, sends commands, and orchestrates infrastructure tasks. |
Salt Minion | Agent installed on managed nodes that executes commands from the master. |
Salt States | Declarative YAML configuration files that define desired system states (e.g., package installations). |
Grains | Static metadata about a system (e.g., OS version, IP address), useful for targeting specific nodes. |
Pillars | Secure, per-minion data storage for secrets and configuration details. |
Runners | Python modules executed on the master to perform complex orchestration tasks. |
Reactors | Event listeners that trigger actions in response to system events. |
Beacons | Minion-side watchers that emit events based on system changes (e.g., file changes or CPU spikes). |
Feature | Description |
---|---|
Agent or Agentless | SALT can operate in agent (minion-based) or agentless (masterless) mode. |
Scalability | Capable of managing tens of thousands of nodes efficiently. |
Event-Driven | Reacts to real-time system changes via beacons and reactors, enabling automation at scale. |
Python Extensibility | Developers can extend modules or create custom ones using Python. |
Secure | Employs ZeroMQ for communication and AES encryption for data security. |
Role-Based Config | Dynamically applies configurations based on server roles using grains metadata. |
Granular Targeting | Targets systems using name, grains, regex, or compound filters for precise management. |
SALT is widely used across industries for tasks like:
Provisioning new systems and applying base configurations.
Enforcing security policies and managing firewall rules.
Installing and enabling software packages (e.g., HTTPD, Nginx).
Scheduling and automating patching across multiple environments.
Monitoring logs and system states with automatic remediation for issues.
Managing VM and container lifecycles (e.g., Docker, LXC).
Remote Command Execution:
salt '*' test.ping
(Pings all connected systems).
salt 'web*' cmd.run 'systemctl restart nginx'
(Restarts Nginx service on all web servers).
State File Example (YAML):
nginx:
pkg.installed: []
service.running:
- enable: True
- require:
- pkg: nginx
Feature | Salt | Ansible | Puppet | Chef |
---|---|---|---|---|
Language | YAML + Python | YAML + Jinja | Puppet DSL | Ruby DSL |
Agent Required | Optional | No | Yes | Yes |
Push/Pull | Both | Push | Pull | Pull |
Speed | Very Fast | Medium | Medium | Medium |
Scalability | High | Medium-High | Medium | Medium |
Event-Driven | Yes | No | No | Limited |
SALT ensures secure communication and authentication:
Authentication: Uses public/private key pairs to authenticate minions.
Encryption: Communicates via ZeroMQ encrypted with AES.
Access Control: Defines granular controls using Access Control Lists (ACLs) in the Salt Master configuration.
For organizations seeking enhanced usability, SaltStack Config offers a graphical interface to streamline workflow management. Additionally, SALT's integration with VMware Tanzu provides advanced automation for enterprise systems.
On a master node (e.g., RedHat):
sudo yum install salt-master
On minion nodes:
sudo yum install salt-minion
Configure /etc/salt/minion
with:
master: your-master-hostname
Then start the minion:
sudo systemctl enable --now salt-minion
Accept the minion on the master:
sudo salt-key -L # list all keys
sudo salt-key -A # accept all pending minion keys
Git-based states with gitfs
Masterless setups for container deployments
Custom modules in Python
Event-driven orchestration with beacons + reactors
Let give an example of have 3 different environments DEV (Development), PREP (Preproduction), and PROD (Production), now let's dig a little deeper and say we have 3 different regions EUS (East US), WUS (West US), and EUR (European) and we would like these patches to be applied on changing dates, such as DEV will be patched on 3 days after the second Tuesday, PREP will be patched on 5 days after the second Tuesday, and PROD will be 5 days after the 3rd Tuesday. The final clause to this mass configuration is, we would like the patches to be applied on the Client Local Time.
In many configurations such as AUM, or JetPatch, you would need several different Maintenace Schedules or plans to create this setup. With SALT, the configuration lies inside the minion, so configuration is much more defined, and simple to manage.
You want to patch three environment groups based on local time and specific schedules:
Environment | Schedule Rule | Timezone |
---|---|---|
Dev | 3rd day after 2nd Tuesday of the month | Local |
PREP | 5th day after 2nd Tuesday of the month | Local |
Prod | 5th day after 3rd Tuesday of the month | Local |
Each server knows its environment via Salt grains, and the local timezone via OS or timedatectl
.
Set Custom Grains for Environment & Region
Create a Python script (run daily) that:
Checks if today matches the schedule per group
If yes, uses Salt to target minions with the correct grain and run patching
Schedule this script via cron or Salt scheduler
Use Salt States to define patching
On each minion, configure /etc/salt/minion.d/env_grains.conf
:
grains:
environment: dev # or prep, prod
region: us-east # or us-west, eu-central, etc.
Then restart the minion:
sudo systemctl restart salt-minion
Verify:
salt '*' grains.items
Create patching/init.sls
:
update-packages:
pkg.uptodate:
- refresh: True
- retry:
attempts: 3
interval: 15
reboot-if-needed:
module.run:
- name: system.reboot
- onlyif: 'test -f /var/run/reboot-required'
Let’s build run_patching.py
. It:
Figures out the correct date for patching
Uses salt
CLI to run patching for each group
Handles each group in its region and timezone
#!/usr/bin/env python3
import subprocess
import datetime
import pytz
from dateutil.relativedelta import relativedelta, TU
# Define your environments and their rules
envs = {
"dev": {"offset": 3, "week": 2},
"prep": {"offset": 5, "week": 2},
"prod": {"offset": 5, "week": 3}
}
# Map environments to regions (optional)
regions = {
"dev": ["us-east", "us-west"],
"prep": ["us-east", "eu-central"],
"prod": ["us-east", "us-west", "eu-central"]
}
# Timezones per region
region_tz = {
"us-east": "America/New_York",
"us-west": "America/Los_Angeles",
"eu-central": "Europe/Berlin"
}
def calculate_patch_date(year, month, week, offset):
second_tuesday = datetime.date(year, month, 1) + relativedelta(weekday=TU(week))
return second_tuesday + datetime.timedelta(days=offset)
def is_today_patch_day(env, region):
now = datetime.datetime.now(pytz.timezone(region_tz[region]))
target_day = calculate_patch_date(now.year, now.month, envs[env]["week"], envs[env]["offset"])
return now.date() == target_day and now.hour >= desired_hour
def run_salt_target(environment, region):
target = f"environment:{environment} and region:{region}"
print(f"Patching {target}...")
subprocess.run([
"salt", "-C", target, "state.apply", "patching"
])
def main():
for env in envs:
for region in regions[env]:
if is_today_patch_day(env, region):
run_salt_target(env, region)
if __name__ == "__main__":
main()
Make it executable:
chmod +x /srv/scripts/run_patching.py
Test it:
./run_patching.py
Edit crontab:
crontab -e
Add daily job:
# Run daily at 6 AM UTC
0 6 * * * /srv/scripts/run_patching.py >> /var/log/salt/patching.log 2>&1
This assumes the local time logic is handled in the script using each region’s timezone.
Test patching states on a few dev nodes first (salt -G 'environment:dev' -l debug state.apply patching
)
Add Slack/email notifications (Salt Reactor or Python smtplib
)
Consider dry-run support with test=True
(in pkg.uptodate
)
Use salt-run jobs.list_jobs
to track job execution
Use Salt Beacons + Reactors to monitor and patch in real-time
Integrate with JetPatch or Ansible for hybrid control
Add patch deferral logic for critical services
Write to a central patching log DB with job status per host
Minions:
Monitor the date/time via beacons
On patch day (based on local logic), send a custom event to the master
Master:
Reacts to that event via a reactor
Targets the sending minion and applies the patching
state
/etc/salt/minion.d/patchday_beacon.conf
beacons:
patchday:
interval: 3600 # check every hour
This refers to a custom beacon we will define.
/srv/salt/_beacons/patchday.py
import datetime
from dateutil.relativedelta import relativedelta, TU
import pytz
__virtualname__ = 'patchday'
def beacon(config):
ret = []
grains = __grains__
env = grains.get('environment', 'unknown')
region = grains.get('region', 'unknown')
# Define rules
rules = {
"dev": {"offset": 3, "week": 2},
"prep": {"offset": 5, "week": 2},
"prod": {"offset": 5, "week": 3}
}
region_tz = {
"us-east": "America/New_York",
"us-west": "America/Los_Angeles",
"eu-central": "Europe/Berlin"
}
if env not in rules or region not in region_tz:
return ret # invalid or missing config
tz = pytz.timezone(region_tz[region])
now = datetime.datetime.now(tz)
rule = rules[env]
patch_day = (datetime.date(now.year, now.month, 1)
+ relativedelta(weekday=TU(rule["week"]))
+ datetime.timedelta(days=rule["offset"]))
if now.date() == patch_day:
ret.append({
"tag": "patch/ready",
"env": env,
"region": region,
"datetime": now.isoformat()
})
return ret
On the master:
salt '*' saltutil.sync_beacons
Enable it:
salt '*' beacons.add patchday '{"interval": 3600}'
/etc/salt/master.d/reactor.conf
reactor:
- 'patch/ready':
- /srv/reactor/start_patch.sls
/srv/reactor/start_patch.sls
{% set minion_id = data['id'] %}
run_patching:
local.state.apply:
- tgt: {{ minion_id }}
- arg:
- patching
This reacts to patch/ready
event and applies the patching
state to the calling minion.
Restart the minion: systemctl restart salt-minion
Confirm the beacon is registered: salt '*' beacons.list
Trigger a manual test (simulate patch day by modifying date logic)
Watch events on master:
salt-run state.event pretty=True
Confirm patching applied:
salt '*' saltutil.running
patching/init.sls
Already shared, but here it is again for completeness:
update-packages:
pkg.uptodate:
- refresh: True
- retry:
attempts: 3
interval: 15
reboot-if-needed:
module.run:
- name: system.reboot
- onlyif: 'test -f /var/run/reboot-required'
Real-time and event-driven – no need for polling or external scripts
Timezone-aware, thanks to local beacon logic
Self-healing – minions signal readiness independently
Audit trail – each event is logged in Salt’s event bus
Extensible – you can easily add Slack/email alerts via additional reactors
Track patching event completions per minion
Store patch event metadata: who patched, when, result, OS, IP, environment, region, etc.
Generate readable reports in:
CSV/Excel
HTML dashboard
JSON for API or SIEM ingestion
Let’s log each successful patch into a central log file or database (like SQLite or MariaDB).
/srv/reactor/start_patch.sls
Add a returner to store job status.
{% set minion_id = data['id'] %}
run_patching:
local.state.apply:
- tgt: {{ minion_id }}
- arg:
- patching
- kwarg:
returner: local_json # You can also use 'mysql', 'elasticsearch', etc.
local_json
)In /etc/salt/master
:
returner_dirs:
- /srv/salt/returners
ext_returners:
local_json:
file: /var/log/salt/patch_report.json
Or use a MySQL returner:
mysql.host: 'localhost'
mysql.user: 'salt'
mysql.pass: 'yourpassword'
mysql.db: 'salt'
mysql.port: 3306
Enable returners:
salt-run saltutil.sync_returners
If using JSON log, create a post-processing script to build reports:
process_patch_log.py
import json
import csv
from datetime import datetime
def load_events(log_file):
with open(log_file, 'r') as f:
return [json.loads(line) for line in f if line.strip()]
def export_csv(events, out_file):
with open(out_file, 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=[
'minion', 'date', 'environment', 'region', 'result'
])
writer.writeheader()
for e in events:
writer.writerow({
'minion': e['id'],
'date': datetime.fromtimestamp(e['_stamp']).isoformat(),
'environment': e['return'].get('grains', {}).get('environment', 'unknown'),
'region': e['return'].get('grains', {}).get('region', 'unknown'),
'result': 'success' if e['success'] else 'failure'
})
events = load_events('/var/log/salt/patch_report.json')
export_csv(events, '/srv/reports/patching_report.csv')
If you want to display reports via a browser:
Flask or FastAPI
Bootstrap or Chart.js
Reads JSON/CSV and renders:
✅ Last patch date per server
📍 Patching success rate per region/env
🔴 Highlight failed patching
📆 Monthly compliance timeline
Would you like a working example of that Flask dashboard? I can include the full codebase if so.
send_report_email.py
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["Subject"] = "Monthly Patch Report"
msg["From"] = "patchbot@example.com"
msg["To"] = "it-lead@example.com"
msg.set_content("Attached is the patch compliance report.")
with open("/srv/reports/patching_report.csv", "rb") as f:
msg.add_attachment(f.read(), maintype="text", subtype="csv", filename="patching_report.csv")
with smtplib.SMTP("localhost") as s:
s.send_message(msg)
Schedule that weekly or monthly with cron
.
from flask import Flask, render_template
import csv
from collections import defaultdict
app = Flask(__name__)
@app.route('/')
def index():
results = []
success_count = defaultdict(int)
fail_count = defaultdict(int)
with open('/srv/reports/patching_report.csv', 'r') as f:
reader = csv.DictReader(f)
for row in reader:
results.append(row)
key = f"{row['environment']} - {row['region']}"
if row['result'] == 'success':
success_count[key] += 1
else:
fail_count[key] += 1
summary = [
{"group": k, "success": success_count[k], "fail": fail_count[k]}
for k in sorted(set(success_count) | set(fail_count))
]
return render_template('dashboard.html', results=results, summary=summary)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
<!DOCTYPE html>
<html>
<head>
<title>Patch Compliance Dashboard</title>
<style>
body { font-family: Arial; padding: 20px; }
table { border-collapse: collapse; width: 100%; margin-bottom: 30px; }
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background-color: #f4f4f4; }
.fail { background-color: #fdd; }
.success { background-color: #dfd; }
</style>
</head>
<body>
<h1>Patch Compliance Dashboard</h1>
<h2>Summary</h2>
<table>
<tr><th>Group</th><th>Success</th><th>Failure</th></tr>
{% for row in summary %}
<tr>
<td>{{ row.group }}</td>
<td>{{ row.success }}</td>
<td>{{ row.fail }}</td>
</tr>
{% endfor %}
</table>
<h2>Detailed Results</h2>
<table>
<tr><th>Minion</th><th>Date</th><th>Environment</th><th>Region</th><th>Result</th></tr>
{% for row in results %}
<tr class="{{ row.result }}">
<td>{{ row.minion }}</td>
<td>{{ row.date }}</td>
<td>{{ row.environment }}</td>
<td>{{ row.region }}</td>
<td>{{ row.result }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
pip install flask
python app.py
Then visit http://localhost:5000
or your server’s IP at port 5000.
If you use Elasticsearch, Splunk, or Mezmo:
Use a returner like es_return
, splunk_return
, or send via custom script using REST API.
Normalize fields: hostname, env, os, patch time, result
Filter dashboards by compliance groupings
Component | Purpose | Tool |
---|---|---|
JSON/DB logging | Track patch status | Returners |
Post-processing script | Normalize data for business | Python |
CSV/Excel export | Shareable report format | Python |
HTML dashboard | Visualize trends/compliance | Flask, Chart.js, Bootstrap |
Email automation | Notify stakeholders |
|
SIEM/Splunk integration | Enterprise log ingestion | REST API or native returners |
An interesting development came from Microsoft as it released a new terminal-based editor with open source license.
I kind of liked it at first glance until I tried my hands on a shell script written in this editor and then I ran into:
The issue is that it added the classic Windows-style line endings, which is not liked by UNIX-like systems.
I knew it was too good to be true to have something perfect for Linux from Microsoft 🤦
Here are the highlights of this edition :
Harness the power of machine learning to create digital agents and more with hot courses like Learning LangChain, The Developer's Playbook for Large Language Model Security, Designing Large Language Model Applications, and more.
Part of the purchase goes to Code for America! Check out the ebook bundle here.
No blog entries yet
Terms of Use Privacy Policy Guidelines We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.