正在顯示「 2025 年 」的所有結果
4月 25, 2025 C# WPF
### 先去 CODE

可以通過引入並呼叫 `SetThreadExecutionState` 去達成防止電腦進入休眠及睡眠模式。

```c#
/**
 * application main window
 */
public partial class MainWindow : Window {

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);

    [FlagsAttribute]
    public enum EXECUTION_STATE : uint {
        ES_AWAYMODE_REQUIRED = 0x00000040,
        ES_CONTINUOUS = 0x80000000,
        ES_DISPLAY_REQUIRED = 0x00000002,
        ES_SYSTEM_REQUIRED = 0x00000001,
    }
    /**
     * constructor
     */
    public MainWindow() {

        // initialize component
        InitializeComponent();

        // set startup event handler
        App.Current.Startup += new StartupEventHandler((sender, e) => {

            // keep screen awake
            SetThreadExecutionState(EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
        });

        // set exit event handler
        App.Current.Exit += new ExitEventHandler((sender, e) => {

            // restore screen state
            SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
        });
    }
}
```
4月 23, 2025 19 Things


### 大阪

自從上次西藏之旅後,已經很久沒有出國旅行了。趁著這次復活節假期,終於有機會來到大阪。不過,近來新聞頻傳,甚至有所謂的預言家聲稱,2025年4月至8月日本可能發生「南海海槽大地震」,導致許多人對前往日本旅遊卻步。出發前,我心裡也有些忐忑,但轉念一想:「現在不去,難道要等到地震後才去嗎?」於是,最終還是決定按原計劃出發!  

這次的旅程以大阪為中心,入住的是位於道頓堀河畔的**難波道頓堀假日酒店**。酒店地理位置極佳,無論是前往心齋橋、難波,或是道頓堀美食街都非常方便。唯一的缺點是周邊有不少牛郎店,十字路口常有年輕人在招攬客人,熱情邀請路人去他們的俱樂部飲酒作樂。不過,他們通常不會主動搭訕外國遊客,因此對觀光客的影響並不大。  

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjC8KyOL6ru9wbDpgUkmCNRF3kknocbbuVZDJxO31UR4EJAn9ep_4cjv6XsV1tnTJChqFUsHZns7UUBgwIq5eK4nwMlWyLnGUxlwy4rC4MlB5YL5LTIQtYNWGYLXyjM6-QbXDg0p1TIxg80sNnTqE5M5wvd3f7CMf5cQmCT1cBl72EKz2vmr-CHZxIrv30/s1600/70cfc6cc-a86f-4c6e-8ea4-42e8c16b8a8d.jpg '間房唔錯 都大大地可以放到個喼')

### DAY 1 : 到著

這次搭乘的是國泰航空,早上八點多起飛,抵達關西機場時已是當地時間下午一點多。接著轉乘南海電鐵前往難波站,再步行至道頓堀,抵達酒店時已接近下午四點。由於一路上只吃了飛機餐,早已飢腸轆轆,於是立刻衝去附近的**一蘭拉麵**填飽肚子——畢竟到了晚上,排隊人潮可是相當驚人的!  

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYg0GC3_qLLXO-EQ7xGvwHrLyxGfXLswaTYvi-pJRW3I23saXEhIpJGgi2suzffiXH7AyoKUaPKch3RiiEj17T69Vwk_NXb1XqpgdfoSldjU3PCw_oi_6Lads7rmBXuc0og4VeDKQqmgPercgveX957KCa80ieZvKmvu2IHt1MU-Md9KjUp6pRbHmY8-4/s1600/IMG_3915.HEIC '點餐')

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsK1fLtpCp06lGCfdp7yKAdG6VVNFFgAAlc9xttpqjC-8TXDXjjL8jiBddPfWkyMzfvGTCB2RXMGDm4c3lfX3My7hh33YwMFwOc8t0YcGwR4DHgxJVWYNhQHKDgZESzhstUjNl9gbFfRE9kuJygNseVstJaIH7O9Bl5FAc5tz1qwAXRClU0Bl2FO3n1TQ/s1600/IMG_3924.HEIC '食返個拉面先 真係好肚餓')

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9JsqfypTn1gka2P5RxImXgZlDsurMAEPoIhJ-IscriHStDSxPtG6oYxOFdryldy0kZfh8zrrq0B7VOIKQ4ZXsCEaeyyu2E7zDIVUBHCkrcENLFRRFcwobhahXGoHbCuIEKZmITyfg-ifE7bqtIhlfG5Qq6n4-FYf2FPlCKf74ikv7kcwwfvgtj34H_K0/s1600/IMG_3923.HEIC '叫左個多面 食完本身個面之後可以放呢個碟上去上面 order here 度,職員就會幫你加多個面')

