golang で URL を結合する

golang で URL を結合する

  • url.Parse と path.Join を使う
  • path.Join だけだと正しく結合できない場合がある1
    • 実行結果の https:/golang.org/pkg/net/url のようにスラッシュが削除される

サンプルコード

package main

import (
    "fmt"
    "net/url"
    "path"
)

const (
    srcURL = "https://golang.org/pkg/"
    path1  = "net"
    path2  = "url"
)

func main() {
    fmt.Printf("path.Join だけで結合: %s\n", path.Join(srcURL, path1, path2))

    u, err := url.Parse(srcURL)
    if err != nil {
        panic(err)
    }
    u.Path = path.Join(u.Path, path1, path2)
    fmt.Printf("url.Parse と path.Join で結合: %s\n", u)
}

実行結果

path.Join だけで結合: https:/golang.org/pkg/net/url
url.Parse と path.Join で結合: https://golang.org/pkg/net/url

参考にさせていただいたサイト

golang で UUID を使う

golang で UUID を使う

サンプルコード

package main

import (
    "fmt"

    "github.com/google/uuid"
)

func main() {
    // uuid.NewRandom() はランダムなバージョン 4 の UUID を返す
    id1, err := uuid.NewRandom()
    if err != nil {
        panic(err)
    }
    fmt.Printf("id1, %s, %s, %s\n", id1.Variant(), id1.Version(), id1)

    // uuid.New() はランダムな UUID を返すか、パニックを起こす
    // uuid.New() は uuid.Must(uuid.NewRandom()) と等価
    id2 := uuid.New()
    fmt.Printf("id2, %s, %s, %s\n", id2.Variant(), id2.Version(), id2)
}

サンプルコード実行結果

id1, RFC4122, VERSION_4, ce33574c-5716-4c02-b833-2651b2a90a84
id2, RFC4122, VERSION_4, 5775e7ad-ee61-48f9-9e35-fd7d890b40f5

Raspberry Pi 4 でローカル DNS サーバを立てる

Raspberry Pi 4 と BIND 9 で自宅ネットワーク用の DNS サーバを立てました。
ラズパイに sv.homenetwork というドメイン名を付けてみます。

f:id:arrowislandnai:20201231170609p:plain

ラズパイ情報

$ cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
NAME="Raspbian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

ラズパイに BIND 9 をインストールする

$ sudo apt install bind9
$ named -v
BIND 9.11.5-P4-5.1+deb10u2-Raspbian (Extended Support Version)

BIND 9 を設定する

192.168.1.0/24homenetwork というゾーンにします。
ラズパイには sv.homenetwork というドメイン名を付けてみます。

/etc/bind/named.conf

このファイルから /etc/bind/named.conf.options
/etc/bind/named.conf.local をインクルードするみたいです。
このファイルはデフォルトのままで、設定変更しませんでした。

$ cat /etc/bind/named.conf
// This is the primary configuration file for the BIND DNS server named.
//
// Please read /usr/share/doc/bind9/README.Debian.gz for information on the 
// structure of BIND configuration files in Debian, *BEFORE* you customize 
// this configuration file.
//
// If you are just adding zones, please do that in /etc/bind/named.conf.local

include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";

/etc/bind/named.conf.options

このファイルに DNSサーバの設定を書いていきます。

$ cat /etc/bind/named.conf.options
options {
    // サーバの作業ディレクトリ
    // このディレクトに後述するゾーンファイルを置くみたいです
    directory "/var/cache/bind";

    // 自身が属するネットワークからのクエリのみ許可する
    allow-query { localnets; }; 

    // ゾーン転送するセカンダリサーバは存在しないので無効にする
    allow-transfer { none; };

    // 自身で解決できない問い合わせを別のフルリゾルバに転送する
    // 自宅ルータの 192.168.1.1 がフォワーダとして動作し
    // プロバイダのフルリゾルバに転送してくれます
    forwarders { 192.168.1.1; };

    // フォワーダから回答が得られない場合はエラーとなる
    forward only;

    // DNSSEC を無効
    dnssec-validation no;

    // IPv6 のクエリに対応する
    listen-on-v6 { any; };
};

/etc/bind/named.conf.local

