HEWEN's blog 2025-07-16T20:47:27+08:00 wen.bin.he@foxmail.com Go Embedding Series — 03. The Ultimate Guide to go:embed Syntax and Usage 2025-07-16T20:04:00+08:00 HeWen https://ehewen.com/blog/go-embed # The Ultimate Guide to `go:embed` Syntax and Usage > Series: Go Embedding Features — The Ultimate Guide to `go:embed` Syntax and Usage Since the release of Go 1.16, the `embed` package has become part of the standard library, allowing developers to embed static resources directly into the compiled binary. Whether you're building a web application, CLI tool, or want to avoid external file dependencies during deployment, `go:embed` is a powerful asset that should not be overlooked. --- ## 1. Detailed Syntax Overview ### 1.1 Importing the `embed` Package ```go import "embed"

You must explicitly import the embed package, even if you only use its annotations.

1.2 Three Supported Variable Types

Type Description
string Reads file content as UTF-8 encoded text
[]byte Raw binary file data
embed.FS Virtual read-only file system for multiple files or directories

1.3 Example: Embedding a Single File

import _ "embed"

//go:embed hello.txt
var helloText string
fmt.Println(helloText)

Text is embedded as UTF-8. You can also embed binary files:

//go:embed logo.png
var logo []byte

1.4 Example: Embedding Multiple Files (Wildcard)

//go:embed static/*.js static/*.css
var assets embed.FS

Supports glob patterns (wildcards), but not recursive ones like **. The glob matches paths, not file content.

1.5 Example: Embedding a Directory

//go:embed templates/*
var tmplFS embed.FS

This is ideal for web projects or template engines.


2. Practical Use Cases

2.1 Build a Self-Contained Web App

//go:embed static/*
var staticFiles embed.FS

func main() {
	fs := http.FS(staticFiles)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(fs)))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

No need to deploy the static/ directory separately—the app binary includes it all.


2.2 Embed and Render HTML Templates

//go:embed templates/*.html
var tmplFS embed.FS

func renderTemplate(w http.ResponseWriter) {
	t := template.Must(template.ParseFS(tmplFS, "templates/index.html"))
	t.Execute(w, map[string]string{"Title": "Welcome!"})
}

Perfect for use with html/template or text/template.


2.3 Embed Default Config Files

//go:embed config.yaml
var configYAML []byte

func loadDefaultConfig() Config {
	var c Config
	_ = yaml.Unmarshal(configYAML, &c)
	return c
}

Great for CLI apps that need a default configuration, which users can override via --config.


2.4 Embed SQL or Migration Scripts

//go:embed migrations/*.sql
var migrationFS embed.FS

func runMigrations(db *sql.DB) {
	files, _ := fs.ReadDir(migrationFS, "migrations")
	for _, f := range files {
		data, _ := migrationFS.ReadFile("migrations/" + f.Name())
		db.Exec(string(data))
	}
}

Perfect for tools like gorm, sqlc, or golang-migrate.


3. Working with embed.FS

3.1 ReadFile

data, err := fs.ReadFile(embedFS, "templates/index.html")

Returns the file as []byte, suitable for parsers or direct output.


3.2 ReadDir

entries, err := fs.ReadDir(embedFS, "static")
for _, entry := range entries {
	fmt.Println(entry.Name())
}

Similar to os.ReadDir, but operates on the embedded filesystem.


3.3 Open File Handle

f, err := embedFS.Open("static/logo.png")
defer f.Close()
io.Copy(w, f) // Directly write to HTTP response

Note: embed.FS is read-only—you cannot modify embedded files.


4. Limitations and Caveats

Limitation Description
Compile-Time Embedding Only Files must exist at build time; runtime changes are not supported
No Recursive Globs (**) You must list each subdirectory explicitly or use specific patterns (e.g. *.ext)
Relative Paths Paths are relative to the Go file, not the project root
Read-Only Access Embedded files are immutable and cannot be written to
No Dynamic Paths //go:embed paths must be string constants, not variables or expressions

5. Comparison with Third-Party Tools

Feature go:embed rice statik
Built-in ✅ Yes ❌ No ❌ No
Supports Directory ✅ Yes ✅ Yes ✅ Yes
Hot Reload Support ❌ No ✅ Yes (dev mode) ❌ No
Build Performance ✅ Fast ❌ Slower (requires code generation) ❌ Slower
Ease of Use ✅ Simple ❌ Complex ❌ Moderate

Recommendation: use go:embed for all small to medium projects; only consider rice if hot reloading is essential.


6. Debugging Tips & Common Pitfalls

6.1 Embedded File Not Found?

  • Ensure the go:embed comment is placed directly above the variable.
  • Confirm the path is relative to the .go file containing the directive.
  • File names and extensions are case-sensitive.

6.2 File Not Updating?

Embedded resources are locked at build time. Rebuild the binary after changing embedded files.

6.3 UTF-8 Issues with Chinese or Non-ASCII Characters?

Make sure the embedded files are saved as UTF-8. If reading []byte, manually convert:

str := string(data) // assuming UTF-8 encoding

7. Best Practices Summary

✅ Use embed.FS for multi-file or directory-based resource management ✅ Organize static resources into folders like static/, templates/, or assets/ ✅ Prefer go:embed over external dependencies or Docker bind mounts ✅ Combine with go generate or checksum checks for enhanced build workflows


Example Projects


Conclusion