飽餐一頓後,我們直奔日本橋電器街(俗稱「大阪的秋葉原」),想看看有沒有新推出的動漫周邊或有趣的玩意。結果,光是夾糖機就玩了將近一小時,戰利品是一大堆巧克力,心情大好!途中還經過一家扭蛋店,立刻開啟「代購模式」,一邊拍照一邊幫朋友挑選。沒想到,這一扭就是兩、三個小時,直到店家關燈打烊,我們才依依不捨地離開。  

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXnHp6yuvpTFEhWQFB1Rd77xm3hrp_znsG2EPIx9JfWCdqkqXGcoMqWRQJsLdbgP4At17SYUNXovEQ6vMPWbgKPfa2_Gyc1JF866A-RXC0Qk53E6qF4Zk4bqEEcfD1ArnHm_dwaLwkvnVWL4qfzsu105hc6upyFun16ZUMOLmFNibJWE3DvJRyqZJwJXg/s1600/IMG_4008.HEIC '電玩街必經的街角 Taito Station')

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrH9ZHEHLTQPfO8pvfWE0F6iaCvwKJS2raZpITJVEmSe8AiynxZdIGgUGhBuXXH_CU6Znw0sqhz0jkoti9-NTmbF56hj4YR6lJERcNxDeLVwqM5p6H10AYy2Ji9FQpCwLK0QY6XsXi9cZi1lhEv9TxyBmGpgefU9DyW3gB8IKIdOeQvOg4LH6ehy3te4M/s1600/IMG_3928.HEIC '玩左好耐夾糖機')

回到道頓堀時已將近晚上十點,許多店鋪都已打烊。原來不只香港,大阪的商店也關得挺早的!最後,我們隨意找了一家居酒屋解決晚餐。不過,日本居酒屋在晚上十點後通常會加收「座位費」(約350日圓/人),即使不點酒水也會計入帳單。雖然我們不喝酒,但既然來了,還是點了些壽司、炒飯等經典菜色,體驗當地的飲食文化。這一頓下來,總共花了近一萬日圓,但至少填飽了肚子。回到酒店後,疲憊不堪的我們立刻倒頭就睡,畢竟明天還有更多行程等著呢!  

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrfMjlKAfnWrGC9xoKfSr_kLxxAoNocj4ecYmQwTZZmDN0x5C_tREOVwQKg-6lQPum3uz__-f3m5ZKU4ochyphenhyphenUFHwvcSovP1r86eMH5me2nS0sLjlmbgNJQVgmM1tktuGlrR38Nx6qofgsgBj5Q27QQqEuNe59UyvEbZ0VhpPL9TZlcGlmmw1e7Zg9cSY4/s1600/c2bec96d-b49a-4bb8-901c-14b94a65e42d.jpg '人地去居酒屋飲酒 我地去居酒居食野')

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivNDof7i2reW6mlga8XBRuS4LQFj-ZngkNEFMFEKKa0a2K-R0JgaD_mA17rEnD5uXT_VaCT9tOCO2CQEa7cwFZW76GD2_zo_Iq4rsx3OYfmqFMxlX38tMC23xgIVGQeY4GgHrOHJa_pNOcJ7PVgcstM8x4LBYTv9BWjIkvH2C0kwb5JmTRblbeiVcwTgA/s1600/IMG_4014.HEIC '食呀食')

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX9w5sdM8Ae_GV8cNHGvf9mstCiSpYsmkyEFpVgUWWXM25iGFF02XzQEvywHI73eUThg-Eqo4rYt9PfTE7SNMSz9fwqqRCY3w9ererGw3U05mo4mDkDchmh8OEvhRhKtss_3-1WrwZ3GP-bt1kTzQJ1BIdzD76nozQTigBCzorcTW8Lgg6Hi8-aV47Wl4/s1600/3631b8ad-a6fd-49bc-bdde-27fcd874cb30.jpg '食呀食')

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxJvhUxDnismrtTWJO8Idz3IVd0RX9TVl-6J2_QHFyqpvGW7rpcmcjhpiq5od1gmAfSSiNsIcGvPYplBRtkxEwbU3zgUo8JXChbRCYzUWK3SCCAmTVjSeZRiHeRoJgce9kAZ4yM9jPtxLMe2z4wIMPyB59TlXsErHgyR7iBf7ytDUPdMM6_ohEcvYmPt4/s1600/IMG_4015.HEIC '食呀食')

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgop2cu3xFIE8Xbn_c9_DIaDN5o8IU7n-e_F3vQcjoZD48QUtVVrfTSH-lko-e-K5RAzFW17CJYZC8csUWxI67RdqbXvujYm5GU0FO91CB7ip9wMnMLqx6ZUklo6QfuDHZaF5VTFBRMb8OUSpD2oKG8M2IoCk3mKVgWyln0mMDiEO4oGqC8aJ7cGhgWSaY/s1600/IMG_4017.HEIC '食呀食')

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyUU_di9hNHx9DAiOfdpZo5GOZdGYFl7jlo-koKDy8HSvi8v49B1aFD-Q2uzPAbEMxW_H2DFsXDQ6aYjSed0ypIpxdAIA3pAvQG3n758V6WqPBfChgu8w0Mxe3khrO8aJvfiQV-Ek5VySI6N-uEOUBMG6YwSWU_P2LqV4z2R-GTnEc1_9QD7bijl-lcCA/s1600/1aa856af-0989-4fbc-b2ef-d70a901493e0.jpg '原來都有唔少朱古力')
4月 22, 2025 C# WPF
### 先去 CODE

