Index by Tag
Filter blog posts by topic
Filter blog posts by topic
(in about 549 words)
posted on 04/10/2026 By Ofir Yaron
If you work on many projects, you know the pattern: a project gets your full attention for a few weeks, then sits untouched for months until a client comes back with a new request. Meanwhile, its node_modules folder sits on your disk taking up hundreds of megabytes — and so does the one next to it, and the one next to that.
Every few months I'd notice my disk filling up and go hunting through my projects folder, checking sizes and deleting node_modules by hand. It's not hard, it's just tedious. So eventually I wrote a small script to do it for me.
nmclean finds every node_modules folder under a directory, shows how much space each one takes, and lets you delete them all at once or decide one by one:
$ nmclean ~/projects
Searching for node_modules in /Users/me/projects ...
1) 648M /Users/me/projects/api/node_modules
2) 580M /Users/me/projects/web/frontend/node_modules
3) 92M /Users/me/projects/old-demo/node_modules
Found 3 folders, 1.3G total.
[a] delete all [o] one by one [q] quit: o
(1/3) 648M /Users/me/projects/api/node_modules delete? [y/n/q]: n
Skipped.
(2/3) 580M /Users/me/projects/web/frontend/node_modules delete? [y/n/q]: y
Deleted.
(3/3) 92M /Users/me/projects/old-demo/node_modules delete? [y/n/q]: y
Deleted.
Freed 672M.Nothing is lost — the next time you open one of those projects, npm install brings everything back.
node_modules, skips nested ones, and asks before deleting anything.
At first this lived in my own scripts folder. Then I started moving some of my work to another machine at home, and as a freelancer I sometimes work on a machine provided by a client. Copying scripts around by hand got old fast, so I pushed it to a public repo. Now installing it anywhere is one line:
curl -fsSL https://raw.githubusercontent.com/OfirYaron/nmclean/main/install.sh | bashThis installs it to ~/.local/bin/nmclean. If you prefer to read the code first (you should!), clone the repo and run ./install.sh instead.
nmclean # search the current directory
nmclean ~/projects # search a specific directory
nmclean -n # dry run: list sizes, delete nothing
nmclean -y # delete everything found without askingIf your projects always live in one place, set it as the default in your ~/.zshrc or ~/.bashrc:
export NMCLEAN_ROOT="$HOME/projects"Point it at your projects folder, not your entire home directory. Your home directory also holds node_modules folders that aren't project dependencies — globally installed npm packages (under ~/.nvm, for example) and editor extensions. Start with a dry run (-n) to see what it finds before deleting anything.
nmclean is the first in what I plan to be a small collection of utilities I use day to day. If you have an idea for an improvement or a feature you'd like to see, open an issue or a pull request — I'd love for it to grow beyond my own needs.
Your NAS is already running 24/7, has redundant storage, and is significantly more reliable than a Raspberry Pi. There's no reason to run a dedicated device for Home Assistant when a Docker container on your NAS can do the same job — and restart automatically after power cuts, updates, or reboots without any intervention from you.
This guide shows how to deploy it in a way that is correct, minimal, and easy to recover.
Home Assistant stores all your automations, integrations, and device pairings in its /config directory. This must live on your NAS, outside the container, so it survives container updates and replacements.
Via SSH:
mkdir -p /volume1/docker/homeassistant/configOr create the same path using Synology File Station.
This project uses Docker Compose files committed to Git. The Home Assistant stack lives in this folder.
If you are deploying via Portainer:
home-assistantdocker-compose.yaml from this folder, or paste its contents into the web editorIf you are deploying directly from the NAS via SSH:
cd /path/to/this/repo/home-assistant
docker compose up -dservices:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
hostname: homeassistant
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8123/"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
network_mode: host
volumes:
- /volume1/docker/homeassistant/config:/config:rw
environment:
TZ: Asia/Jerusalem
restart: unless-stoppednetwork_mode: host
Home Assistant uses mDNS and Zeroconf to auto-discover devices (smart bulbs, Chromecasts, Apple TV, etc.) on your local network. These protocols do not cross Docker's default bridge network. Host networking is the correct and supported solution, not a workaround.
restart: unless-stopped
The container restarts automatically after the NAS reboots or if the process crashes — but it stays stopped if you deliberately stop it. Using always instead would restart the container even after an intentional docker stop, which is the wrong behaviour.
TZ: Asia/Jerusalem
Setting the timezone via an environment variable is portable across all NAS platforms. The older pattern of bind-mounting /etc/localtime from the host can break on non-Synology systems or when DST changes are handled differently by the host OS.
security_opt: no-new-privileges:true
Prevents any process inside the container from gaining more privileges through setuid/setgid binaries. Correct default for any container that doesn't explicitly need privilege escalation.
No privileged: true
The original tutorial enables privileged: true unconditionally. This grants the container full root access to the host kernel — effectively the same as running without any container isolation. Home Assistant does not need this for normal operation. If you have a USB Zigbee or Z-Wave stick, use a devices: entry (commented out in the compose file) to expose only that specific device.
Once the container is running and the healthcheck passes:
http://<NAS-IP>:8123Follow the onboarding flow to create your owner account, set your location, and connect your first devices.
Because your config lives at /volume1/docker/homeassistant/config and your compose definition is committed here in Git, recovery after a disk swap or NAS replacement is:
# On the new NAS, after restoring /volume1/docker/homeassistant/config from backup:
git clone <this-repo>
cd Nas-Config/home-assistant
docker compose up -dThat's it. All automations, device pairings, and integrations are in the config directory. Nothing is stored inside the container.
Uncomment and adjust the devices block in docker-compose.yaml:
devices:
- /dev/ttyUSB0:/dev/ttyUSB0To find the correct device path on your Synology:
ls /dev/tty*Plug the stick in, run the command again, and the new entry is your device. No privileged: true required.
If you access Home Assistant via a reverse proxy (e.g. Caddy routing homeassistant.yourdomain.com to localhost:8123), Home Assistant will reject the requests by default. There is no environment variable to override this — it must be configured in configuration.yaml.
Add the following to /volume1/docker/homeassistant/config/configuration.yaml:
http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.1use_x_forwarded_for: true
Tells Home Assistant to read the real client IP from the X-Forwarded-For header set by the proxy, rather than treating all requests as coming from the proxy itself.
trusted_proxies
The list of IPs Home Assistant will accept proxy headers from. Since Caddy runs on the same host with network_mode: host, 127.0.0.1 is sufficient. If your reverse proxy runs in a Docker bridge network instead, also add its subnet (e.g. 172.17.0.0/16).
After saving, restart the container:
docker restart homeassistant(in about 662 words)
posted on 06/04/2026 By Ofir Yaron
As an Osek Murshe (עוסק מורשה) in Israel, getting paid is a double-edged sword. You see a large sum enter your bank account, but you know deep down that a significant chunk of it doesn't belong to you. It belongs to the Tax Authority (Mas Hachnasa) and the VAT office (Ma'am).
The most common mistake? Treating the Total Invoice as your actual income. By the time the 15th or 23rd of the month rolls around, you've spent the money you were supposed to "put aside."
If you have an iPhone, Mac, or iPad, you can solve this forever with a single tap. Here is how to build your own Israeli Tax Splitter using Apple Shortcuts.
Since an Osek Murshe invoice includes 18% VAT, we have to "strip" that VAT first to find your actual income before calculating your 30% income tax.
Ask for Input (Set type to Number).Calculate → Provided Input ÷ 1.18.Set Variable → Name it BaseIncome.Calculate → Provided Input − BaseIncome.Set Variable → Name it VATAmount.Calculate → BaseIncome × 0.30.Set Variable → Name it IncomeTax.
Israeli tax dates are specific: Income Tax is usually due the 21st of the next month, and Ma'am is due the 23rd of the next Odd month. (In some cases Tax is due every Odd month as well — you may change accordingly.)
For the Income Tax (The 21st):
Adjust Date → Add 1 month to Current Date.Get Dates from Input → Tap the variable, choose Date Format: Custom, and type MM/21/yyyy.Get Time Between Dates → Calculate days from Current Date to this new date.Set Variable → DaysUntilTax.For the VAT (The Odd-Month 23rd):
Format Date → Use Custom format M (to get the month number).Calculate → Formatted Date mod 2.If statement:
Adjust Date (Add 1 month to current date).Adjust Date (Add 2 months).Get Dates from Input → Set format to MM/23/yyyy.Get Time Between Dates → Find the remaining days and Set Variable to DaysUntilVAT.
Finally, add a Show Result action to display your "Clean" numbers:
💰 Osek Murshe Breakdown
Net Profit: ₪[BaseIncome]
💸 Put Aside Now:
VAT (18%): ₪[VATAmount]
Income Tax (30%): ₪[IncomeTax]
📅 Deadlines:
Income Tax: Due in [DaysUntilTax] days.
VAT (Ma'am): Due in [DaysUntilVAT] days.The next time a client pays you, don't just smile at the bank notification — run the shortcut, move the "Put Aside" amount to a separate savings sub-account, and sleep better at night knowing you are 100% tax-ready.
Add this shortcut as a Widget on your iPhone Home Screen. The next time a client pays you, don't just smile at the bank notification — run the shortcut, move the "Put Aside" amount to a separate savings sub-account, and sleep better at night knowing you are 100% tax-ready.
Grab my shortcut here if you want a head start! But if you have the time, I'd suggest building it from scratch. It's a great way to learn how everything fits together, and it'll make you much more proficient at creating your own tools later on.
In post, I will explore the lifecycle of Docker and explain how to use it correctly when building a Dockerfile. Docker provides a powerful and efficient way to package, distribute, and run applications in isolated environments called containers. Let's dive in!
The build phase is the first step in the Docker lifecycle. During this phase, a Docker image is created from a Dockerfile. The Dockerfile contains a set of instructions that define the steps to build the image. These instructions can include pulling base images, executing commands, copying files, and configuring the image. Docker reads the Dockerfile and builds a layered image based on the specified instructions. Each instruction in the Dockerfile creates a new layer, allowing for efficient image sharing and faster builds when changes are made.
After a successful build, an image is created. An image serves as a static, read-only template that contains all the necessary instructions for creating a container. It includes the application or service code, the runtime environment, required libraries, and dependencies needed to run the application. Images are portable and can be shared across different environments and systems, ensuring consistent execution.
A container is an instance of an image that runs as a separate process. Containers provide isolation and encapsulation, allowing applications to run consistently across different environments. They can be started, stopped, paused, and deleted. When you start a container from an image, Docker creates a new container instance based on the specified image, runs it as a process, and provides an isolated runtime environment. Containers have their file systems, network interfaces, and process trees, making them lightweight and independent units of deployment.
To start a container from an image, you can use the docker run command. This command creates a new container instance based on the specified image and runs it as a process. It also provides options to configure the container's runtime behavior, such as network settings, environment variables, port mappings, and resource constraints. Running a container allows you to interact with the application or service it contains.
Containers can exist in different states throughout their lifecycle. Let's explore these states:
Docker provides a set of commands to manage containers and images:
Manage Containers: You can use commands like docker start, docker stop, docker pause, docker unpause, docker restart, and docker rm to manage containers. These commands allow you to start, stop, pause, resume, restart, and remove containers as needed.
Manage Images: Docker images can be managed using commands such as docker build, docker pull, docker push, docker tag, and docker rmi. These commands enable you to build new images, pull existing images from a registry, push images to a registry, tag images with versions or labels, and remove images when they are no longer needed.
When writing a Dockerfile, you have access to various instructions that help define the image's build process and runtime behavior. Let's take a look at some commonly used instructions and their purposes:
Throughout the lifecycle, you can make changes to the Dockerfile, rebuild the image, and recreate or update containers based on the updated image. This flexibility allows for easy iteration and maintenance of your application or service.
Remember, containers are designed to be disposable, as such it's important understand the lifecycle and use Dockerfile installation routine correctly so the important data is not part of the image creating and is able to be persisted per container.
As a developer or DevOps engineer, you're likely familiar with those long and repetitive commands you use frequently in your terminal. They can be time-consuming and prone to typos, which hampers your productivity. But fear not! There's a solution: aliases.
In this article, we'll explore how aliases can speed up your workflow, making your life easier and more efficient.
Why Aliases? Aliases allow you to create custom shortcuts for commonly used commands, making them shorter and easier to remember. With aliases, you can eliminate the need to type lengthy commands repeatedly, saving you time and reducing the chances of errors. They offer flexibility and convenience, enabling you to streamline your day-to-day tasks and improve your productivity.
To create an alias, you'll need to edit your shell configuration file, typically ~/.bash_profile, ~/.bashrc or ~/.zshrc. This file contains settings and configurations for your terminal session. Let's say you have a long command you frequently use:
longcommand --option1 value1 --option2 value2 argumentYou can create an alias for it using the following steps:
alias shortcommand='longcommand --option1 value1 --option2 value2 argument'(Replace shortcommand with the desired name for your alias.)
source ~/.bash_profileWhat if you want your alias to accept dynamic parameters? Fortunately, aliases can be defined as functions, allowing you to pass arguments. Let's consider the previous example with parameters:
mycommand() {
longcommand --option1 "$1" --option2 "$2"
}In this example, mycommand is the alias name, defined as a function that accepts two parameters ("$1" and "$2"").
source ~/.bash_profileNow, you can use the alias with parameters just like any other command:
mycommand value1 value2By leveraging aliases and command shortcuts, you can significantly enhance your workstation and optimize your day-to-day tasks. They provide a simple, yet powerful way to reduce typing, eliminate errors, and save time. Whether you're a developer or a DevOps engineer, incorporating aliases into your workflow will boost your productivity and improve your overall development experience.
So, go ahead and start creating aliases for your frequently used commands. Tailor them to your specific needs and watch your efficiency soar.
Professional Tip, you can also host and share them using a public repository on GitHub, that way you can download and install them on any machine you work on.
Amazon Elastic File System (EFS) is a scalable, highly available, and secure file system designed to provide shared access to data across multiple instances in the cloud. In this tutorial, I'll show you how to create a new EFS drive and all its required assets using a bash script so that it can be connected to a predefined EC2 machine.
Prerequisites:
First, we'll set the variables we need to run the script. Replace REGION, EC2_NAME, and EFS_NAME with your desired values.
REGION=eu-central-1
EC2_NAME=MY_MACHINE
EFS_NAME=my_shared_efs_driveUsing the AWS CLI, we'll get the EC2 instance ID by EC2 instance name:
EC2ID=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$EC2_NAME" \
--query 'Reservations[*].Instances[*].{ID:InstanceId}' \
--output text \
--region $REGION)
echo EC2 instance ID: $EC2IDNext, we'll get the VPC and subnet IDs for the EC2 instance:
VPCID=$(aws ec2 describe-instances --instance-ids $EC2ID \
--query 'Reservations[*].Instances[*].{VPC:VpcId}' \
--output text \
--region $REGION)
echo VPC ID: $VPCID
SUBNETID=$(aws ec2 describe-instances --instance-ids $EC2ID \
--query 'Reservations[*].Instances[*].{Subnet:SubnetId}' \
--output text \
--region $REGION)
echo Subnet ID: $SUBNETIDWe'll create two security groups - one for the EC2 instance and one for the mount target - and allow ingress traffic for each:
EC2_SGID=$(aws ec2 create-security-group \
--region $REGION \
--group-name efs-ec2-sg \
--description "Amazon EFS SG for EC2 instance" \
--vpc-id $VPCID \
--query 'GroupId' \
--output text)
echo EC2 SG ID: $EC2_SGID
MT_SGID=$(aws ec2 create-security-group \
--region $REGION \
--group-name efs-mt-sg \
--description "Amazon EFS SG for mount target" \
--vpc-id $VPCID \
--query 'GroupId' \
--output text)
echo Mount Target SG ID: $MT_SGID
aws ec2 authorize-security-group-ingress \
--group-id $EC2_SGID \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0 \
--region $REGION
aws ec2 authorize-security-group-ingress \
--group-id $MT_SGID \
--protocol tcp \
--port 2049 \
--source-group $EC2_SGID \
--region $REGIONNow we can create the EFS drive using the following command:
FSID=$(aws efs create-file-system \
--encrypted \
--creation-token $EFS_NAME \
--tags Key=Name,Value=$EFS_NAME \
--regionA friend of mine recently bought a new MacBook Pro and wants to dive into the world of development. He asked me to recommend some development essentials, so in the hope of helping others in the same position, let's get started.
First of all, congratulations on joining the elite club of Mac users! You now have a fancy new machine, but it's time to put it to good use beyond binge-watching Netflix. Why not try your hand at development? Before you begin, let's discuss some essential tools that will help you become the coolest kid on the block (or at least the most productive one).
As a developer, having the right tools at your disposal can significantly improve your productivity and workflow. From code editors and version control systems to command-line utilities and productivity apps, there are many tools that can help you work faster and more efficiently.
In this post, I'll cover some of the essential developer tools for Mac users, including both well-known and lesser-known tools that can make your life as a developer easier. Whether you're a beginner or an experienced developer, this roundup is sure to include something useful for you.
One of the most important tools for any developer is a code editor. Mac users have several great options to choose from, including:
Version control systems are essential for managing changes to your code and collaborating with others. Some popular options for Mac users include:
Package managers make it easy to install and manage software packages and libraries. Some popular options for Mac users include:
Collaboration tools are essential for working with others on software development projects. Some popular options for Mac users include:
Finally, productivity tools can help you manage your time and stay focused on your work. Some popular options for Mac users include:
In conclusion, these are just a few of the essential developer tools for Mac users that can help you work more efficiently and effectively. Whether you're a beginner or an experienced developer, using the right tools can make a big difference in your productivity and workflow. So take some time to explore these tools and find the ones that work best for you!
By mastering commonly used Bash commands and exploring more advanced ones, you can become a more efficient and effective user of the command line. By automating repetitive tasks with scripts, you can reduce the risk of mistakes and free up your time to focus on more productive work. In this post, we'll cover a range of useful Bash commands and show you how to use them to streamline your workflow and automate tasks.
find . -name "**.txt"grep "hello" **.txtsed 's/foo/bar/g' myfile.txtawk -F ',' '{print $2}' myfile.csvPipes (|) are used to redirect the output of one command as input to another command. This allows you to chain together commands to perform more complex operations. Here are a few examples:
To find all files with a .txt extension and count the number of lines in each file, you can use:
find . -name "*.txt" | xargs wc -lThe find command searches for all .txt files, and the output is piped to xargs, which passes each file as an argument to the wc -l command to count the number of lines. To search for a specific string in all .txt files in the current directory and display the matching lines, you can use:
grep "search string" *.txt | awk '{print "File: " $2 " - " $0}'The grep command searches for the specified string in all .txt files, and the output is piped to awk, which formats the output to display the matching lines with the corresponding file name. Pipes are a powerful tool for combining and manipulating data in the command line, and mastering them can greatly improve your productivity as a Bash user.
Get Local IP
curl ifconfig.meGet IP from terminal:
wget -qO- https://ipecho.net/plain ; echoGet response from EKS pod by it’s IP:
wget -qO- http://10.1.1.249:8080/ ; echoHandy git pull:
find . -type d -depth 1 -exec git --git-dir={}/.git --work-tree=$PWD/{} pull \;This bash command uses the find command to search for directories (-type d) in the current directory (.), at a depth of 1 (-depth 1). For each directory found, it runs the git pull command in that directory (-exec git --git-dir={}/.git --work-tree=$PWD/{} pull ;).
Here's a breakdown of what each part of the command does:
In summary, this command searches for directories in the current directory that contain a Git repository, and runs git pull in each repository to fetch and merge any changes from the remote repository.
A Terraform module is a reusable piece of Terraform code that encapsulates a set of related resources and their configuration. Modules can be used to create, modify, and delete resources as a single unit, making it easy to manage complex infrastructure components. Modules can also be shared across different projects, making it easy to reuse code and standardize resource configurations across different environments.
A module consists of a directory containing one or more Terraform configuration files. The directory can also contain other files, such as variables, outputs, and scripts. A module can be published to a public or private registry or stored locally.
Here is an example of how to create a Terraform module that creates an Amazon Elastic Container Service (ECS) cluster in an AWS infrastructure as code project:
provider "aws" {
region = var.region
}
resource "aws_ecs_cluster" "cluster" {
name = var.name
}
variable "region" {
type = string
default = "us-west-2"
}
variable "name" {
type = string
default = "my-cluster"
}In the example above, we have created a module that creates an ECS cluster using the AWS provider. We have defined two variables (region and name) that can be customized when the module is used. The module consists of one resource (aws_ecs_cluster) that creates an ECS cluster with the specified name.
Using Terraform Modules
Using a Terraform module in an AWS infrastructure as code project involves the following steps:
Declare the module: In your main Terraform configuration file, you need to declare the module and provide its source location. You can declare the module using the module block and specify the source location using the source attribute.
provider "aws" {
region = "us-west-2"
}
module "ecs-cluster" {
source = "./module/ec2-cluster"
name = "my-cluster"
}
Customize the variables
You can customize the variables defined in the module by specifying their values in the module block.
provider "aws" {
region = "us-west-2"
}
module "ecs-cluster" {
source = "./module/ec2-cluster"
name = "my-cluster"
region = "us-east-1"
}
Use the module resources
Once the module has been declared and customized, you can use its resources in your main configuration file by referencing them using their output values.
provider "aws" {
region = "us-west-2"
}
module "ecs-cluster" {
source = "./module/ec2-cluster"
name = "my-cluster"
region = "us-east-
}Tampermonkey is a very popular userscript manager, it makes it very easy to create scripted automations, manage your userscripts and it's available for Chrome, Microsoft Edge, Safari, Opera Next, and Firefox.
personally I use it mostly to automate my repetitive online actions, but I did use it as a developer to script a webpage funnel and have it run with the click of a button instead of do going through the website funnel manually every time just to check a mini feature at the end of the funnel.
To install [Tampermonkey](https://www.tampermonkey.net/)
since Tampermonkey script use Vanila javascript, over time I've found my self recreating these basic utility function that can come handy:
const mouseEvent = new Event('dblclick', {
'view': window,
'bubbles': true,
'cancelable': true
});Changing an input value that also handles React states:
function dispatchValueChange(input, newValue){
let lastValue = input.value;
input.value = newValue;
let event = new Event('input', { bubbles: true });
event.simulated = true;
let tracker = input._valueTracker;
if (tracker) {
tracker.setValue(lastValue);
}
input.dispatchEvent(event);
}
function dispatchValueChangeById(elementId, newValue){
const element = document.getElementById(elementId);
dispatchValueChange(element, newValue);
}Wait functions to have a proper wait when changing rendered page:
function waitForElementById(elementId) {
return new Promise((resolve) => {
const checkExist = setInterval(function () {
if (document.getElementById(elementId)) {
clearInterval(checkExist);
resolve();
}
}, 300);
});
}
function waitForElementBySelector(elementSelector) {
return new Promise((resolve) => {
const checkExist = setInterval(function () {
if (document.querySelectorAll(elementSelector).length) {
clearInterval(checkExist);
resolve();
}
}, 300);
});
}The component content will be based on Markdown files to make it natural for a developer to maintain.
the markdown files will be loaded and styled, added with a Copy-to-Clipboard button and a better code area styling necessary for a dev blog.
{
label: 'Blog',
path: '/blog',
}Load the markdown using markdown-to-jsx Library
Extract hidden fields from the Markdown