Your SSH Port Change Was Ignored: systemd Socket Activation on Ubuntu

TL;DR — On Ubuntu 22.10 and later, sshd is started by systemd socket activation. The listening port belongs to ssh.socket, not to /etc/ssh/sshd_config. Editing Port in sshd_config is silently ignored. Fix it with a ssh.socket drop-in.

The Symptom

You uncomment Port yourNumber in /etc/ssh/sshd_config, restart the service, and nothing changes. What makes this especially confusing is that sshd itself insists the config was read correctly:

$ sudo sshd -T | grep -i "^port"
port yourNumber

$ sudo ss -tlnp | grep sshd
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=174486,fd=3),("systemd",pid=1,fd=202))
LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=174486,fd=4),("systemd",pid=1,fd=203))

The config says yourNumber. The kernel says 22. The giveaway is in that last column: the listening file descriptor is held by systemd (pid=1), not by sshd.

Root Cause

Ubuntu even documents this in the shipped config file, though it is easy to scroll past:

# Port and ListenAddress options are not used when sshd is socket-activated,
# which is now the default in Ubuntu.

Diagnosis in Three Commands

systemctl is-enabled ssh.socket
sudo ss -tlnp | grep sshd
sudo sshd -T | grep -i "^port"
ObservationDiagnosis
enabled + fd owned by systemdSocket activation. The port lives in ssh.socket.
sshd -T shows the new port, ss shows the old oneSame thing — config parsed, then ignored.
sshd -T still shows port 22Your edit was never read. Check sshd_config.d/.
Unit ssh.socket does not existClassic mode. sshd_config is authoritative.

The Fix: A ssh.socket Drop-In

Keep socket activation and put the port where it actually belongs. Use systemctl edit rather than writing under /lib/systemd/system/, so your change survives package upgrades.

sudo systemctl edit ssh.socket

Add:

[Socket]
ListenStream=
ListenStream=0.0.0.0:yourNumber
ListenStream=[::]:yourNumber
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -tlnp | grep ":yourNumber"

The empty ListenStream= line is mandatory. systemd list directives are additive: without the empty assignment to reset the list, you append yourNumber to the inherited 22 and keep listening on both. Omitting it produces no warning and no error — just a config that looks right and does nothing you wanted.

Alternative: Turn Socket Activation Off

If you would rather have sshd_config be the single source of truth — which you need anyway for ListenAddress or Match blocks — revert to the traditional daemon:

sudo systemctl disable --now ssh.socket
sudo systemctl enable ssh.service
sudo systemctl daemon-reload
sudo systemctl restart ssh.service

If port 22 is still held afterwards, a leftover drop-in is re-enabling socket behaviour. Move it aside and reload:

ls -l /etc/systemd/system/ssh.service.d/
sudo mv /etc/systemd/system/ssh.service.d/00-socket.conf /root/
sudo systemctl daemon-reload && sudo systemctl restart ssh.service

Which Approach?

Drop-in on ssh.socketDisable socket activation
Where the port is configuredsystemd unitsshd_config
ListenAddress / Match honouredNoYes
Memory footprintOn demandResident (a few MB)
Survives apt upgradeYesYes
Chance of confusing you again in six monthsHigherLower

Don’t Lock Yourself Out

sudo ufw allow yourNumber/tcp
# from a SECOND terminal, with the original session still open:
ssh -p yourNumber user@host
# only once that succeeds:
sudo ufw delete allow 22/tcp
ItemNote
Existing sessionKeep the current SSH window open until the new port is verified.
Out-of-band accessConfirm you can reach a serial/VNC console before restarting.
Cloud firewallSecurity groups on AWS/Azure/GCP are a separate allow-list from ufw.
Stale port 22If it lingers after disable --now, a reboot releases it for good.
Never edit/lib/systemd/system/ssh.socket — overwritten on package update.
Rollbacksudo systemctl revert ssh.socket && sudo systemctl restart ssh.socket

Takeaway

sshd -T tells you what sshd believes. ss -tlnp tells you what the kernel is doing. When the two disagree, look at who owns the file descriptor — that column is where the real answer is. Under socket activation, systemd owns the socket and sshd is merely handed the result.

Transcribe Audio to Text with Microsoft 365 Word Online

This method works not only for English, but also for Japanese and other languages. Here are the steps for using the Transcribe feature in Word for the web (Word Online).

Steps

  1. Sign in to the official Microsoft 365 site and open Word for the web (Word Online).
  2. Create a new blank document.
  3. Click the Home tab in the top menu.
  4. On the right side of the Home tab, click the down arrow next to the Dictate button and select Transcribe.
    • If Transcribe does not appear, the feature may not be enabled for your account, or it may be limited by your version.
  5. A transcription sidebar opens on the right. Click Upload audio and select the .m4a file you want to transcribe.
  6. Once the upload finishes, transcription starts automatically. The resulting text appears in the sidebar when it is done.
  7. You can then click part or all of the transcript to insert it into the document, or simply copy and paste it.