下面是 view model

```c#
// 定義一個 private log message 的 ovservable collection (如果用普通 list 的話, wpf 會唔識 reactive update 個 UI)
private ObservableCollection<LogMessage> _logMessages = new ObservableCollection<LogMessage>();

// 再定義一個 public 的 Log messages 用來呼叫 OnPropertyChanged() 通知 wpf update 返個 UI
public ObservableCollection<LogMessage> LogMessages {

	get => _logMessages;

	set {

		if (_logMessages != value) {

			_logMessages = value;

			OnPropertyChanged();
		}
	}
}

```
  
下面是 xaml

```xaml
<ListView ItemsSource="{Binding Path=LogMessages}"
          ScrollViewer.HorizontalScrollBarVisibility="Disabled"
          Grid.Row="1"
          Grid.Column="0">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="Focusable"
                    Value="False" />
            <Setter Property="IsHitTestVisible"
                    Value="False" />
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid Margin="0,0,0,10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto" />
                    <RowDefinition Height="auto" />
                    <RowDefinition Height="auto" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Path=Title}"
                           Foreground="{Binding Path=Color}"
                           FontWeight="Bold"
                           TextWrapping="Wrap"
                           FontSize="14"
                           Grid.Row="0"
                           Grid.Column="0" />
                <TextBlock Text="{Binding Path=Message}"
                           Foreground="{Binding Path=Color}"
                           TextWrapping="Wrap"
                           Grid.Row="1"
                           Grid.Column="0" />
                <TextBlock Text="{Binding Path=LogAt}"
                           Foreground="#999999"
                           TextWrapping="Wrap"
                           Margin="0,5,0,0"
                           Grid.Row="2"
                           Grid.Column="0" />
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
```

這樣就可以輕鬆畫出 listview
  
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHDnqOrMsv-k7UQ1XwRdKniVsScEVFzEcHtZ2BRHYJPyrj1a8oU351yp3C8_4GGXs24J7Ac1WMnTeTGMkoo4-aMqzhfAO05fIKgEpup330JGQgffYNuW_GEPgjEbIhYOlnSxHLIp0mM6QBzc2BQCK2RnnGY9f62g4QdVDbA-LrqBtGMPeJhRs4KHETE5c/s1600/2025-04-22%2014.29.16.png)
4月 22, 2025 C# WPF
### 情況

因為有時我們在按下按鈕時,背後 command 的工作並不是在 ui thread 上執行,所以 canExecute 沒有辦法即時回應畫出結果到 UI 上。

### 解決

在 Java 上可以 invalidate ui 去叫 ui component 重新 repaint 過個 UI component。

而 WPF 上都可以叫過差不多的手法去完成,不過是 CommandManager 專用的方法。

```c#
// run this to invalidate ui
CommandManager.InvalidateRequerySuggested();
```
4月 04, 2025 C# PowerShell
### 出現問題

最近要為一個沒有 API 的平台制作 Automation,搵過好多唔同既方法,最後發現 stackoverflow 竟然係因最多人推介用 `PowerShell` 來解決,在先前係用過 "按鍵精靈" 同埋 "AutoIT" 的經驗,既然今次又要做 Automation 就不如用啲未用過既方法來開始。

### PowerShell

經過一陣子的研究發現,原來 `PowerShell` 都係需要引入 .dll 程式庫來實現一些原生的 window 功能,而今次我要實行的是模擬 Mouse 及 Keyboard 的動作,對 Web 介面進行 Automation。簡單的追可以通過 WinForms 去實行。

```ps1
// 引入 System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms
```

然後可以通過直接寫入 C# 代碼到 PowerShell 中即時運行。

以下的代碼是引入了外部的 `user32.dll` 以實行對 Mouse 及 Keyboard 的模擬動作。