go:embed represents a significant step toward standardized, modular resource management in Go. It greatly simplifies deployment for CLI apps, web servers, and containerized applications. If you’re still manually copying static files during deployment—now’s the time to embrace go:embed.

]]>
Go Embedding Series — 02. Interface Embedding in Go Design and Patterns 2025-07-16T15:10:00+08:00 HeWen https://ehewen.com/blog/go-interface-embedding Interface Embedding in Go: Design and Patterns

Series: Go Embedding Features — Interface Embedding


1. What is Interface Embedding?

Interface embedding in Go allows one interface to embed one or more other interfaces, thereby extending or reusing interface definitions.

The embedded interface’s method sets are combined into the new interface, meaning the new interface implicitly contains all methods from the embedded ones.

Example:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// WriterCloser embeds Reader and Writer
type WriterCloser interface {
    Reader
    Writer
    Close() error
}

The WriterCloser interface includes Read, Write, and Close methods. This makes interface definitions modular and easy to compose.


2. Use Cases for Interface Embedding

  • Interface Reuse: Avoid re-declaring methods by reusing existing interface definitions.
  • Interface Extension: Add new capabilities on top of existing interfaces.
  • Multi-interface Composition: Combine responsibilities into more powerful interfaces.
  • Flexible Design: Promotes modular, layered design of system components.

3. Syntax Details

  • You can embed one or more interfaces and declare additional methods.
  • The resulting method set includes all embedded interfaces plus any new ones.
  • A type must implement all methods from the embedded interfaces to satisfy the new interface.
  • Order of embedding does not affect behavior.
  • The empty interface interface{} is often used, but embedding is best suited for structured capability composition.

4. Real-World Example

package main

import "fmt"

// Base interfaces
type Speaker interface {
    Speak() string
}

type Mover interface {
    Move() string
}

// Composite interface
type Animal interface {
    Speaker
    Mover
    Sleep() string
}

// Implementing Animal
type Dog struct{}

func (d Dog) Speak() string {
    return "Woof!"
}

func (d Dog) Move() string {
    return "Run"
}

func (d Dog) Sleep() string {
    return "Zzz"
}

func main() {
    var a Animal = Dog{}
    fmt.Println(a.Speak()) // Woof!
    fmt.Println(a.Move())  // Run
    fmt.Println(a.Sleep()) // Zzz
}

This example shows how to combine interfaces to define richer abstractions without duplication.


5. Underlying Mechanism and iface Layout

5.1 Method Set Composition Diagram

+---------------------+
|    ReadWriter       |
+---------------------+
| + Read(p []byte)    |  ← from Reader
| + Write(p []byte)   |  ← from Writer
+---------------------+

    ↑            ↑
    |            |
+-------+   +--------+
| Reader|   | Writer |
+-------+   +--------+
| Read  |   | Write  |
+-------+   +--------+

Explanation: The ReadWriter interface includes methods from both Reader and Writer.


5.2 Internal Structure of iface

In Go, an interface variable (non-empty) is implemented internally as:

+-------------------------+
|         iface           |
+-------------------------+
| + tab (method table)    | -------------------+
| + data (value pointer)  |                   |
+-------------------------+                   |
                                              v
                                  +----------------------------+
                                  |         itab (table)       |
                                  +----------------------------+
                                  | + type descriptor          |
                                  | + interface descriptor     |
                                  | + func[0]: Read method     |
                                  | + func[1]: Write method    |
                                  +----------------------------+
  • tab points to the method table (itab).
  • data points to the underlying value.

When a method is called, it uses the function pointer from the itab.


5.3 Method Call Flow

For a method call like:

rw.Write(p)

Internally, it’s compiled to:

iface.tab.func[1](iface.data, p)
  • Function is resolved via itab.
  • First argument is the actual value (data).
  • Remaining arguments are user inputs.

5.4 Multi-Level Interface Embedding

+-----------------------------+
|       ReadWriteCloser       |
+-----------------------------+
| + Read(p []byte)            |
| + Write(p []byte)           |
| + Close()                   |
+-----------------------------+

     ↑            ↑          ↑
     |            |          |
+-------+   +--------+   +--------+
| Reader|   | Writer |   | Closer |
+-------+   +--------+   +--------+
| Read  |   | Write  |   | Close  |
+-------+   +--------+   +--------+

ReadWriteCloser collects all methods from Reader, Writer, and Closer.


5.5 Effect on iface and eface

  • iface is used for non-empty interfaces (those with methods). It has a method table.
  • eface is used for empty interfaces interface{}. It stores only type and data pointers.
  • Embedding enlarges the method set, so itab expands but the calling mechanism remains constant.

6. Patterns and Best Practices

6.1 Interface Segregation Principle

Split large interfaces into small, focused ones. Use embedding to compose broader ones.

6.2 Decorator Pattern via Embedding

Interfaces can be embedded to extend behavior without changing existing types.

6.3 Domain-Driven Design with Composable Interfaces

Define fine-grained domain behaviors and aggregate them into richer interfaces via embedding.


7. How Interface Embedding Works in the Compiler

  • In cmd/compile/internal/types/type.go, the Interface type represents all methods of an interface.
  • Embedded interfaces are recursively merged using addInterfaceMethods and related helpers.
  • During type checking (typecheck), the compiler ensures all embedded methods are implemented.
  • At runtime, Go builds complete method tables that reflect the final method set.

8. Conclusion

Interface embedding in Go is a powerful feature that enables interface reuse, modularity, and extension. Understanding how it works — both at the syntax and runtime level — is key to designing clean, extensible systems in Go.

