2020年4月24日 星期五

在 AWS EC2 Instance 的 Security group (安全群組) 新增 inbound rule

AWS EC2 instance 預設只有開啟 SSH (埠 22);要新增服務時,除了 instance 內 OS 的防火牆需要設定相關的 inbound rules 之外,instance 的 security group 也要新增 inbound rules,才能夠讓外部存取服務。


本文參考自:

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/authorizing-access-to-an-instance.html

裡面的內文跟現在 2020/04/24 的操作方式 (介面) 有些不同,下面操作步驟根據現在介面說明,但是保留原文參考:

1. EC2 控制台 -> 導航窗格的 INSTANCES / Instances -> 在右邊的窗格選擇要設定的 Instance:左下方 Description 標籤的右邊有 Security groups,可以查看目前的 Security group、inbound rules、outbound rules 等資訊;

In the navigation pane of the Amazon EC2 console, choose Instances. Select your instance and look at the Description tab; Security groups lists the security groups that are associated with the instance. Choose view inbound rules to display a list of the rules that are in effect for the instance.


2. 導航窗格 -> NETWORK & SECURITY / Security Groups -> 在右邊的窗格選擇要設定的 Instance 的 Security group:右邊窗格會出現該 Security group 的 Details 窗格;

In the navigation pane, choose Security Groups. Select one of the security groups associated with your instance.


3. Details 窗格 -> Inbound 標籤 -> Edit inbound rules:會跳轉到 Edit inbound rules 頁面,在該頁面的下方點選 "Add rule";

In the details pane, on the Inbound tab, choose Edit. In the dialog, choose Add Rule, and then choose SSH from the Type list.


4. 在 Type 欄位選擇知名的服務類型 (例如,HTTP,會自動帶入 Port range) 或是自訂服務
(需要自己輸入 Port range),Source 可以選擇 Anywhere 或是 Custom (需要自己選擇或是輸入 CIDR blocks);

In the Source field, choose My IP to automatically populate the field with the public IPv4 address of your local computer. Alternatively, choose Custom and specify the public IPv4 address of your computer or network in CIDR notation. For example, if your IPv4 address is 203.0.113.25, specify 203.0.113.25/32 to list this single IPv4 address in CIDR notation. If your company allocates addresses from a range, specify the entire range, such as 203.0.113.0/24.

For information about finding your IP address, see Before you start.


5. 按下在頁面的右下邊的 "Save rules",儲存新增的 inbound rule。

Choose Save.

2020年3月26日 星期四

Vigor2960 使用者設定檔 - 使用 Active Directory Server 認證

這一篇是如何使用2960搭配AD/LDAP做VPN帳號認證連線一文的補充說明,或許是因為居易希望能夠同時適用 AD/LDAP,因此說明文件中對於 AD (有自己獨特的定義) 的設定說明實在是不夠清楚。


名詞解釋


屬性:

1. 名稱 (Common Name, CN):使用者名稱 (例如,John Smith) 或伺服器名稱,AD 使用的是 CN 而不是下列的 UID;
2. 使用者識別碼 (User ID, UID):使用者登入識別碼 (例如,jsmith) ,通常是 CN 簡化的識別碼;
3. 組織名稱 (Organizational Unit, OU):組織內單位的名稱 (例如,IT);
4. 組織 (Organizational, O):組織的名稱 (例如,ABC.com);
5. 網域元件 (Domain Component, DC):將組織的網域名稱拆成個別的網域元件 (例如,ABC 跟 com),AD 使用的是 DC 而不是 O;
6. 國別 (Country, C):國家名稱,一般較少使用;
7. 相對識別名稱 (Relative Distinguished Name, RDN):是跟目錄樹結構無關的部份,通常 RDN 的值會是 CN 或是 UID;
8. 識別名稱 (Distinguished Name, DN):用來代表一個 LDAP 物件的名稱 / 路徑的絕對位置,每一個 DN 都是由 RDN 跟路徑所組成。



屬性名稱對於大小寫字母沒有區別,但是一般在使用時屬性名稱會使用小寫字母。



屬性的定義:

屬性 (Attribute) = 值 (value),

例如,ou=IT、dc=ABC、或是 dc=com 等。



Vigor2960 AD 認證設定


在 "單一帳號/活動目錄管理" 設定畫面中,有下列幾個項目需要設定:

1. 綁定方式,分為:

1) 匿名模式:"使用 anonymous 匿名帳號登入 AD/LDAP 查詢";網域控制站 (Domain Controller, DC) 應該是沒有在接受匿名帳號查詢的,這個選項不適用於 AD 環境。

2) 簡易模式:"使用空白帳號登入 AD/LDAP 查詢";DC 應該是沒有在接受空白帳號查詢的,這個選項不適用於 AD 環境。

3) 常規模式:"使用指定帳號登入 AD/LDAP 查詢(需設定 Regular DN 和密碼)",只有這項是適用於一般的 AD 環境。可是居易對於這項設定的說明幾乎是沒有。

所以,AD 環境下只能夠選用常規模式


2. 伺服器IP位址:
AD/LDAP 的伺服器 IP,在 AD 環境具有全域目錄 (Global Catalog, GC) 功能 DC 的 IP。


3. 通用名稱識別符:
以什麼屬性做為帳號比對的依據,常見的有 cn 和 uid;AD 使用的是 cn 而不是 uid。



4. 基本識別名稱 (Base DN):
"從哪個基本網域名稱路徑開始查詢認證的帳號",

要從目錄樹狀結構的那裡開始查詢認證的帳號,一般會是 dc,例如,dc=ABC, dc=com;如果認證的帳號全部屬於某個組織單位,也可以加上 ou 成為 ou=IT, dc=ABC, dc=com。



5. Group DN (非必要):
檢查所要認證的帳號是否有在指定的群組內。



6. Regular DN:
"輸入指定的帳號路徑",

這個部份是最難的部份,因為我們通常不會記得 AD 物件的 DN。不過,透過在 DC 的命令提示字元下達 dsquery user -samid 命令可以得到物件的 DN。例如,要利用 AD 上的 VPNusr 這個帳號 (需要先在 AD 上建立這個帳號) 來進行認證查詢,可以利用下列的命令來查詢其 DN:

dsquery user -samid "VPNusr"


可以得到

"cn=VPNusr,ou=IT,dc=ABC,DC=com"


再將查到的 DN (去除隻引號) 填入 Regular DN 欄位。


7. 常規密碼:

認證查詢的帳號 (例如,上例的 VPNusr) 的密碼。



在完成上述的設定後,在 "基本識別名稱" 旁邊會出現按鈕,點進去可以看到 Base DN 下的樹狀結構,如果看不到,表示上面的設定有錯誤。


另外,Vigor2960無法顯示有中文的 DN,當組織單位等屬性有使用中文時,會查不到使用者。









2020年3月16日 星期一

Windows Server 2008 R2 Event Log 無法啟動

事由:使用者無法登入,DNS無法查詢。在登入 DC (Windows Server 2008 R2) 後,在開啟事件檢視器時出現:Event Log 服務未啟動。到 services.msc 啟動 Windows Event Log 出現:Windows 無法啟動本機的 Windows Event Log 服務,錯誤5:存取被拒絕。


解法:

1. 把 C:\Windows\System32\winevt\Logs 裡的檔案先移到其他資料夾;

2. 重設相關的權限 (參考自 https://support.microsoft.com/en-us/help/2751670/we-are-seeing-an-error-where-we-are-unable-to-access-the-security-log):

1) 檢查 C:\Windows\System32\winevt\Logs 的 NTFS 權限 (permissions),eventlog 使用者需要有完全控制的權限;

2) 執行 regedit,檢查 HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Security 的權限,移除 eventlog 的權限;

3) 新增 "本機" 的 "NT service\EventLog" (就是 eventlog ) 的讀取權限到 HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Security;

3. 重新啟動  Windows Event Log 服務。


備註:
因為步驟 1 跟 步驟 2 是同時做的,所以無法驗證是不是只要做步驟 1 或是步驟 2 即可。

在 httpd 上使用 PHP-FPM

在撰寫本文時,使用的是利用 homebrew 安裝的 PHP 7.4.3 (含內建的 PHP-FPM) 以及 httpd 2.4.1。PHP-FPM 適用於重負載 (Heavy-Loaded) 的網站,對於一般在本機上開發 PHP 的開發者,並不一定需要使用 PHP-FPM。通常 PHP-FPM 會配合對於多執行緒處理較佳的 Nginx,而非 httpd。在閱讀本文前,建議先閱讀 PHP 與伺服器之間的應用程式介面以及在 Apache HTTP Server 的多程序處理模組


基礎常識


PHP-FPM (FastCGI Process Manager) 是 PHP 實作快速共用閘道介面 (Fast Common Gateway Interface, FastCGI) 的管理套件。FastCGI 是一個加強版的 CGI 協定,它將 CGI 應用程式包裝起來,利用 FastCGI 伺服器來建立程序以及管理從網頁伺服器傳送來的請求。它不僅保留了 CGI 的好處,而且還提升了 CGI 的性能,適合應用於重負載的網站。


根據 PHP 手冊,PHP-FPM 具有下列的特性

1. 能夠優雅起動或停止的先進程序管理;
2. 能夠根據不同的使用者 ID (uid) / 群組 ID (gid) / 根目錄更改 (chroot) / 環境 (environment) 等啓動工作者 (Worker) 子程序,監聽不同的通訊埠以及使用不同的 php.ini;
3. 標準串流 stdout 以及 stderr 記錄;
4. 操作碼 (Opcode) 快取意外損壞時緊急重新啟動;
5. 支援加速上傳;
6. 慢速記錄 (slowlog):記錄執行異常緩慢的腳本,包含腳本名稱以及 PHP 回溯 (Backtraces),使用 ptrace 系統呼叫或是類似的機制去讀取遠端程序的執行資料;
7. 提供特殊的 fastcgi_finish_request() 函式,能夠在完成請求以及刷新全部資料時,繼續執行耗時的操作 (例如,影音轉檔、統計處理等);
8. 動態或靜態的子程序生成;
9. 和 Apache 模組狀態類似的基本 SAPI 狀態資訊;
10. 以 php.ini 為基礎的組態檔。


安裝


使用 Homebrew 安裝的 PHP 已經包含了 相同版本的 PHP-FPM。

設定 httpd.conf


PHP-FPM 大多使用在重負載 (Heavy-Loaded) 的網站上,因此建議 httpd 的多程序處理模組 (Multi-Processing Module, MPM) 使用 Event 模組 (停用預設的 Prefork 模組)。需要開啟 httpd.conf 的代理 (Proxy) 模組以及 FastCGI 代理模組,讓 httpd 能夠將請求轉傳到 FastCGI 伺服器處理。httpd.conf 需要進行下列的組態設定
LoadModule mpm_event_module lib/httpd/modules/mod_mpm_event.so
LoadModule proxy_module lib/httpd/modules/mod_proxy.so
LoadModule proxy_fcgi_module lib/httpd/modules/mod_proxy_fcgi.so


設定 PHP 檔案的處理器,讓副檔名為 php 的請求傳送到 PHP-FPM 伺服器:
<IfModule mpm_event_module>
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/usr/local/var/run/php/php-fpm.sock|fcgi://localhost/"
    </FilesMatch>
    AddType text/html .php
</IfModule>