```ps1
Add-Type -TypeDefinition @"
using System;
using System.Threading;
using System.Runtime.InteropServices;

/**
 * mouse class
 */
public class Mouse {
    
    // mouse action
    public const uint MOUSEEVENTF_LEFTDOWN = 0x02;
    public const uint MOUSEEVENTF_LEFTUP = 0x04;
    public const uint MOUSEEVENTF_RIGHTDOWN = 0x08;
    public const uint MOUSEEVENTF_RIGHTUP = 0x10;

    [DllImport("user32.dll")]
    public static extern bool GetCursorPos(out POINT lpPoint);

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT {
        public int X;
        public int Y;
    }

    [DllImport("user32.dll")]
    public static extern bool SetCursorPos(int x, int y);

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
}

/**
 * keyboard class
 */
public class Keyboard {

    // keys
    public const byte VK_CONTROL = 0x11;
    public const byte VK_RETURN = 0x0D;
    public const byte VK_A = 0x41;
    public const byte VK_B = 0x42;
    public const byte VK_C = 0x43;
    public const byte VK_D = 0x44;
    public const byte VK_E = 0x45;
    public const byte VK_F = 0x46;
    public const byte VK_G = 0x47;
    public const byte VK_H = 0x48;
    public const byte VK_I = 0x49;
    public const byte VK_J = 0x4A;
    public const byte VK_K = 0x4B;
    public const byte VK_L = 0x4C;
    public const byte VK_M = 0x4D;
    public const byte VK_N = 0x4E;
    public const byte VK_O = 0x4F;
    public const byte VK_P = 0x50;
    public const byte VK_Q = 0x51;
    public const byte VK_R = 0x52;
    public const byte VK_S = 0x53;
    public const byte VK_T = 0x54;
    public const byte VK_U = 0x55;
    public const byte VK_V = 0x56;
    public const byte VK_W = 0x57;
    public const byte VK_X = 0x58;
    public const byte VK_Y = 0x59;
    public const byte VK_Z = 0x5A;
    
    // actions
    public const uint KEYEVENTF_KEYDOWN = 0x0000;
    public const uint KEYEVENTF_KEYUP = 0x0002;

    [DllImport("user32.dll")]
    public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo);
}
"@
```

當引入完上面的代碼後,可以在下面自己加入你想要運行的代碼去呼叫引入的代碼。

```ps1
// Mouse 左鍵按下
[Mouse]::mouse_event([Mouse]::MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);

// Mouuse 左鍵升起
[Mouse]::mouse_event([Mouse]::MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
```

這樣就可以用 PowerShell 呼叫 C# 來逹成你想要的功能了,正因為呼叫的是 C#,所以是機乎所有的 Window 功能也可以通過 PowerShell 去運行,所以要運行 `.ps1` 的時候可能需要另外的權限。
3月 16, 2025 Javascript
### 長話短說

先去 Code 睇一睇先:

```js
// 取得現在月份的第1日
const startOfMonth = moment().startOf('month').format('YYYY-MM-DD hh:mm:ss');

// 取得現在月份的最後一日
const endOfMonth = moment().endOf('month').format('YYYY-MM-DD hh:mm:ss');
```

### Regexp check timezone string

可以用以下的 regex 來檢查傳入的 string 是否為 timezone 時間字串。

只用於檢查基本的 format,不會細緻檢查內容問題。

```js
// 用來 check 基本時間 + timezone 資料
const regex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(|\\.[0-9]+)(Z|[\\+\\-][0-9]{2}:[0-9]{2})$/;
```


### Router 經常壞

話說現在買的 Router 都經常壞,好多時買返嚟一兩年就開始越嚟越慢,初都仲可以 Reset 一下就快返,過多一排就 Reset 完都好快越嚟越慢,甚至有連唔到線嘅情況。呢個問題都持續咗好幾年,唔知幾時開始已經習慣咗兩年換一隻。為咗長遠解決呢個情況,所以決定買隻 Mini PC 返嚟起一隻自己嘅 Router。

### 預備工作

要達成今次嘅目的,要準備以下工具:

- Mini PC一部
- Ubuntu 24.04LTS Desktop
- 普通家用 Router (用來發放Wi-Fi訊號)

#### Mini PC

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglT7t1LfZoQH4GaXyI_q-r9vB9__Rg7HJXO_dRSvK1WSDhw73rALFmDwDGsbGrTrEcaJLAZAkmlPi6_te_V5oXbxrJfbynhNn0koGx0UlWNThtJ21LnCL0cnD1gIvwYavC29vLMPglEPUIamt5IVUE853RtZWwOhmS79jcOjWtkXeaxth62wlVAAorZSA/s1600/ae23c701-495e-4313-ac0a-1a2b86f3a527.jpg 'Mini PC')