Key points

  • Supports multiple languages, including English and Japanese, with good accuracy.
  • Handy for turning personal recordings or meeting audio into text.
  • The uploaded audio and the transcript are temporarily stored in the cloud, so be careful with confidential material.

Build an AI-Native 3D Modeling Environment on Ubuntu 24 with Claude Code + Blender MCP


Why This Workflow?

Traditional Blender tutorials usually teach you to:

  • Learn keyboard shortcuts
  • Learn polygon modeling
  • Learn UV mapping
  • Learn materials
  • Learn rendering
  • Learn animation

It often takes weeks or even months before creating a usable game asset.

With Claude Code and Model Context Protocol (MCP), the workflow changes dramatically.

Instead of manually modeling everything, you simply describe what you want:

Natural Language
Claude Code
Blender MCP
Blender creates the model

Your role shifts from being a modeler to becoming a designer and reviewer.


Final Workflow

After completing this guide, your pipeline will look like this:

Claude Code
Blender MCP
Blender
├── game.glb → Godot
└── model.stl → 3D Printing

The same asset can later be used both inside a game and as a printable model.


Development Environment

This article uses the following environment:

SoftwareVersion
Ubuntu24.04
Blender5.x
Claude CodeLatest
uvLatest
Blender MCPLatest

Installing Blender

Instead of using the Ubuntu package repository, download Blender directly from the official website https://www.blender.org

Extract it into a fixed location.

sudo mkdir -p /opt/blender
sudo tar -xJf blender-5.x-linux-x64.tar.xz \
-C /opt/blender \
--strip-components=1

Run Blender:

/opt/blender/blender

If Blender launches successfully, the installation is complete.


Create a Desktop Launcher (KDE)

Create:

/usr/share/applications/blender.desktop

Example:

[Desktop Entry]
Version=1.0
Type=Application
Name=Blender
Exec=/opt/blender/blender %F
Icon=/opt/blender/blender.svg
Terminal=false
Categories=Graphics;

After saving the file, Blender will appear in the KDE launcher and can be pinned to the desktop or taskbar.


Install Claude Code

Verify Claude Code is installed:

claude --version

Example:

2.x.x

Install uv

Verify:

uv --version

Example:

uv 0.11.x

Download Blender MCP

Create a workspace:

mkdir -p ~/dev/mcp
cd ~/dev/mcp
git clone https://github.com/ahujasid/blender-mcp.git

Install the Blender Add-on

Open Blender.

Navigate to:

Edit
Preferences
Add-ons
Install from Disk

Select:

addon.py

Enable the add-on.

Press N inside the 3D Viewport.

You should now see:

BlenderMCP

This confirms that the Blender side has been configured correctly.


Configure Claude Code

Navigate to your project directory.

cd ~/Projects/MyGame

Register the MCP server:

claude mcp add blender uvx blender-mcp

Verify:

claude mcp list

Expected output:

blender
✓ connected

This means Claude Code can now communicate with Blender.


Verify the Connection

First, ask Claude what Blender tools are available:

What Blender MCP tools are available?

Claude should list tools such as:

  • Scene Information
  • Materials
  • Viewport Screenshot
  • PolyHaven
  • Sketchfab
  • Hyper3D Rodin
  • Hunyuan3D

Then ask Claude to inspect the current scene:

Read the current Blender scene.

Typical response:

Camera
Cube
Light

At this point, Claude is communicating with Blender successfully.


Generate Your First AI Model

Now simply ask Claude:

Create a low-poly wooden crate.
Requirements:
- Game Ready
- Low Poly
- Wood Material
- Centered at World Origin
- Suitable for Godot

Claude will:

  1. Control Blender through MCP
  2. Create the mesh
  3. Create materials
  4. Update the Blender scene automatically

No manual modeling is required.


Create an AGENTS.md

One of the biggest productivity improvements is creating an AGENTS.md file inside your project.

Instead of repeating requirements every time, define them once.

Example:

  • Godot 4 project
  • Export GLB
  • Low Poly
  • Triangle count under 500
  • Bottom-center pivot
  • Apply all transforms
  • Save .blend
  • Export .glb

Once configured, a prompt like:

Create a pine tree.

is enough for Claude to generate assets following your project standards automatically.


Result

After completing the setup, your workflow becomes:

Describe what you want
Claude Code
Blender MCP
Blender automatically creates the model

Instead of spending months learning traditional modeling, you can focus on:

  • Game design
  • Gameplay
  • Asset planning
  • Iteration

while AI handles most of the repetitive work.


What’s Next?

The next logical step is integrating Godot into the workflow:

Claude Code
Blender MCP
Godot
AI-generated GDScript
Playable Game Prototype

With that setup, you’ll have a complete AI-assisted game development pipeline running entirely on Ubuntu.

Building Unreal Engine 5.8 from Source on Ubuntu 24.04