このファイルでゾーンファイルを指定するみたいです。

$ cat /etc/bind/named.conf.local
zone "homenetwork" IN {
    // 権威サーバの情報
    // セカンダリサーバ(slave)は存在しないので
    // プライマリサーバ(master)とします
    type master;
    
    file "homenetwork.zone";
};
zone "1.168.192.in-addr.arpa" IN {
    type master;
    file "homenetwork.rev";
};

/var/cache/bind/homenetwork.zone

正引き用のゾーンデータを記述するゾーンファイルです。
refresh, retry, expire はセカンダリサーバが存在しないので、
使われないような気がします。

$ cat /var/cache/bind/homenetwork.zone
$TTL 3600 
@ IN SOA sv.homenetwork. root.homenetwork. (
  2015123105 ; serial
  3H         ; refresh
  15M        ; retry
  1W         ; expire
  1H )       ; minimum

 IN NS sv
sv IN A 192.168.1.10

/var/cache/bind/homenetwork.rev

逆引き用のゾーンデータを記述するゾーンファイルです。

$ cat /var/cache/bind/homenetwork.rev
$TTL 3600 
@ IN SOA sv.homenetwork. root.homenetwork. (
  2015123105 ; serial
  3H         ; refresh
  15M        ; retry
  1W         ; expire
  1H )       ; minimum

 IN NS sv.homenetwork.
10 IN PTR sv.homenetwork.

設定ファイルのチェックを行う

$ sudo named-checkconf
$ sudo named-checkzone 1.168.192.in-addr.arpa /var/cache/bind/homenetwork.rev
zone 1.168.192.in-addr.arpa/IN: loaded serial 2015123105
OK
$ sudo named-checkzone homenetwork /var/cache/bind/homenetwork.zone
zone homenetwork/IN: loaded serial 2015123105
OK

ラズパイが問い合わせる DNS サーバを自分自身にする

自宅ネットワークでは DHCP を使用しているので
ラズパイの /etc/dhcpcd.conf に以下を追記します。

static domain_name_servers=127.0.0.1
static domain_name=homenetwork

BIND 9 の再起動

BIND 9 を再起動して、ステータスに異常がないか確認します。

$ sudo systemctl restart bind9
$ sudo systemctl status bind9

DHCP サーバが配布する DNS サーバのアドレスをラズパイのアドレスに変更する

DHCP を使用している場合は DHCP サーバ の設定を変更します。

正引きテスト

作業用 PC からテストします。まずは直接 192.168.1.10 に問い合わせてみます。
status: NOERRORflagsaa があります。
大丈夫そうです。

$ dig @192.168.1.10 sv.homenetwork
; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.1.10 sv.homenetwork
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61550
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1

...

;; ANSWER SECTION:
sv.homenetwork.     3600    IN  A   192.168.1.10

;; AUTHORITY SECTION:
homenetwork.        3600    IN  NS  sv.homenetwork.

...

外部のドメインも試してみます。今度は flagsra があります。
大丈夫そうです。

$ dig @192.168.1.10 www.google.com

; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.1.10 www.google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63049
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

...

;; ANSWER SECTION:
www.google.com.     42  IN  A   172.217.24.132

...

次に systemd-resolved を経由して問い合わせてみます。
こっちも大丈夫そうです。

$ dig sv.homenetwork

; <<>> DiG 9.16.1-Ubuntu <<>> sv.homenetwork
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30774
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

...

;; ANSWER SECTION:
sv.homenetwork.     3038    IN  A   192.168.1.10

...

逆引きテスト

dig @192.168.1.10 -x 192.168.1.10dig -x 192.168.1.10 を試して、
大丈夫なことを確認しました。

systemd-resolved 経由の問い合わせがうまくいかないとき

  • systemd-resolve --status して Current DNS Server を確認する
  • systemctl restart systemd-resolved で systemd-resolved を再起動してみる

参考にさせていただいたサイト

Raspberry Pi 4 のセットアップ

ラズパイ4スターターキット 4GB版秋月電子で購入しました。
行った設定などを書いておきます。

OS 情報

ラズパイ4スターターキット 4GB版 には Raspbian インストール済 microSD が付属してます。 そのため OS は付属の Raspbian を使うことにしました。ラズパイに microSD を挿すだけで OK です