今次選用嘅 Mini PC 係 Lenovo 出嘅異能者 (ERAZER) 系列!上網搵咗好耐,就算最細機箱嘅 ITX 都仲係好大,同埋就算用 Desktop CPU 都比較食電,今次選擇嘅 Mini PC 係用 Intel N100 CPU,配埋 16GB RAM 加上 256GB SSD,拎嚟裝個Linux來做Router絕對綽綽有餘,所以除此之外仲會拎佢嚟做埋電視盒子!實行一物多用!最緊要嘅係呢隻機竟然自帶兩個千兆網口,佢嘅設計簡直係天生就係Router材料,加上本身自己仲有Wi-Fi,雖然係Wi-Fi5,不過一般辦公睇片已經非常足夠。Mini PC 的技術配置如下:

- Intel N100 CPU 
- 16GB RAM
- 256GB SSD
- HDMI端口×3 
- USB 3.0端口×3 
- 千兆網口×2
- Audio輸出×1
- 12V電源輸入
- 預裝左 Window 11 (簡體版)

#### Ubuntu 24.04LTS Desktop

今次點解要用 Desktop 版,既然要做埋電視盒子,當然唔可以淨係用 Command Line 介面,而且 Ubuntu Desktop 版都非常易用,裝個瀏覽器就已經可以睇到好多相睇嘅嘢。

既然部機都預裝咗 Windows 11,點解唔用 Windows 11? 因為 Windows 其實好大食,Router 嘅工作好簡單,唔需要裝晒成個 Windows,所以今次裝 Ubuntu 都係裝最 Minimal,務求用最少嘅電力消耗,就可以達到我哋嘅目的。

#### 普通家用 Router

雖然 Mini PC 自帶 Wi-Fi,但係要令 Wi-Fi 發放訊號都要有驅動程式配合,這部份比較麻煩,而且做出嚟效果唔係咁好,所以發放 Wi-Fi 訊號都係留返俾傳統家用 Router 做返。呢度只需要一隻普通能夠發放 Wi-Fi 信號嘅家用 Router 就可以,要注意嘅就只係隻 Router 需要支援 AP 模式(即是有線中繼模式)。

有線中繼模式是指路由器不再需要網路地址轉換(network address translation) 和 DHCP 的功能,只需要發放 Wi-Fi 訊號和做交換器(switch)的功能就可以。

### 開始工作

首先去 Ubuntu 的官方網站 Download Ubuntu 24.04LTS Desktop 版本的 ISO 檔案下來,然後用 Rufus 等等可以將 ISO 檔案寫入到 USB 手指成為可開機 (bootable) 的安裝媒體就可以了。

喺呢度先假設螢光幕前嘅,你已經有一定嘅Linux經驗,所以詳細嘅安裝過程就喺度跳過,直接進入定的部份。

#### 硬件連線

首先我哋要將所有硬件連接好,由網絡供應商的光纖轉譯器輸出的網線插入 Mini PC 的網線口 1,再將 Mini PC 網線口 2 連接到你的家用 Router。連接如以下圖片:

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYu2KafM7ScQpLPmgHsJBaoZbO2r9gXNYfRyonWsb0x6YrJZWsleMyUvt1okPmogtihNki2MTFpauYG0HNP66F8ZfD6ElBiUoNt25E0K3mfya38LoqTDXcNDgqP9GqH0NvJBM9IrO497vJhGP_N5HT4XMs53CThVL8nPBUhEAAIp5wzGaGUX65IDlvaUM/s1600/111.png)

#### 檢查 Network Interfaces

當所有的網線都連接好後,開機進入你的 Mini PC 然後按 `CTRL` + `ALT` + `T` 打開 Terminal,然後輸入以下指令:

```sh
# 用來列出本機的所以 Network Interface (或者都可以叫 Lan Port)
$ ip addr
```

大約會看到以下的圖片:

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqyflOdpRMRyHNI0MS3QsF20wNhgph6ygqlIiqPUPbIUv6QKWm6w3LqBofKvxihoaqz9Kgv3ov8ZaSnESFiwYjE4j_JNk1NRXoDkRZQNh6YybnWcF4GlSPHr2Tfpk2uqmIM6YRPmNalwmADo-DCrBAJ9efXoDxzlkS4ctbZ0EmoiArluFVt5s4biSQzLU/s1600/222.png)

上面圖片顯示左呢部機入面的三個 Network Interface,其實是有 4 個的,還有一個是 Wifi 的 Interface (wlp2s0)。三個 Network Interface 的解讀如下:

- lo (Loopback Interface,即是 localhost 或者 127.0.0.1,是指本身自己)
- enp1s0 (即是乙太網路端口 1,連接著外網)
- enp3s0 (即是乙太網路端口 2,連接著內網)