By leveraging small, composable interfaces and embedding them thoughtfully, you can build maintainable and flexible software architectures.

]]>
Go Embedding Series — 01. In-depth Explanation and Practical Use of Struct Embedding in Go 2025-07-16T12:00:00+08:00 HeWen https://ehewen.com/blog/go-struct-embedding In-depth Explanation and Practical Use of Struct Embedding in Go

Series Topic: Go Embedding Series — Struct Embedding Chapter


1. What is Struct Embedding?

Struct embedding is a composition mechanism provided by Go. It allows embedding one struct anonymously inside another, enabling “promotion” of fields and methods—meaning the outer struct can directly access the inner struct’s fields and methods without explicitly referencing the inner struct’s name.

Example:

type A struct {
    X int
}

func (a A) Hello() {
    fmt.Println("Hello from A:", a.X)
}

type B struct {
    A  // Anonymous embedding of struct A
    Y int
}

b := B{A: A{X: 10}, Y: 20}
fmt.Println(b.X) // Directly access field X from A, outputs 10
b.Hello()        // Directly call method Hello from A, outputs "Hello from A: 10"

Struct embedding offers an alternative to inheritance, making code reuse more flexible and fitting Go’s design philosophy.


2. Use Cases of Struct Embedding

  • Code Reuse: Avoid duplicating fields and methods by abstracting common parts into base structs.
  • Interface Implementation: Achieve interface composition and reuse by embedding.
  • Behavior Composition: Combine different embedded structs to create new behavior patterns, replacing inheritance.

3. Syntax Details of Struct Embedding

  • Embedded fields can be either concrete types or pointer types.
  • Embedding multiple fields of the same type causes access conflicts and requires explicit field naming.
  • Method calls are ambiguous and cause compile errors if multiple embedded structs implement the same method.
  • Embedded fields can also be interfaces, enabling flexible composition.

4. Practical Example of Struct Embedding

package main

import "fmt"

type Logger struct {
    Prefix string
}

func (l Logger) Log(msg string) {
    fmt.Println(l.Prefix, msg)
}

type Service struct {
    Logger    // Anonymous embedding of Logger
    ServiceID string
}

func main() {
    s := Service{
        Logger: Logger{Prefix: "[Service]"},
        ServiceID: "svc01",
    }
    s.Log("starting")  // Directly call the embedded Logger’s method
}

This example demonstrates how to add logging functionality to a business struct via struct embedding for convenient calls.


5. Deep Dive into the Underlying Implementation of Struct Embedding

5.1 Memory Layout and Field Offsets

At its core, struct embedding is just an anonymous field, and the memory layout is similar to ordinary fields but access is automatically “promoted.”

Consider:

type A struct {
    X int32
    Y int32
}

type B struct {
    A      // Anonymous embedding of struct A
    Z  int32
}

5.2 Memory Layout Diagram (32-bit system example)

Memory layout of B struct:

+----------+----------+----------+
|   A.X    |   A.Y    |    Z     |
+----------+----------+----------+
| 4 bytes  | 4 bytes  | 4 bytes  |
+----------+----------+----------+

Offset Table:
Field      Offset (bytes)
A.X        0
A.Y        4
Z          8

5.3 Compiler Conversion of Field Access

When accessing b.X, the compiler translates it to b.A.X:

// Your code
b.X = 100

// Compiler-generated access
b.A.X = 100

Here, b.X does not exist as an explicit field in B, but because of anonymous embedding, the compiler promotes A’s fields, making X appear as a direct member of B.

5.4 Access Path Diagram

b (variable of type B)
│
├── A (anonymous embedded struct)
│    ├── X
│    └── Y
└── Z

Accessing b.X means taking b’s memory address, adding the offset for A (0), then adding the offset of X within A (0), arriving at the memory location storing X.

5.5 Multi-layer Embedding Example

type C struct {
    B  // Anonymous embedding of B
    W int32
}

Memory layout diagram:

Memory layout of C:

+----------+----------+----------+----------+
|  A.X     |  A.Y     |   B.Z    |    W     |
+----------+----------+----------+----------+

Field Access:
c.X => c.B.A.X
c.Z => c.B.Z

Accessing c.X generates code that first accesses c, then through B, then through A to X.


5.6 Summary

  • Anonymous embedding is just “inlined” fields in the struct; no extra memory overhead is introduced.
  • The compiler promotes methods and fields from embedded structs for simpler access.
  • For multi-layer embedding, access paths expand into multiple nested field accesses.

This static field promotion mechanism is key to Go’s “composition over inheritance” design.


6. Go Compiler Source Code Analysis for Struct Embedding

Type system:

  • Anonymous fields are defined in cmd/compile/internal/types/type.go, where struct fields are represented by Field structs with the Embedded boolean marking anonymity.

Type checking:

  • Promotion of anonymous fields and method sets happens in cmd/compile/internal/typecheck, mainly in typecheck.go and helper functions.
  • This code recursively promotes methods from embedded fields to the outer struct.

Code generation:

  • Code generation is mainly in cmd/compile/internal/gc.
  • Field access and method calls handled in cmd/compile/internal/gc/ssa.go and cmd/compile/internal/gc/gen.go.
  • The older walk.go file has been refactored or split across these files.

6.2 Code Snippet Examples

Anonymous field definition in cmd/compile/internal/types/type.go:

type Field struct {
    Sym      *Sym   // field name symbol
    Type     Type   // field type
    Embedded bool   // whether field is anonymous (embedded)
    // ...
}

Anonymous field promotion:

  • The addMethodSets function in cmd/compile/internal/typecheck/typecheck.go merges methods from embedded fields into the outer type’s method set.