$ cat /etc/os-release 
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
NAME="Raspbian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

OS の GUI を無効化する

GUI は使わないので無効にします。

  • sudo raspi-config を実行します
  • 1 System Options > S5 Boot / Auto Login > B1 Console または B2 Console Autologin を選択して、ラズパイを再起動します
    • 私は B1 Console を選択しました

Wi-Fi を無効化する

有線で接続するので Wi-Fi を無効にします。

  • /etc/rc.localiwconfig wlan0 txpower off を追記して、再起動します
    • sudo iwconfig wlan0 txpower off を実行しただけでも Wi-Fi は無効になります。ただし再起動後にまた Wi-Fi が有効になります
    • このため /etc/rc.localとかに書いておいた方がいいと思います

起動中に LED を点滅させる

起動してるのか分かりにくいので LED を点滅させます。

  • /boot/config.txtdtparam=act_led_trigger=heartbeat を追記して、再起動します
  • act_led_trigger で緑色 LED 、pwr_led_trigger で赤色 LED が点滅します

ブラウザからラズパイを管理できるようにする

Cockpit をインストールすると、ブラウザからラズパイの状態確認やシャットダウン、再起動が行えます。

Cockpit インストール

$ sudo apt install dirmngr
$ sudo apt install cockpit

Cockpit で HTTPS を無効化する

/etc/cockpit/cockpit.confAllowUnencrypted = true を設定します。

$ cat /etc/cockpit/cockpit.conf
[WebService]
AllowUnencrypted = true

ブラウザからログインする

  • http://ラズパイの IP アドレス:9090 にアクセスします f:id:arrowislandnai:20201230165332p:plain

参考にさせていただいたサイト

golang でシグナルを扱う

golang でシグナルを扱う

  • Go でシグナルを扱うには singal.Notify を使う
  • 以下のサンプルコードでは SIGINT = ^C を受信するとアプリケーションが停止する
  • sigC := make(chan os.Signal, 1) のように必ずバッファありチャネルが必要
  • os.Interrupt と os.Kill の2つはすべての OS で使えることが保証されている
package main

import (
    "log"
    "os"
    "os/signal"
    "sync"
    "time"
)

func main() {
    // 受信するシグナル
    // Interrupt Signal = syscall.SIGINT
    sigs := []os.Signal{os.Interrupt}

    // 必ずバッファありチャネルが必要
    // https://budougumi0617.github.io/2020/09/06/why_signal_notify_want_buffered_channel/
    sigC := make(chan os.Signal, 1)
    signal.Notify(sigC, sigs...)

    var wg sync.WaitGroup

    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            select {
            case <-time.After(2 * time.Second):
                log.Println("waiting...")
            case sig := <-sigC:
                log.Println("got signal", sig)
                return
            }
        }
    }()

    wg.Wait()
}

参考サイト

sed で置換する

実行例

  • 名前が チ で終わる犬に忠犬をつける
$ cat sample.xml 
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <dog name="ソフィア" age="7" />
    <dog name="ポチ" age="5" />
    <dog name="コロ" age="10" />
    <dog name="ハチ" age="5" />
</root>
$ sed -i -E 's/([ア-ン]+チ)/忠犬\1/g' sample.xml
$ cat sample.xml 
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <dog name="ソフィア" age="7" />
    <dog name="忠犬ポチ" age="5" />
    <dog name="コロ" age="10" />
    <dog name="忠犬ハチ" age="5" />
</root>

コマンド説明

  • sed -i -E 's/([ア-ン]+チ)/忠犬\1/g' sample.xml
    • -i で置換した結果を元ファイルに上書きする
    • -E 拡張正規表現を使用する
    • 後方参照は \番号 で行う。キャプチャには () を使用する

XPath で指定したノード名でない、ノードを取得する

XPath で指定したノード名でない、ノードを取得する

サンプルXML

<root>
    <animals>
        <dog>sophia</dog>
        <dog>koro</dog>
        <panda>xiangxiang</panda>
    </animals>
</root>

ノード名が panda でない animals の子ノードを取得する

  • /root/animals/*[not(self::panda)]
<dog>sophia</dog>
<dog>koro</dog>

参考