A step-by-step guide to building Unreal Engine 5.8 from source on Ubuntu 24.04. This article focuses on environment setup only and does not cover runtime issues such as Vulkan rendering, GPU memory, or project-specific troubleshooting.


Introduction

Unlike Windows, Unreal Engine on Linux is typically built directly from source because Epic Games Launcher is not available.

This article documents the complete environment setup process, including:

  • Obtaining Unreal Engine source access
  • Downloading the source code
  • Installing dependencies
  • Building the engine
  • Configuring the NVIDIA driver
  • Launching Unreal Editor

This guide intentionally excludes runtime troubleshooting, which will be covered separately.


Prerequisites

Before starting, make sure you have:

  • Ubuntu 24.04 LTS
  • Git
  • A GitHub account
  • An Epic Games account

You must link your GitHub account to your Epic Games account.

After accepting Epic’s GitHub organization invitation, you’ll gain access to the Unreal Engine repository.


1. Clone Unreal Engine

Create a workspace:

mkdir -p ~/projects_ue5
cd ~/projects_ue5

Clone the engine:

git clone https://github.com/EpicGames/UnrealEngine.git

The initial clone requires tens of gigabytes of disk space, so ensure sufficient free storage before starting.


2. Download Dependencies

Enter the source directory:

cd UnrealEngine

Run:

./Setup.sh

This script automatically downloads:

  • Third-party libraries
  • Toolchains
  • ShaderCompileWorker dependencies
  • Platform SDKs

During execution, you may notice references to distributions such as Rocky Linux.

This is completely normal.

The script downloads dependencies for multiple supported Linux environments, even when running on Ubuntu.


3. Generate Project Files

Run:

./GenerateProjectFiles.sh

During the first execution, Unreal Engine automatically downloads and configures the required .NET SDK.

If the output reports that the Android SDK is missing, it can safely be ignored unless Android development is required.


4. Build Unreal Engine

The simplest build command is:

make -j$(nproc)

Alternatively, Unreal provides its own build script:

Engine/Build/BatchFiles/Linux/Build.sh UnrealEditor Linux Development

The initial build can take anywhere from tens of minutes to several hours depending on your hardware.

After a successful build, the editor executable is located at:

Engine/Binaries/Linux/UnrealEditor

5. Launch Unreal Editor

Launch the editor:

Engine/Binaries/Linux/UnrealEditor

Or open a specific project:

Engine/Binaries/Linux/UnrealEditor MyProject.uproject

The first launch usually takes significantly longer than subsequent launches because Unreal Engine needs to perform several initialization tasks, including:

  • Starting ShaderCompileWorker
  • Compiling shaders
  • Initializing the Derived Data Cache (DDC)

This behavior is expected.


6. NVIDIA Driver Considerations

When launching Unreal Engine, you may encounter a warning similar to:

Installed Driver: xxx.xx
Recommended Driver: xxx.xx

Avoid upgrading the driver immediately.

Instead, check the Unreal Editor log first.

If the log explicitly reports missing Vulkan extensions, then upgrading the NVIDIA driver is appropriate.

After upgrading, verify the installation using:

nvidia-smi

It is also recommended to verify the Vulkan runtime:

vulkaninfo

Ensure that Vulkan initializes correctly before attempting to launch Unreal Engine again.


7. Verify the Environment

After completing the previous steps, create a new Unreal Engine project.

For the initial verification, it is recommended to use:

  • Games
  • Blank
  • Blueprint

This provides the simplest project configuration for confirming that the development environment has been built successfully.

Once the editor opens correctly, the environment setup is complete.


Useful Commands

Check the NVIDIA driver

nvidia-smi

Check the Vulkan runtime

vulkaninfo

Find large directories

sudo du -xh --max-depth=1 /

Conclusion

Building Unreal Engine 5.8 on Ubuntu mainly consists of the following steps:

  1. Obtain Epic Games source access
  2. Clone the Unreal Engine repository
  3. Run Setup.sh
  4. Run GenerateProjectFiles.sh
  5. Build the engine
  6. Verify or update the NVIDIA driver if necessary
  7. Launch Unreal Editor and create a test project

Although Linux requires source compilation, the overall workflow is straightforward once the environment has been configured correctly.

This article focuses solely on setting up the development environment. Runtime topics such as Vulkan configuration, rendering features, GPU memory management, and Linux-specific troubleshooting will be covered in separate articles.

Ubuntu SSH Port Configuration: Switch Between ssh.service and ssh.socket

Option 1: Restore Traditional ssh.service Mode

This approach is the easiest to understand and is suitable if you previously managed the SSH port through /etc/ssh/sshd_config.

Disable Socket Activation and Enable SSH Service

sudo systemctl disable --now ssh.socket
sudo systemctl enable --now ssh.service

Check Current SSH Port Configuration