Code generation:

  • Offset calculation and field access generation happen in ssa.go and gen.go by generating machine code for struct field selectors and method calls based on offsets.

6.3 Recommendations

To dive deeper into struct embedding implementation:

  • Clone the Go source repository
  • Focus on cmd/compile/internal/ subdirectories: types/, typecheck/, and gc/
  • Search keywords like Embedded, Field, and methodSet
  • Use IDEs such as GoLand or VSCode for code navigation and call graph exploration

7. Advantages, Disadvantages, and Best Practices of Struct Embedding

Advantages

  • Simple and clear composition for code reuse
  • Improves code reuse without heavy inheritance
  • Supports method promotion and overriding, flexible extension

Disadvantages

  • Excessive embedding can make code hard to follow
  • Multi-layer nesting may cause confusing field accesses
  • Cannot fully replace traditional inheritance (e.g., polymorphism)

Recommendations

  • Follow the “composition over inheritance” principle in design
  • Limit embedding depth to maintain code clarity
  • Combine interfaces with embedding for flexible designs

8. Conclusion

Struct embedding is a core feature of Go’s composition model, enabling flexible code reuse and interface implementations. Understanding its syntax and runtime mechanism helps write elegant and efficient Go code. Meanwhile, diving into the compiler’s underlying implementation clarifies Go’s design philosophy and aids debugging skills.

]]>
DNS ECS Lookup and Principles with Go 2025-07-14T19:42:00+08:00 HeWen https://ehewen.com/blog/DNS-ECS What is DNS ECS?

In traditional DNS resolution, authoritative DNS servers typically see only the IP address of the recursive resolver—not the real IP of the end-user. As a result, they can’t return CDN nodes optimized for the user’s actual location, causing slower access and degraded user experience.

ECS (EDNS Client Subnet) is a DNS protocol extension (defined in RFC 7871) that allows recursive resolvers to include a portion of the client’s IP subnet in DNS queries. Authoritative servers can then return responses tailored to the user’s region.


How ECS Works

  1. A client sends a DNS query to the recursive resolver.
  2. The resolver extracts the subnet (e.g., 1.2.3.0/24) from the client IP.
  3. The resolver includes an ECS option in the DNS request to the authoritative server.
  4. The authoritative server uses the subnet to return a geographically optimized IP address.
  5. The response is sent back to the client with improved relevance and performance.

This process enables DNS-based geographic targeting without requiring any change on the client side.


ECS Support Landscape

While ECS is powerful, not all DNS providers support it—or they support it to different degrees.

✅ Public DNS Servers with Good ECS Support

Region Provider DNS IP ECS Support Notes
CN AliDNS 223.5.5.5 ✅ Good Fully supports ECS, widely used
CN Tencent DNSPod 119.29.29.29 ✅ Good Fast and stable
CN AliDNS (Backup) 223.6.6.6 ✅ Good Same as above
CN Baidu Public DNS 180.76.76.76 ✅ Partial Inconsistent ECS support
Global Google DNS 8.8.8.8 ✅ Excellent Mature and widely adopted
Global Google DNS 8.8.4.4 ✅ Excellent Same as above
Global Cloudflare 1.1.1.1 ✅ Excellent Fast and privacy-focused
Global Cloudflare 1.0.0.1 ✅ Excellent Same as above
Global Quad9 9.9.9.9 ✅ Good Security & privacy enhanced DNS

⚠️ Limited ECS Support

Provider DNS IP ECS Support Issue
114 DNS (CN) 114.114.114.114 ⚠️ Limited Often discards ECS info; poor geo optimization

Tip: For ECS-based optimization (e.g., CDN routing), avoid using DNS servers with limited or no ECS support.


How to Test ECS Support

Option 1: Use dig with ECS

dig +subnet=1.2.3.0/24 www.example.com @dns-server-ip

If the response includes an OPT record with ECS subnet info, the DNS server supports ECS. Otherwise, it may ignore the ECS extension.

Option 2: Programmatic Test in Go

Use a Go program to craft a DNS request with ECS and inspect the returned data. (See example below.)

Option 3: Compare DNS Results Across Regions

Check whether the same domain returns different IPs from different regions when ECS is used.


ECS DNS Lookup in Go

Below is a simple Go program using miekg/dns to issue an ECS-enabled DNS query:

package main

import (
	"fmt"
	"log"
	"net"

	"github.com/miekg/dns"
)

func LookupWithECS(domain, clientIP string, dnsServer string) ([]string, error) {
	m := new(dns.Msg)
	m.SetQuestion(dns.Fqdn(domain), dns.TypeA)

	o := new(dns.OPT)
	o.Hdr.Name = "."
	o.Hdr.Rrtype = dns.TypeOPT

	ecsIP := net.ParseIP(clientIP).To4()
	if ecsIP == nil {
		return nil, fmt.Errorf("invalid IPv4 address: %s", clientIP)
	}
	ecs := &dns.EDNS0_SUBNET{
		Code:          dns.EDNS0SUBNET,
		Family:        1,
		SourceNetmask: 24,
		SourceScope:   0,
		Address:       ecsIP,
	}
	o.Option = append(o.Option, ecs)
	m.Extra = append(m.Extra, o)

	c := new(dns.Client)
	in, rtt, err := c.Exchange(m, dnsServer)
	if err != nil {
		return nil, fmt.Errorf("DNS query failed: %w", err)
	}

	fmt.Printf("DNS query RTT: %v\n", rtt)

	var ips []string
	for _, ans := range in.Answer {
		if a, ok := ans.(*dns.A); ok {
			ips = append(ips, a.A.String())
		}
	}

	for _, extra := range in.Extra {
		if opt, ok := extra.(*dns.OPT); ok {
			for _, option := range opt.Option {
				if ecsResp, ok := option.(*dns.EDNS0_SUBNET); ok {
					fmt.Printf("ECS response: family=%d, netmask=%d, address=%s\n",
						ecsResp.Family, ecsResp.SourceNetmask, ecsResp.Address)
				}
			}
		}
	}

	return ips, nil
}