#### 設定每一個 Network Interface 的地址

每一個 Network Interface 都需要一個 IP 地址才能運作,所以我們需要先設定每一個 Network Interface 的取得地址方法。我們用以下指令修改一下設定檔:

```sh
# 修改 /etc/netplan/50-cloud-init.yaml
sudo vm /etc/netplan/50-cloud-init.yaml
```

變更為以下的內容:

```yaml
network:
  version: 2
  ethernets:
    enp1s0:
      dhcp4: true
    enp3s0:
      addresses: [192.168.5.1/24]
```

上面的 `enp1s0` 設定為使用 DHCP 取得 IP 地址,而 `enp3s0` 則是手動設定一個 IP 地址 `192.168.5.1` 和 Submask 為 `255.255.255.0` (即是前 24bits)。

完成設定後可以輸入下面指令立即套用設定:

```sh
# 套用 netplan 設定
$ sudo netplan apply
```

#### 啟用 Linux 的 IP Forward 功能

Linux 系統本身已經有IP地址轉換的功能,但是預設的情況下是關閉的,我們可以透過修改配置文件來啟用這個功能,方法如下:

```sh
# 修改 /etc/sysctl.conf
$ sudo vi /etc/sysctl.conf
```

然後找出下面這一句,把最開始的 `#` 號刪除:

```sh
net.ipv4.ip_forward=1
```

完成設定後可以輸入下面指令立即套用設定:

```sh
# 套用設定
$ sudo sysctl -p
```

#### 啟用 iptables 的 NAT 功能

要實現路由器功能,我們還需要使用 Linux 內建的工具 iptables 來幫我們把IP地址進行轉譯工作。可以輸入以下指令來實現:

```sh
# 把網段 192.168.5.0/24 傳出的數據包的 source (IP) address 都偽裝成為 enp1s0 的 IP 地址
sudo iptables -t nat -A POSTROUTING -s 192.168.5.0/24 -o enp1s0 -j MASQUERADE
```

上面的意思是指,修改 `nat` 表格,把所有 IP 來自 `192.168.5.0/24` 的數據包 (Packet) 都轉發到 `enp1s0` 這個 Network Interface 上。這樣內網的地址 (例如內網有一部 PC IP 地址是 `192.168.5.10/24` 時) 就能經由 Mini PC (Default Gateway) 進行數據包轉譯連接上 Internet 了。

### iptables 設定

既然要用 MiniPC 來做 Router 功能,都一定要設定最少限度的 Firewall 來防止有外來的人可以 SSH 入去你的 MiniPC,以下會列出一些基本會到用的指令 :

```sh
# 列出所有現有的 iptables rules
$ sudo iptables -vL --line-number

# 把進入本機的數據包全部掉棄
# 因為先前 (如果中間沒有 match 到任何一條 rules)
$ sudo iptables -P INPUT DROP

# 把推送的數據包全部掉棄 (如果中間沒有 match 到任何一條 rules)
$ sudo iptables -P FORWARD DROP

# 把送出的數據包全部批准 (如果中間沒有 match 到任何一條 rules)
$ sudo iptables -P OUTPUT ACCEPT

# 我們起一條 Chain (MY-FIREWALL) 用來裝住重覆使用的 rules
$ sudo iptables -N MY-FIREWALL

# 加入規則到 MY-FIREWALL, 把來自 enp1s0 (WAN) 介面進入的數據包 (如果是已經由內部連接的話), 就可以通過
$ sudo iptables -A MY-FIREWALL -i enp1s0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# 加入規則到 MY-FIREWALL, 把來自 enp3s0 (LAN) 介面進入的數據包設定為通過
$ sudo iptables -A MY-FIREWALL -i enp3s0 -j ACCEPT

# 加入規則到 MY-FIREWALL, 把來自 lo (Localhost) 介面進入的數據包設定為通過
$ sudo iptables -A MY-FIREWALL -i lo -j ACCEPT

# 把 MY-FIREWALL 套用到進入本機的數據包
$ sudo iptables -A INPUT -j MY-FIREWALL

# 把 MY-FIREWALL 套用到進入推送的數據包
$ sudo iptables -A FORWARD -j MY-FIREWALL
```

另外介紹多幾個關於 `iptables` 除錯 (debug) 相關的 command,可以幫助大家快速脫離新手村。