上面的組態設定使用了 FilesMatchsetHandler 兩個指令將 PHP 檔案傳送到指定的 Unix 插座 (Socket)。其中,FilesMatch 透過指定的檔案名稱來限制區塊內指令的適用範圍,它可以使用規則運算式 (Regular Expressions) 來表示檔案名稱;setHandler 指定代理要傳遞請求到那一種插座 (Socket),以及插座的識別碼。


PHP-FPM 和 httpd 之間的通訊可以使用網路插座或是 Unix 插座。Unix 插座是一種程序間的通訊機制,它允許在同一台電腦上的兩個程序可以同時開啟一個 Unix 插座來進行數據交換。它使用檔案的位址作為識別碼 (例如上面的 /usr/local/var/run/php/php-fpm.sock),通訊在系統核心進行處理。網路插座是另一種程序間的通訊機制,它允許兩個程序通過同一網路插座來進行數據交換。它使用網路位址加上通訊埠作為識別碼,通訊經由網路堆疊處理。使用網路插座的兩個程序可以是不同電腦上的程序,也可以是同一個電腦上經由 IP 繞回 (Loopback) 介面通訊的兩個不同程序。網路插座透過網際網路協定通訊,可以跨電腦通訊,使用上較為靈活但是額外的處理成本較高,Unix 插座則是相反。


上面的組態檔使用的是 Unix 插座。如果要用網路插座,則處理器的設定要改為:
setHandler "proxy:fcgi://127.0.0.1:9000"


其中,127.0.0.1 為本機的繞回位址,如果 PHP-FPM 跟 httpd 在不同的主機,則要改成 PHP-FPM 主機的 IP 位址;9000 為 PHP-FPM 的監聽埠。


除了使用上述的 setHandler 來進行通訊之外,還可以使用 ProxyPassMatch 或是 ProxyPass。除非有特殊的需求,建議使用FilesMatch 和 setHandler 即可。

PHP-FPM 組態檔 php-fpm.conf


php-fpm.conf 設定組態選項的語法跟 php.ini 一樣。預設的路徑前綴為 /usr/local/var,需要注意的組態選項只有 error_log (其他建議使用預設值即可):
error_log = log/php-fpm.log


注意:
1. 這項設定僅會記錄 PHP-FPM 本身的錯誤訊息;PHP 腳本錯誤會記錄在 php.ini 的 error_log 或是程序池組態檔的 error_log。
2. 可以變更記錄檔的路徑;如果使用 brew services 來啟動 PHP-FPM,記得要修改 /usr/local/opt/php/homebrew.mxcl.php.plist 中 StandardErrorPath 下的字串值,不然每次 brew services 在重啟 PHP-FPM 時,預設的檔案夾內還是會再產生記錄檔。
3. 當啓動 PHP-FPM 時出現類似下列訊息,代表組態檔裡的錯誤記錄指令沒有設定好,或是啟動的是 macOS 內建的 PHP-FPM:
ERROR: failed to open error_log (/usr/var/log/php-fpm.log): No such file or directory (2)



