顯示具有 Tool 標籤的文章。 顯示所有文章
顯示具有 Tool 標籤的文章。 顯示所有文章

2026-09-18

NaviServer 4.99.34, 5.0.6, and 5.1.1

NaviServer 最近釋出了一系列的新版,4.99.34, 5.0.6, 與 5.1.1。

5.1 系列是個重要的版本更新,自 5.1.0 開始實驗性的支援 HTTP/3 協定(透過 nghttp3,需要 OpenSSL 4.0 以上的版本),從 5.1.1 開始 HTTP/3 實作的狀態從 expermiental 變更為 ready-for-use。

2025-12-04

NaviServer: Database

NaviServer 目前提供了二種資料庫介面, nsdb 與 後來擴充一些功能的 nsdbi。 nsdb 為 NaviServer 的內建模組(只是需要自行設定載入模組),而 nsdbi 需要使用者自己編譯並且加入到 NaviServer。

注意:如果想要使用 OpenACS 並且資料庫使用 PostgreSQL,那麼必須安裝 nsdbpg 模組。 下面使用 PostgreSQL 測試 nsdb 以及 nsdbpg 模組。使用者需要編譯 nsdbpg 模組並且加入到 NaviServer。 如果要自己編譯 nsdbpg 模組,注意其 Makefile 中 NAVISERVER 所設定的位置, 以及 dbpg.h 中 pg_config.h 和 libpq-fe.h 標頭檔的位置(openSUSE 安裝的位置放在 /usr/include/pgsql)。 下面是標示差異點的 patch 檔案。

--- dbpg.h.org    2025-12-03 16:37:40.171256167 +0800
+++ dbpg.h    2025-12-03 16:37:58.707205142 +0800
@@ -25,7 +25,7 @@
  * pg_config.h. However, the PACKAGE_* macros conflict with
  * NaviServer's packaging information, so we drop these.
  */
-#include <pg_config.h>
+#include <pgsql/pg_config.h>
 #undef PACKAGE_VERSION
 #undef PACKAGE_TARNAME
 #undef PACKAGE_STRING
@@ -34,7 +34,7 @@
 #undef PACKAGE_URL
 
 #include <nsdb.h>