func main() {
	domain := "www.baidu.com"
	clientIP := "1.2.3.4"
	dnsServer := "119.29.29.29:53"

	ips, err := LookupWithECS(domain, clientIP, dnsServer)
	if err != nil {
		log.Fatalf("LookupWithECS failed: %v", err)
	}
	fmt.Printf("Resolved IPs: %v\n", ips)
}

ECS + HTTPDNS: A Modern Combination

Traditional DNS over UDP is vulnerable to hijacking and spoofing. HTTPDNS addresses this by resolving domains over HTTPS (DoH), which:

  • Protects against DNS pollution
  • Ensures accurate IP targeting
  • Supports native client IP identification

By combining ECS and HTTPDNS, services can achieve both security and location-aware routing, greatly enhancing performance in mobile and multi-region scenarios.


Conclusion

  • ECS allows DNS servers to return IPs based on the client’s geographic subnet.
  • Many public DNS servers now support ECS, but 114 DNS does not fully support it and should be avoided in precision scenarios.
  • You can test ECS support using dig +subnet, Go code, or regional comparisons.
  • HTTPDNS and ECS together form a strong foundation for modern DNS performance and security strategies.
]]>
i3wm Keyboard Shortcuts Cheatsheet & Tips 2025-07-14T15:42:00+08:00 HeWen https://ehewen.com/blog/i3wm-shortcuts i3wm (i3 Window Manager) is a popular tiling window manager among developers and power users due to its lightweight, keyboard-centric, and customizable nature. Mastering its shortcuts can significantly enhance your productivity.


🔧 Basic Modifier Key

By default, i3 uses a Mod key (usually Alt or Win/Super) as a prefix for most keybindings.

You can confirm the Mod key in ~/.config/i3/config with lines like set $mod Mod4.


🧭 Window Management

Shortcut Action
Mod + Enter Open terminal (i3-sensible-terminal)
Mod + d Launch app (dmenu or rofi)
Mod + Shift + q Close current window
Mod + f Toggle fullscreen
Mod + Shift + Space Toggle floating/tiling mode
Mod + Space Focus toggle between modes

🧱 Layout Switching

Shortcut Layout
Mod + e Split mode (default)
Mod + s Stack layout
Mod + w Tabbed layout
Mod + h/v Horizontal / Vertical split

🎯 Focus & Window Movement

Shortcut Action
Mod + Arrow keys / h j k l Move focus
Mod + Shift + Arrow/hjkl Move window
Mod + Tab Cycle focus

🧪 Workspace Management

Shortcut Action
Mod + 1~9 Switch workspace
Mod + Shift + 1~9 Move window to workspace
Mod + Ctrl + ← / → Next/Previous workspace

🚪 Exit & Reload i3

Shortcut Action
Mod + Shift + e Exit i3 session
Mod + Shift + r Reload i3 config

📊 Polybar i3 Workspace Module (Example)

[module/i3]
type = internal/i3
format = <label-state>
label-focused = %name%
label-focused-background = #268bd2
label-unfocused = %name%
label-visible = %name%
label-urgent = %name%

⚙ Custom Keybindings in i3 Config

# Use rofi instead of dmenu
bindsym $mod+d exec rofi -show drun

# Lock screen
bindsym $mod+Shift+x exec i3lock -c 000000

  • rofi: Application launcher with themes
  • i3lock: Lightweight screen locker
  • picom: Window transparency & shadow
  • feh: Wallpaper setting tool
  • polybar / i3status: Custom status bars
  • flameshot: Screenshot utility
  • i3-gnome-pomodoro: Integrates GNOME Pomodoro with i3 (see Pomodoro Tools Guide)

✅ Summary

i3wm is a minimalist yet powerful tool. Learning the keyboard shortcuts will make your workflow smooth and productive. Combine it with tools like rofi, polybar, and gnome-pomodoro to build your ultimate Linux setup.

Happy tiling! 💻✨

]]>
The Ultimate Guide to Pomodoro Tools 2025-07-14T15:10:00+08:00 HeWen https://ehewen.com/blog/pomodoro-tools The Pomodoro Technique has become a go-to productivity method for developers, writers, and students alike. This article presents the best Pomodoro apps across platforms, and also provides a complete GNOME Pomodoro + i3 + Polybar setup for Linux power users.


🍅 What Is the Pomodoro Technique?

Developed by Francesco Cirillo, the basic cycle is:

  • 25 minutes focused work + 5 minutes break (one Pomodoro)
  • After 4 Pomodoros, take a longer break
  • Helps reduce distractions, improve time awareness and output

🖥 Desktop

Tool Name Platforms Highlights
Pomatez Windows / macOS / Linux Modern UI, offline, task management
GNOME Pomodoro Linux (GNOME / i3) GNOME-native, works well with i3
Tomighty Windows / macOS (legacy) Minimal, open source, classic UI
Pomolectron Windows / macOS / Linux Electron-based, desktop notifications

📱 Mobile