grep -n '^Port' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/* 2>/dev/null

If no custom port is configured, add the following line to /etc/ssh/sshd_config:

Port <CUSTOM_PORT>

Restart SSH Service

sudo systemctl restart ssh.service

Verify Listening Port

sudo ss -ltnp | grep <CUSTOM_PORT>

If you see output such as:

*: <CUSTOM_PORT>

or

0.0.0.0:<CUSTOM_PORT>

then the configuration has been applied successfully.


Option 2: Keep ssh.socket and Change the Listening Port

If you prefer Ubuntu’s newer socket-activation approach, modify ssh.socket instead of sshd_config.

Create a Systemd Override

sudo mkdir -p /etc/systemd/system/ssh.socket.d
sudo nano /etc/systemd/system/ssh.socket.d/listen.conf

Add the following content:

[Socket]
ListenStream=
ListenStream=<CUSTOM_PORT>

Reload and Restart

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo systemctl status ssh.socket
sudo ss -ltnp | grep <CUSTOM_PORT>

Important Note

The following line is critical:

ListenStream=

It clears the default listening port before setting the new one.

Without this line, systemd may continue listening on both the default SSH port and the custom port simultaneously.

Granting Sudo or Write Access to Non-Interactive AI Agents on Linux

When you let an AI coding agent (Codex, Claude Code, or any CLI agent) operate on a remote Linux server over SSH, you eventually hit a wall the moment it tries to run sudo:

sudo: a password is required

The agent is not “broken.” It is simply running in a non-interactive shell, with no TTY attached, so sudo has nowhere to read the password from. Even if you know the password, you cannot type it into a remote process the agent spawned.

This post documents two practical ways to fix this. They solve the same symptom from very different angles.


The Root Cause

Non-interactive shell + sudo asking for password = deadlock
agent ──ssh──> remote bash (no tty) ──> sudo ──> "Password:"
nobody can answer

There are three theoretical escape routes:

  1. Make the operation not need sudo (file-level permissions / ACL)
  2. Make sudo stop asking for a password (NOPASSWD in sudoers)
  3. Force a pseudo-TTY and pipe the password (works locally, fragile remotely)

Approach 3 is rarely worth the trouble on a remote machine. Below are the two that actually scale.


Approach A — NOPASSWD in sudoers

Tell sudo to skip the password prompt for a specific user. The agent still calls sudo, but it goes through without authentication friction.

When to use

  • The agent legitimately needs root-level operations across the system
  • The target paths are spread across multiple root-owned locations
  • You are comfortable granting (a scoped or full) passwordless root

Setup

Always edit sudoers through visudo — it validates syntax before saving. Direct vim /etc/sudoers* is dangerous: one typo can lock the entire sudo subsystem.

# Use visudo with vim as the editor
sudo EDITOR=vim visudo -f /etc/sudoers.d/your-agent

Full passwordless root (simplest, but broadest):

your-user ALL=(ALL) NOPASSWD: ALL

Scoped to specific commands (safer):

Cmnd_Alias TARGET_OPS = \
/bin/cp * /opt/your-app/target-dir/*, \
/bin/mv * /opt/your-app/target-dir/*, \
/bin/mkdir /opt/your-app/target-dir/*, \
/bin/rm /opt/your-app/target-dir/*
your-user ALL=(ALL) NOPASSWD: TARGET_OPS

Fix permissions (visudo usually does this automatically):

sudo chmod 440 /etc/sudoers.d/your-agent
sudo chown root:root /etc/sudoers.d/your-agent

Verification

sudo su your-user
sudo -n cp /tmp/src /tmp/dst
OutputMeaning
Silent successNOPASSWD is active
sudo: a password is requiredConfig not picked up — check filename, syntax, permissions
cp: cannot stat '...'sudo already passed; this is a cp error, not a permission issue

Filename rules for /etc/sudoers.d/

RuleDetail
Allowed charactersletters, digits, -, _
Forbidden. and ~ — sudo silently ignores files containing these
File mode0440, owner root:root
Load orderalphabetical; later files override earlier ones

Trade-offs

  • ✅ Simple, well-understood, standard for CI / agents
  • ✅ Works for any command, including ones that need real root (package installs, service restarts)
  • ⚠️ NOPASSWD: ALL effectively gives that account unrestricted root
  • ⚠️ Hard to express “any operation on this directory” — sudoers matches on commands, not on paths as a primary concept
  • ⚠️ Shell redirection (>) and pipes are handled by the shell, not sudo, so sudo echo x > file doesn’t work the way you’d expect

Approach B — POSIX ACL (setfacl)

Step back and ask the more useful question: does the agent actually need sudo, or does it just need write access to one directory?

If the answer is the latter, the cleanest solution doesn’t touch sudo at all. You give the user filesystem-level permission to the directory and the agent runs every command without sudo.

When to use

  • The agent only needs to write to a specific directory tree
  • You want to avoid granting any form of sudo
  • You want shell redirection, sed -i, git, rsync, tar, etc. to all “just work”

Setup

TARGET=/opt/your-app/target-dir
# Grant rwx on existing files and directories
sudo setfacl -R -m u:your-user:rwx "$TARGET"
# Default ACL so newly created files inherit the permission
sudo setfacl -R -d -m u:your-user:rwx "$TARGET"

The two commands are both required. Without the default ACL, files created inside $TARGET later will revert to the original ownership and the agent will lose write access to them.

Verification

getfacl /opt/your-app/target-dir

You should see entries like:

user::rwx
user:your-user:rwx
default:user:your-user:rwx

Now the agent can do whatever it wants inside that tree, with no sudo prefix:

cp src.md /opt/your-app/target-dir/
sed -i 's/old/new/' /opt/your-app/target-dir/*.md
echo "data" > /opt/your-app/target-dir/output.log
rm /opt/your-app/target-dir/stale.tmp
mkdir /opt/your-app/target-dir/subdir

Trade-offs

  • ✅ Native semantics — “user X owns write rights to directory Y” is exactly what ACL expresses
  • ✅ No sudoers changes, no security audit on /etc/sudoers.d/
  • ✅ Directory ownership stays root, respecting FHS conventions for /opt/
  • ✅ Redirection, pipes, and arbitrary tools all work
  • ⚠️ A package upgrade that replaces files inside the target may strip ACLs from the new files
  • ⚠️ Doesn’t help if the agent legitimately needs root for non-file operations (systemd, networking, etc.)

Side-by-Side

DimensionNOPASSWD sudoersPOSIX ACL
Conceptual axisAuthorizes commandsAuthorizes files/directories
Agent needs sudo prefixYesNo
Scope of trustBroad (or carefully enumerated)Tight (one directory tree)
Supports shell redirectionNo (shell handles it before sudo)Yes
Risk if account compromisedPotentially full rootWrite access to one directory
Best forSystem-wide tasks, package management, service controlWriting into a specific app/data directory
Setup complexityLow–mediumLow
MaintenanceGrows as commands growOne-time

A Quick Decision Tree

Does the agent need root for anything beyond writing one directory?
├─ Yes → NOPASSWD in /etc/sudoers.d/
│ (prefer scoped command list over `ALL` if you can)
└─ No → POSIX ACL with setfacl
(no sudo involved, no sudoers risk)

A Note on Local vs Remote Agents

You may have noticed that the same agent running locally can happily prompt you for a sudo password mid-task, while the same agent over SSH cannot. That is not a bug, it is a property of the channel:

  • Locally, the agent shares stdin with your terminal. When sudo writes Password:, the agent passes it to your screen, you type, the keystrokes go back into the child process. The TTY chain is intact.
  • Remotely, the agent’s SSH invocation typically runs in capture-stdout mode without an interactive TTY. The password prompt appears on the remote side, but there is no live keyboard wired to it.

That is the whole reason both approaches above are framed around “make the password go away” rather than “answer the password prompt.”


Conclusion

Two clean options, with different philosophies:

  • NOPASSWD is the right answer when the agent genuinely needs root and you want the standard SRE-style solution.
  • ACL is the right answer when the agent only needs to write somewhere, and you want the smallest, most surgical permission change.

For most “let my AI agent write into this one project directory” cases, ACL is the better starting point. Reach for NOPASSWD when the work genuinely crosses the root boundary.

Creating a Fully Restorable Windows System Backup (GPT + EFI + MSR + C)


Recommended Full System Backup Strategy

Boot into a Linux live USB environment.


① Backup the GPT Partition Table

sgdisk --backup=/media/xxx/DiskBackup/gpt.bin /dev/nvme0n1

✔ Includes both primary and backup GPT headers
✔ Very small file
✔ Mandatory

This preserves your exact partition structure.


② Backup the EFI Partition

Assuming EFI is p1:

dd if=/dev/nvme0n1p1 of=/media/xxx/DiskBackup/efi.img bs=4M status=progress

EFI contains your bootloader and UEFI boot files.


③ Backup the MSR Partition (16MB)

Assuming MSR is p2:

dd if=/dev/nvme0n1p2 of=/media/xxx/DiskBackup/msr.img bs=4M status=progress

⚠ Takes about 1 second
⚠ Technically optional
⚠ But since it’s tiny, backing it up completes the snapshot


④ Backup C: Using partclone

partclone.ntfs -c -s /dev/nvme0n1p3 -o /media/xxx/DiskBackup/c.img

✔ Only backs up used NTFS blocks
✔ Much faster than dd
✔ Image size is significantly smaller


Full Restore Procedure

If the system ever fails:


1️⃣ Restore GPT

sgdisk --load-backup=gpt.bin /dev/nvme0n1

2️⃣ Restore EFI

dd if=efi.img of=/dev/nvme0n1p1 bs=4M

3️⃣ Restore MSR (Optional)

dd if=msr.img of=/dev/nvme0n1p2 bs=4M

4️⃣ Restore C:

partclone.ntfs -r -s c.img -o /dev/nvme0n1p3

5️⃣ (Safest Step) Rebuild Boot Files

Boot into Windows recovery or installation media and run:

bcdboot C:\Windows /f UEFI

The Real Cause of Intermittent Ubuntu Boot Failures on Hyper-V

— and Why “Preparing in Advance” Solves It Once and for All

When running Ubuntu on Hyper-V, some users encounter a very confusing issue:

  • On a cold boot, the system occasionally drops into emergency mode
  • Errors indicate failure to load kernel modules or mount the root filesystem
  • Repeatedly clicking “Stop → Start” eventually allows Ubuntu to boot normally
  • The system disk and files are not corrupted

This problem is hard to reproduce reliably and difficult to search for online.
After a complete investigation, the conclusion is clear:

This is not accidental, nor mysterious behavior —
it is a classic engineering problem caused by insufficient boot-time preparation.


1. The Key Conclusion (Important)

The root cause is not a broken system.

The real issue is:

Linux boots faster than the virtual disk is ready.

And the solution can be summarized in one sentence:

Move work that is normally done during boot
to a point before the system starts booting.


2. Where Exactly Does the Failure Occur?

A simplified Linux boot sequence looks like this:

GRUB
 → Linux kernel
 → initramfs (minimal early boot environment)
 → Mount root filesystem (/)
 → systemd startup

The failure happens precisely at this transition:

initramfs → mounting the root filesystem

In a Hyper-V cold-boot scenario:

  • The kernel has already started mounting /
  • But the virtual disk controller / I/O path is still initializing
  • The device “will exist very soon”, but does not yet exist at that moment

Linux does not wait indefinitely by default, so the mount fails and the system
drops into emergency mode.


3. Why Rebooting Sometimes “Fixes” It

This is where many people are misled.

What actually happens:

First cold boot

  • Virtual device initialization is slow
  • Disk readiness lags behind kernel startup

Subsequent boots

  • Controllers, caches, and resources are already warm
  • The disk becomes ready much faster

This creates the illusion:

“If I restart a few times, it works.”

But this only changes the probability, not the underlying problem.


4. The Real Solution: Prepare in Advance

The effective fix consists of four steps:

sudo update-initramfs -u -k all
sudo nano /etc/default/grub

Change:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"

to:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash rootdelay=10"

Then run:

sudo update-grub
sudo reboot

What This Achieves

  • update-initramfs
    Ensures all required kernel modules are already available at boot time,
    instead of being loaded on demand.
  • rootdelay=10
    Explicitly tells the kernel to wait (up to 10 seconds) for the virtual disk
    before attempting to mount /.
  • update-grub
    Applies the new boot configuration.
  • reboot
    Activates the changes, which only take effect during startup.

Together, these steps eliminate the boot-time race condition entirely.


Appendix

Why rootdelay Fixes Disks but Breaks Your Assumptions

This section exists to clarify an important but easily misunderstood point:

rootdelay fixes a storage-layer race condition —
it does not fix system readiness as a whole.

Understanding this distinction is critical to applying the solution safely.


1. What rootdelay Actually Does (and What It Does Not)

The kernel parameter:

rootdelay=10

has a very narrow and specific purpose:

  • It delays mounting the root filesystem (/)
  • It gives block devices (e.g. virtual disks) extra time to appear
  • It only affects the transition: initramfs → mount /

This makes it effective for scenarios such as:

  • Hyper-V cold boot
  • Slow virtual disk initialization
  • Storage controllers that appear shortly after kernel start

However, rootdelay does not:

  • Delay systemd startup globally
  • Delay network stack initialization
  • Synchronize higher-level services
  • Fix Layer-3 (IP) configuration issues

It operates entirely at the storage boundary.


2. Why This Can Accidentally “Break” Networking

Many Linux systems implicitly assume:

“If the system boots, the network will be provided by DHCP.”

This assumption is usually correct on:

  • Home networks
  • Cloud images
  • Default Ubuntu installations

But it is not universally valid, especially in:

  • Enterprise networks
  • Corporate VLANs
  • Environments with statically assigned IP addresses

When rootdelay is introduced:

  • Boot timing changes slightly
  • Service initialization order may shift
  • Latent configuration assumptions become visible

If the network does not provide DHCP, the system will now clearly fail to acquire an address —
not because rootdelay broke networking, but because:

The system was never correctly configured for the network it was on.


3. The Key Misinterpretation to Avoid

It is tempting to conclude:

“After adding rootdelay, networking stopped working.”

This is not the correct causal model.

The correct interpretation is:

rootdelay removed a storage race
→ system booted deterministically
→ network configuration assumptions were exposed
→ DHCP failed (as expected in static IP environments)

In other words:

rootdelay did not break networking —
it removed a distraction that previously hid the real issue.


4. Static IP Environments Require Explicit Configuration

In networks where IP addresses are assigned manually, the correct fix is not boot-time tuning, but network-layer correctness.

That means explicitly configuring:

  • IP address
  • Subnet mask
  • Default gateway
  • DNS servers

For example (NetworkManager):

nmcli connection modify "Wired connection 1" \
ipv4.method manual \
ipv4.addresses <STATIC_IP>/<PREFIX> \
ipv4.gateway <GATEWAY> \
ipv4.dns "<DNS1> <DNS2>"

Then forcing a reconnection:

nmcli connection down "Wired connection 1"
nmcli connection up "Wired connection 1"

This is not a workaround, and it is not related to rootdelay.

It simply aligns the system with the reality of the network.


5. Engineering Takeaway

The deeper lesson is not about rootdelay itself, but about scope:

A fix that is correct at one layer
should never be assumed to generalize upward.

  • rootdelay fixes storage readiness
  • It does not define system readiness
  • It cannot compensate for incorrect Layer-3 assumptions

Fixing “wrong fs type / bad superblock” on an External Drive

(When Linux Sees /dev/sda but No /dev/sda1)

When mounting an external drive, I hit the following error:

sudo mount /dev/sda /media/external
mount: /media/external: wrong fs type, bad option, bad superblock on /dev/sda, ...

At first glance, this looks like a “broken filesystem.”
In reality, Linux could not see any partition at all (no /dev/sda1), so I was effectively trying to mount the entire disk device instead of a partition.

This post documents a safe, reproducible recovery workflow:

  • Identify the real problem
  • Create a full disk image with ddrescue
  • Use TestDisk to locate the lost partition
  • (Optional) Write the partition table back so the disk mounts normally again

Boot Sector vs. Partition Table — What Was Actually Broken?

Before doing anything, it’s critical to understand where the failure occurred:

  • Partition table (MBR/GPT)
    • Lives at the very beginning of the disk
    • Describes where partitions start/end
    • If this is missing or corrupt, you won’t even get /dev/sda1
  • Boot sector / filesystem metadata
    • Lives inside a partition
    • If only this is damaged, you usually still see /dev/sda1, but mounting fails

👉 In this case, the partition table was missing/corrupt, which is why lsblk showed sda but no sda1.


0) Safety Rules (Read This First)

  • Do NOT format the disk
  • Do NOT run destructive commands
    (e.g., mkfs, wipefs without -n, or random “repair” tools)
  • Always double-check the device name
    One typo in /dev/... can destroy your system disk
  • If you see many USB resets or I/O errors in dmesg,
    suspect cable/enclosure/power issues first

1) Confirm the Disk Is Detected

lsblk -o NAME,SIZE,MODEL,SERIAL,TYPE,MOUNTPOINTS

Identify your external drive by size and model.
Example:

/dev/sda   ~476GiB

2) Check for Partitions or Filesystems

lsblk -f
sudo fdisk -l /dev/sda

Key observation

  • If you see only sda and no sda1/sda2,
    the partition table is likely missing or corrupt.

Force a partition table reread (safe):

sudo partprobe /dev/sda
lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL,MODEL /dev/sda

Read-only signature checks:

sudo wipefs -n /dev/sda
sudo file -s /dev/sda

If wipefs -n shows nothing and file -s prints only data,
Linux does not recognize any partition table or filesystem header.

Quick read test (read-only):

sudo dd if=/dev/sda of=/dev/null bs=1M count=16 status=progress

If this runs at normal speed, the disk is at least readable.


3) Create a Full Disk Image First (Strongly Recommended)

Make sure another disk has enough free space (≥ disk size) and supports large files (avoid FAT32):

df -hT /mnt/recovery

Install tools:

sudo apt update
sudo apt install -y gddrescue testdisk

Create directories:

sudo mkdir -p /mnt/recovery/sda_backup
sudo mkdir -p /mnt/recovery/sda_recovered

First ddrescue pass (fast, minimal retries)

sudo ddrescue -f -n /dev/sda \
  /mnt/recovery/sda_backup/sda.img \
  /mnt/recovery/sda_backup/sda.log

Optional second pass if there were read errors:

sudo ddrescue -f -d -r3 /dev/sda \
  /mnt/recovery/sda_backup/sda.img \
  /mnt/recovery/sda_backup/sda.log

If you end up with 100% rescued and 0 read errors, the entire disk has been safely captured.


4) Use TestDisk on the Image to Find the Lost Partition

sudo testdisk /mnt/recovery/sda_backup/sda.img

In the interactive UI:

  1. Select the disk image
  2. Analyse → Quick Search
    (Use Deeper Search only if needed)
  3. Highlight a candidate partition and press P to list files

If P shows your real folders/files, that partition entry is correct.

Optional: Copy Files Out

  • Press a to select all → C (uppercase) to copy
  • Destination:/mnt/recovery/sda_recovered

If space is limited, copy only what you need.


5) Restore the Partition Table to the Original Disk

⚠️ Do this only after you have a full backup image.

Run TestDisk on the real device:

sudo testdisk /dev/sda

Steps:

  1. Analyse → Quick Search
  2. Highlight the correct partition and press P to confirm files
  3. Back in the list:
    • Set the correct entry to P (Primary)
    • Set wrong/overlapping entries to D (Deleted)
  4. Press Enter → Write → Y

This writes the recovered partition table back to /dev/sda.


6) Reload Partition Info and Mount (Read-Only First)

sudo partprobe /dev/sda
sudo partx -u /dev/sda
lsblk -f /dev/sda

You should now see:

/dev/sda1

Mount read-only to verify:

sudo mkdir -p /media/external
sudo mount -o ro /dev/sda1 /media/external

If everything looks correct:

sudo umount /media/external
sudo mount /dev/sda1 /media/external

Troubleshooting Notes

  • Seeing NTFS when you expected exFAT
    Trust what TestDisk reports; the drive may have been formatted differently than you remember.
  • NTFS “hibernated / unsafe state” warnings
    Best fix is on Windows:chkdsk /f Linux-side helper:sudo ntfsfix /dev/sda1
  • I/O errors or USB resets
    Change cable/port/enclosure, avoid hubs, ensure sufficient power, then image with ddrescue.
  • TestDisk can’t list files
    The filesystem metadata may be damaged. As a last resort, use PhotoRec (filenames/folders are usually lost).

Summary

  • The mount error was a symptom; the key clue was no /dev/sda1
  • The real issue was a missing or corrupt partition table
  • Best practice is always:

Image first with ddrescue, then analyze and recover with TestDisk.

Surface Laptop 2 修复 BLInitializeLibrary failed / 0xc0000001

适用于系统文件仍在,但EFI 引导区损坏导致无法启动、自动修复失败、bootrec 扫描不到 Windows 的情况。

以下步骤已被实际验证有效。


#️⃣1. 制作恢复 U 盘(Linux 环境)

① 下载微软恢复镜像

选择设备型号(如 Surface Laptop 2)
输入序列号
下载 ZIP(约 7.8GB)


② 在 Linux 中将 U 盘格式化为 FAT32(最重要步骤)

先确认你的 U 盘设备名称,例如:

lsblk

假设是 /dev/sda(⚠ 注意:千万不要选错)

格式化为 FAT32:

sudo umount /dev/sda*
sudo mkfs.fat -F 32 /dev/sda

③ 挂载 U 盘

sudo mkdir -p /mnt/usb
sudo mount /dev/sda /mnt/usb

④ 解压微软恢复镜像

unzip Surface_Recovery.zip -d surf

⑤ 将恢复文件复制到 U 盘(必须复制内容,而不是文件夹)

sudo cp -r surf/* /mnt/usb/

⑥ 卸载 U 盘

sudo umount /mnt/usb

#️⃣2. 从 U 盘启动进入恢复界面

  1. 插入 U 盘
  2. 长按 音量 –
  3. 按电源
  4. 松开电源但继续按住音量 –
  5. 进入 Windows 蓝色恢复界面

#️⃣3. 打开命令提示符

疑难解答 → 高级选项 → 命令提示符


#️⃣4. 可选:修复系统文件(推荐)

sfc /scannow /offbootdir=C:\ /offwindir=C:\Windows
dism /image:C:\ /cleanup-image /restorehealth

#️⃣5. 运行 bootrec(如果扫描到 0 个 Windows,继续下一步)

bootrec /fixmbr
bootrec /fixboot
bootsect /nt60 sys
bootrec /fixboot
bootrec /scanos

#️⃣6. 手动重建 EFI 引导分区(真正解决问题的步骤)

① 打开 diskpart

diskpart
list volume

记住EFI 分区编号:
通常是 100MB FAT32(如 Volume 1)


② 选择 EFI 分区并挂载为 Z:

sel volume 1
assign letter=Z
exit

③ 清空 EFI 目录并重建

cd /d Z:\
rmdir /S /Q Z:\EFI
mkdir Z:\EFI

④ 将 Windows 引导文件写入 EFI(最关键一步)

bcdboot C:\Windows /s Z: /f UEFI

看到:

启动文件创建成功

代表修复成功。


#️⃣7. 重启

exit

选择:

Continue → Continue to Windows

系统即可恢复正常启动。


🟦 总结(极简版流程)

# Linux 制作恢复盘
sudo umount /dev/sdX*
sudo mkfs.fat -F 32 /dev/sdX
sudo mount /dev/sdX /mnt/usb
unzip Recovery.zip -d surf
sudo cp -r surf/* /mnt/usb/
sudo umount /mnt/usb

# Windows 修复:
sfc /scannow /offbootdir=C:\ /offwindir=C:\Windows
dism /image:C:\ /cleanup-image /restorehealth
bootrec /fixmbr
bootrec /fixboot
bootsect /nt60 sys
bootrec /fixboot
bootrec /scanos

diskpart
list volume
sel volume 1
assign letter=Z
exit

cd /d Z:\
rmdir /S /Q Z:\EFI
mkdir Z:\EFI

bcdboot C:\Windows /s Z: /f UEFI
通过 WordPress.com 设计一个这样的站点
从这里开始