-#include <libpq-fe.h>
+#include <pgsql/libpq-fe.h>
 
 /*
  * Forward compatibility, in case a new version of the module is compiled
--- Makefile.org    2025-12-03 16:39:07.524319290 +0800
+++ Makefile    2025-12-03 16:39:23.699037548 +0800
@@ -27,7 +27,7 @@
 # version of this file under either the License or the GPL.
 
 ifndef NAVISERVER
-    NAVISERVER  = /usr/local/ns
+    NAVISERVER  = /var/lib/naviserver
 endif
 
 #

不過除了直接修改,也可以使用設定參數的方式,下面就是編譯與安裝的指令:

make NAVISERVER=/var/lib/naviserver PGLIB=/usr/lib64 PGINCLUDE=/usr/include/pgsql
sudo make NAVISERVER=/var/lib/naviserver install

在安裝完 nsdbpb 以後,接下來修改 nsd-config.tcl,首先加入相關的變數:

# For database
dict set defaultConfig db_name           danilo
dict set defaultConfig db_user           danilo
dict set defaultConfig db_password       danilo
dict set defaultConfig db_host           localhost
dict set defaultConfig db_port           5432

加入 nsdb 模組,讓 NaviServer 在啟動時會載入模組。

ns_section ns/server/default/modules {
    if {$nscpport ne ""} {ns_param nscp nscp}
    ns_param    nslog               nslog    
    ns_param    nscgi               nscgi
    ns_param    nsperm              nsperm
    ns_param    nsdb                nsdb
    ns_param    revproxy            tcl
}

然後加入 nsdbpg driver 的設定。

ns_section ns/db/drivers {
    ns_param postgres nsdbpg
}

接下來加入 connection pool 的設定。使用者可以設定不止一個 pool,我在這裡只是驗證如何設定,所以只加入一個。

ns_section ns/server/default/db {
   #ns_param Pools pool1,pool2
   ns_param Pools pool1
   ns_param defaultpool pool1
}

ns_section ns/db/pools {
    ns_param pool1 "This is pool1 for PostgreSQL"
}

ns_section ns/db/pool/pool1 {
   ns_param Connections        10
   ns_param LogMinDuration     10ms
   ns_param LogSQLerrors       false
   ns_param driver             postgres
   ns_param DataSource         ${db_host}:${db_port}:${db_name}
   ns_param user               $db_user
   ns_param password           $db_password
}

最後寫一個簡單的測試程式測試。

<%
     ns_adp_puts "NaviServer nsdbpg module - get PostgreSQL version<br>"

     set db [ns_db gethandle]
     set result [ns_db 1row $db "select version() as version"]
     ns_adp_puts [ns_set get $result version]
%>

下面嘗試安裝 nsdbi 以及 nsdbipg。

首先是 nsdbi,下面就是編譯與安裝的指令:

make NAVISERVER=/var/lib/naviserver
sudo make NAVISERVER=/var/lib/naviserver install

再來是 nsdbipg,下面就是編譯與安裝的指令:

make NAVISERVER=/var/lib/naviserver PGLIB=/usr/lib64 PGINCLUDE=/usr/include/pgsql
sudo make NAVISERVER=/var/lib/naviserver install

接下來修改 nsd-config.tcl,首先是加入模組(有二種方式,一種是全域的加入方法,一種是 per server 的加入方式, 這裡使用後一種):

ns_section ns/server/default/modules {
    if {$nscpport ne ""} {ns_param nscp nscp}
    ns_param    nslog               nslog
    ns_param    nscgi               nscgi
    ns_param    nsperm              nsperm
    ns_param    revproxy            tcl
    ns_param    nsdbipg1            nsdbipg
}

以及 nsdbipg1 的設定:

ns_section ns/server/default/module/nsdbipg1 {
    ns_param   default        true ;# This is the default pool
    ns_param   handles        10   ;# Max open handles to db.
    ns_param   maxwait        10   ;# Seconds to wait if handle unavailable.
    ns_param   maxidle        0    ;# Handle closed after maxidle seconds if unused.
    ns_param   maxopen        0    ;# Handle closed after maxopen seconds, regardless of use.
    ns_param   maxqueries     0    ;# Handle closed after maxqueries SQL queries.
    ns_param   checkinterval  600  ;# Check for idle handles every 10 minutes.

    ns_param   datasource     "host='localhost' port=5432 user='danilo' password='danilo' dbname='danilo'"
}

最後寫一個簡單的測試程式測試。

<%
     ns_adp_puts "NaviServer nsdbipg module - get PostgreSQL version<br>"

     dbi_1row -array result {select version() as version}
     ns_adp_puts $result(version)
%>

如果將取得結果回傳為一個串列:

set result [dbi_rows {select a, b from t}]

foreach {a b} $result {
  lappend lresult [list $a $b]
}

nsdbi 使用 dbi_dml 執行 insert/update/delete 指令。nsdbi 支援 bind variable,使用 :variable 的形式來代表變數。

dbi_dml {insert into test values (3, 'z')}
dbi_dml {insert into test (a, b, c) values (:a, :b, :c)}
set numRows [dbi_dml {update test set b = 'z' where a = 1}]
dbi_dml {delete from test where a = 3}

要注意的是,database handle 由 NaviServer 所管理,所以你在測試程式並沒有看到開啟 handle 的動作。

另外,如果使用 NaviServer 提供的資料庫介面,建議修改 systemd service 檔案 nsd.service, 以 PostgreSQL 來說,最好加入 postgresql.service 的部份,所以可以改為如下:

[Unit]
Description=NaviServer
#After=network.target
After=network.target postgresql.service
Wants=postgresql.service

NaviServer: CGI and ADPs

NaviServer 本身支援 ASP/PHP 類似的寫法,可以內嵌 Tcl 程式碼到網頁中, 稱為 NaviServer Dynamic Pages (ADPs),其副檔名為 adp,可以參考下列的網頁:
NaviServer ADP Development

主要的語法有以下三種,第一種為使用 script 標籤:

<script language="tcl" runat="server" stream="on">
...
</script>

第二種則是短標籤:

<% ... %>

第三種則是印出變數的短標籤:

<%= ... %>

NaviServer 已經內建 nscgi 模組。檢查 nsd-config.tcl(或者是你使用的設定檔),確定有載入模組:

ns_section ns/server/default/modules {
    if {$nscpport ne ""} {ns_param nscp nscp}
    ns_param    nslog               nslog
    ns_param    nscgi               nscgi
    ns_param    nsperm              nsperm
    ns_param    revproxy            tcl
}

如果想要使用以前使用 Tcl 寫的 CGI 程式、或者是要使用 NaviServer 練習撰寫 CGI 程式, 只要注意 NaviServer cgi-bin 設定的位置正確的放置檔案,以及正確的加入 Tcl interpreter 的設定即可。 接下來修改 nsd-config.tcl:

ns_section ns/server/default/module/nscgi {
    ns_param    map                 "GET  /cgi-bin $home/cgi-bin"
    ns_param    map                 "POST /cgi-bin $home/cgi-bin"
    ns_param    interps              CGIinterps
    #ns_param   allowstaticresources true    ;# default false; serve static resources from cgi directories
}
    
ns_section ns/interps/CGIinterps {
    ns_param    .pl                 "/usr/bin/perl"
    ns_param    .tcl                "/usr/bin/tclsh"
    ns_param    .sh                 "/bin/bash"
}

PHP 目前在網頁伺服器主要有三種執行的方式,使用 CGI,使用 FastCGI 以及內嵌到網頁伺服器裡。 如果使用 PHP 8.x,php cgi 程式 (php-cgi) 在 openSUSE Tumbleweed 的套件為 php8-fastcgi。 如果想透過 NaviServer 使用 CGI 的方式執行 PHP 是可行的,只要修改 nsd-config.tcl 加入 PHP interpreter 的設定:

ns_section ns/interps/CGIinterps {
    ns_param    .pl                 "/usr/bin/perl"
    ns_param    .php                "/usr/bin/php-cgi"
    ns_param    .tcl                "/usr/bin/tclsh"
    ns_param    .sh                 "/bin/bash"
}

然後寫一個測試程式:

<?php

phpinfo();

?>

2024-05-09

ATOM to HTML

下面的程式使用 TclCurl 自網站下載 ATOM XML 的資料, 下載以後使用 tDom 分析並且將 title 與 link 的資料儲存為 html 格式。 只有使用自己的部落格資料測試過。

#!/usr/bin/env tclsh

package require TclCurl
package require tdom

proc get_atom {url} {
    try {
        set curlHandle [curl::init]
        $curlHandle configure -url $url -bodyvar result
        $curlHandle setopt CURLOPT_HTTP_VERSION 2TLS

        catch { $curlHandle perform } curlErrorNumber
        if { $curlErrorNumber != 0 } {
            throw error [curl::easystrerror $curlErrorNumber]
        }
    } on error {em} {
        error "Error: $em"
    } finally {
       $curlHandle cleanup
    }

    return $result
}

proc parse {XML ofname} {
    set doc [dom parse $XML]
    set root [$doc documentElement]
    set ns {xmlns http://www.w3.org/2005/Atom}
    $doc selectNodesNamespaces $ns
    set titleList [$root selectNodes //xmlns:entry/xmlns:title]
    set linkList [$root selectNodes {//xmlns:entry/xmlns:link[@rel='alternate']}]

    set out [open $ofname w 0666]
    foreach tnode $titleList lnode $linkList {
        set ntitle [$tnode text]
        set nlink [$lnode getAttribute href]

        puts $out "<a href=\"$nlink\">$ntitle</a><br>"
    }
    close $out
}

if {$argc == 2} {
    set url [lindex $argv 0]
    set ofile [lindex $argv 1]
} else {
    puts "Usage:"
    puts "\ttclsh atom2html.tcl url filename"
    exit
}

if {[catch {set data [get_atom $url]} err]} {
    puts $err
} else {
    parse $data $ofile
}

2024-04-17

Eclipse Mosquitto

Eclipse Mosquitto 是一套開放原始碼的 MQTT Broker。

在 openSUSE 安裝:

sudo zypper in mosquitto

如果要執行的話,使用 systemctl 啟動服務:

sudo systemctl start mosquitto

再來使用 tcl.mqttc 驗證是否可以正確連線。

Subscribe:
package require mqttc

mqttc client "tcp://localhost:1883" "USERSSub" 1 -cleansession 1
client subscribe  "MQTT Examples" 1
while 1 {
    if {[catch {set result [client  receive]}]} {
        puts "Receive error!!!"
        break
    }
    if {[llength $result] > 0} {
        puts "[lindex $result 0] - [lindex $result 1]"
        if {![string compare -nocase [lindex $result 1] "Exit"]} {
            break
        }
    }
}
client unsubscribe  "MQTT Examples"
client close
Publish:
package require mqttc

mqttc client "tcp://localhost:1883" "USERSPub" 1 -timeout 1000
client publishMessage "MQTT Examples" "Hello MQTT!" 1 0
client publishMessage "MQTT Examples" "Exit" 1 0
client close

先執行 Subscribe 的部份,再使用 Publish 發送訊息,確定可以正確執行。


下面是另外一個使用 tcl.mqttc 測試 Subscribe 與 Publish 功能的測試程式:

package require Thread
package require mqttc
catch {console show}

set ::gThread [thread::create {thread::wait} ]
set result 0

proc subscribe { } {
    thread::send -async $::gThread {
        package require mqttc

        mqttc client "tcp://localhost:1883" "USERTest1" 1 -cleansession 1
        client subscribe  "MQTT Examples" 1
        while 1 {
            if {[catch {set result [client  receive]}]} {
                puts "Receive error!!!"
                break
            }
            if {[llength $result] > 0} {
                puts "[lindex $result 0] - [lindex $result 1]"
                if {![string compare -nocase [lindex $result 1] "Exit"]} {
                    break
                }
            }
        }
        client unsubscribe  "MQTT Examples"
        client close
    } ::result
}

subscribe
puts "started test..."

after 250

mqttc client "tcp://localhost:1883" "USERTest2" 1 -timeout 1000
client publishMessage "MQTT Examples" "Hello MQTT!" 1 0
client publishMessage "MQTT Examples" "Exit" 1 0
client close

vwait ::result

2024-04-10

Apache HTTP Server and rivet-fcgi

簡介

Apache HTTP Server 是 Apache 軟體基金會的一個開放原始碼的網頁伺服器軟體,可以在大多數電腦作業系統中運行, 以其高擴充的能力以及眾多的 module 著稱。雖然現在效能被其它的網頁伺服器超過,但是也仍然是一個高效能的伺服器。

在 openSUSE 安裝 Apache2:

sudo zypper install apache2

檢查 Apache 版本:

sudo httpd -v

建立 ssl.conf 設定檔

[req]
prompt = no
default_md = sha256
default_bits = 2048
distinguished_name = dn
x509_extensions = v3_req

[dn]
C = TW
ST = Taiwan
L = Taipei
O = Orange Inc.
OU = IT Department
emailAddress = admin@example.com
CN = localhost

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.localhost
DNS.2 = localhost
IP.1 = 127.0.0.1

透過指令建立開發測試用途的自簽憑證

openssl req -x509 -new -nodes -sha256 -utf8 -days 3650 -newkey rsa:2048 \
-keyout apache.key -out apache.crt -config ssl.conf

將 apache.key 複製到 ssl.key 目錄,apache.crt 複製到 ssl.crt 目錄(需要使用 su 切換到 root 身份或者使用 sudo)。

將 /etc/apache2/vhosts.d/vhost-ssl.template 複製為 vhost-ssl.conf,並修改內容如下:

# Template for a VirtualHost with SSL
# Note: to use the template, rename it to /etc/apache2/vhost.d/yourvhost.conf.
# Files must have the .conf suffix to be loaded.
#
# See /usr/share/doc/packages/apache2/README.QUICKSTART for further hints
# about virtual hosts.
#
# This is the Apache server configuration file providing SSL support.
# It contains the configuration directives to instruct the server how to
# serve pages over an https connection. For detailing information about these
# directives see http://httpd.apache.org/docs/2.4/mod/mod_ssl.html
#
# Do NOT simply read the instructions in here without understanding
# what they do.  They're here only as hints or reminders.  If you are unsure
# consult the online docs. You have been warned.
#

<IfDefine SSL>
<IfDefine !NOSSL>

##
## SSL Virtual Host Context
##

<VirtualHost _default_:443>

    #  General setup for the virtual host
    DocumentRoot "/srv/www/htdocs"
    #ServerName www.example.com:443
    #ServerAdmin webmaster@example.com
    ErrorLog /var/log/apache2/error_log
    TransferLog /var/log/apache2/access_log

    #   SSL Engine Switch:
    #   Enable/Disable SSL for this virtual host.
    SSLEngine on

    #   OCSP Stapling:
    #   Enable/Disable OCSP for this virtual host.
    SSLUseStapling  on

    #   You can use per vhost certificates if SNI is supported.
    SSLCertificateFile /etc/apache2/ssl.crt/apache.crt
    SSLCertificateKeyFile /etc/apache2/ssl.key/apache.key
    #SSLCertificateChainFile /etc/apache2/ssl.crt/vhost-example-chain.crt

    #   Per-Server Logging:
    #   The home of a custom SSL log file. Use this when you want a
    #   compact non-error SSL logfile on a virtual host basis.
    CustomLog /var/log/apache2/ssl_request_log   ssl_combined

    <Directory />
        Options FollowSymLinks
        AllowOverride All
        #    Order deny,allow
        #    Deny from all
    </Directory>

    <Directory /srv/www/htdocs>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
    </Directory>

</VirtualHost>

</IfDefine>
</IfDefine>

主要就是 SSLCertificateFile 與 SSLCertificateKeyFile 這二個項目的設定,端視所放置的位置。


Apache 提供不同的 multiprocessing modules (MPMs) 可以使用,主要有下列的分別:

  • Prefork MPM
  • Worker MPM
  • Event MPM

HTTP/2 支援無法在 Prefork MPM 下執行,所以需要設定使用其它的 MPM,目前建議使用 Event MPM。

Enable http2 mod:

sudo a2enmod http2

Enable HTTP2 flag:

sudo a2enflag HTTP2

Switch from prefork to event:

sudo zypper remove apache2-prefork
sudo zypper install apache2-event

In /etc/sysconfig/apache2 make sure to set

APACHE_MPM="event"

與

APACHE_START_TIMEOUT="10"

重啟 Apache 2:

sudo systemctl restart apache2

To automatically start the apache server after a reboot:

sudo systemctl enable apache2

Apache 內建支援 CGI,default-server.conf 在目錄內啟用 CGI 與設定 handler 的設定如下:

ScriptAlias /cgi-bin/ "/srv/www/cgi-bin/"

# "/srv/www/cgi-bin" should be changed to whatever your ScriptAliased
# CGI directory exists, if you have that configured.
#
<Directory "/srv/www/cgi-bin">
    AllowOverride None
    Options +ExecCGI -Includes
    <IfModule !mod_access_compat.c>
        Require all granted
    </IfModule>
    <IfModule mod_access_compat.c>
        Order allow,deny
        Allow from all
    </IfModule>
</Directory>

而 Options +ExecCGI 就是允許執行 CGI 的設定。


檢查目前載入的模組:

sudo apache2ctl -M

(如果沒有 apache2ctl 這個指令,可以嘗試使用 sudo httpd -M)

FastCGI

Apache mod_fcgid 是 Apache Http Server 用來支援 FastCGI 協定的模組。 FastCGI 是另外一種克服 CGI 缺點的發展路線,也就是將網頁伺服器與應用程式伺服器分開的方式, 而程式會持續執行,這樣就不需要每一個請求都需要產生一個子行程來運行。

下面是在 openSUSE 安裝的方式:

sudo zypper install apache2-mod_fcgid

接下來設定 Apache Http Server 的部份。
首先允許必要的模式:

sudo a2enmod proxy
sudo a2enmod proxy_fcgi
sudo a2enmod setenvif
sudo a2enmod fcgid

下面使用的方式配合 spawn-fcgi。
我們需要撰寫 spawn-fcgi 的 systemd service,在 /usr/lib/systemd/system 目錄下建立 spawnfcgi.service, 通訊使用 UNIX Socket,內容如下:

[Unit]
Description=Spawn FCGI service
After=nss-user-lookup.target

[Service]
Type=forking
Environment=WORKERS=1
ExecStart=/usr/bin/spawn-fcgi \
    -F ${WORKERS} \
    -u wwwrun \
    -g www \
    -s /var/run/%p.sock \
    -P /var/run/%p.pid \
    -- /usr/bin/rivet-fcgi
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

如果使用 TCP:

Description=Spawn FCGI service
After=nss-user-lookup.target

[Service]
Type=forking
Environment=WORKERS=3
ExecStart=/usr/bin/spawn-fcgi \
    -F ${WORKERS} \
    -u wwwrun \
    -g www \
    -a 127.0.0.1 -p 9000 \
    -P /var/run/%p.pid \
    -- /usr/bin/rivet-fcgi
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

(其中 -u 指定 user,-g 指定 group,隨著平台的不同可能有不同的設定。
rivet-fcgi 是我自己撰寫的工具。)

下面是測試我自己寫的工具的設定。
修改 /etc/apache2/conf.d 目錄下的檔案 mod_fcgid.conf,通訊使用 UNIX Socket,內容如下:

##
#<FilesMatch "\.php$">
#    AddHandler fcgid-script .php
#    Options +ExecCGI
#    FcgidWrapper /srv/www/cgi-bin/php5 .php
#</FilesMatch>
##
DirectoryIndex index.rvt index.tcl
<FilesMatch "\.(rvt|tcl)$">
    SetHandler "proxy:unix:/var/run/spawnfcgi.sock|fcgi://localhost/"
    #CGIPassAuth on
</FilesMatch>

如果使用 TCP:

DirectoryIndex index.rvt index.tcl
<FilesMatch "\.(rvt|tcl)$">
    SetHandler "proxy:fcgi://127.0.0.1:9000/"
    #CGIPassAuth on
</FilesMatch>

而後啟動(或重新啟動)spawn-fcgi 與 apache2 的服務,接著進行測試是否有正確設定。
(注意:PHP-FPM 也可以使用類似的設定手法,只是 spawn-fcgi 設定的部份變成為設定 php-fpm 的參數, 而 web server 這邊的副檔名參數與連線參數需要修改。)

參考資料

2024-01-23

List ODBC data sources

這是一個簡單的程式,列出目前環境中 ODBC 的 data sources。

#!/usr/bin/env tclsh

#
# A simple program to list ODBC Data Sources by Tcl/Tk, version 0.1
#

package require Tcl 8.6
package require Tk
package require tablelist
package require tdbc::odbc

if { [catch {package require awthemes}]==0} {
    ttk::setTheme "awlight"
}

wm geometry . 600x400+100+100

frame .menubar -relief raised -bd 2
pack .menubar -side top -fill x

ttk::menubutton .menubar.file -text File -menu .menubar.file.menu
menu .menubar.file.menu -tearoff 0
.menubar.file.menu add command -label Quit -command Exit
ttk::menubutton .menubar.help -text Help -menu .menubar.help.menu
menu .menubar.help.menu -tearoff 0
.menubar.help.menu add command -label About -command HelpAbout
pack .menubar.file .menubar.help -side left

# Contextual Menus
menu .menu
foreach i [list Exit] {
    .menu add command -label $i -command $i
}

if {[tk windowingsystem]=="aqua"} {
    bind . <2> "tk_popup .menu %X %Y"
    bind . <Control-1> "tk_popup .menu %X %Y"
} else {
    bind . <3> "tk_popup .menu %X %Y"
}

# Get data sources list and list
tablelist::tablelist .t -columns {0 "DSN" 0 "Driver"} -stretch all \
    -background white -font {Helvetica -14}
pack .t -fill both -expand 1 -side top
set sources [::tdbc::odbc::datasources]
foreach {dsn driver} $sources {
   .t insert end [list $dsn $driver]
}

# Handle special key
bind all <F1> HelpAbout

#=================================================================
# Event Handler
#=================================================================

proc Exit {} {
    set answer [tk_messageBox -message "Really quit?" -type yesno -icon warning]
    switch -- $answer {
        yes exit
    }
}

proc HelpAbout {} {
    set ans [tk_messageBox -title "About" -type ok -message \
    "Using TDBC-ODBC to list ODBC Data Sources." ]
}

2024-01-21

RSS to HTML

下面的程式使用 TclCurl 自網站下載 RSS XML 的資料, 下載以後使用 tDom 分析並且將 title 與 link 的資料儲存為 html 格式。

#!/usr/bin/env tclsh

package require TclCurl
package require tdom

proc get_rss {url} {
    try {
        set curlHandle [curl::init]
        $curlHandle configure -url $url -bodyvar result
        $curlHandle setopt CURLOPT_HTTP_VERSION 2TLS

        catch { $curlHandle perform } curlErrorNumber
        if { $curlErrorNumber != 0 } {
            throw error [curl::easystrerror $curlErrorNumber]
        }
    } on error {em} {
        error "Error: $em"
    } finally {
       $curlHandle cleanup
    }

    return $result
}

proc parse {XML ofname} {
    set doc [dom parse $XML]
    set root [$doc documentElement]
    set titleList [$root selectNodes //item/title]
    set linkList [$root selectNodes //item/link]

    set out [open $ofname w 0666]
    foreach tnode $titleList lnode $linkList {
        set ntitle [$tnode text]
        set nlink [$lnode text]
        puts $out "<a href=\"$nlink\">$ntitle</a><br>"
    }
    close $out
}

if {$argc == 2} {
    set url [lindex $argv 0]
    set ofile [lindex $argv 1]
} else {
    puts "Usage:"
    puts "\ttclsh rss2html.tcl url filename"
    exit
}

if {[catch {set data [get_rss $url]} err]} {
    puts $err
} else {
    parse $data $ofile
}

2023-12-25

unixODBC isql

unixODBC 提供了 isql 這個命令列工具。要注意的是,有不少資料庫的命令列工具也取名為 isql,使用前應該先使用下列的指令確定:

isql --version

下面是指令的參數:

isql DSN [USER [PASSWORD]] [options]

下面的範例是使用 DSN=PostgreSQL 查詢資料庫版本,並且輸出為 html 的例子:

echo "select version() as version" | isql PostgreSQL -b -w > result.html

(-b 代表不要 interactive mode,而 -w 代表輸出 html table 結果。)

然後接下來撰寫一個 Tcl 程式測試:

#!/usr/bin/env tclsh
if {$argc >= 1} {
    set statement [lindex $argv 0]
} elseif {$argc == 0} {
    puts "Please input a SQL statement"
    exit
}

set var [list echo $statement | isql PostgreSQL -b -w > result.html]
exec {*}$var

2023-11-03

openSUSE: Lighttpd and Tcl CGI

Lighttpd 是一款以 BSD 授權條款開源的網頁伺服器, 設計目的以安全、快速、小巧為主要設計理念。Lighttpd 和 Nginx 都是主打性能(二者的效能在伯仲之間)的網頁伺服器, 同時在使用情況上有一定範圍的重覆,只是 Lighttpd 在開始搶佔市佔率的時候被發現有一些 memory leak 的問題, 雖然在之後陸陸續續問題有所修正,但是在之後已無法取得更多的市佔率。

即使如此,當使用的情況主要是以靜態網頁為主並且不需要太多功能(Lighttpd 的 memory leak 主要發生在動態網頁, 而 Lighttpd 在靜態網頁方面十分高效),或者是在硬體有一些限制的情況下,需要設定簡單、輕量級的網頁伺服器, 就十分適合使用 Lighttpd。另外,和 Nginx 不同的是,Lighttpd 同時支援 CGI 與 FastCGI。

Install Lighttpd (@openSUSE):

sudo zypper install lighttpd lighttpd-mod_openssl

注意:Lighttpd 的 conf 目錄在 openSUSE 權限設定為 640,所以一般使用者無法直接讀取。 另外,lighttpd-mod_openssl 視安裝的版本而定,需要檢查才知道是否需要安裝


使用下列的指令查看 Lighttpd 的版本資訊,有 ssl 字樣表示支援 SSL。

sudo lighttpd -v

如果要啟動服務,使用下列的指令:

sudo systemctl start lighttpd.service

如果要查詢目前服務的狀態,使用下列的指令:

sudo systemctl status lighttpd.service

如果要停止服務,使用下列的指令:

sudo systemctl stop lighttpd.service

如果要重新啟動服務,使用下列的指令:

sudo systemctl restart lighttpd.service

接下來使用自簽憑證設定 HTTPS。
首先建立 ssl.conf 設定檔:

[req]
prompt = no
default_md = sha256
default_bits = 2048
distinguished_name = dn
x509_extensions = v3_req

[dn]
C = TW
ST = Taiwan
L = Taipei
O = Orange Inc.
OU = IT Department
emailAddress = admin@example.com
CN = localhost

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.localhost
DNS.2 = localhost
IP.1 = 127.0.0.1

透過指令建立開發測試用途的自簽憑證:

openssl req -x509 -new -nodes -sha256 -utf8 -days 3650 \
-newkey rsa:2048 -keyout lighttpd.pem -out lighttpd.pem -config ssl.conf

(注意:如果是其它需要使用 private key 與 public key 的 server, 那麼 -keyout 可以設置為 lighttpd.key,而 -out 可以設置為 lighttpd.csr,以此類推)

可以透過以下指令查看 lighttpd.pem 內容:

openssl x509 -in lighttpd.pem -text

建立 /etc/lighttpd/ssl 目錄,將 lighttpd.pem 複製到 /etc/lighttpd/ssl 目錄下。

修改 /etc/lighttpd/modules.conf,加入下列的內容:

server.modules = (
#  "mod_rewrite",
  "mod_access",
#  "mod_auth",
#  "mod_authn_file",
#  "mod_redirect",
#  "mod_setenv",
#  "mod_alias",
   "mod_openssl",
)

修改 /etc/lighttpd/lighttpd.conf,加入下列的內容:

$SERVER["socket"] == ":443" {
        ssl.engine = "enable"
        ssl.pemfile = "/etc/lighttpd/ssl/lighttpd.pem"
        server.name = "localhost"
        server.document-root = "/srv/www/htdocs/"
}

Lighttpd 自 1.4.56 開始支援 HTTP/2(目前預設值為啟用)。
如果你想要嘗試 enable/disable HTTP/2,使用下列的設定:

server.feature-flags += ("server.h2proto" => "enable")

CGI

接下來加入 CGI 支援。

修改 /etc/lighttpd/modules.conf,修改並加入下列的內容:

server.modules = (
#  "mod_rewrite",
  "mod_access",
#  "mod_auth",
#  "mod_authn_file",
#  "mod_redirect",
#  "mod_setenv",
   "mod_alias",
   "mod_cgi",
   "mod_openssl",
)

修改 /etc/lighttpd/lighttpd.annotated.conf,加入下列的內容(因為要使用 Tcl 寫 CGI 程式, 所以要避免被視為靜態檔案):

static-file.exclude-extensions = ( ".tcl", ".php", ".pl", ".fcgi", ".scgi" )

Lighttpd 的 CGI 主要可以有二種風格的設定方式,一種是以 file extension 為主, 修改 /etc/lighttpd/lighttpd.conf:

cgi.assign = ( ".tcl"  => "/usr/bin/tclsh",
               ".cgi" => "/usr/bin/tclsh" )

另外一種則是配合 mod_alias,將 CGI 程式放在 /cgi-bin:

# For execute CGI in /cgi-bin
alias.url += ( "/cgi-bin" => server_root + "/cgi-bin" )
$HTTP["url"] =~ "^/cgi-bin" {
   cgi.assign = ( "" => "" )
}

要使用何種風格的設定方式看使用者的喜好。

FastCGI

接下來是 FastCGI 的設定。
修改 /etc/lighttpd/modules.conf,修改並加入下列的內容:

server.modules = (
#  "mod_rewrite",
  "mod_access",
#  "mod_auth",
#  "mod_authn_file",
#  "mod_redirect",
#  "mod_setenv",
   "mod_alias",
   "mod_cgi",
   "mod_fastcgi",
   "mod_openssl",
)

spawn-fcgi

下面使用的方式配合 spawn-fcgi。
我們需要撰寫 spawn-fcgi 的 systemd service,在 /usr/lib/systemd/system 目錄下建立 spawnfcgi.service,內容如下:

[Unit]
Description=Spawn FCGI service
After=nss-user-lookup.target

[Service]
Type=forking
Environment=WORKERS=1
ExecStart=/usr/bin/spawn-fcgi \
    -F ${WORKERS} \
    -u lighttpd \
    -g lighttpd \
    -a 127.0.0.1 -p 9000 \
    -P /var/run/%p.pid \
    -- /usr/bin/rivet-fcgi
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

(其中 -u 指定 user,-g 指定 group,隨著平台的不同可能有不同的設定。)

下面是測試我自己寫的工具 rivet-fcgi 的設定, 修改 /etc/lighttpd/lighttpd.conf:

fastcgi.server = (
    ".rvt" =>
        (( "host" => "127.0.0.1",
            "port" => 9000,
            "docroot" => "/srv/www/htdocs"
        )),
    ".tcl" =>
        (( "host" => "127.0.0.1",
            "port" => 9000,
            "docroot" => "/srv/www/htdocs"
        ))
)

而後啟動(或重新啟動)spawn-fcgi 與 lighttpd 的服務,接著進行測試是否有正確設定。

php-fpm

php-fpm 設定檔在 openSUSE 主要為 /etc/php8/fpm/php-fpm.d/www.conf。在安裝以候使用預設值就可以順利執行, 如果有需要再視自己的需求修改。

修改 /etc/lighttpd/lighttpd.conf,加入下面的設定:

fastcgi.server = (
    ".php" =>
        (( "host" => "127.0.0.1",
            "port" => 9000,
            "docroot" => "/srv/www/htdocs"
        ))
)

而後啟動(或重新啟動)spawn-fcgi 與 lighttpd 的服務,接著進行測試是否有正確設定。 在 /srv/www/htdocs 目錄下建立 info.php:

<?php

phpinfo();

?>

如果瀏緊 info.php 可以得到資訊,就表示環境架設成功。

參考資料

2023-10-21

openSUSE: Nginx, fcgiwrap and Tcl CGI

Nginx 是非同步框架的網頁伺服器,在靜態檔案的效能上十分高效, 而且時常被用來作為反向代理、Http Cache、負載平衡器。

Install nginx (@openSUSE):

sudo zypper install nginx

如果要作為提供靜態檔案服務的 web server,openSUSE 的預設設定已經足夠, 下面只是我個人習慣更新 /etc/nginx/nginx.conf 加入下面的設定:

       server {
                listen 80;
                listen [::]:80;
                server_name localhost;
                
                #access_log /var/log/nginx/host.access.log main;
                
                location / {
                        root /srv/www/htdocs;
                        try_files $uri/ $uri =404;
                        index index.html index.htm;
                }

接下來使用自簽憑證設定 HTTPS。
首先建立 ssl.conf 設定檔:

[req]
prompt = no
default_md = sha256
default_bits = 2048
distinguished_name = dn
x509_extensions = v3_req

[dn]
C = TW
ST = Taiwan
L = Taipei
O = Orange Inc.
OU = IT Department
emailAddress = admin@example.com
CN = localhost

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.localhost
DNS.2 = localhost
IP.1 = 127.0.0.1

透過指令建立開發測試用途的自簽憑證:

openssl req -x509 -new -nodes -sha256 -utf8 -days 3650 \
-newkey rsa:2048 -keyout nginx.key -out nginx.crt -config ssl.conf

將 nginx.key 與 nginx.crt 複製到 /etc/nginx/ssl 目錄(需要使用 su 切換到 root 身份或者使用 sudo)。

更新 /etc/nginx/nginx.conf 加入下面的設定:

        server {
                listen 443 ssl;
                listen [::]:443 ssl;
                http2 on;
                server_name localhost;

                ssl_certificate /etc/nginx/ssl/nginx.crt;
                ssl_certificate_key /etc/nginx/ssl/nginx.key;

                ssl_protocols TLSv1.2 TLSv1.3;

        #       ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
        #       ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384;
        #       ssl_prefer_server_ciphers on;

        #       ssl_ecdh_curve prime256v1;

        #       ssl_early_data on;

        #       ssl_session_cache shared:SSL:10m;
        #       ssl_session_timeout 10m;

                location / {
                        root /srv/www/htdocs;
                        index index.html index.htm;
                }
        }

        include vhosts.d/*.conf;

這樣就有一個支援 HTTPS 的 web server 可以用來測試。

如果要加入 HTTP/2 支援,更新 HTTPS 設定如下(NgINX 1.25.1 以上適用):

                listen 443 ssl;
                listen [::]:443 ssl;
                http2 on;

啟動 Nginx:

sudo systemctl start nginx

如果要在重新開機後會自動啟動 nginx server,使用下列的指令:

sudo systemctl enable nginx

因為 Nginx 支援 FastCGI 但是不支援 CGI, 所以需要 fcgiwrap 將網頁請求透過 FastCGI 協定傳給 CGI 程式執行。 如果有執行 CGI 的需求才需要安裝 fcgiwrap。

Install fcgiwrap (@openSUSE):

sudo zypper install fcgiwrap fcgiwrap-nginx

fcgiwrap 安裝後需要啟動服務:

sudo service fcgiwrap start

如果要在重新開機後會自動啟動 fcgiwrap service,使用下列的指令:

sudo systemctl enable fcgiwrap

在 /etc/nginx 下加入 fcgiwrap.conf,檔案內容如下:

location /cgi-bin/ {
    gzip off;
    root /srv/www;
    fastcgi_pass unix:/var/run/fcgiwrap.sock;
    include /etc/nginx/fastcgi_params;
    fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
}

在想要加入 CGI 支援的 server section 加入下面的設定:

        include fcgiwrap.conf;

重新啟動 Nginx:

sudo systemctl restart nginx

在 /srv/www/cgi-bin/ 撰寫 env.cgi 作為測試。

#!/usr/bin/tclsh
package require ncgi
package require html

::html::init
::ncgi::header

set title "Print Environment"
puts [::html::head $title]
puts [::html::bodyTag]
puts [::html::h1 $title]
puts [::html::tableFromArray env]
puts [::html::end]

需要將 env.cgi 的權限設為可執行。如果沒問題,就可以使用 Nginx 開發或者是執行 CGI 程式。


spawn-fcgi 用來啟動 FastCGI process。 spawn-fcgi 一開始是 Lighttpd 的一部份,不過現在已經獨立出來可以供其他 Web Server 使用。 當使用者撰寫了一個 FastCGI 服務,可以使用 spawn-fcgi 進行管理。

Install spawn-fcgi (@openSUSE):

sudo zypper install spawn-fcgi

接下來的設定是使用 spawn-fcgi 啟動我們撰寫的 FastCGI 服務。
這裡使用 tcl-fcgi (pure Tcl) 測試。 下面就是測試的程式 vclock.tcl,來自 tcl-fcgi 的 example(我將檔案放在 /srv/www/cgi-bin,需要將權限設為可執行):

#! /usr/bin/env tclsh
# vclock.tcl -- originally borrowed from Don Libes' cgi.tcl but rewritten
#


package require ncgi
package require textutil
package require Fcgi
package require Fcgi::helpers

namespace eval vclock {
    namespace path ::fcgi::helpers

    variable EXPECT_HOST    http://expect.sourceforge.net
    variable CGITCL         $EXPECT_HOST/cgi.tcl
    variable TEMPLATE [textutil::undent {
        <!doctype html>
        <html><head><title>Virtual Clock</title></head>
        <body>
        <h1>Virtual Clock - fcgi.tcl style</h1>
        <p>Virtual clock has been accessed <%& $counter %> times since
        startup.</p>
        <hr>
        <p>At the tone, the time will be <strong><%& $time %></strong></p>
        <% if {[dict get $query debug]} { %>
            <pre>     Query: <%& $query %>
            Failed: <%& $failed %></pre>
        <% } %>
        <hr>
        <h2>Set Clock Format</h2>
        <form method="post">
        Show:
        <% foreach name {day month day-of-month year} { %>
          <input type="checkbox" id="<%& $name %>" name="<%& $name %>"
                 <%& [dict get $query $name] ? {checked} : {} %>>
          <label for="<%& $name %>"><%& $name %></label>
        <% } %>
        <br>
        Time style:
        <% foreach value {12-hour 24-hour} { %>
          <input type="radio" id="<%& $value %>" name="type" value="<%& $value %>"
                 <%& [dict get $query type] eq $value ? {checked} : {} %>>
          <label for="<%& $value %>"><%& $value %></label>
        <% } %>
        <br>
        <input type="reset">
        <input type="submit">
        </form>
        <hr>
        See Don Libes' cgi.tcl and original vclock
        at the <a href="<%& $CGITCL %>"><%& $CGITCL %></a>
        </body>
        </html>
    }]
}


proc vclock::main {} {
    variable CGITCL
    variable TEMPLATE

    proc page {query failed counter time CGITCL} [tmpl_parser $TEMPLATE]

    set counter 0

    while {[FCGI_Accept] >= 0} {
        incr counter

        puts -nonewline "Content-Type: text/html\r\n\r\n"

        lassign [validate-params {
            day          boolean                   false
            day-of-month boolean                   false
            debug        boolean                   false
            month        boolean                   false
            type         {regexp ^(?:12|24)-hour$} 24-hour
            year         boolean                   false
        } [query-params {day day-of-month debug month type year}]] query failed

        set format [construct-format $query]
        set time [clock format [clock seconds] -format $format]

        puts [page $query $failed $counter $time $CGITCL]

        ncgi::reset
    } ;# while {[FCGI_Accept] >= 0}
}


proc vclock::construct-format query {
    if {[dict get $query type] eq {}} {
        return {%r %a %h %d '%y}
    }

    set format [expr {
        [dict get $query type] eq {12-hour} ? {%r} : {%T}
    }]

    foreach {name fragment} {
        day { %a}
        month { %h}
        day-of-month { %d}
        year { '%y}
    } {
        if {[dict get $query $name] ne {}} {
            append format $fragment
        }
    }

    return $format
}


# If this is the main script...
if {[info exists argv0] && ([file tail [info script]] eq [file tail $argv0])} {
    vclock::main
}

我們需要撰寫 spawn-fcgi 的 systemd service,在 /usr/lib/systemd/system 目錄下建立 spawnfcgi.service,內容如下:

[Unit]
Description=Spawn FCGI service
After=nss-user-lookup.target

[Service]
Type=forking
Environment=WORKERS=1
ExecStart=/usr/bin/spawn-fcgi \
    -F ${WORKERS} \
    -u nginx \
    -g nginx \
    -s /var/run/%p.sock \
    -P /var/run/%p.pid \
    -- /srv/www/cgi-bin/vclock.tcl
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

在 /etc/nginx 目錄下加入 spawnfcgi.conf,內容如下:

location /vclock/ {
    gzip off;
    fastcgi_pass unix:/var/run/spawnfcgi.sock;
    include /etc/nginx/fastcgi_params;
}

在想要加入 spawn-fcgi 支援的 server section 加入下面的設定:

        include spawnfcgi.conf;

啟動 spawn-fcgi:

sudo systemctl start spawnfcgi

重新啟動 Nginx:

sudo systemctl restart nginx

使用瀏覽器瀏覽 http://localhost/vclock/,檢查結果是否正確。


我們可以注意到,上面的方案只能夠指定執行某一個 script,我們需要一個能夠解讀 script 並且執行的一般性方案。 下面是測試我自己寫的工具 rivet-fcgi 的設定。

首先需要修改 /usr/lib/systemd/system 目錄下的 spawnfcgi.servic。

For unix socket -

[Unit]
Description=Spawn FCGI service
After=nss-user-lookup.target

[Service]
Type=forking
Environment=WORKERS=3
ExecStart=/usr/bin/spawn-fcgi \
    -F ${WORKERS} \
    -u wwwrun \
    -g www \
    -s /var/run/%p.sock \
    -P /var/run/%p.pid \
    -- /usr/bin/rivet-fcgi
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

For Tcp socket -

[Unit]
Description=Spawn FCGI service
After=nss-user-lookup.target

[Service]
Type=forking
Environment=WORKERS=3
ExecStart=/usr/bin/spawn-fcgi \
    -F ${WORKERS} \
    -u wwwrun \
    -g www \
    -a 127.0.0.1 -p 9000 \
    -P /var/run/%p.pid \
    -- /usr/bin/rivet-fcgi
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

需要修改 /etc/nginx 目錄下的 spawnfcgi.conf。

For unix socket -

location ~ \.(rvt|tcl)$ {
    gzip off;
    root /srv/www/htdocs;
    try_files $uri/ $uri =404;
    fastcgi_pass unix:/var/run/spawnfcgi.sock;
    include /etc/nginx/fastcgi_params;
    fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
}

For Tcp socket -

location ~ \.(rvt|tcl)$ {
    gzip off;
    root /srv/www/htdocs;
    try_files $uri/ $uri =404;
    fastcgi_pass 127.0.0.1:9000;
    include /etc/nginx/fastcgi_params;
    fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
}

啟動 spawn-fcgi 與 NGINX,接下來進行測試。 如果成功,當副檔名名為 .rvt 或者是 .tcl,就會透過 FastCGI 協定傳送到 rivet-fcgi 執行。

PHP-FPM 也是類似的修改手法,PHP-FPM 需要設定好自己的設定 (FPP-FPM 有實作自己的 daemon,所以不需要使用 spawn-fcgi,只需要設定好相關的設定), 然後再設定 NGINX FastCGI 相關的部份即可。

2023-10-08

Change the Screen Resolution (Linux)

Linux 上可以使用 command line tool xrandr 來設定 screen resolution。

我之所以會有這個需要,是因為在使用 Wine 玩某些舊遊戲的時候,有些舊遊戲在結束以後並不會正確的將螢幕解析度設回來。所以寫了一個小程式可以使用 xrandr 快速的設定回來。目前我這台電腦設定值是 1366x768,所以小程式的預設值我也是這樣設。

#!/usr/bin/tclsh

if {$argc >= 1} {
    set mysize [lindex $argv 0]
} elseif {$argc == 0} {
    set mysize 1366x768
}

set var [list xrandr -s $mysize]
exec {*}$var

2020-10-04

Remove .*history* file

 最近我在試著使用 zsh 作為平常使用的 shell(還在測試中),所以試寫了一個會移除 .*history* 檔案的 zsh shell script:

#!/usr/bin/env zsh

pushd $(pwd)
cd $HOME
for file in $(ls .*history*); do
    rm $file && touch $file
done
popd

那麼如果用 Tcl 寫呢?程式應該會是這個樣子:

#!/usr/bin/env tclsh

set currdir [pwd]
cd $::env(HOME)
set files [glob -nocomplain -type f .*history*]
foreach filename $files {
    file delete -force $filename
    if {[file exists $filename]==0} {
        close [open $filename a]
    }
}
cd $currdir

2020-06-29

Generate a file nv2808.xml

寫入資料到 nv-2808.xml。
Source code 搬到 github。


一個小工具。 XQCN 是 Qualcomm 使用的檔案格式,用來記錄 RF 相關的資料。其中 NV 2808 被用來設定為軟體版本的資訊(如果有使用的話)。

舉例來說,如果一個案子叫 FIREBIRD,地區是給北美 (NA) 使用的,目前的開發進度在 MP1,目前的版本是第一版,版本號就是 FIR_NA_MP1_001。這個工具就是用來產生 NV2808 的資料。

一般而言整合的方法有二個方式,第一個是將 NV-2808.xml 轉成 XQCN 檔案再使用 Qualcomm 提供的工具整合,第二個方式是將原始的 QCN 檔案轉為 NV XML 檔案,和 NV-2808.xml 整合以後再使用 Qualcomm 提供的工具轉為 XQCN 檔案。

總之,如果 XQCN 檔案也有放到版本管理中(例如 git),最終成品需要有版本號碼才行,用這樣的方式做 XQCN 檔案版本控管。

2019-09-19

RabbitMQ/MQTT

RabbitMQ 是實現進階訊息佇列協議 AMQP (Advanced Message Queuing Protocol) 的中間件軟體, 支援的協議版本為 0.9.1,也有 plugin 可以支援 1.0。

官網上有提供安裝檔案可以使用,如果在 openSUSE 15.1,可以使用下列的指令安裝:
sudo zypper in rabbitmq-server rabbitmq-server-plugins

如果要啟動服務,可以使用下列的指令:
sudo service rabbitmq-server start

如果要重啟服務,可以使用下列的指令:
sudo service rabbitmq-server restart

如果知道服務的狀態,可以使用下列的指令:
sudo service rabbitmq-server status

如果要停止服務,可以使用下列的指令:
sudo service rabbitmq-server stop


RabbitMQ 有支援 MQTT 3.1 的 plugin。下面是啟用的方式(啟用後需要重開 server 才會生效):
sudo rabbitmq-plugins enable rabbitmq_mqtt

設定檔案在 /etc/rabbitmq/rabbitmq.conf,因為只是要測試,所以我把 port 改為 1883,啟用預設的 username/password, 並且關閉允許匿名連線:
## ----------------------------------------------------------------------------
## RabbitMQ MQTT Adapter
##
## See https://github.com/rabbitmq/rabbitmq-mqtt/blob/stable/README.md
## for details
## ----------------------------------------------------------------------------

# =======================================
# MQTT section
# =======================================

## TCP listener settings.
##
mqtt.listeners.tcp.1 = 127.0.0.1:1883
mqtt.listeners.tcp.2 = ::1:1883

## TCP listener options (as per the broker configuration).
##
# mqtt.tcp_listen_options.backlog = 4096
# mqtt.tcp_listen_options.recbuf  = 131072
# mqtt.tcp_listen_options.sndbuf  = 131072
#
# mqtt.tcp_listen_options.keepalive = true
# mqtt.tcp_listen_options.nodelay   = true
#
# mqtt.tcp_listen_options.exit_on_close = true
# mqtt.tcp_listen_options.send_timeout  = 120

## TLS listener settings
## ## See https://rabbitmq.com/mqtt.html and https://rabbitmq.com/ssl.html for details.
#
# mqtt.listeners.ssl.default = 8883
#
# ssl_options.cacertfile = /path/to/tls/ca_certificate_bundle.pem
# ssl_options.certfile   = /path/to/tls/server_certificate.pem
# ssl_options.keyfile    = /path/to/tls/server_key.pem
# ssl_options.verify     = verify_peer
# ssl_options.fail_if_no_peer_cert  = true
#


## Number of Erlang processes that will accept connections for the TCP
## and TLS listeners.
##
# mqtt.num_acceptors.tcp = 10
# mqtt.num_acceptors.ssl = 10

## Whether or not to enable proxy protocol support.
## Once enabled, clients cannot directly connect to the broker
## anymore. They must connect through a load balancer that sends the
## proxy protocol header to the broker at connection time.
## This setting applies only to STOMP clients, other protocols
## like STOMP or AMQP have their own setting to enable proxy protocol.
## See the plugins or broker documentation for more information.
##
# mqtt.proxy_protocol = false

## Set the default user name and password used for anonymous connections (when client
## provides no credentials). Anonymous connections are highly discouraged!
##
mqtt.default_user = guest
mqtt.default_pass = guest

## Enable anonymous connections. If this is set to false, clients MUST provide
## credentials in order to connect. See also the mqtt.default_user/mqtt.default_pass
## keys. Anonymous connections are highly discouraged!
##
mqtt.allow_anonymous = false

## If you have multiple vhosts, specify the one to which the
## adapter connects.
##
mqtt.vhost = /

## Specify the exchange to which messages from MQTT clients are published.
##
mqtt.exchange = amq.topic

## Specify TTL (time to live) to control the lifetime of non-clean sessions.
##
mqtt.subscription_ttl = 1800000

## Set the prefetch count (governing the maximum number of unacknowledged
## messages that will be delivered).
##
mqtt.prefetch = 10

下面是使用 tcl.mqttc 測試 Subscribe 與 Publish 功能的測試程式:
package require Thread
package require mqttc
catch {console show}

set ::gThread [thread::create {thread::wait} ]
set result 0

proc subscribe { } {
    thread::send -async $::gThread {
        package require mqttc

        mqttc client "tcp://localhost:1883" "USERTest1" 1 -cleansession 1 \
                     -username guest -password guest
        client subscribe  "MQTT Examples" 1
        while 1 {
            if {[catch {set result [client  receive]}]} {
                puts "Receive error!!!"
                break
            }
            if {[llength $result] > 0} {
                puts "[lindex $result 0] - [lindex $result 1]"
                if {![string compare -nocase [lindex $result 1] "Exit"]} {
                    break
                }
            }
        }
        client unsubscribe  "MQTT Examples"
        client close
    } ::result
}

subscribe
puts "started test..."

after 250

mqttc client "tcp://localhost:1883" "USERTest2" 1 -timeout 1000 \
             -username guest -password guest
client publishMessage "MQTT Examples" "Hello MQTT!" 1 0
client publishMessage "MQTT Examples" "Exit" 1 0
client close

vwait ::result

2019-04-16

使用 QEMU 執行 openSUSE/AARCH64 image

因為 tclBlend 我接到一個 issue,在 AARCH64 下編譯失敗,所以需要一個 AARCH64 模擬器來除錯。


首先要安裝 QEMU-ARM:
sudo zypper install qemu-arm

要下載一個 openSUSE AARCH64 image 來進行測試(我使用 unxz 解壓縮)。
wget https://download.opensuse.org/ports/aarch64/factory/images/openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64-Current.raw.xz
unxz openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64-Current.raw.xz

如果使用 efi image,需要有 Unified EFI BIOS 模擬器正確的載入 boot loader 才行(不然無法正確執行)。我從 Linaro 下載一個來使用。
wget https://releases.linaro.org/components/kernel/uefi-linaro/16.02/release/qemu64/QEMU_EFI.img.gz
gzip -d QEMU_EFI.img.gz 

然後就可以這樣執行:
qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios QEMU_EFI.img -serial stdio -device virtio-net-device,netdev=hostnet0,id=net0,mac=52:54:00:09:a4:37 -netdev user,id=hostnet0  -drive if=none,format=raw,file=openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64-Current.raw,id=hd0  -device virtio-blk-device,drive=hd0

(使用者帳號為 root,密碼為 linux)

參考文章:
openSUSE:AArch64
Documentation/Platforms/ARM
Linux on AArch64 ARM 64-bit Architecture
Building ARM Servers With UEFI And ACPI


就 ARM 來說,有二種描述 hardware scheme 的方式,一種是 UEFI/ACPI,一種是 Device Tree。Linux kernel 可以編譯為二種都支援。文件中有提到:

ACPI support in drivers and subsystems for ARMv8 should never be mutually exclusive with DT support at compile time.

At boot time the kernel will only use one description method depending on parameters passed from the boot loader (including kernel bootargs).

Regardless of whether DT or ACPI is used, the kernel must always be capableof booting with either scheme (in kernels with both schemes enabled at compile time).

2018-08-16

EMQ/emqttd (Erlang MQTT Broker)

EMQ (Erlang MQTT Broker) is a distributed, massively scalable, highly extensible MQTT message broker written in Erlang/OTP.

在 openSUSE 從 source code 安裝,需要下列的套件:
sudo zypper in erlang erlang-observer

(erlang/otp version 需要 >= R17)

再來是下載 source code 並且編譯:
git clone https://github.com/emqtt/emq-relx.git
cd emq-relx && make

如果要執行的話:
cd _rel/emqttd && ./bin/emqttd console


再來使用 tcl.mqttc 驗證是否可以正確連線,這只是進行簡單的驗證。
Subscribe:
package require mqttc

mqttc client "tcp://localhost:1883" "USERSSub" 1 -cleansession 1 
client subscribe  "MQTT Examples" 1
while 1 {
    if {[catch {set result [client  receive]}]} {
        puts "Receive error!!!"
        break
    }
    if {[llength $result] > 0} {
        puts "[lindex $result 0] - [lindex $result 1]"
        if {![string compare -nocase [lindex $result 1] "Exit"]} {
            break
        }
    }
}
client unsubscribe  "MQTT Examples"
client close

Publish:
package require mqttc

mqttc client "tcp://localhost:1883" "USERSPub" 1 -timeout 1000
client publishMessage "MQTT Examples" "Hello MQTT!" 1 0
client publishMessage "MQTT Examples" "Exit" 1 0
client close

先執行 Subscribe 的部份,再使用 Publish 發送訊息,確定可以正確執行。

2018-05-24

Tcl and SWIG as a C/C++ Development Tool

Tcl and SWIG as a C/C++ Development Tool
SWIG and Tcl (3.0 doc)
Swig (Tcler's Wiki)


接下來我會花一點時間使用看看 SWIG,然後看能不能成功的將我需要的 C library 部份轉成 Tcl interface。因為不是很急迫,所以我只是開始收集資料而已。

2017-11-23

Install NaviServer on openSUSE

NaviServer is a high performance web server written in C and Tcl. NaviServer 是 AOLserver 的分支。

如果要自己手動安裝:

./configure --prefix=/var/lib/naviserver --with-tcl=/usr/lib64 --with-zlib=/usr/lib64 --with-openssl=/usr/lib64
make
sudo make install

如果在 make 的時候遇到 -fstack-protector-strong 或者是 -fstack-clash-protection 無法辨識的問題, 修改 include/Makefile.global, 將 -fstack-protector-strong 或者是 -fstack-clash-protection 改為 -fstack-protector。 這是編譯套件時 GCC 新舊版本不同所造成的狀況。

sed -i s/stack-protector-strong/stack-protector/g include/Makefile.global
sed -i s/stack-clash-protection/stack-protector/g include/Makefile.global

NaviServer 預設的使用者是 nsadmin,所以需要使用下列的指令來增加使用者:

sudo useradd -r -c 'NaviServer' -d/var/lib/naviserver -U -M -s/bin/bash nsadmin

再來需要修改 NaviServer 目錄的擁用者設定,修改擁有者 user:group 為 nsadmin:nsadmin。

sudo chown -R nsadmin:nsadmin /var/lib/naviserver

再來要設定 nsadmin 帳號的密碼,使用 passwd 改變 nsadmin 使用者的密碼:

sudo passwd nsadmin

再來是執行 NaviServer

sudo -i -u nsadmin  /var/lib/naviserver/bin/nsd -f -t /var/lib/naviserver/conf/nsd-config.tcl

再來瀏覽 http://localhost:8080/,如果出現 NaviServer 的訊息就表示成功了。

/var/lib/naviserver/conf 下還有其它的設定範例,包含 openacs-config.tcl, sample-config.tcl, simple-config.tcl, 也可以用來參考。

因為希望使用 80 port 與 443 port,所以修改 nsd-config.tcl:

dict set defaultConfig ipaddress   "127.0.0.1 ::1"
dict set defaultConfig httpport    80
dict set defaultConfig httpsport   443
dict set defaultConfig nscpport    ""
dict set defaultConfig home        [file dirname [file dirname [info nameofexecutable]]]
dict set defaultConfig hostname    localhost
dict set defaultConfig pagedir     {$home/pages}
dict set defaultConfig logdir      {$home/logs}
dict set defaultConfig certificate {$home/certificates/server.pem}
dict set defaultConfig vhostcertificates {$home/certificates/certificates}

如果要檢查設定是否格式正確,使用下列的指令(加上 -T),在檢查是否有打字錯誤時很有用:

/var/lib/naviserver/bin/nsd -t /var/lib/naviserver/conf/nsd-config.tcl -T

openSUSE 的 systemd service 檔案放在 /usr/lib/systemd/system,加入 nsd.service

[Unit]
Description=NaviServer
After=network.target
#After=network.target postgresql.service
#Wants=postgresql.service
 
[Service]
Type=forking
PIDFile=/var/lib/naviserver/logs/nsd.pid
Environment="LANG=en_US.UTF-8"
# In case, a site is using Google Perfortools malloc with the system-malloc patch for Tcl:
# Environment="LD_PRELOAD=/usr/lib/libtcmalloc.so"
ExecStartPre=-/bin/rm -f /var/lib/naviserver/logs/nsd.pid
 
# Standard startup with a privileged port
ExecStart=/var/lib/naviserver/bin/nsd -u nsadmin -g nsadmin -t /var/lib/naviserver/conf/nsd-config.tcl \
-b 127.0.0.1:80,[::1]:80#2,127.0.0.1:443,[::1]:443#2
 
Restart=on-abnormal
KillMode=process
 
[Install]
WantedBy=multi-user.target

讓 systemctl 重啟 daemon 並且重新載入設定:

sudo systemctl daemon-reload

啟動 nsd.service:

sudo systemctl start nsd.service

查詢 nsd.service:

sudo systemctl status nsd.service

停止 nsd.service:

sudo systemctl stop nsd.service

重新啟動 nsd.service:

sudo systemctl restart nsd.service

以上手動安裝的部份。在知道怎麼手動安裝以後,我就寫了 script 來產生 RPM 檔案,成果請看 naviserver-spec。

2017-11-14

Apache 2, Apache Rivet and openSUSE

這篇假設你已經在 openSUSE 安裝了 Tcl,如果沒有,那需要先安裝才行。測試的環境為 openSUSE Leap 42.3。

Apache HTTP Server 2 在 openSUSE 安裝的方式:
sudo zypper in apache2

如果要啟動 Apache 2:
sudo systemctl start apache2

如果要停止 Apache 2:
sudo systemctl stop apache2

如果要開機的時候就啟動服務,使用:
sudo chkconfig apache2 on

如果不要,使用:
sudo chkconfig apache2 off

如果需要公開在網路上,還需要設定防火牆,否則只能在 localhost 使用。


再來安裝 Apache Rivet,如果要在 openSUSE 安裝,先設定軟體庫:
sudo zypper addrepo https://download.opensuse.org/repositories/Apache:/Modules/openSUSE_Leap_42.3/ Apache-Modules

更新軟體庫:
sudo zypper refresh

更新以後,使用下列的指令安裝:
sudo zypper install apache2-mod_rivet

加入 Apache Rivet module 到 Apache 2:
sudo a2enmod rivet
(* 如果要移除,使用 sudo a2dismod rivet 來移除)

接下來重新啟動 Apache 2:
sudo systemctl restart apache2

到 /srv/www/htdocs/ 目錄下,建立 hello.rvt,內容如下:
<? set hello_message "Hello world" ?>
<html>
  <head>
    <title><?= $hello_message ?></title>
  </head>
  <body><?= [::rivet::html $hello_message pre b] ?></body>
</html>
瀏覽 http://localhost/hello.rvt,如果有看到訊息表示成功安裝。


再來設定 Apache2 HTTPS 的部份。一開始先確定 mod_ssl 有開啟:
sudo a2enmod ssl

再來是設定 Self-Signed Certificates 的部份,在 Linux 上使用 OpenSSL 建立。Create a config file for your certificate :
 [req]
default_bits       = 2048
default_keyfile    = localhost.key
distinguished_name = req_distinguished_name
req_extensions     = req_ext
x509_extensions    = v3_ca

[req_distinguished_name]
countryName                 = Country Name (2 letter code)
countryName_default         = US
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = New York
localityName                = Locality Name (eg, city)
localityName_default        = Rochester
organizationName            = Organization Name (eg, company)
organizationName_default    = localhost
organizationalUnitName      = organizationalunit
organizationalUnitName_default = Development
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_default          = localhost
commonName_max              = 64

[req_ext]
subjectAltName = @alt_names

[v3_ca]
subjectAltName = @alt_names

[alt_names]
DNS.1   = localhost
DNS.2   = 127.0.0.1
Run the following 2 commands using OpenSSL to create a self-signed certificate in openSUSE with OpenSSL :
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config localhost.conf -passin pass:YourSecurePassword

sudo openssl pkcs12 -export -out localhost.pfx -inkey localhost.key -in localhost.crt

然後將我們製造的檔案複製到 Apache 的目錄下:
sudo cp localhost.crt /etc/apache2/ssl.crt/server.crt
sudo cp localhost.key /etc/apache2/ssl.key/server.key

如果是 Officially Signed Certificate,可以參考 OpenSUSE: Setting Up a Secure Web Server with SSL。

再來修改 /etc/sysconfig/apache2 的設定,
APACHE_START_TIMEOUT="10" 
APACHE_SERVER_FLAGS="SSL"

複製 /etc/apache2/vhost.d/vhost-ssl.template 到 /etc/apache2/vhost.d/vhost-ssl.conf,主要的設定如下:
<IfDefine SSL>
<IfDefine !NOSSL>

##
## SSL Virtual Host Context
##

<VirtualHost *:443>

    #  General setup for the virtual host
    DocumentRoot "/srv/www/htdocs"
    #ServerName www.example.com:443
    #ServerAdmin webmaster@example.com
    ErrorLog /var/log/apache2/error_log
    TransferLog /var/log/apache2/access_log

    #   SSL Engine Switch:
    #   Enable/Disable SSL for this virtual host.
    SSLEngine on

    #   You can use per vhost certificates if SNI is supported.
    SSLCertificateFile /etc/apache2/ssl.crt/server.crt
    SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
    #SSLCertificateChainFile /etc/apache2/ssl.crt/vhost-example-chain.crt

    #   Per-Server Logging:
    #   The home of a custom SSL log file. Use this when you want a
    #   compact non-error SSL logfile on a virtual host basis.
    CustomLog /var/log/apache2/ssl_request_log   ssl_combined

</VirtualHost>

</IfDefine>
</IfDefine>

接下來讓 main site 使用 HTTPS,編輯 /etc/apache2/default-server.conf,加入下面的設定:
IncludeOptional /etc/apache2/conf.d/*.conf
IncludeOptional /etc/apache2/vhosts.d/*.conf

接下來重新開啟 Apache 2,
sudo systemctl restart apache2

瀏覽 https://localhost/hello.rvt, 如果有看到訊息表示成功安裝(PS. 因為是 Self-Signed Certificates,所以 Firefox 會看到警告訊息)。

若要強制使用 SSL,需要開啟 mod_rewrite 才行。
sudo a2enmod rewrite

再來修改 /etc/apache2/vhost.d/vhost-ssl.conf
<VirtualHost *:80>
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L] 
</VirtualHost>

<VirtualHost *:443>

    #  General setup for the virtual host
    DocumentRoot "/srv/www/htdocs"
    #ServerName www.example.com:443
    #ServerAdmin webmaster@example.com
    ErrorLog /var/log/apache2/error_log
    TransferLog /var/log/apache2/access_log

    #   SSL Engine Switch:
    #   Enable/Disable SSL for this virtual host.
    SSLEngine on

    #   You can use per vhost certificates if SNI is supported.
    SSLCertificateFile /etc/apache2/ssl.crt/server.crt
    SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
    #SSLCertificateChainFile /etc/apache2/ssl.crt/vhost-example-chain.crt

    #   Per-Server Logging:
    #   The home of a custom SSL log file. Use this when you want a
    #   compact non-error SSL logfile on a virtual host basis.
    CustomLog /var/log/apache2/ssl_request_log   ssl_combined

</VirtualHost>

* 2017/11/18 更新:更新 rewrite 的規則

接下來重新開啟 Apache 2,
sudo systemctl restart apache2

這樣就會強制都使用 HTTPS 瀏覽。