Tool Name Platforms Highlights
Forest iOS / Android Gamified focus—grow trees while you focus
Focus To-Do iOS / Android / Desktop To-do list + Pomodoro + charts
TickTick iOS / Android / Web Task manager with built-in Pomodoro
Focus Plant iOS / Android Cute plant-growing gamification

🧪 Self-hosted / Open-source (Great for Linux/i3 Users)

1. Pomatez

  • ✅ Cross-platform & offline
  • ✅ Task labels, intervals, sounds
  • ✅ Lightweight, modern design

2. GNOME Pomodoro

  • ✅ Wayland and X11 support
  • ✅ Integrates with i3 and Polybar via D-Bus
  • ✅ Custom sounds and break behavior

🔧 Setup Guide: i3 + GNOME Pomodoro

✅ Install

sudo pacman -S gnome-pomodoro  # Arch
# or Debian/Ubuntu
sudo apt install gnome-pomodoro

✅ i3 Keybinding

Add this to ~/.config/i3/config:

# Launch GNOME Pomodoro
bindsym $mod+Shift+p exec --no-startup-id gnome-pomodoro

Press Mod + Shift + P to open the UI.


📟 Integrate GNOME Pomodoro with Polybar

✅ Requirements

sudo pacman -S i3-gnome-pomodoro

✅ Script: ~/.config/polybar/scripts/pomodoro.sh

#!/bin/bash

STATUS=$(i3-gnome-pomodoro status --always 2>/dev/null || echo "N/A")
echo "🍅 $STATUS"

Make it executable:

chmod +x ~/.config/polybar/scripts/pomodoro.sh

✅ Polybar Module

Add to ~/.config/polybar/config.ini:

[module/pomodoro]
type = custom/script
exec = ~/.config/polybar/scripts/pomodoro.sh
interval = 5
label = %output%

Then include it:

modules-right = pomodoro ...

✅ Quick Recommendations

Scenario Recommended Tools
Cross-platform & offline Pomatez
Linux + tiling WM GNOME Pomodoro + Polybar
Gamified motivation Forest / Focus Plant
Task planning + timer TickTick / Focus To-Do
Minimal focus Tomighty / Tomato Timer

✍️ Summary

Pomodoro isn’t just about a timer — it’s about training attention and rhythm.

With the right tool and setup, you can create a distraction-free and enjoyable work environment.

]]>
The Complete Guide to SEO Optimization with Jekyll 2025-07-14T11:23:00+08:00 HeWen https://ehewen.com/blog/jekyll-seo When building a blog or static site, Search Engine Optimization (SEO) is a critical step in attracting organic traffic. Although Jekyll is a lightweight and efficient static site generator, it doesn’t offer complex SEO features out of the box. Fortunately, with proper configuration and structure, you can achieve excellent SEO performance.

This article walks you through how to optimize your Jekyll site for SEO.


1. Enable the jekyll-seo-tag Plugin

This is the official SEO plugin for Jekyll. It automatically adds meta tags such as title, description, Open Graph, Twitter Card, and more.

Installation

Add the plugin in your _config.yml:

plugins:
  - jekyll-seo-tag

Then include the following in your site’s <head> section:

{% seo %}

Example Output:

<title>The Complete Guide to SEO Optimization with Jekyll | My Blog</title>
<meta
  name="description"
  content="A guide on using jekyll-seo-tag and structural optimization to build an SEO-friendly Jekyll site."
/>
<meta
  property="og:title"
  content="The Complete Guide to SEO Optimization with Jekyll"
/>
<meta name="twitter:card" content="summary" />
...

2. Set Up robots.txt and sitemap.xml

robots.txt

This file tells search engines what they are allowed to crawl:

User-agent: *
Disallow:
Sitemap: https://yourdomain.com/sitemap.xml

Place it in the root of your site.

sitemap.xml

Use the official plugin to generate it automatically:

plugins:
  - jekyll-sitemap

A sitemap.xml file will be generated at the root of your site, listing all pages for search engines to crawl.

For multilingual sites, consider using a custom sitemap. Example below:

👉 See multilingual sitemap example: Link to your article


3. Add Page Descriptions (excerpt)

By default, Jekyll doesn’t include a meta description. You can add one manually at the beginning of each post:

---
title: The Complete Guide to SEO Optimization with Jekyll
excerpt: A guide on using jekyll-seo-tag and structural optimization to build an SEO-friendly Jekyll site.
---

When combined with jekyll-seo-tag, the excerpt will be used as the meta description.


4. Semantic HTML and Clear Structure

  • Use semantic tags like article, section, nav, etc.
  • Only use one <h1> per page, and follow hierarchy with <h2> to <h4>
  • Use <nav> for navigation and breadcrumbs
  • Add title attributes to links and alt attributes to images

5. Multilingual SEO (e.g., jekyll-polyglot)

If you’re using a multilingual plugin like jekyll-polyglot, keep these in mind:

  • Use <link rel="alternate" hreflang="xx"> to indicate language versions
  • Generate language-specific URLs in your sitemap.xml
  • Keep consistent folder structures like / and /en/ to avoid confusion

Example:

<link rel="alternate" hreflang="zh" href="https://yourdomain.com/" />
<link rel="alternate" hreflang="en" href="https://yourdomain.com/en/" />

6. Image Optimization

  • Use WebP format (or compressed JPEG/PNG)
  • Add alt attributes for image search
  • Use semantic file names like jekyll-seo-example.webp

7. Social Media Previews (Open Graph / Twitter Cards)

jekyll-seo-tag handles most of this, but you can also customize in front matter:

image: /assets/images/jekyll-seo-preview.png
twitter:
  card: summary_large_image

This ensures your post displays nicely when shared on Twitter, Weibo, Telegram, etc.


8. Analytics Integration (Optional)

Recommended tools:

  • Google Search Console (crawl and indexing reports)
  • Bing Webmaster Tools
  • Baidu Webmaster Tools (for Chinese traffic)
  • Umami / Cloudflare Analytics / Plausible (privacy-friendly analytics)

Summary Checklist ✅

Item Done
Install jekyll-seo-tag
Configure robots.txt
Configure sitemap.xml
Add excerpt descriptions
Optimize images
Set social share images
Add hreflang for languages
Use semantic HTML5

A well-structured and SEO-optimized Jekyll blog can bring you long-term organic traffic and improve your visibility in search results. To further improve performance, consider external SEO strategies like backlinks, content marketing, and social sharing.

]]>
How to Use Fail2Ban for Server Protection 2025-07-14T08:51:00+08:00 HeWen https://ehewen.com/blog/fail2ban-security-guide

✅ What is Fail2Ban?

Fail2Ban is a log-based intrusion prevention tool. It automatically scans log files (such as Nginx, SSH, etc.), detects suspicious behaviors like failed logins or 404 attacks, and blocks offending IPs via iptables or other firewall actions.


🔧 Install Fail2Ban

Ubuntu / Debian

sudo apt install fail2ban

CentOS / RHEL

sudo yum install epel-release
sudo yum install fail2ban

🧱 Core Components

Type Description
jail The service to monitor (e.g. sshd, nginx)
filter Regex rules to match malicious patterns
action How to respond to a match (ban IP, write file)

🧪 Example 1: Blocking Web Scanners (Multiple 404s)

Filter: /etc/fail2ban/filter.d/nginx-404.conf

[Definition]
failregex = ^<HOST> -.*"(GET|POST).*(HTTP|HTTPS)/1.[01]" 404
ignoreregex =

Jail: /etc/fail2ban/jail.d/nginx-404.conf

[nginx-404]
enabled  = true
filter   = nginx-404
logpath  = /var/log/nginx/access.log
maxretry = 10
findtime = 600
bantime  = 3600
action   = iptables[name=nginx-404, port=http, protocol=tcp]

🧪 Example 2: Prevent Brute-Force on Basic Auth (401)

Nginx configuration:

location /admin/ {
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

Filter: /etc/fail2ban/filter.d/nginx-http-auth.conf

[Definition]
failregex = ^<HOST> -.*"(GET|POST).*(HTTP|HTTPS)/1.[01]" 401
ignoreregex =

Jail: /etc/fail2ban/jail.d/nginx-http-auth.conf

[nginx-http-auth]
enabled  = true
filter   = nginx-http-auth
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime  = 3600
action   = iptables[name=nginx-auth, port=http, protocol=tcp]

💡 Alternative: Use deny in Nginx (no iptables)

For containers or Kubernetes environments, you can write bans directly into an Nginx file:

[Definition]
actionban   = /bin/bash -c 'echo "deny <ip>;" >> /etc/nginx/denylist.conf && nginx -s reload'
actionunban = /bin/bash -c 'sed -i "/deny <ip>;/d" /etc/nginx/denylist.conf && nginx -s reload'

Nginx config:

include /etc/nginx/denylist.conf;

error_page 403 /403.html;
location = /403.html {
    root /usr/share/nginx/html;
    internal;
}

⏱ Remaining Ban Time Viewer

Fail2Ban doesn’t provide a direct command to view remaining ban time. The following script estimates it using the log and ban duration:

📄 Script: fail2ban-ban-remaining.sh

#!/bin/bash
# fail2ban-ban-remaining.sh
# Show remaining ban time for an IP

if [ $# -ne 2 ]; then
  echo "Usage: $0 <jail-name> <ip-address>"
  exit 1
fi

JAIL="$1"
IP="$2"
LOG="/var/log/fail2ban.log"
BANTIME=3600  # Update this to match your jail config

ban_time=$(grep "Ban $IP" "$LOG" | grep "\[$JAIL\]" | tail -1 | awk '{print $1" "$2}')
if [ -z "$ban_time" ]; then
  echo "No ban record found for $IP in jail $JAIL"
  exit 2
fi

ban_epoch=$(date -d "$ban_time" +%s 2>/dev/null)
now_epoch=$(date +%s)
elapsed=$(( now_epoch - ban_epoch ))
remaining=$(( BANTIME - elapsed ))

if [ $remaining -le 0 ]; then
  echo "Ban expired for $IP"
else
  echo "IP $IP was banned at $ban_time"
  echo "Remaining ban time: $remaining seconds ($((remaining/60)) min)"
fi

Usage:

chmod +x fail2ban-ban-remaining.sh
./fail2ban-ban-remaining.sh nginx-http-auth 1.2.3.4

🧼 Permanent Ban and Manual Unban

  • Permanent ban: bantime = -1
  • Unban manually:
fail2ban-client set nginx-http-auth unbanip 1.2.3.4

📁 References


You can integrate Fail2Ban with firewalls like iptables, nftables, or Kubernetes ingress controllers to form a layered defense strategy.

]]>
Integrate golangci-lint into GitHub PR Comments Using Reviewdog 2025-07-12T23:30:00+08:00 HeWen https://ehewen.com/blog/reviewdog 🐶 Integrate golangci-lint into GitHub PR Comments Using Reviewdog