```sh
# 為 rule 加入 comment
$ sudo iptables -I INPUT -i enp1s0 -j DROP -m comment --comment "drop all incomming connnection from wan"

# 用來 log 低經過的 traffic
# log level 6 是 info 級別
# log prefix 就可以等你用 grep command 來 filter 走你想要看到的 log messages
$ sudo iptables -I INPUT -j LOG --log-level 6 --log-prefix "MY-CUSTOM-LOG: "

# 例如 log 低由 enp1s0 進入 tcp port 80 的 traffic
# 可以幫助你快速知道你所打的規則正確或者錯誤
$ sudo iptables -I INPUT -i enp1s0 -p tcp --dport 80 -j LOG --log-level 6 --log-prefix "MY-CUSTOM-LOG: "

# 另外你需要開多一個 terminal 來查看即時的 log 實況
$ tail -f /var/log/syslog

# 或者其他 version 的 linux 可能要用下面呢個 command
$ journalctl -f
```

#### 如果你的 MiniPC 有 Docker 運行

因為 Docker 句會使用到 `iptables` 來進行路由分隔和攔截,如果要配入 Docker 使用的話就需要進一些修改,以達到可以和 Docker 一起共存。

上面最後 `sudo iptables -A FORWARD -j MY-FIREWALL` 這一句不要運行,如果運行了可以用下面這句去清除 :

```sh
# 把 MY-FIREWALL 從推送中移除
$ sudo iptables -D FORWARD -j MY-FIREWALL
```

然後我們可以依照 Docker 的方法去設定 `iptables` :

```sh
# 建立一個叫 DOCKER-USER 的 Chain
$ sudo iptables -N DOCKER-USER

# 把 MY-FIREWALL 套用到進入 DOCKER-USER 的數據包
$ sudo iptables -A DOCKER-USER -j MY-FIREWALL

# 把 DOCKER-USER 套用到進入推送的數據包
$ sudo iptables -A FORWARD -j DOCKER-USER

# 加入規則到 MY-FIREWALL, 把來自 enp1s0 (WAN) 介面進入的數據包設定為掉棄
# 因為先前 `sudo iptables -A MY-FIREWALL -i enp1s0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT` 已經將已連線的數據包接入了
# 所以如果由 enp1s0 (WAN) 來的數據包到達這裡必需要掉棄, 否則接下來 FORWARD 的 docker rules 會把數據 route 到 docker container 裡面, 外來的 WAN IP 就能直接進入到 docker container 的 port
$ sudo iptables -A MY-FIREWALL -i enp1s0 -j DROP
```

詳細的說明可以參考官網解說 : [https://docs.docker.com/engine/network/packet-filtering-firewalls/](https://docs.docker.com/engine/network/packet-filtering-firewalls/)

這樣便完成了。

### iptables-persistent

經過上面的設定,你的網路已經基本上安全了。

不過 `iptables` 的設定不是永久的,當你 reboot 台電腦時所有 `iptables` 的設定都會清空,如果要永久儲存就必需要用 `iptables-persistent` 來實現。

```sh
# 安裝 iptables-persistent
$ sudo apt-get install iptables-persistent

# 如果要儲存現時的 iptables 規則, 可以這樣輸入
$ sudo netfilter-persistent save

# 如果要載入已儲存的 iptables 規則, 可以這樣輸入 (開機時會自動載入)
$ sudo netfilter-persistent reload
```

這樣就真的設定好 `iptables` 了。

### DHCP

通常家用 Router 都會自帶了 DHCP 的服務,而當你的 Router 使用 AP 模式時這個服務可能會隨之而關閉,所以你需要在你的 MiniPC 上起一個 DHCP 服務來派 IP 給連線到網絡的機器。

#### 安裝 `isc-dhcp-server`

今次我地會用套件 `isc-dhcp-server` 去完成。首先要安裝好先 :

```sh
# 安裝 isc-dhcp-server 
sudo apt install isc-dhcp-server
```

#### 設定 `isc-dhcp-server`

裝好之後我地要先去 `/etc/dhcp/dhcpd.conf` 設定好我們想要的環境,例如 ip range, default dns 等等。

```sh
$ vi /etc/dhcp/dhcpd.conf
```

看看有沒有以下的設定:

```conf
# network domain name
option domain-name "myhome-lan";

# default dns ip address (8.8.8.8 & 8.8.4.4 are google dns service) 
option domain-name-servers 8.8.8.8 8.8.4.4;

# default lease time
default-lease-time 600;

# max lease time
max-lease-time 7200;

# 如果你的 DHC, 是 network 上主要的 DHCP, 就要加入 authoritative
authoritative;

# 設定每一個 network 的屬性
subnet 192.168.5.0 netmask 255.255.255.0 {

    # specify gateway
    option routers 192.168.5.1;

    # specify subnet mask
    option subnet-mask 255.255.255.0;

    # specify the range of lease IP address
    range dynamic-bootp 192.168.5.50 192.168.5.200;
}
```

#### 設定 `isc-dhcp-server` 的生效 network interface

然後我們需要設定 DHCP 生效的 network interface :

```sh
// 修改 /etc/default/isc-dhcp-server
$ vi /etc/default/isc-dhcp-server
```

找出下面這句,然後修改為你想要生效的 network interface :

```sh
INTERFACESv4="enp3s0"
```

#### 啟動並查看 DHCP

當所有東西都設定好後,就可以啟動 DHCP 服務。

```sh
$ sudo systemctl start isc-dhcp-server
```

然後查看運行情況 :

```sh
$ sudo systemctl status isc-dhcp-server
```

如果想查看現在租用出去的 ip 地址,可以用以下兩個方法 :

```sh
# 精簡版
$ dhcp-lease-list

# 詳細版
$ cat /var/lib/dhcp/dhcpd.leases
```

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcbtUMy7K9YR1Y4Qm6BcqeeJlTo_yXQFDAqFSFAzsq1rgVD9xoXDwT6dbsArOuDX2cqgR2pc_1N5_KT1r4WwQhI6WRJuyg8Ams12dCuKNuzC6Y9VJ6wYKptgAORYfy3EqVuz8ISjXmIYBlxNJhlMu9UwAl1uoxECuFBtQ06J0NPv8KpF18tjVtUbX-3f8/s1600/%E8%9E%A2%E5%B9%95%E6%93%B7%E5%8F%96%E7%95%AB%E9%9D%A2%202025-02-28%20192027.png '精簡版')