在啟動 PHP-FPM 時需要至少建立一個主程序 (Master Process),每個主程序則會建立多個稱為工作者 (Worker) 的子程序來回應及處理請求;這些子程序會形成一個 PHP 程序池 (A Pool of PHP Processes)。為了方便管理,PHP-FPM 將建立程序池所使用的組態檔獨立出來。程序池組態檔通常會存放在 php-fpm.d 檔案夾裡。在 php-fpm.conf 的最後一行設定會將組態檔包含進來:
include=/usr/local/etc/php/7.4/php-fpm.d/*.conf


會使用 *.conf 是因為 PHP-FPM 的特性之一是可以有多個程序池,以用來處理不同的請求 (例如,當 httpd 有多個虛擬主機時,每個主機可以有自己的 PHP 程序池)。讓每個程序池有自己的組態檔,可以方便進行組態設定。

程序池組態檔 pool_name.conf


PHP-FPM 會依據程序池組態檔的設定值來建立 PHP 程序池。程序池組態檔的檔案名稱一般為程序池名稱加上 conf 副檔名。例如,在 php-fpm.d 檔案夾裡有一個  www.conf  程序池組態檔,它的程序池名稱就是 www。一般在建立程序池組態檔時都會藉由複製或是修改 www.conf 來完成。程序池組態檔需要設定的組態選項有下列幾項:
[userName]

user = userName
group = userGruop
listen = /usr/local/var/run/php/php-fpm.sock
php_admin_value[error_log] = /usr/local/var/log/php-fpm/$pool.error.log


首先,如果要根據不同的 uid 跟 gid 建立程序池,可以利用使用者名稱做為程序池的名稱。監聽 (listen) 的設定需要和 httpd.conf 配合,上例為使用 unix 插座時的檔案位置。如果是使用網路插座,則要更改為下列的設定:
listen = 127.0.0.1:9000


每個程序池可以有自己的錯誤記錄檔。在啟用程序池的錯誤記錄檔前,需要先啟動 log_errors 指令,php.ini 的 error_log 的組態設定將會被取代。最後,更改完後記得將檔名更改為 userName.conf。


在程序池設定檔中有 listen.* 指令用來設定監聽的權限,因為 macOS 是 BSD 衍生系統,所以不用進行設定。另外,程序管理員 (pm) 的相關組態選項,則需要依照使用情況進行調整,不在本文的範圍內。


PHP-FPM 的特性之一是能夠根據不同的 uid/gid/chroot/environment 等啓動工作者 (Worker) 子程序,監聽不同的通訊埠以及使用不同的 php.ini。要讓 PHP-FPM 同時執行多個 PHP 程序池,可以透過多個的程序池組態檔進行設定。


啓動 PHP-FPM


在使用 Homebrew 安裝完 PHP後,因為是伺服器的關係,會在 /usr/local/sbin 檔案夾建立 php-fpm 的符號連結 (Symbolic Link)。/usr/local/sbin 一般並不在 PATH 環境變數中,再加上 macOS 也有內建 php-fpm,如果直接執行 php-fpm 會出現下列的錯誤:
ERROR: failed to open configuration file '/private/etc/php-fpm.conf


透過下列的指令,可以知道執行的並不是 /usr/local/sbin/php-fpm
which php-fpm


因此執行 PHP-FPM 時,需要加上完整的路徑:
/usr/local/sbin/php-fpm


因為 PHP-FPM 預設會在前景執行 (使用的是 nodaemonize 模式),要停止 PHP-FPM 只要在終端機按下 control + c 即可。


比較方便的做法是使用 brew services,將 PHP-FPM 設為登入後自動執行的任務項:
brew services start php


當修改過相關的組態檔,需要重新啓動 PHP-FPM 時,只需要執行:
brew services restart php


2020年3月12日 星期四

PHP 的組態設定

在撰寫本文時,使用的是利用 homebrew 安裝的 PHP 7.4.3 (以及 PHP 內建的 PHP-FPM)、httpd 2.4.1。

提到 PHP 的組態設定,第一個想到的一定是 PHP 的初始化組態檔 -- php.ini,它負責 PHP 各種指令 (Directives) 的預設組態設定。 但是除了 php.ini 之外,PHP 還提供了許多組態設定的方式:當 SAPI 為 Apache 2.0 Handler 時,使用者可以透過 httpd 組態檔 httpd.conf 以及每個檔案夾下的超文件取存 (Hypertext) 設定檔 .htaccess 進行組態設定;當 SAPI 為 CGI/FastCGI 時,從 PHP 5.3.0 開始可以透過使用者組態檔 .user.ini (user_ini.filenamephp.ini 的預設值) 對每一個檔案夾進行組態設定;在執行時期則可以使用組態設定函式 ini_set() 在腳本中進行組態設定。

雖然這些方式都可以進行組態設定,但是可以設定的內容卻不盡相同。PHP 將組態設定指令區分為 5 類 (針對 PHP_INI_* 分成 4 類,在指令列表中多了php.ini only),下表列出了這 5 類可以設定的方式:

分類設定方式
PHP_INI_USER 指令可以在使用者的腳本 (使用 ini_set()) 或是在 Windows registry 設定。從 PHP 5.3 開始,指令可以在 .user.ini 設定。PHP 5.0 開始沒有指令屬於這個分類。
PHP_INI_PERDIR 指令可以在 php.ini.htaccesshttpd.conf 或是 .user.ini (從 PHP 5.3 開始) 設定。
PHP_INI_SYSTEM 指令可以在 php.ini 或是 httpd.conf 設定。
PHP_INI_ALL 指令可以在所有的地方設定。
php.ini only 指令只可以在 php.ini 設定。


PHP 組態設定的指令非常的多,如果有需要可以參考指令列表的介紹。


初始化組態檔 php.ini


當 PHP 啟動時,初始化組態檔 php.ini 會被讀取,並且完成相關的指令設定。在 PHP 與伺服器之間的應用程式介面一文中介紹過:
1)使用 SAPI 模組 (Apache 2.0 Handler) 時,PHP 會在網頁伺服器的程序中執行,不需要針對每個請求啟動一個新的程序,所以 php.ini 只會在網頁伺服器啟動時被讀取一次;但是當更改組態檔的內容後,需要重新啟動網頁伺服器才會生效。
2) 使用 CGI 時,每次收到新的請求時,都要啟動一個執行 php-cgi 的程序,讀取 php.ini;所以在更改組態檔內容後,有新的請求就會生效。
3) 使用 FastCGI 時,FastCGI 伺服器會建立一個主要程序以及多個工作者子程序,不需要針對每個請求啟動一個新的程序,所以php.ini 只會在 FastCGI 伺服器啟動時被讀取一次;更改完組態檔內容後,需要重新啟動 FastCGI 伺服器才會生效。
4) 使用 CLI 時,每次收到新的請求時都要讀取 php.ini;在更改組態檔內容後,有新的請求就會生效。


另外,在使用 CGI/FastCGI 時,如果要對全網站或是部份檔案夾進行設定,分別要在 php.ini 中的 [HOST=www.example.com] 或是 [PATH=/www/mysite] 區塊進行設定。布林 (boolean) 組態指令的開啟值可以是 1、On、True 或是 Yes;關閉值可以是 0、Off、False 或是 No。

httpd 組態檔 httpd.conf 以及 .htaccess


當 PHP 做為 httpd 的模組使用時,使用者可以透過 httpd.conf 以及 .htaccess 進行組態設定。要能夠利用這兩個檔案設定組態,使用者還需要對檔案夾開啟 "AllowOverride Options" 或是 "AllowOverride All" 權限。httpd.conf 可以設定 PHP_INI_ALL、PHP_INI_PERDIR、以及 PHP_INI_SYSTEM 等類型的指令;.htaccess 則只能夠設定 PHP_INI_ALL 以及 PHP_INI_PERDIR。在 httpd.conf 更改 PHP 組態設定需要重啟 httpd,.htaccess 則是在更改組態設定後,有新的請求就會生效。

httpd.conf 可以使用下列四項指令進行組態設定:


指令用途說明
php_value name value用來設定指令的數值。PHP_INI_ALL、PHP_INI_PER。不能用來設定布林組態指令。
php_flag name on|off用來設定布林組態指令。PHP_INI_ALL、PHP_INI_PER。
php_admin_value name value用來設定指令的數值。不能夠在 .htaccess 中設定,任何指令使用php_admin_value 設定後,不能夠被 .htaccessini_set() 覆寫;要清除先前的設定值,值要設為 none。
php_admin_flag name on|off用來設定布林組態指令。不能夠在 .htaccess 中設定,任何指令使用php_admin_value 設定後,不能夠被 .htaccessini_set() 覆寫。


設定範例 1 (httpd.conf):

<ifmodule mpm_prefork_module>
    LoadModule php7_module /usr/local/opt/php/lib/httpd/modules/libphp7.so
    AddHandler application/x-httpd-php .php
    AddType text/html .php
    php_flag display_errors Off
</ifmodule>


上面的設定會將錯誤顯示更改為不顯示 (display_errors Off)。

設定範例 2 (.htaccess):

php_flag display_errors On
php_value error_reporting 32759


範例 1 在 httpd.conf 中使用 php_flag 而不是 php_admin_flag,所以範例 2 可以在 .htaccess 中改寫錯誤顯示的設定為開啟。另外,PHP 常數只可以在 PHP 內使用,無法在 PHP 外 (例如,httpd.conf 或是 .htaccess) 使用。如果要在這兩個檔案使用,必需要使用位元遮罩 (Bitmask) 數值代替。這裡的 32759 代表的是 E_ALL & ~E_NOTICE。

PHP-FPM 組態檔 php-fpm.conf 以及程序池組態檔



適用於 PHP-FPM 的組態檔,設定的方式同 php.ini,詳細的設定請參考

使用者組態檔 .user.ini


從 PHP 5.3.0 開始,當 SAPI 為 CGI/FastCGI 時,PHP 支援基於每個檔案夾的使用者組態 .user.ini;同時將原有 PECL 的 htscanner 延伸套件廢棄。因為每個請求都會由網頁伺服器轉交給 FastCGI 伺服器 (例如,PHP-FPM) 處理,.user.ini 的組態設定方式和 php.ini 相同。當使用 CGI/FastCGI 時,不可以再使用 php_value、php_flag、php_admin_value、以及 php_admin_flag 進行設定,因為這 4 個組態設定指令是專門提供給 PHP 模組 (Apache 2.0 Handler) 使用的,而當採用 CGI/FastCGI 時,httpd 就不會再載入這個模組。

如果檔案夾中的 .htaccess 中有使用 php_value 或是 php_flag,在瀏覽時該檔案夾內的網頁時會出現 500 Internal Server Error。在 httpd 的 error log 則會出現:

/temp/.htaccess: Invalid command 'php_flag', perhaps misspelled or defined by a module not included in the server configuration


同樣地,當沒有使用 PHP 模組時,不可以在 httpd.conf 使用 php_value、php_flag、php_admin_value 或是 php_admin_flag 進行組態設定。



注意:
1. .user.ini 存放在公開的檔案夾中,它的內容可以被使用者讀取,不要在檔案內進行敏感性的設定。建議可以在 .htaccess 禁止使用者讀取:
<Files ".user.ini">
    Require all denied
</Files>


2. PHP-FPM 的程序池裡的工作者程序需要一小段時間才能夠全部都讀取到重新設定後的 .user.ini (user_ini.cache_ttl 的預設值是 5 分鐘),如果要設定值立刻生效,可以重新啟動 PHP-FPM。

組態設定函式 ini_set()


使用組態設定函式 ini_set() 進行組態設定是最靈活,但是可以設定的指令最少的方式。在腳本中使用 ini_set() 對特定的組態選項進行設定後,組態選項將會在腳本執行期間保留此新值,並且在腳本結束後恢復原來的值。因此可以根據腳本執行上的需求,對每個腳本進行不同的組態設定。ini_set() 只能夠設定 PHP_INI_ALL 類型的指令 (因為唯一的 PHP_INI_USER 指令 tidy.clean_output 從 PHP 5.0 開始變成了PHP_INI_PERDIR 了)。

ini_set() 的函式定義如下:
ini_set ( string $varname , string $newvalue ) : string


如果設定成功會傳回設定選項的舊值,失敗則會傳回 FALSE。

範例 3:
<?php
declare(strict_types = 1);

echo ini_get('display_errors');
if (!ini_get('display_errors')) {
    ini_set('display_errors', '1');
}
echo ini_get('display_errors');
ini_set('error_reporting', '32759'); //32759 為 E_ALL & ~E_NOTICE 的位元遮罩值


範例 3 會使用 ini_get() 檢查錯誤顯示的設定,並且設定為顯示錯誤資訊,方便進行程式除錯。


注意:因為用 ini_get() 的參數宣告為 string,當使用強型別 (declare(strict_types = 1);) 時,'E_ALL'、'E_WARNING'、'E_NOTICE' 等整數常數會被當成是字串傳入;此時只能夠使用數值進行組態值設定。若要使用常數設定 error_reporting,可以使用 error_reporting() 函式:
error_reporting(E_ALL & ~E_NOTICE);


2020年3月4日 星期三

物件導向 PHP 與 MySQL (MariaDB) - 1. 使用 MySQLi 進行連線與簡易的 CRUD 操作


使用 MySQLi 進行連線與簡易的 CRUD 操作

本文將介紹如何以物件導向的方式使用 MySQLi 延伸套件連結 MySQL (MariaDB) Server,並進行簡易的資料新增、查詢、更新、刪除等操作。


建立連線

PHP 可以使用 mysqli 類別建立與 MySQL Server 間的連線,要實例化一個 mysqli 物件需要依序提供:MySQL Server 的 IP、資料庫的使用者名稱、使用者密碼、資料庫的名稱、MySQL Server 的連接埠、以及要使用的插座 (Socket) 或是具名管道 (Named Pipe);後兩者一般都是使用預設值即可。

下列的程式碼為物件導向 PHP 使用 mysqli 來建立與 MySQL Server 間的連線:
$dbHost = 'localhost';
$dbUser = 'db_username';
$dbPassword = 'db_user_password';
$dbName = 'db_name';
$connect = new mysqli($dbHost, $dbUser, $dbPassword, $dbName);
if ($connect->connect_errno) {
    echo "Failed to connect to MySQL: (" . $connect->connect_errno
             . ") " . $connect->connect_error . PHP_EOL;
}
echo $connect->host_info . PHP_EOL;


CRUD

在建立了跟資料庫伺服器的連線後,就可以對選定的資料庫進行資料的建立 (Create)、讀取 (Read)、更新 (Update)、以及刪除 (Delete) 等操作;這四項資料操作取其首字母,而被簡稱為 CRUD。MySQL 是關聯式資料庫管理系統 (Relational DataBase Management System, RDBMS),使用結構化查詢語言 (Structured Query Language, SQL) 進行管理。SQL 使用資料操作語言 (Data Manipulation Language, DML) 來處理資料庫中資料表裡的資料,這些操作對應到 CRUD 分別是 INSERT、SELECT、UPDATE、以及 DELETE。

以 PHP 開發的應用中,查詢敘述通常是動態產生的 (例如,使用者在表單上輸入要查詢的資料),如果沒有妥善的處理查詢敘述,非常容易讓惡意的使用者以 SQL 隱碼攻擊 (SQL Injection) 的方式,破壞或是入侵資料庫系統。mysqli 提供了預備敘述,讓輸入參數化以避免 SQL 隱碼攻擊。

INSERT

下例會在資料表中新增資料,假設資料 (姓名、電子郵件信箱、住址、以及訊息等) 是由使用者輸入而取得:
$name = '許杰夫';
$email = 'jeff.hsu@gmail.com';
$zip = 10001;
$address = '台北市中正區中正路1號';
$score = 98.9;
$message = '資料新增';

$sql = "INSERT INTO `users` (`name`, `email`, `zip`, `address`, `score`, `message`) 
            VALUES (?, ?, ?, ?, ?, ?)";
$stmt = $connect->prepare($sql);
if (!$stmt) {    
    echo "Prepare failed: (" . $connect->errno . ") " . $connect->error;
}
$stmt->bind_param('ssisds', $name, $email, $zip, $address, $score, $message);
$stmt->execute();
$stmt->close();

為了避免 SQL 隱碼攻擊,需要:
1. 將查詢敘述參數化,mysqli 使用問號 ? 做為佔位符 (Placeholder) 來取代變數名稱;
2. 利用 mysqli::prepare 產生預備敘述 (建立 mysqli_stmt 物件);
3. 使用 mysqli_stmt::bind_param 將變數綁定到預備敘述作為參數,其中第一個參數為變數型別,分別以:i 代表整數、s 代表字串、d 代表雙精度浮點數、b 代表二進位大型物件;
4. 使用 mysqli_stmt::execute 執行綁定參數後的預備敘述;
5. 在查詢完成後使用 mysqli_stmt::close() 將查詢敘述關閉。

SELECT

下例會讀取資料表中第一筆到最後一筆的資料:
$id = $connect->insert_id;
$sql = "SELECT * FROM `users` WHERE `id` <= ?";
$stmt = $connect->prepare($sql);
if (!$stmt) {    
    echo "Prepare failed: (" . $connect->errno . ") " . $connect->error;
} 
$stmt->bind_param('i',$id);
$stmt->execute();

$stmt->bind_result($id, $name, $email, $zip, $address, $score, $message);
while($stmt->fetch()){
    echo $id . " , " . $name . " , " . $email . " , " . $zip
        . $address . " , " . $score . " , " . $message . PHP_EOL;
}
$stmt->close();


在本例中:
1. 使用了 mysqli::$insert_id,傳回最後查詢 (通常是 INSERT) 時自動生成的 ID (當資料表中有 AUTO_INCREMENT 欄位時);
2. 預備敘述的參數綁定與執行;
3. mysqli_stmt::bind_result 將預備敘述執行結果 (結果集合) 中的欄位名稱綁定到指定的變數;
4. mysqli_stmt::fetch 會將結果集合中的資料逐筆對應到由  mysqli_stmt::bind_result 綁定的變數;
5. 每執行一次 mysqli_stmt::fetch,結果集合就會少一筆記錄,直到結果集合成為空集合, mysqli_stmt::fetch 會傳回 false。
6. 在查詢完成後使用 mysqli_stmt::close() 將查詢敘述關閉。

除了上述的輸出方式之外,也可以透過 mysqli_stmt::get_result 將結果集合轉換為 mysqli_result 物件集合後,再使用 mysqli_result::fetch_object 以物件的方式輸出資料。如下列的程式:
$objSet = $stmt->get_result();
while($obj = $objSet->fetch_object()){
    echo $obj->id . " , " . $obj->name . " , " . $obj->email . " , " . $obj->zip
        . $obj->address . " , " . $obj->score . " , " . $obj->message , PHP_EOL;
}

要注意的是,在使用 mysqli_stmt::get_result 後,會將結果集合清空。

UPDATE

下例會更新資料表中的最後一筆新增的資料:
$name = '許班杰';
$email = 'benjamin@test.com';
 
$sql = "UPDATE `users` SET `name` = ?, `email` = ? WHERE `id` = ?";
$stmt = $connect->prepare($sql);
if (!$stmt) {    
    echo "Prepare failed: (" . $connect->errno . ") " 
            . $connect->error;
} 
$stmt->bind_param('ssi', $name, $email, $id);
$stmt->execute();
$stmt->close();


DELETE

下例會刪除資料表中的最後一筆新增的資料:
$sql = "DELETE FROM `users` WHERE `id` = ?";
$stmt = $connect->prepare($sql);
if (!$stmt) {    
    echo "Prepare failed: (" . $connect->errno . ") "
         . $connect->error;
} 
$stmt->bind_param('i',$id);
$stmt->execute();
$stmt->close();
$connect->close();

在所有的查詢工作結束後,記得要將連線關閉,釋放佔用的資源。

小結

本文介紹了如何使用 MySQLi 延伸套件 (包含 mysqli、mysqli_stmt、mysqli_result 等類別),以物件導向的方式操作 MySQL (MariaDB) 資料庫;並進行簡單的 CRUD 操作。使用MySQLi 最大的限制是只能夠對  MySQL (MariaDB) 資料庫進行操作,當資料庫系統轉換到其他的系統時,需要重新撰寫程式碼。在下文中將介紹 PHP Data Object (PDO) 延伸套件,可以避免這個問題。

2020年3月1日 星期日

PHP 開啟 MySQL 的錯誤報告

為了避免敏感的資料庫資訊因為程式碼的錯誤而曝露,PHP 預設不會報告在使用 mysqli 或是 PDO 操作 MySQL 時的錯誤。程式設計人員需要決定是否啟用相關的錯誤報告。 要特別注意的是,當開啟錯誤報告後,不要讓報告內容呈現在一般的使用者前。

MySQLi


mysqli 使用的是mysqli_driver::$report_mode (或是mysqli_report)。例如,如果要在發生查詢錯誤時丟出例外 (但是忽略警告),可以進行下列的設定:
$driver = new mysqli_driver();
$driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT;


或是簡單的使用
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

其中,MYSQLI_REPORT_ERROR 代表開啟錯誤報告,MYSQLI_REPORT_STRICT 代表在錯誤時丟出例外 (throw Exceptions for errors);管道符號 | 是位元運算子中的 "或",可以用來設定多個旗標 (bitwise disjunction of flags)。

PDO


PDO 使用的是 PDO::setAttribute。例如,如果要在發生查詢錯誤時丟出例 (但是忽略警告),可以進行下列的設定:
$connection = new pdo("mysql:localhost;dbname=db_name", "user_name", "user_password");
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

其中,PDO::ATTR_ERRMODE 代表開啟錯誤報告,PDO::ERRMODE_EXCEPTION 代表在錯誤時丟出例外。

2019年12月17日 星期二

使用 Homebrew 安裝 PHP


移除使用 Homebrew/PHP Tap 安裝的舊版本 PHP


自 2018 年 3 月 31 日起,原屬於 Main Taps 的 Homebrew/PHP 版本庫被封存;所有的 Formulae 會被遷移至主要版本庫 Homebrew/core 中或是被刪除。因為 Homebrew 不再支援舊的 Homebrew/PHP Tap,如果之前有使用 Homebrew/PHP Tap 安裝過 PHP,需要先將其完整的移除。如果是新安裝或是使用  Homebrew/core 安裝 PHP,可以跳過本小節。

首先,需要更新所有的 Formulae、升級已安裝的軟體、以及移除所有舊的 Formulae:
brew update
brew upgrade
brew cleanup


(Homebrew 的簡介請參考。)


檢查目前安裝的套件中有那些跟 PHP 有關聯:
brew list | grep php


找到已安裝的 PHP 以及相關的套件後,使用 brew uninstall 將它們解除安裝;例如,之前安裝了 PHP 5.6 以及延伸套件 xdebug:
brew uninstall --force php56 php56-xdebug
brew cleanup


可以再次檢查是否完整的移除相關的 PHP 套件:
brew list | grep php


最後,清除 PHP 設定檔:
rm -Rf /usr/local/etc/php/*



安裝 PHP


PHP 有多個發行分支 (Release Branch),分屬於不同的主要版本 (Major Version)。每一個發行分支都會有兩年的完整支援,再加上一年的關鍵安全問題支援。PHP 5 的最後一個發行分支 PHP 5.6 在 2018 年 12 月 31 日停止了所有的支援,使得目前仍有支援的主要版本只剩下了 PHP 7。下表列出了 PHP 目前支援的發行分支(參考自):


分支 初始發行日 完整支援到 安全問題支援到
7.2 2017 年 11 月 30 日 2019 年 11 月 30 日 2020 年 11 月 30 日
7.3 2018 年 12 月 6 日 2020  年 12 月 6 日 2021 年 12 月 6 日
7.4 2019 年 11 月 28 日 2021 年 11 月 28 日 2022 年 11 月 28 日


因為 PHP 7.2 的完整支援已經在 2019 年 11 月 30 日到期,所以目前 Homebrew/core 只提供 PHP 7.3 以及 PHP 7.4 的安裝。下列令可以安裝 Homebrew/core 上的 PHP 7.4:
brew update
brew upgrade
brew install php@7.41


註1. Homebrew 現在改用 "php@發行分支" (例如 PHP@7.3) 取代原來的 "PHP發行分支無句點" (例如 PHP74);如果不指定要安裝的發行分支,預設會安裝最新的發行分支。


使用 PHP 的 Apache 2 Handler 模組


在 "PHP 與伺服器之間的應用程式介面" 一文中,介紹了 PHP 共有 4 種不同 SAPIs。針對不同的開發環境,可以選擇適合的SAPI。如果是做為個人網頁開發,建議可以選擇設定上較為簡便的 Apache 2 Handler 模組


1. 設定 httpd.conf


根據 PHP 手冊,使用 PHP 的 Apache 2 Handler 模組,不建議 httpd 使用執行緒類型的 MPM (請參考);而且使用 Homebrew/core 安裝的 PHP 也不再支援執行緒安全 (Thread Safty) 選項2。因此,httpd 的 MPM 模組3使用預設的 Prefork 模組即可。


確認 httpd.conf 載入的是 Prefork 模組:
LoadModule mpm_prefork_module lib/httpd/modules/mod_mpm_prefork.so


要讓 httpd 能夠處理 php 文件,需要:
1) 在啟動時就戴入 Apache 2 Handler (libphp) 模組,
2) 讓 httpd 知道副檔名為 php 的檔案要交由 php 模組處理,
3) 執行完 php 後生成的文件的內容類型 (Content Type) 是 text/html。


在 IfModule mpm_prefork_module 區塊加上下列的設定4
<IfModule mpm_prefork_module>
    LoadModule php7_module /usr/local/opt/php/lib/httpd/modules/libphp7.so
    <FilesMatch \.php$>
        SetHandler application/x-httpd-php
    </FilesMatch>
    AddType text/html .php
</IfModule>



最後,確認 httpd 在尋找目錄首頁的順序與類別:
<IfModule dir_module>
    DirectoryIndex index.php index.html
</IfModule>


重新啟動 HTTP Server:
sudo apachectl -k stop
sudo apachectl start



2. 測試 PHP 網頁是否正常

在文件根目錄檔案夾新增 index.php,內容如下:
<?php

phpinfo();


在瀏覽器的網址列輸入:
http://localhost/info.php


應該可以看到系統和 PHP 的相關資訊。

(如果網站要提供服務給其他人,在測試完後記得要將 index.php 刪除。)


註2. 之前的 Homebrew/php tap 可以使用 --with-thread-safety 或是 --enable-maintainer-zts 選項,讓 Apache 2 Handler 模組可以在多執行緒環境下執行。

註3. 在 "Apache HTTP Server 的多程序處理模組 (Multi-Processing Module, MPM)" 一文中介紹了 httpd 在 unix 系統的 3 種 MPM。在不同的 MPM 模組下,要使用不同的 SAPI。

註4. 考量安全因素,建議使用 SetHandler 取代 AddHandler;因為使用 AddHandler 時只要檔案名稱中有 .php (例如 test.php.png),就會被送到 PHP 解析器執行。這讓惡意的使用者可以利用這個方式上傳惡意的程式碼,並且加以執行。

2019年12月13日 星期五

在 macOS 安裝 Apache HTTP Server


Apache HTTP Server (httpd) 是一款常見的開源網頁伺服器軟體。近年來面對 Nginx、IIS 等網頁伺服器軟體的競爭,仍然具有很高的市佔率。是不少的網頁開發套件 (例如,XAMPP) 內建的網頁伺服器。httpd 的設定問題容易在網路上找到相關的解決方法,適合做為小型開發專案或是輕量服務需求的網頁伺服器。

卸載 macOS 內建的 httpd


檢查 macOS 是否內建 Apache HTTP Server (httpd)1,並且已經將其載入到 Launchd:
sudo launchctl list | grep httpd


如果 macOS 有內建 httpd,並且已經將其載入到 Launchd 時;必須先將其從 Launchd 中卸載:
sudo apachectl stop
sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist 2>/dev/null


(launchctl 以及 Launchd 的簡介請參考。)


註1:配合 macOS Server 5.7 以後不再支援來自開源軟體的圖形化管理介面,從 macOS 10.14 開始,已不再內建 httpd (請參考 macOS Server 的前世今生)。


使用 Homebrew 安裝 httpd

安裝 httpd 最簡單的方式是使用網頁開發套件。但是,使用套件會有下列的缺點:打包好的軟體不一定包含需要的軟體、軟體的版本受到限制、更新的速度較慢等。為了安裝與管理應用程式,套件管理工具被廣為使用。其中 Homebrew 是 macOS 使用者最常使用的套件管理工具 (Homebrew 的簡介請參考)。


使用 Homebrew 安裝 httpd 只需要執行下列的命令:
brew update
brew upgrade
brew install httpd


Homebrew 會執行下列的安裝工作:
1. 安裝檔案夾在 /usr/local/Cellar/httpd/2.24.xx (xx為版本號碼),
2. 執行程式 (例如,apachectl、httpd 等) 會被符號連結 (symlink) 到 /usr/local/bin,
3. 設定檔則是放在 /usr/local/etc/httpd,
4. /usr/local/opt 檔案夾中的 apache-httpd、apache2、以及 httpd 是安裝檔案夾的符號連結。


我們可以透過執行 echo $PATH 檢查 PATH 環境變數的設定,確認在使用 apachectl 等命令時,會優先執行使用 Homebrew 安裝的 httpd:
echo $PATH
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin


因為 /usr/local/bin 在路徑設定的最前面,所以被 symlink 到該檔案夾的命令會被優先執行。


安裝完成後可以執行下列的命令來啟動 httpd:
apachectl start


開啟瀏覽器,連線到 http://localhost:8080/,可以看到 It works! 的網頁。

注意:存取記錄 (Access Log) 跟錯誤記錄 (Error Log) 預設是記錄在 /usr/local/var/log/httpd/ 檔案夾,並且對應到 /private/var/log/apache2/ 檔案夾。如果之前已經使用過 root 啟動過 httpd,命令前面需要加上 sudo,不然執行 apachectl start 時會出現需要 root 權限的提示,執行 httpd -k start 時會出現 AH00091: httpd: could not open error log file /private/var/log/apache2/error_log.
解決的方法為使用 root 權限刪除記錄檔。

設定 httpd (修改 httpd.conf)


/usr/local/etc/httpd/httpd.conf 是 httpd 的主要設定檔。為了使用上的需求,我們需要修改某些設定。下面列出幾個常見的設定。


監聽埠

安裝完後的預設監聽埠是 8080,而不是常用的 80 埠;我們需要找到 httpd.conf 中的 Listen 設定,更改為監聽 80 埠:
Listen 80


伺服器名稱

當沒有在 httpd.conf 設定伺服器名稱時,在啟動或關閉 httpd 時會出現下列的訊息:
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using MacBook-Pro.local. Set the 'ServerName' directive globally to suppress this message


找到 ServerName 設定,取消註解並且改為下列的設定:
ServerName localhost


文件根目錄

做為個人開發平台,預設的文件根目錄 /usr/local/var/www,在文件的相關操作上會比較不方便。將文件根目錄改設為使用者家目錄下的 www 檔案夾,可以方便文件的編修以及檢視。找到 httpd.conf 中的 DocumentRoot 設定,指定文件根目錄到使用者家目錄下的檔案夾:

DocumentRoot "/Usrs/user_name/www"
<directory "/usr/local/www></directory>


在文件根目錄設定的下方會有一個針對該檔案夾的設定,我們需要改為新的文件根目錄:
<Directory "/Usrs/user_name/www">


注意:因為 macOS 的系統完整保護 (System Integrity Protection, SIP)機制,建議文件根目錄設定為使用者家目錄下的檔案夾,或是使用預設值。


在使用者家目錄建立文件根目錄檔案夾並且新增一個 index.html 檔案:
mkdir ~/www
echo "<h1>My Page</h1>" > ~/www/index.html



重新啟動 httpd:
sudo apachectl -k restart


注意:因為使用了 80 埠,此時命令要加上 sudo。


開啟瀏覽器,連線到 http://localhost/,可以看到 My Page 的網頁。



將 httpd 設定為開機時啟動的守護行程 (Daemon)


要讓 httpd 能夠在開機自動執行,可以執行下列的命令:
sudo brew services start httpd


brew services 的簡介可以參考這裡

安裝 Homebrew-services 後,也可以使用它將 httpd 從  Launchd 移除:
sudo brew services stop httpd


或是重新加入 Launchd:
sudo brew services restart httpd


將 httpd 設定為登入時啟動的任務項 (Agent)


要讓 httpd 能夠在登入後自動執行 (注意,此時 httpd.conf 中的監聽埠不可以小於 1024,LOG 檔的擁有者要是使用者),可以執行下列的命令:
brew services start httpd


安裝 Homebrew-services 後,也可以使用它將 httpd 從任務項中移除:
brew services stop httpd


或是重新加入任務項:
sudo brew services restart httpd


僅啟動 httpd


Homebrew-services 也可以只用來啟動 httpd:
brew services run httpd


brew services run 使用目前使用者的權限執行,所以要注意監聽埠、檔案夾權限等設定問題。



2019年12月12日 星期四

在 WSUS 伺服器環境中如何替 Windows 10 安裝 .net Framework 3.5

Windows 10 不再內建 .net Framework 3.5,如果需要使用 .net Framework 3.5 的功能,需要透過 [控制台/程式集/開啟或關閉 Windows 功能] 自行安裝。

如果內部有使用 WSUS 伺服器,在安裝 .net Framework 3.5 時,出現了 0X800F0954 的錯誤;代表 WSUS 伺服器無法讓你正確下載 .net Framework 3.5。那是因為 "Windows 10 Feature on Demand" 中的  .net Framework 3.5 只有支援到 Windows 10 1703。



可以使用下列兩種方式來解決這個問題:
1. 讓電腦離開網域,安裝完後再重新加入網域。

2. 讓電腦暫時不受 WSUS 伺服器管理。

(1) 在 [工作管理員/服務] 停用 wuauserv;

(2) 執行 regedit,展開至HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU,將 UseWUServer 的數值資料改成 0;

(3) 在 [控制台/程式和功能/開啟或關閉 Windows 功能],安裝 .net Framework 3.5;

(4) 執行 regedit,展開至HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU,將 UseWUServer 的數值資料改回來 1;

(5) 重新啟用 wuauserv。




另外,如果要安裝 XPS Viewer,記得在步驟 (3) 之後要執行下列的命令:

dism /Online /Add-Capability /CapabilityName:XPS.Viewer~~~~0.0.1.0

2019年11月7日 星期四

macOS Server 的前世與今生

雖然名稱為 macOS Server,這是一套只適合用來管理 Apple 自有服務的 Mac APP。

前世:Apple 的伺服器作業系統


相較於 Microsoft 的 Windows NT Server 作業系統,Apple 在早期也有一套伺服器作業系統,稱為 Mac OS X Server。兩者都是以圖形化介面來管理各種網路服務伺服器。不同的是 Windows NT Server 採用的是 "Wintel" 架構,系統可以安裝在不同廠商出品的 X86 主機上;而 Mac OS X Server 則是隨著 Apple 伺服器主機一起出售,或是購買後安裝在 Mac 主機上。


Apple 在 1999 年推出稱為 Mac OS X Server 的伺服器作業系統 (當時的桌面作業系統稱為 Mac OS X),並且在 2002 年推出名稱為 Xserve 的機架伺服器。作業系統並不支援中文,只有支援英文等少數的語系。Xserve 在售量上並不理想,經過了多次的更新後,在 2011 年停產。雖然 Apple 建議使用者可以選購安裝有 Mac OS X Server 的 Mac Pro、Mac Mini 或是 iMac Pro 主機做為替代,但是從 2011 年 (桌面作業系統為 Mac OS X Lion 10.7) 開始,Apple 不再販售可獨立安裝的 Mac OS X Server 作業系統,而是將 Mac OS X Server 的功能獨立成一個名稱為 OS X Server 的 APP,在 Mac App Store 上販售。隨著 OS X 在 2016 年更名為 macOS (Sierra 10.12),這個獨立的 APP 也被改名為 macOS Server。


早期的 OS X Server 使用圖形化介面來管理各種網路服務伺服器,除了 Apple 本身的服務外,這些伺服器還包含了 Apache HTTP Server (httpd)、MySQL1、RADIUS、BIND、以及 Sendmail 等服務。透過統一的圖形化介面,使用者不再需要透過繁雜的指令就能夠輕鬆的管理網路伺服器。不過,和 Windows Sever 自行開發各種伺服器軟體不同的是,這些伺服器軟體大多是源自於開源軟體,OS X Server 只是為這些伺服器軟體加上圖形化的管理介面。


註1:2009 年的 Mac OS X Server 10.6 (含) 以前的版本支援 MySQL,2011 年的 Mac OS X 10.7 Server (以 APP 型式發行的) 以後的版本則改用 PostgreSQL。可能是因為 2009 年 Oracle 收購了之前收購了 MySQL 的 SUN Microsystems。

今世:macOS Server


雖然改採 APP 的型式發行,macOS server 仍然會隨著 macOS 的更新而更新。從 OS X 10.10 Yosemite 開始,發行版號就不再跟 macOS 一樣,而是有自己的版號:OS X Server 4.0。一開始的 macOS Server 仍然提供來自開源軟體的伺服器的圖型化管理介面;但是從 2018 年發行的 macOS Server 5.7 (對應的是 macOS 10.14) 開始,這些伺服器軟體的圖型化管理介面都被移除了,只提供了 Open Directory、「描述檔管理程式」和 Xsan 管理等的管理介面2


所以,從 macOS Server 5.7 開始,雖然名稱為 macOS Server,但是卻只是一套只適合用來管理 Apple 自有服務的 Mac APP;跟之前的版本的功能有著非常顯著的不同。如果習慣舊有的 macOS Server 管理介面,macOS 就別升級到 macOS Mojave 10.14 或是更新的版本。至於 Mac App Store 目前 (2019/11/07) 只有最新的 macOS Server 5.9 可以購買。


註2:雖然 Apple 官方並沒有明說,不過一直以來 macOS Server 的伺服器軟體大多是源自於開源軟體,開源社群對此其實一直都有一些批評。

2019年10月28日 星期一

PHP 與伺服器之間的應用程式介面 (Server Application Programming Interface, SAPI)

PHP 和伺服器之間的應用程式介面 (Application Programming Interface, API) 可分為四種,分別為:共用閘道介面 (Common Gateway Interface, CGI)、伺服器應用程式介面 (Server Application Programming Interface, SAPI)、快速共用閘道介面 (Fast Common Gateway Interface, FastCGI)、以及命令列介面 (Command Line Interface, CLI) 等。透過這四種 API,可以在伺服器上執行 PHP 程式。

CGI

共用閘道介面 (Common Gateway Interface, CGI) 是一個用來在網頁伺服器上執行外部應用程式的標準協定 (Protocol)。早期要經由網頁伺服器執行其它的應用程式都需要透過 CGI。例如,當網頁服務收到具有 php 副檔名的頁面請求時,會啟動一個執行 php-cgi 的程序來處理 .php 網頁,並在網頁內容執行完畢後關閉該程序。


CGI 模式的執行流程為:
網頁伺服器收到請求 -> 啟動 php-cgi 程序 -> php-cgi 處理請求 -> 關閉 php-cgi


使用 CGI 的好處是穩定,當 PHP 網頁發生執行錯誤時只會影響 php-cgi,其它的程序不會受到影響。CGI 的主要缺點是執行效能,在每一次收到一個新的請求時,都要啟動一個執行 php-cgi 的程序,這個程序需要載入 PHP 的相關設定檔案以及所有的擴充。這樣的過程需要花費較多的時間以及伺服器資源。因此 Fork-and-Execute 模式成為 CGI 最為人所詬病的一個缺點。

SAPI

根據維基百科,伺服器應用程式介面 (Server Application Programming Interface, SAPI) 是網頁伺服器的直接模組介面,是一種由網頁伺服器所提供的應用程式介面,用來擴展網頁伺服器的功能1

不同的網頁伺服器所提供的介面並不相容,因此應用程式需要針對不同的網頁伺服器開發專用的 SAPI 模組 (因此 SAPI 模式也被稱為模組模式)。例如,PHP 針對 IIS2 以及 httpd3 等都有相對應的 SAPI 模組。SAPI 模組使用直接模組介面,所以只要將模組資訊寫入網頁伺服器的設定檔中,在網頁伺服器啟動時就會被載入。例如,將下列 PHP SAPI 模組資訊寫入 Apache HTTP Server (httpd) 的設定檔 httpd.conf 中,當重新啟動 httpd 時,PHP 的 SAPI 模組就會被載入:
LoadModule php7_module /usr/local/opt/php/lib/httpd/modules/libphp7.so


模組模式的執行流程為:
網頁伺服器收到請求 -> php 模組


使用 SAPI 模組執行 PHP 意謂著 PHP 會在網頁伺服器的程序中執行,而不再需要針對每個請求啟動一個新的程序。例如,當 httpd 採用 Prefork 模式4時,在啟動時就會預先 fork 一些子程序來等待請求,當有新的請求出現時,httpd 會調用閒罝的子程序來回應請求。這樣的過程可以減少啟動新程序所花費的時間以及降低伺服器的負載。

穩定性是使用 SAPI 模組會遭遇到的最大問題。因為 PHP 在網頁伺服器的程序中執行,當 PHP 應用程式出問題時,可能會使得網頁伺服器發生當機的情況。當 PHP 在執行時具有網頁伺服器程序的所有權限,這也會造成資訊安全問題。最後,目前除了 httpd 之外,主流的網頁伺服器皆不支援 SAPI 模組。


註1. SAPI 有時也泛指伺服器與應用程式之間的 API,因此除了 SAPI 外,CGI、FastCGI、以及 CLI 等也都可以視為是不同類型的 SAPI。
註2. IIS 上的 SAPI 被稱為 ISAPI,ISAPI 的擴充都是實作成為 DLLs,會被載入由 IIS 控制的程序中。從 IIS 7.0 開始,已經統一改用 FastCGI,而不建議使用 ISAPI。
註3. httpd 2.x 上的 SAPI 稱為 Apache 2.0 Handler。
註4. Prefork 是 httpd 的一種 MPM,相關介紹介請參考


FastCGI


和 CGI 相同,快速共用閘道介面 (Fast Common Gateway Interface, FastCGI) 也是一個協定;它是 CGI 的加強版,不僅保留了 CGI 的好處,而且還提升了 CGI 的性能。FastCGI 將 CGI 應用程式包裝起來,並且使用 FastCGI 伺服器來建立程序並且管理從網頁伺服器送來的請求。

FastCGI 的運作機制為,當系統啟動 FastCGI 伺服器時,會建立一個主要程序 (Master Process),Master Process 會建立多個稱為工作者 (Worker) 的子程序;當收到請求時,網頁伺服器可以透過插座 (Socket) 或是 TCP 連線跟 FastCGI 模組進行通信;Master Process 會選擇一個閒置的 Worker Process 處理並回應請求,處理完畢後 Worker Process 會回到閒置狀態,而不是結束行程。當 Worker Process 不夠時,Master Process 可以動態配罝新的 Worker Process;當有過多的 Worker Process 閒置時,則可以適度的終止 Worker Process。這樣的機制可以解決 CGI 需要反覆的建立程序的缺點。


FastCGI 模式的執行流程為:
網頁伺服器收到請求 -> 主要程序 -> 工作者子程序


PHP-FPM(FastCGI Process Manager)是 PHP 實作 FastCGI 的管理套件。通常 PHP 在搭配 httpd 使用時,預設會使用 Prefork 加上 SAPI 的方式。但是如果基於效能考量,httpd 可以使用 Event 模組,再搭配使用 PHP-FPM。


CLI

PHP 的命令列介面 (Command Line Interface, CLI) 主要的用途是開發 Shell 應用程式。通常我們在終端機執行的 PHP Script 就是透過 CLI。例如,我們可透過下列的指令查詢 PHP 的版本以及所使用的 SAPI 為何:
php -v


PHP 7.3.10 (cli) (built: Sep 30 2019 19:59:54) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.10, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.10, Copyright (c) 1999-2018, by Zend Technologies


在版本號碼 7.3.10 後面的 (cli),表示在終端機執行的 PHP 是使用 CLI SAPI。


CLI 模式的執行流程為:
終端機輸入請求 -> CLI SAPI


大部份的腳本語言 (Script Language) 都有提供交互模式,進行簡易的程式執行與測試。從 PHP 5.1.0 開始,藉由 php -a 指令,CLI SAPI 提供了交互模式:
php -a
Interactive shell


我們可以在互動模式下執行程式或內建的函數:
php > phpinfo();


要結束交互模式可以使用 control+z 或是 control+c 組合鍵。


從 PHP 5.4.0 開始,CLI SAPI 還提供一個簡單的內建網頁伺服器,供使用者進行簡單的 PHP 網頁程式開發測試。不過,這個伺服器在執行時僅使用單一執行緒程序,如果有請求受阻時,應用程式將會停滯。使用下列的指令可以執行內建網頁伺服器:
php -S localhost:8000



終端機會有下列的訊息:
PHP 7.3.11 Development Server started at Wed Oct 30 16:36:39 2019
Listening on http://localhost:8000
Document root is /usr/local/Cellar/httpd/2.4.41_1
Press Ctrl-C to quit.



結論


要使用 PHP 開發 Shell 應用程式或是進行簡易的程式測試與除錯,可以選擇 CLI;如果是要用來做為網頁應用程式開發使用,則可以使用 SAPI 或是 FastCGI。CGI 因為效能較差,再加上目前主流的網頁伺服器都支援具有增強功能的 FastCGI,因此已經較少人使用。


在使用 httpd + PHP + MySQL 開發網頁應用時,目前多數的套件預設為使用 SAPI,在設定上也較為簡單,所以初學者適合使用 SAPI。但是如果是使用 nginx 或是 IIS 做為網頁伺服器,或是重視應用程式的效能,則需要使用 FastCGI。

2019年10月14日 星期一

Homebrew 簡介


Homebrew 簡介


要在 macOS 上安裝應用程式可以透過:Mac App Store (MAS)、應用程式的官網、或是下載原始碼等方式。MAS 提供了簡便的應用程式安裝方式,只要在 App Store 搜尋到需要的應用程式,再點選「取得」即可安裝。但是,有許多的應用程式(特別是許多系統開發工具),並沒有在 MAS 上架,無法藉由 MAS 取得。而由較具規模的組織或公司所開發的應用程式,大多有設有官網可以下載應用程式,安裝過程也十分簡單1。但是某些透過進階安裝的應用程式,較不容易控管其版本與安裝內容,而且在移除時需要注意是否有殘留的檔案或資料夾。下載原始碼安裝,雖可以讓使用者量身打造自己的應用程式。但是應用程式彼此之間的相依性問題以及未來的升級管理,都會對使用者造成困擾。

為了安裝與管理應用程式,不少的作業系統、伺服端執行環境、程式語言等,都提供了套件管理工具。例如,Debian 的 APT、openSUSE 的 Zypper、Fedora 及 CentOS 的 YUM 、node.js 的 NPM、或是 Python 的 PIP 等。而在 macOS,Homebrew 就是最被廣為使用的套件管理工具。

Homebrew 是 macOS 缺少套件的管理工具 (The missing package manager for macOS)2,官網為 https://brew.sh/。Brew 通常可以指 "釀造啤酒",Homebrew 是在家裡釀造啤酒;套件管理工具 Homebrew,代表使用者需要 "自行"(home) "下載並安裝" 套件(brew)。因為借用了釀造啤酒一詞,所以在相關的術語中,使用了配方 (Formula)、酒桶 (Keg)、酒瓶 (Bottle)、出酒閥 (Tap)、酒窖 (Cellar) 等許多和釀造啤酒有關的名詞。相關術語的介紹請參考下一小節。

Homebrew 是以 Ruby 語言寫成的命令列套件管理 (command line package manager) 程式 ,它提供了一個統一的方法讓使用者可以輕易地在 macOS 上進行安裝、升級與移除軟體。它的功能有 git 版本庫訪問 (Repository Access)、相依性處理 (Dependency Solving)、以及套件安裝 (Package Installation) 等。

註1. 雙擊下載的 DMG 檔會自動掛載虛擬磁碟機,並且開啟一個 Finder 視窗:1)視窗內如果有一個 APP 檔和指向 "應用程式" 資料夾的捷徑,只需要將 APP 檔拖拉至 "應用程式" 資料夾捷徑即可完成安裝。2)視窗內如果沒有 "應用程式" 資料夾捷徑,通常代表需要雙擊 APP 檔或是 PKG 檔,進行進階安裝。
註2. Homebrew 現在也可以用於 Linux 以及 Windows Subsystem for Linux(WSL),所以英文版的首頁已經改為 The missing package manager for macOS (or Linux)。


Homebrew 相關術語


釀造啤酒需要 Formula、Keg、Bottle、Tap、Cellar 等器具或場所。Homebrew 借用了這些詞語做為相關的術語。首先,釀造啤酒需要有 Formula 來決定需要使用那些材料以及其使用量。在 Homebrew 中,Formula 是一種特定領域語言 (Domain-specific language, DSL),是由 Ruby 指令碼集所構成的套件定義檔,用來管理軟體間的相依性、原始碼的下載、以及進行軟體的配置與編譯等。在使用 Homebrew 時,會先從 Git 版本庫 (Homebrew 預設使用的是 GitHub 提供的版本庫) 中下載 Formulae。

當釀好酒後,需要 Keg 來存放成品,並且需要 Cellar 來存放的 Kegs。在 Homebrew 中,Keg 為套件的安裝前綴,通常包含了套件名稱以及版本,而 Cellar 則為 存放 Kegs 的目錄。預設 Cellar 路徑為 /usr/local/Cellar,完整的套件安裝路徑預設為:
/usr/local/Cellar/套件名稱/版本

要將 Keg 中的啤酒倒出來,通常會在 Keg 上加上 Tap(Tap a keg);Tap 當名詞時是指出酒閥,當動詞時是指在 Keg 加上 TAP 的動作。在 Homebrew,Tap 當名詞時是指由 formulae 所形成的 Git 版本庫3,當動詞時是指在本機建立這些 Git 版本庫的複本。

啤酒除了可以裝在 Keg 中之外,也可以分裝在小酒瓶(Bottle)中。在 Homebrew 中, Bottle 是依照預設參數編譯好的套件 (通常為 .tar.gz 檔),會依照打包時的路徑進行安裝。Homebrew 的目標是希望所有的套件都是利用 Bottle 安裝(We aim to bottle everything.)。


下表參考自:https://github.com/Homebrew/brew/blob/master/docs/Formula-Cookbook.md#homebrew-terminology

名稱描述範例
Formula套件定義/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/foo.rb
KegFormula 的安裝前綴預設為 /usr/local/Cellar/套件名稱/版本
opt prefixKeg 現行安裝版本的 symlink/usr/local/opt/套件名稱
Cellar所有的 Kegs 將被安裝在此/usr/local/Cellar
TapFormulae (以及/或是命令) 的 Git 版本庫homebrew/core 在本機會存放在 /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core
Bottle預建的 Keg (預先編譯好的套件)在 .tar.gz 前會有 bottle 關鍵字,例如qt-4.8.4.mavericks.bottle.tar.gz
Cask用來安裝 macOS 原生 Apps 的 homebrew 擴充/Applications/MacDown.app/Contents/SharedSupport/bin/macdown
Brew Bundle用來描述相依性的 homebrew 擴充brew 'myservice', restart_service: true


註3. Taps 之前被分類為:
1) 主要版本庫 (Main repository),通常指的是 homebrew/core;
2) 主要 Taps (Main taps),包含了 homebrew/nginx、homebrew/php、homebrew/science 等;
3) 其他有趣的 Taps (Other interesting taps) 或是第三方版本庫 (Third-party repositories),格式為 <user/repo> 的 Taps。
但是從 2018 年(Homebrew 1.5.0)開始,大部份的 Main taps 已經被移入 Main repository,Tap 也被更名為第三方版本庫(請參考這裡這裡以及這裡)。


安裝以及移除 Homebrew



安裝 Xcode 命令列工具 (Command Line Tools)4
xcode-select --install


要安裝 Homebrew 只需要在終端機命令列提示貼上下列的指令:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"


要移除 Homebrew 只需要在終端機命令列提示貼上下列的指令:
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"


註4. Command Line Tools 為 Xcode 的子集,是一個獨立套件。能在 macOS 終端機介面提供 clang、cpp、GCC、git、make、perl 等許多常用的工具,實用程序和編譯器。

軟體安裝簡易流程


1. 找到軟體所屬的 Tap(如果套件不屬於 Homebrew/core)。

2. 更新所有的 Formulae、升級已安裝的軟體、以及移除所有舊的 formulae
brew update
brew upgrade
brew cleanup


3. 下載軟體所屬的 Tap (不屬於主要版本庫的套件)
brew tap <user/repo>


4. 查詢軟體的安裝選項 (如果需要的話)
brew info <formula>


5. 安裝套件5, 6, 7
brew install <formula> [--with-參數]


6. 將已安裝套件的服務加入 Launchd,設定為開機啟動的守護行程 (daemon),brew services 的介紹請參考
brew services start <formula>


註5. 如果沒有使用安裝選項,預設是使用 bottled 版本。
註6. Homebrew 預設會將套件安裝在 /usr/local/Cellar,執行檔則會 symlink 到 /usr/local/bin,設定檔會在 /usr/local/etc,而 /usr/local/opt 則是套件現行版本的 symlink。
註7. 在安裝完套件後,如果顯示套件為 keg-only,表示套件將僅安裝在 Cellar,而不會被連結到 /usr/local;通常的原因是因為電腦中已經相同名稱的套件。如果要手動符號連結到 /usr/local/ 可以使用 brew link 指令。


指令簡易介紹


顯示 Homebrew 版本
brew -v


檢查 Homebrew 狀態
brew doctor


使用說明
brew help


顯示 Homebrew 安裝路徑
brew --prefix


更新電腦上全部的 Formulae
brew update


更新所有已安裝的套件
brew upgrade


更新指定的套件
brew upgrade <formula>


列出過時 (需要更新) 的套件
brew outdated


列出目前電腦上所有版本庫的名稱
brew tap


下載或更新指定的 Tap
brew tap <homebrew/homebrew-repo>
brew tap <user/repo>


移除指定的 Tap
brew untap <homebrew/homebrew-repo>
brew untap <user/repo>


查詢套件的資訊  (可以用來查詢套件有那些安裝參數可以使用)
brew info <formula>


安裝套件
brew install <formula> [--with-參數]


移除套件
brew uninstall <formula>


將套件符號連結至 /usr/local
brew link <formula>


移除已安裝套件的 symlink,但是不移除套件
brew unlink <formula>


將已安裝套件的服務加入 Launchd,設定為開機啟動的守護行程(brew services 的介紹請參考
brew services start <formula>


顯示已安裝的套件清單
brew list


顯示已安裝的特定套件的安裝內容(檔案夾以及檔案)
brew list <formula>


顯示全部可安裝的套件清單
brew search


搜尋特定的套件
brew search <formula>


列出指定軟體的依賴關係
brew deps <formula>


顯示特定套件的安裝路徑
brew --prefix <formula>


移除所有舊的 formulae
brew cleanup


移除舊的 formula
brew cleanup <formula>


固定第三方版本庫
brew tap-pin <username/repo>


停止對套件進行 update/upgrade
brew pin <formula>


恢復對套件進行 update/upgrade
brew unpin <formula>



補充說明:


1. 安裝套件前一定要先執行 brew update,並建議執行 brew upgrade。

2. 在安裝軟體時,如果有多個版本庫所提供的套件 (配方) 名稱重複,brew 會依照下列的順序來決定要使用那一個 formula:(1) 使用 tap-pin 固定的版本庫 (pinned taps)、(2) 核心配方 (core formulae,在 homebrew/core 的 formulae)、(3) 其它的版本庫 (other taps)。

3. 其他的操作可以參考 https://docs.brew.sh/FAQ.html


2019年10月9日 星期三

使用 Homebrew 安裝 MariaDB


MariaDB 簡介


MariaDB 是一個關聯式資料庫管理系統(Relational Database Management System, RDBMS),是一個主要由開源社群維護、採用 GPL v2 授權的 MySQL 分叉(Fork)。MariaDB 由 MySQL 的主要創始人 Ulf Michael Widenius 所主導開發,會建立這個分支的原因之一是原本收購了 MySQL AB 的 Sun Microsystems 被資料庫巨擘 Oracle 給收購了,使得 MySQL 有被閉源的風險。和 MySQL 的名稱來自 Widenius 女兒 My 一樣,MariaDB 的名稱來自 Widenius 女兒的名字 Maria。

要選擇 MySQL 或是 MariaDB 並不是一件容易的事,因為兩者之間的差異逐漸擴大。本文將僅介紹 MariaDB 的安裝以及簡易的使用方式。

安裝 MariaDB

要在 macOS 上安裝 MariaDB 十分簡單,只需要執行下列的命令:

brew update
brew upgrade
brew install mariadb


(Homebrew 的簡介請參考。)


啟動與連線 MariaDB Server


為了要保持跟 MySQL 的操作相容性,MariaDB 大部份的操作命令沿用了 MySQL 的命令。要啟動 MariaDB server 需要執行下列的命令:
mysql.server start

mysql.server 是 MariaDB 用來啟動、停止、重新啟動、以及檢查狀態的腳本 (Script)。


要停止 MariaDB server 需要執行下列的命令:
mysql.server stop --user=[my_current_user]


因為是使用目前的使用者啟動,所以在停止時需要加上使用者名稱 --user=[my_current_user]。如果沒有加上使用者名稱,會出現下列的錯誤訊息:
ERROR! MariaDB server process #Pro_ID is not running!


若是使用下列的命令檢查伺服器的狀態:
mysql.server status


則會出現下列的錯誤訊息
ERROR! MariaDB is not running, but PID file exists


解決的方式,一樣是在命令中加入 --user 這個參數。


在啟動 MariaDB 後,可以使用下列的命令連線 MariaDB:
sudo mysql -uroot


剛安裝完 MariaDB 時,root 帳號是沒有密碼的。在之前的版本,只要使用本機使用者帳號即可連線 MariaDB,這樣的機制會有資訊安全的疑慮。因此,從 MariaDB 10.4.3 開始,在未設定 root 帳號密碼前,MariaDB 會使用 unix_socket 插件 (Plugin)保護 root@localhost,需要透過系統用戶憑證來連線。如果沒有使用 sudo,會出現下列的錯誤:
ERROR 1698 (28000): Access denied for user 'root'@'localhost'


設定 root 密碼


傳統設定密碼的方式為使用 mysqladmin 命令:
sudo mysqladmin -uroot password your_password


在設定好 root 的密碼後,可以使用傳統的認證方式 (mysql_native_password) 連線到 MariaDB:
mysql -uroot -pyour_password


MariaDB 也提供了 mysql_secure_installation 這個設定腳本,來進行密碼以及伺服器的相關設定,並且建議在上線服務前需要先執行該腳本。在設定完密碼後,可以直接執行該命令 (未設定 root 密碼時,要加上 sudo):
mysql_secure_installation


該腳本會進行下列的設定:

1. 是否更換為 unix_socket 認證:建議選擇 N,保留目前的認證方式1

2. 設定 MariaDB 的 root 密碼:密碼已經設定好了,可以選擇 N,否則選 Y 進行密碼設定;

3. 移除匿名使用者:建議選擇 Y;

4. 禁止 root 遠端登入:建議選擇 Y;

5. 移除 test 資料庫:建議選擇 Y;

6. 重新載入權限表格:建議選擇 Y,讓上面的設定生效。


註1:在選擇 Y 後,目前的 mysql_native_password 密碼會失效,在不是使用 sudo 命令時,將無法繼續執行腳本。

將 mysqld 設定為守護行程 (daemon)


brew services start mysql



brew services 的簡介可以參考這裡

使用 Homebrew-services 設定守護行程或是任務項


前情提要:在 macOS 的啟動守護行程一文中介紹了 macOS 的守護行程 (Daemon) 以及任務項 (Agent),在 Homebrew 簡介一文中,介紹了如何使用 Homebrew 安裝套件。

本文將以 Apache HTTP Server 為例,介紹如何將安裝好的套件設定為守護行程或是任務項。

將 httpd 設定為 Daemon


要讓 httpd 能夠在開機自動執行,可以執行下列的指令:
sudo brew services start httpd


上面的指令包含了下面的動作:

1. 自動安裝 homebrew-services(用來整合 Homebrew 跟 macOS 的 launchctl 管理器)。


2. 將 httpd 的 plist 檔案(homebrew.mxcl.httpd.plist)複製到 /Library/LaunchDaemons (加入到 Launchd) :
sudo cp /usr/local/opt/httpd/homebrew.mxcl.httpd.plist /Library/LaunchDaemons
sudo chown root:wheel /Library/LaunchDaemons/homebrew.mxcl.httpd.plist
sudo chmod 644 /Library/LaunchDaemons/homebrew.mxcl.httpd.plist


3. 將下列的檔案夾或是檔案的擁有者更改為 root:
/usr/local/opt/httpd
/usr/local/opt/httpd/bin
/usr/local/opt/httpd/bin/httpd
/usr/local/var/homebrew/linked/httpd


注意:我們在執行 Homebrew 時,使用的是目前登入的使用者權限,因此在升級、移除或是重新安裝 httpd 時,要使用 'sudo rm' 指令手動移除 httpd 舊版本的 Keg 檔案夾。直接使用 brew uninstall httpd 會出現下列的錯誤:
Error: Could not remove httpd keg! Do so manually:
  sudo rm -rf /usr/local/Cellar/httpd/2.4.41_1



4. 執行 sudo launchctl load 將 httpd 加入 launchd:
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.httpd.plist



在使用 Homebrew 安裝軟體時,plist 檔案預設會安裝在 Keg 檔案夾中,若要讓服務在開機時使用 root 權限自動執行 (成為守護行程),就要使用上述的 brew services 指令進行設定,或是以手動的方式執行上面的步驟 2、3、以及 4。


我們可以使用 launchctl list 指令檢查 httpd 是否已經被加入 launchd 中:
sudo launchctl list | grep httpd



將 httpd 從 Launchd 中移除或是重新加入


要將 httpd 從 Launchd 中移除,可以執行下列的指令:
sudo brew services stop httpd


在修改過 plist 檔案,或是要重新加入 Launchd,可以使用下列的指令:
sudo brew services restart httpd


將 httpd 設定為 Agent


要讓 httpd 能夠在登入後自動執行,可以執行下列的指令:
brew services start httpd



上面的指令包含了下面的動作:

1. 自動安裝 homebrew-services(如果之前沒有安裝過的話)。


2. 將 Keg 檔案夾中的 plist 檔案複製到 ~/Library/LaunchAgents:
cp /usr/local/opt/httpd/homebrew.mxcl.httpd.plist ~/Library/LaunchAgents


3. 執行 launchctl load,啟動 httpd:
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.httpd.plist



注意:使用 brew services 讓服務 (例如本文中的 httpd) 成為 Daemon 或是 Agent,主要的差異在於是否使用 sudo 指令。

將 httpd 從守護行程中移除或是重新加入


安裝 Homebrew-services 後,也可以使用它將 httpd 從任務項中移除:
brew services stop httpd


或是重新加入任務項 (在修改 homebrew.mxcl.httpd.plist 之後):
sudo brew services restart httpd


僅啟動 httpd


Homebrew-services 也可以只用來啟動 httpd:
brew services run httpd



列出可以被管理的服務


使用下列的指令可以列出所有可以被 brew services 管理的套件:
brew services list


或是簡單的使用:
brew services


如果先前執行過:
brew services start httpd


會輸出下列的資訊:
Name    Status  User  Plist
httpd   started userName /Users/userName/Library/LaunchAgents/homebrew.mxcl.httpd.plist
php     stopped       
redis   stopped  


顯示目前 httpd 是登入啟動的任務項 (plist 檔在 ~/Library/LaunchAgents/)。

如果使用 sudo 執行:
sudo brew services start httpd


再一次執行 brew services 則會列出:
Name    Status  User  Plist
httpd   started root  /Library/LaunchDaemons/homebrew.mxcl.httpd.plist
php     stopped       
redis   stopped  


顯示目前 httpd 是守護行程 (plist 檔在 /Library/LaunchDaemons/)。


注意:當把 httpd 設定為守護行程後,雖然在執行 sudo brew services stop httpd 後,會將 httpd 的 plist 檔從  /Library/LaunchDaemons/ 中移除,httpd 的 plist 檔仍然存在於  ~/Library/LaunchAgents/ 中。


提醒:因為 Mac 大部份是用來做為開發用,因此建議將服務設為任務項,在登入時啟動即可。

附記

在 2020/02/03 曾出現過 brew services - Unknown command 的問題,雖然問題很快就被解決,但是使用者要執行 brew update 才能夠再使用 brew services。

2019年5月30日 星期四

Windows 10 1803 安裝 XPS Viewer


在 AD Domain 並且使用 WSUS

1. 暫時停止 wuauserv 

工作管理員 -> 服務 -> wuauserv -> 停止


2. 執行 regedit

展開至HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU,將UseWUServer 改成 0。


3. 控制台 -> 程式和功能 ->  開啟或關閉 Windows 功能

安裝 .net Framework 3.5


4. 安裝 XPS Viewer (命令提示字元-系統管理員)

dism /Online /Add-Capability /CapabilityName:XPS.Viewer~~~~0.0.1.0


5. 將 UseWUServer 改回 1

HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU


6. 啟動 wuauserv

2019年2月11日 星期一

Wacom Intuos 觸控筆無法拖曳 Chrome 視窗以及直接反白文字

Wacom Intuos 觸控筆在 Windows 下的 Chrome 預設為 "畫筆模式",因此無法拖曳 Chrome 視窗以及直接反白文字。要解決這個問題,可以將 Chrome 加入應用程式,並且在 "觸控筆設定" 的 "映射" 選項中改為 "滑鼠模式"。
實際的操作可以參考:https://www.youtube.com/watch?v=v6SFw7A_Uok

2018年1月30日 星期二

PotPlayer 自動產生依照檔案名稱排序的播放清單 (解決連續播放的順序問題)

設定方式:


將基本設定的 "相似檔案開啟方式" 改為 "同時開啟全部檔案"。


詳細步驟:


PotPlayer 在播放完目前的檔案後,預設會自動播放下一個類似的檔案,而不是依照檔案名稱的順序進行播放。這是因為預設的 "相似檔案開啟方式" 為 "同時開啟相似的檔案"。



在 PotPlayer 按下 "F5" 可以開啟 "偏好設定",在基本設定中可以看到預設的 "相似檔案開啟方式" 為 "同時開啟相似的檔案"



要解決 PotPlayer 連續播放的順序問題,可以將檔案由檔案總管中依序拖曳到 "播放清單" 的 "預設清單" 中,來建立 "播放清單",讓 PotPlayer 依照清單順序播放。



在 PotPlayer 按下 "F6" 可以開啟 "播放清單",再將檔案依序拖曳到 "預設清單" 中,可以讓 PotPlayer 依照清單順序播放


雖然我們可以將播放清單儲存起來,讓 PotPlayer 下一次播放時可以依照該清單順序播放;但是每一次要依照順序播放某一個資料夾內的檔案時,都要使用拖曳的方式來建立播放清單,還是比較沒有效率。




先點選 PotPlayer 的 "預設清單" 後再按下 "F2" 可以開啟 "另存新檔",將目前的播放清單存檔,下一次 (按下 "F3" 開啟播放清單檔案) 就可以讓 PotPlayer 用相同的順序播放



要讓 PotPlayer 自動產生依照檔案名稱排序的播放清單,只需要將預設的 "相似檔案開啟方式" 變更為 "同時開啟全部檔案" 即可。



在 PotPlayer 按下 "F5" 開啟 "偏好設定",在基本設定中將預設的 "相似檔案開啟方式" 更改為 "同時開啟全部檔案",可以讓 PotPlayer 自動產生依照檔案名稱排序的預設清單



在完成設定後,在開啟資料夾內的影音檔案時,皆會自動產生依照檔案名稱排序的預設清單。



開啟任何資料夾內的影音檔案時,皆會自動產生依照檔案名稱排序的預設清單


PS.

如果要讓 PotPlayer 僅播放目前的檔案,則是將"相似檔案開啟方式" 更改為 "僅開啟選取的檔案"。



2018年1月3日 星期三

無法停用 “伴隨系統啟動TeamViewer”

在安裝完 TeamViewer 後,會在 /Library/LaunchDaemons  (表示其為由管理員定義的系統守護行程) 新增一個 com.teamviewer.teamviewer_service.plist,並且設定為開機自動啟動。


 TeamViewer 的喜好設定中的一般設定畫面中,顯示 TeamViewer 是隨著系統啟動的


在 com.teamviewer.teamviewer_service.plist 會有下面的設定:

<key>Disabled</key>
<false/>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>

如果使用 LaunchControl 則可以看到 com.teamviewer.teamviewer_service 為啟動 (Enabled) 而且狀熊為執行中 (Running)。


 com.teamviewer.teamviewer_service 為啟動 (Enabled) 而且狀熊為執行中 (Running)



如果在 TeamViewer 的一般設定中取消核可 “伴隨系統啟動TeamViewer”,在 com.teamviewer.teamviewer_service.plist 中
<key>Disabled</key> 會變成 <true/>,但是 <key>KeepAlive</key>
的值還是 <true/> (TeamViewer 的 plist 檔同時還設了  RunAtLoad 的值為 true),會造成重新開機時 TeamViewer 還是會伴隨系統啟動。是一個不正確的 plist 檔。

而在 LaunchControl 中可以看到 com.teamviewer.teamviewer_service 的狀態為 Disabled 而且 Unloaded,但是事實上我們在執行取消核可 “伴隨系統啟動TeamViewer”時,TeamViewer 還是在執行中。





因為不想要 “伴隨系統啟動TeamViewer”,試過將 com.teamviewer.teamviewer_service.plist 中的 KeepAlive 以及  RunAtLoad 的設定移除或是改為 false,可是在每次重啟 TeamViewer 後,這兩個設定又被還原回去了。

後來找到了原因了,是因為覆寫資料庫 (/var/db/com.apple.xpc.launchd/disabled.plist) 中,鍵值被改為 false 了。使用下列的指令將 disabled.plist 改為 true, TeamViewer 應該就不會伴隨系統啟動了:

sudo launchctl unload -w /Library/LaunchDaemons/com.teamviewer.teamviewer_service.plist

可是這樣,每次要開啟 TeamViewer 時都要執行:

sudo launchctl load -w /Library/LaunchDaemons/com.teamviewer.teamviewer_service.plist


不然 launchd 無法載入服務。

2017年11月14日 星期二

Apache HTTP Server 的多程序處理模組 (Multi-Processing Module, MPM)

在 Unix 系統,Apache HTTP Server (httpd) 2.4 提供了三種不同的多程序處理模組 (Multi-Processing Module,MPM),它們分別是:Prefork,Worker 和 Event。httpd 官網的 MPM 介紹 請參考

Prefork 模組:

Prefork 模組實作了一個非執行緒、預先分叉 (Pre-fork) 的網頁伺服器。當 httpd 在啟動時就預先 fork 一些子程序來等待請求。

優點:較為成熟穩定,兼容所有新舊模組;沒有使用執行緒,可以完全的區隔每一個不同的請求,所以不用擔心執行緒安全的問題。

缺點:每一個子程序相對的會占用更多的系統資源,消耗更多的記憶體;面對高並發 (High Concurrency) 請求時,系統效能會大幅下降。


Worker 模組:

這個模組混合使用多個子程序 (Multi-process) 和多個執行緒 (Multi-threaded)。一個單一控制程序 (父程序) 會預先啟動數個子程序,然後每個子程序會建立一些伺服器執行緒,以及一個監聽執行緒;監聽執行緒負責監聽連線 (Connection),並將連線傳遞給伺服器執行緒進行處理。相較於基於程序的伺服器,因為使用執行緒回應請求,可以有效減少系統資源的使用。

優點:使用較少的記憶體、CPU 時間等系統資源,因此相較於 Prefork 模組,較能夠回應高並發請求。

缺點:因為多個執行緒共享父程序的記憶體地址,所以必須考慮執行緒的安全問題;每個執行緒處理一個連線,會有阻塞 (Blocking) 的問題。


Event 模組:

以 Worker 模組為基礎, Event 模組一樣是使用子程序和執行緒處理客戶諯請求;最大的差別在於 Event 模組會為每個 HTTP 請求對應到一個執行緒,而 Worker 模組則是每一個 HTTP 連線(可能會有多個請求)對應到一個執行緒。在  keep-alive1 連線模式下,httpd 傳統上會持續占用整個子程序 / 執行緒來等待客戶端的資料,造成了自身的劣勢。

而在 Event 模組中,每一個程序會有一個專門的監聽執行緒來管理兩種不同的監聽插座 (Listening Socket):在 Keep-alive 狀態的所有插座,以及已經處理完畢或是只剩下等待回傳資料給客戶端的插座。因此 Event 模組可以修正 Worker 模組會碰到的 keep-alive 問題。這種架構需要利用非阻塞 (Non-blocking) 插座和現代作業系統內核的輪詢 (Polling) 功能,以避免驚群問題 (Thundering Herd Problem)。


優點:解決了在 keep-alive 情境下,資源被沒有 HTTP 請求的連線長期占用的問題。


註1. 當一個客戶端完成第一項請求時,它可以繼續維持連線,讓後續的請求使用相同的插座 (Socket),節省在建立 TCP 連線時的顯著負擔。


模組設定


下列的指令可以檢查目前的 MPM。

httpd -V


Server version: Apache/2.4.29 (Unix)
Server built:   Nov  6 2017 14:32:45
Server's Module Magic Number: 20120211:68
Server loaded:  APR 1.6.3, APR-UTIL 1.6.1
Compiled using: APR 1.6.3, APR-UTIL 1.6.1
Architecture:   64-bit
Server MPM:     prefork
  threaded:     no
    forked:     yes (variable process count)


(預設為使用 prefork 模組)


使用 Homebrew 安裝的 httpd 已經將 3 種模組都預先編譯到套件內了,只需要在 httpd.conf 啟用相關的模組就可以了。例如,要啟用 Event 模組,只需要完成下列的設定:

LoadModule mpm_event_module lib/httpd/modules/mod_mpm_event.so
#LoadModule mpm_prefork_module lib/httpd/modules/mod_mpm_prefork.so
#LoadModule mpm_worker_module lib/httpd/modules/mod_mpm_worker.so


重新啟動 httpd,就可以改為 Event 模組:

httpd -V


Server version: Apache/2.4.29 (Unix)
Server built:   Nov  8 2017 08:51:12
mac1deiMac:~ timmy$ httpd -V
Server version: Apache/2.4.29 (Unix)
Server built:   Nov  8 2017 08:51:12
Server's Module Magic Number: 20120211:68
Server loaded:  APR 1.6.3, APR-UTIL 1.6.1
Compiled using: APR 1.6.3, APR-UTIL 1.6.1
Architecture:   64-bit
Server MPM:     event
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)



預設的 MPM


根㨿 httpd 官網,在安裝 MPM 時,主要考慮下列的兩個問題

1. 系統支援執行緒嗎?

2. 系統支援執行緒安全輪詢 (thread-safe polling)嗎 (特別是 kqueue 以及 epoll 兩個函數)?

如果兩個答案都是肯定的,預設安裝的 MPM 會是 Event 模組;如果只有問題 1 的答案是肯定的,預設的 MPM 會是 Worker 模組;如果兩個答案都是否定的,預設的 MPM 會是 Prefork 模組。因為現代的作業系統都支援這兩個特性,實際上預設的 MPM 都會是 Event 模組。

不過,MPM 的選擇還牽涉到 SAPI 對於 MPM 的支援;例如,在生產環境,如果使用 PHP 的 Apache 2 Handler 模組,就不建議使用執行緒類型的 MPM。相關理由請參考