In modern collaborative development, automated code review is a key factor in improving team efficiency. Ideally, we want a linter like golangci-lint to run automatically when a Pull Request is created and provide direct feedback in GitHub PR comments. This is exactly what reviewdog is made for.

This article explains how to combine golangci-lint with reviewdog using GitHub Actions.


🧰 Tool Overview

  • golangci-lint: An all-in-one linting tool for Go that supports many linters (like govet, errcheck, etc.).
  • reviewdog: A tool that supports various linters and posts the results as comments in GitHub PRs.

📦 Quick Start

1. Create .github/workflows/lint.yml

name: Lint with golangci-lint and reviewdog

on: [pull_request]

jobs:
  golangci-lint:
    name: 🧪 Lint Go Code
    runs-on: ubuntu-latest

    steps:
      - name: 📥 Checkout code
        uses: actions/checkout@v4

      - name: 🔧 Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.21'

      - name: 🧹 Run golangci-lint with reviewdog
        uses: reviewdog/action-golangci-lint@v2
        with:
          github_token: $
          golangci_lint_flags: '--timeout=3m'
          reporter: github-pr-review # Alternatives: github-pr-check / github-check
          level: warning

🎯 Result Preview

In your PR, reviewdog will automatically annotate lint issues directly on the code lines, like this:

reviewdog pr example


🛠 Configuration Options

Parameter Description
github_token GitHub token, usually secrets.GITHUB_TOKEN.
reporter Output type: github-pr-review (inline), github-pr-check, github-check
level Severity level: info, warning, error
golangci_lint_flags Flags passed to golangci-lint, such as config files or filtering rules.

📁 Project Structure Example

.
├── .github/
│   └── workflows/
│       └── lint.yml       # reviewdog integration
├── .golangci.yml          # golangci-lint config
├── main.go
└── go.mod

Sample .golangci.yml

run:
  timeout: 3m
linters:
  enable:
    - govet
    - errcheck
    - staticcheck
    - unused
issues:
  exclude-rules:
    - path: _test\.go
      linters:
        - errcheck

✅ Tips

  • reviewdog/action-golangci-lint automatically installs golangci-lint, no manual setup required.
  • reporter: github-pr-review is ideal for inline annotations during code review.
  • Works with private repos as long as secrets.GITHUB_TOKEN is configured.

🧩 Further Reading


🧠 Conclusion

By integrating golangci-lint with reviewdog in your GitHub PR workflow, you can provide developers with immediate feedback on code style and potential bugs, ultimately improving code quality and team collaboration.

]]>
Code Review Best Practices 2025-07-12T23:20:00+08:00 HeWen https://ehewen.com/blog/code-review Code Review Best Practices

“Code Review is not just about finding bugs — it’s about learning, growing, and building better software together.”

Why Do Code Reviews?

Code reviews are not just for catching bugs. They improve code quality, promote knowledge sharing, reduce technical debt, enforce coding standards, and foster a strong engineering culture within the team.


1. Fundamental Principles

✅ 1. People First – Be Kind and Respectful

  • Review the code, not the person — never make it personal.
  • Use suggestive and collaborative language, such as:
    • “Maybe we could write this in a clearer way?”
    • “Have we considered the case when xxx happens?”

✅ 2. Stay Goal-Oriented

  • Focus: Code correctness > performance > structure > style.
  • Don’t get stuck on personal preferences — follow team conventions.

✅ 3. Small Changes, Small Reviews

  • Avoid submitting massive changes all at once.
  • Ideal PR size: under 200 lines is best (Google’s recommendation), never exceed 400 lines.

2. Suggested Code Review Process

🧠 Author’s Responsibilities:

  • Self-review first: Run linters and tests before submission.
  • Write a clear PR description: Explain the motivation, context, and key changes.
  • Organize commits logically: Group by feature or purpose.
  • Highlight critical parts: Use comments to guide reviewers to important areas.

👀 Reviewer’s Responsibilities:

  • Read the PR description and any related documentation.
  • Review across multiple levels:
    1. Does it meet the functional/requirement goals?
    2. Are there obvious bugs or unhandled edge cases?
    3. Is the code readable and maintainable?
    4. Does it disrupt existing structure? Introduce duplication?
    5. Are tests covering key logic?
  • Provide constructive feedback:
    • Point out issues, but suggestions are even better.
    • Approve if acceptable, or request changes if not.

3. Coding Style Recommendations

  • Follow project conventions (e.g., gofmt, eslint, clang-format).
  • Use clear, descriptive names — avoid abbreviations.
  • Avoid magic numbers, copy-pasted code, and long functions.
  • Use comments appropriately — they should complement clean code, not replace it.

4. Common Code Review Pitfalls

❌ Pitfall ✅ Better Practice
Over-focusing on minor style issues Let linters handle style; reviewers focus on logic
Submitting massive PRs Break them down — one problem or feature per PR
Reviewer gives feedback without context Read the PR description and related docs first
Harsh or vague comments Be polite and provide concrete suggestions
Ignoring tests or edge cases Review boundary conditions, error handling, and test coverage

Tool Purpose
GitHub/GitLab PR/MR Mainstream code review platforms
ReviewDog / Danger CI-integrated review automation
pre-commit / husky Pre-submit format and lint checks
Codecov / Coveralls Test coverage analysis
SonarQube Static analysis & code quality

6. Summary

Efficient code reviews are not just about catching issues—they’re about improving team collaboration and code quality. With a positive and constructive review culture, team members learn from each other and grow together. Remember:

🔁 “Code review is an ongoing conversation — not a debate.”


📚 Further Reading

]]>