### 完成

去到這裡你的 mini pc 已經完全設定為了一隻 router。在 intranet 內再加入一個設定為 AP 模設的 wifi router 設可以發放 wifi 訊號了。
2月 21, 2025
### First

Remember the key settings `otherPortsAttributes`

### Setup

Open your vscode and go to File > Preference > Settings.

![](https://blogger.googleusercontent.com/img/a/AVvXsEgftK4lPx4CE-V-uRBiF7BsaE36LJFdkigH7s9JnEEiUW8RWF_o-_y5exTwKYFFDtYIMiFNAKEqw2mOwuaA2SS49iXRH4br9Mcgz79l4aYkGOFm9qomKzZzFSm6FOubhZHSb8DUcmGSeMh8n9vILkPJ6v-sDloXD0lPJxyKA_erhhUybhcErISZyneVMBo)

Then paste the word `otherPortsAttributes` into the filter in settings panel.

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZZMjXMB_wLK8FZUwkjGP3fm5mGWg8RDrwytxe9tTUgjPo9rpBHBAL4bRlx6m34DNpdPLTricpnbpJJpPKUK6vUzDDGcX1WZo-KxBE5iVD_SvksKzgud3asgJZM-7nd_dYBTqZzxdUHNw20nagMFR_M3vLieYALPypwa6MFPUDgwAkO9XtZSSx2OHiue4/s1600/%E8%9E%A2%E5%B9%95%E6%93%B7%E5%8F%96%E7%95%AB%E9%9D%A2%20%281%29.png)

Press `Add Item` button, then select `onAutoForward` in item and set the value to `ignore`.

![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsEiz7lnmIe2YoXlOgI-F8Ghn4hJNX9W9Djrs-d4eikOHpsbtxfJcheG0s1gXcyIQiN4cdFnNfcsydjrBTSdYJk450YUXXmk0sWfM5_amglF9DkP2HdyDymoWvDuKHnT2puz_OmE7_3R5VUwasUpkmcbWGiyKO7NA3mGpKYXQHNiwMQV2vmJ1GpPtwjWo/s1600/%E8%9E%A2%E5%B9%95%E6%93%B7%E5%8F%96%E7%95%AB%E9%9D%A2%20%282%29.png)

No more `Auto Forwarded` ports.

Finished ~
2月 07, 2025
如果安裝時出現 network settings 但又不可能即時處理得到,其實是可以 bypass 的:

1. 按下 SHIFT + F10 叫出 cmd

2. 輸入 OOBE\BYPASSNRO

之後個安裝就會自已 restart,入返去後就可以 bypass network setting 同埋登入 microsoft account 兩個流程了
過去文章
2025 (9)
4 (5)
3 (1)
2 (3)
2024 (25)
11 (3)
10 (3)
9 (1)
3 (18)
2022 (6)
10 (1)
6 (2)
5 (1)
3 (1)
1 (1)
2021 (21)
11 (7)
7 (1)
6 (2)
5 (2)
4 (6)
3 (2)
2 (1)
2020 (92)
12 (1)
11 (2)
10 (4)
9 (10)
8 (5)
7 (1)
6 (3)
5 (1)
4 (4)
3 (25)
2 (7)
1 (29)
2019 (57)
12 (25)
11 (7)
9 (25)