🍥

Windows から Blazor Web App をデプロイする Debian Server の構成と管理

2023/12/10に公開

はじめに

これは、本論の課題を解決した際のメモ書きで、未来の自分に宛てたものです。
未完成のもので、随時更新される可能性があります。

概要

  • Windowsで開発し、Debianでデプロイします。
    • いったん、Ubuntu Serverで実施したのですが、後に、Debianでやり直しました。
    • 特に断りがなければ、Debianについて記載しています。
    • 経緯から、Ubuntuについては網羅されていません。

前提

  • Windows 11 Pro 22H2
  • Debian 12.6
    • ASP.NET Core 8.0.8
    • nginx 1.22.1
    • MariaDB 10.11.6
  • Ubuntu 22.04.3 LTS ⇒ 使用しなくなりました
    • ASP.NET Core 7.0.13
    • nginx 1.18.0
    • MariaDB 10.6.12
    • PHP 8.1.2-1ubuntu2.14
    • phpMyAdmin 5.1.1

サーバの準備

OS導入

ダウンロード

https://www.debian.org/distrib/index.en.html

https://jp.ubuntu.com/download

ブータブルUSBメモリ作成

https://rufus.ie/ja/

USBブートしてインストール

  • 環境による文字化けを避けるため、ロケールは英語にしておきます。
    • ロケーション日本でロケール英語だと、選択が少し面倒になります。
  • デスクトップは入れません。
  • Webサーバは、nginxを使うので入れません。
  • SSHサーバにします。

OSのバージョン確認

bash
$ cat /etc/os-release
$ cat /etc/debian_version

sudo の準備

  • rootでログインするか、あるいは、suコマンドで昇格します。

導入

bash
# apt install sudo

グループへの参加

bash
# adduser <user> sudo

または、

bash
# gpasswd -a <user> sudo
  • userアカウントでsudoが使えるようになったので、そちらでログインし直します。
    • suコマンドを使った場合は、単にexitします。

ssh 公開鍵認証

認証キー生成

コマンドプロンプト
>ssh-keygen -t ed25519 -f "%USERPROFILE%\.ssh\<keyfile>"

パスフレーズを入力 ⇒ 指定の秘密鍵ファイルに加えて公開鍵ファイル(.pub)が生成

https://learn.microsoft.com/ja-jp/azure/virtual-machines/linux/create-ssh-keys-detailed

現在の認証キーを確認

bash
$ cat ~/.ssh -l
  • 初期状態でディレクトリが存在しません。

認証キー置き換え

bash
$ cp /media/<keyfile>.pub ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys

公開鍵認証の強制 (パスワード認証の禁止)

bash
$ sudoedit /etc/ssh/sshd_config
/etc/ssh/sshd_config
- #PubkeyAuthentication yes
+ PubkeyAuthentication yes
- #PasswordAuthentication yes
+ PasswordAuthentication no

設定を反映

bash
$ sudo systemctl restart ssh

USBメモリからの取り出し

usbデバイスを一覧

bash
$ lsusb
  • デバイスの認識状態を確認できます。

ブロックデバイスを一覧

bash
$ lsblk

SCSIデバイスを一覧

bash
$ ls /dev/sd*

接続デバイスを特定

接続前
$ ls /dev/sd*
/dev/sda  /dev/sdb  /dev/sdb1  /dev/sdc  /dev/sdc1  /dev/sdc2
接続後
$ ls /dev/sd*
/dev/sda  /dev/sdb  /dev/sdb1  /dev/sdc  /dev/sdc1  /dev/sdc2  /dev/sdd  /dev/sdd1
  • 接続前後の相違から、ドライブ名/dev/sddとパーティション名/dev/sdd1を得られます。

マウント

sudo mount <device> <mountpoint>

bash
$ sudo mount /dev/sdd1 /media

アンマウント

sudo umount <mountpoint>

bash
$ sudo umount /media

IP/MACアドレス確認

bash
$ ip address show
$ ip address
$ ip a

※いずれも同等

DHCPによる静的IP割り当て

  • 別途、DHCPサーバー側の設定が必要です。

設定追記

bash
$ sudoedit /etc/dhcp/dhclient.conf
/etc/dhcp/dhclient.conf
#~ ~ ~
send dhcp-client-identifier = hardware;
#~ ~ ~

ubuntsu

より大きな番号の~.yamlを追加することで上書き

bash
$ sudoedit /etc/netplan/01-dhcp-identifier-mac.yaml
/etc/netplan/01-dhcp-identifier-mac.yaml
# This is the network config by user
network:
  ethernets:
    enp2s0:
      dhcp4: true
      dhcp-identifier: mac
  version: 2

追加ファイルのパーミッション設定

bash
$ sudo chmod 600 /etc/netplan/01-dhcp-identifier-mac.yaml

https://www.yokoweb.net/2017/07/02/rtx1210-dhcp-server-mac/

設定反映

bash
$ sudo rm /var/lib/dhcp/*
$ sudo systemctl restart networking

ubuntsu

bash
$ sudo netplan apply

ターミナルからログイン

コマンドプロンプト
>ssh -i "%USERPROFILE%\.ssh\<keyfile>" <user>@<address>

Windowsターミナル設定

プロファイル

設定 > 新しいプロファイル > コマンドライン

コマンドライン
%SystemRoot%\System32\OpenSSH\ssh.exe  -i "%USERPROFILE%\.ssh\<keyfile>" <user>@<address>

ショートカット

エクスプローラー > 新規作成 > ショートカット > 項目の場所

項目の場所
%USERPROFILE%\AppData\Local\Microsoft\WindowsApps\wt.exe nt -p "<profilename>"

https://learn.microsoft.com/ja-jp/windows/terminal/tutorials/ssh

https://learn.microsoft.com/ja-jp/windows/terminal/command-line-arguments?tabs=windows

ルート権限が必要なファイルの編集 (sudoedit)

bash
$ sudoedit <targetfile>
  • デフォルトではnanoが起動します。
    • sudo update-alternatives --config editorで、設定を選択できます。
    • sudo update-alternatives --display editorで、現在の設定を確認できます。
  • セキュリティ上は、sudoeditが望ましいようです。
    • sudoeditでは、
      • ルート権限で作られた一時ファイルをユーザ権限で編集し、編集後にルート権限で更新されます。
      • Ubuntuでは、生成される一時ファイルに拡張子が付かないためか、エディタの設定にかかわらず、シンタックスハイライトが働きませんでした。(Debianでは問題ありません。)
    • sudo nanoとすると、
      • 不必要に権限が付与されます。
      • ユーザの設定でなくルートの設定が使われます。

使用するエディタの変更

bash
$ sudo update-alternatives --config editor

nano の設定

共有設定
$ sudoedit /usr/share/nano/default.nanorc
個別設定
$ nano ~/.nanorc
~/.nanorc
# private settings
set mouse
set nowrap
set linenumbers
set autoindent
set matchbrackets "(<[{)>]}"
set tabsize 2

キー操作

Alt + M: マウスモード切替
Alt + N: 行番号切替
Alt + I: 自動インデント切替

https://www.nano-editor.org/

vim の設定

.vimrc
"set showcmd    " Show (partial) command in status line.
"set showmatch    " Show matching brackets.
set ignorecase    " Do case insensitive matching
"set smartcase    " Do smart case matching
set incsearch   " Incremental search
"set autowrite    " Automatically save before commands like :next and :make
"set hidden   " Hide buffers when they are abandoned
set mouse=a   " Enable mouse usage (all modes)
set number    " 行番号
set hlsearch    " 検索結果のハイライト
"set expandtab    " タブをスペースに展開
set tabstop=2   " タブのサイズ
set shiftwidth=0  " 自動インテントのサイズ (0ならtabstopに従う)
set cursorline    " カーソル行
set clipboard+=unnamed  " クリップボードをOSと共有
set laststatus=2  " ステータス行のサイズ
set showmatch matchtime=1 " 対応する括弧やブレースを表示
set whichwrap=b,s,h,l,<,>,[,],~ " 行をまたいで移動
set nowrap    " 折り返さない

シェル (bash)

基本的な操作

操作 機能 備考
← ‖ Ctrl + b 左の文字
→ ‖ Ctrl + f 右の文字
Ctrl + ← ‖ Alt + b 左の単語
Ctrl + → ‖ Alt + f 右の単語
Home ‖ Ctrl + a 行頭
End ‖ Ctrl + e 行末
BackSpace ‖ Ctrl + h 左の文字を削除
Delete ‖ Ctrl + d 右の文字を削除
Ctrl + BackSpace 左の単語を削除
Ctrl + Delete 右の単語を削除
Ctrl + u 行頭まで削除
Ctrl + k 行末まで削除
Ctrl + _ Undo
Tab ‖ Ctrl + i 自動補完 / 入力候補
↑ ‖ PageUp 前の履歴
↓ ‖ PageDown 後の履歴
Esc , < 最古の履歴
Esc , > 最新の履歴
Ctrl + r 前の履歴の検索
Ctrl + s 後の履歴の検索
Ctrl + g 履歴検索の取消

Ctrl+Sでスクリーンがロック(stop)される設定では使えません。ロックの解除(start)はCtrl+Qです。

その他の操作

  • 以下で確認できます。
bash
$ bind -p

ジョブ管理

  • Ctrl+Zで中断したジョブを制御します。

ジョブ一覧

bash
$ jobs

フォアグラウンドへ

bash
$ fg <job_spec>

バックグラウンドへ

bash
$ bg <job_spec>

強制終了

bash
$ kill -s SIGKILL %<job_spec>

設定

bash
$ nano ~/.bashrc # or ~/.bash_profile
  • 自分用の設定です。
  • 共通設定は/etc/profileにあります。

スクリーンロック設定の切替

  • ロックありに設定されます。
bash
$ stty -ixoff
  • あり/なしが切り替わります。
bash
$ stty -ixon
  • 設定を確認します。
bash
$ stty -a

環境変数の永続的設定

~/.bashrc
# ~ ~ ~
export <key>="<value>"

ファイル転送

  • 以降では、Windows側の操作を記載しますが、Linux側で操作する場合も基本的には同様です。

Windows側操作

WindowsからLinuxへ

コマンドプロンプト
>scp -i "%USERPROFILE%\.ssh\<keyfile>" -r <source> <user>@<address>:<destination>

LinuxからWindowsへ

コマンドプロンプト
>scp -i "%USERPROFILE%\.ssh\<keyfile>" -r <user>@<address>:<source> <destination>

システム操作

シャットダウン

bash
$ sudo shutdown now

再起動

bash
$ sudo shutdown -r now

自分だけなら

bash
$ sudo reboot

追加HDDをマウントする

ドライブを一覧

bash
$ lsblk
bash
$ fdisk -l

マウント状況を一覧

bash
$ mount -v

パーティションのUUIDを得る

sudo blkid <device>

bash
$ sudo blkid /dev/sda1

起動時マウント設定

bash
$ sudoedit /etc/fstab
/etc/fstab
#<file system> <mount point> <type> <options> <dump> <pass>
#~ ~ ~
UUID=<uuid> /mnt/hdd1 ext4 defaults 0 1
# or
/dev/disk/by-uuid/<uuid> /mnt/hdd1 ext4 defaults 0 1

ハードウェア確認

dmidecode

ハードウェア全般情報

bash
$ sudo dmidecode

項目一覧・選択

bash
$ sudo dmidecode -t [type]

lspci

PCIデバイス情報

bash
$ lspci

最大限詳細

bash
$ sudo lspci -vv

スリープ抑止

bash
$ sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

ファイアウォール

nftables

https://www.netfilter.org/projects/nftables/manpage.html

  • 初期状態で、導入済み、かつ、不活性でした。
  • sshで使用中に操作を失敗すると、切断されて再接続できなくなる可能性があるので、慎重に行う必要があります。

設定

bash
$ sudoedit /etc/nftables.conf

初期状態でファイルは存在しません。

/etc/nftables.conf
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;
    # established/related connections
    ct state established,related accept
    # early drop of invalid connections
    ct state invalid drop
    # loopback interface
    iif lo accept
    # ICMP
    icmpv6 type { echo-request, echo-reply, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
    icmp type { echo-request, echo-reply } accept
    # SSH (LANからの接続だけ 'ip/subnetmask')
    ip saddr <local_network> tcp dport ssh accept
    # HTTP/HTTPS
    tcp dport { http, https } accept
    # Blazor Web Apps
    tcp dport { <port>, ... } accept
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
  }

  chain output {
    type filter hook output priority filter; policy accept;
  }

}

設定の反映

bash
$ sudo nft -f /etc/nftables.conf

活殺制御

有効化

bash
$ sudo systemctl enable nftables

無効化

bash
$ sudo systemctl disable nftables

状態確認

bash
$ sudo systemctl status nftables

パッケージ管理

一覧の更新

bash
$ sudo apt update

導入済み一覧

bash
$ apt list --installed

更新可能一覧

bash
$ apt list --upgradable

導入済みパッケージの更新

更新後に不要になったパッケージを除去

bash
$ sudo apt full-upgrade

除去しない

bash
$ sudo apt upgrade

不要になったパッケージの除去

bash
$ sudo apt autoremove

パッケージの探索

完全一致

bash
$ apt list <package>

部分一致

bash
$ apt search <package>

導入

bash
$ sudo apt install <package>

除去

依存先を含める

bash
$ sudo apt --purge remove <package>

含めない

bash
$ sudo apt remove <package>

自動セキュリティ更新

https://zenn.dev/tetr4lab/articles/bbf4e486038da5

.NET Core Runtime

Microsoft パッケージ署名キーを追加

bash
$ wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
$ sudo dpkg -i packages-microsoft-prod.deb
$ rm packages-microsoft-prod.deb
$ sudo apt update

導入

bash
$ sudo apt install aspnetcore-runtime-8.0

https://learn.microsoft.com/ja-jp/dotnet/core/install/linux-debian

https://learn.microsoft.com/ja-jp/dotnet/core/install/linux-ubuntu-2204

バージョン確認

bash
$ dotnet --info

nginx

導入

bash
$ sudo apt install nginx

https://nginx.org/

apache 排除

$ sudo apt --purge remove apache2

バージョン確認

bash
$ /usr/sbin/nginx -v

設定ディレクトリ確認

bash
$ ls /etc/nginx/
conf.d        fastcgi_params  koi-win     modules-available  nginx.conf    scgi_params      sites-enabled  uwsgi_params
fastcgi.conf  koi-utf         mime.types  modules-enabled    proxy_params  sites-available  snippets       win-utf

設定確認

bash
$ sudoedit /etc/nginx/sites-available/default

サーバ名を設定

/etc/nginx/sites-available/default
#~ ~ ~
server {
  listen 80 default_server;
  listen [::]:80 default_server;
  server_name <servername>.<domainname>;
#~ ~ ~
        root /var/www/html;
#~ ~ ~

公開ディレクトリ確認

bash
$ ls /var/www/html
index.nginx-debian.html

Basic認証

導入

bash
$ sudo apt install apache2-utils

パスワード生成

bash
# use '-c' to create it
$ sudo htpasswd -c /var/www/<path>/.htpasswd <user>

パスワード使用

/etc/nginx/sites-available/default
  location <location> {
#~ ~ ~
    # Basic Authentication
    auth_basic "message";
    auth_basic_user_file /var/www/<path>/.htpasswd;
  }

SSL を設定

DNS とファイアウォールを設定

  • DNSとファイアウォールを設定し、サーバにアクセスできるようにします。
  • インターネットからサーバへhttpでアクセスできることを確認します。

証明書の取得と設定、更新

certbot 導入

bash
$ sudo apt install certbot
$ sudo apt install python3-certbot-nginx

証明書の取得

bash
$ sudo certbot certonly --nginx
  • いくつか質問に答えます。途中でキャンセルした場合は、続きから再開できます。
  • 完了すると、証明書と鍵の所在が表示されます。
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/<servername>.<domainname>/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/<servername>.<domainname>/privkey.pem
This certificate expires on <date>.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
  • エラーする場合は、レポートの指示に従って見直せば、大抵、なんとかなります。

証明書を設定

  • 取得した証明書と鍵を設定します。
/etc/nginx/sites-available/default
#~ ~ ~
server {
  listen 80 default_server;
  listen [::]:80 default_server;
  server_name <servername>.<domainname>;
  return 301 https://$host$request_uri;
}
server {
  # SSL configuration
  #
  listen 443 ssl default_server;
  listen [::]:443 ssl default_server;
  server_name <servername>.<domainname>;
  ssl_certificate     <Lets Encrypt Certificate>;
  ssl_certificate_key <Lets Encrypt Key>;
  #
#~ ~ ~
  • httpでのアクセスは、httpsへリダイレクトされます。
    • returnの行をコメントアウトすることでキャンセルできます。

証明書の更新

  • 完了メッセージに示されていたように、既に自動更新が設定されています。
bash
$ cat /etc/cron.d/certbot
  • 以下で自動更新をテストできます。
bash
$ sudo certbot renew --dry-run
  • Timeout during connect (likely firewall problem)と出て失敗するようなら、書かれている通りIPフィルタリングを見直しましょう。
    • Let's Encryptのアドレスは非公開で、かつ、変化する可能性があるため、相手先を制限せずに80番ポート(In)を開けておく必要があります。
    • 自身のファイアウォールだけでなく、ルータの確認もお忘れなく。
  • なお、自動更新に失敗していると、期限切れ前に、Let's Encrypt Expiry Bot <expiry@letsencrypt.org>からLet's Encrypt certificate expiration noticeというメールが送られてきます。

https://letsencrypt.org/ja/docs/

https://eff-certbot.readthedocs.io/en/latest/

証明書の確認

  • 取得されている証明書の状態を確認します。
bash
$ sudo certbot certificates

開発用自己署名証明書

鍵作成

bash
$ sudo openssl genrsa -out <privatekey>.pem 2048

署名要求作成

bash
$ sudo openssl req -new -key <privatekey>.pem -out <signingrequest>.pem

証明書作成

bash
$ sudo openssl x509 -days 365 -req -key <privatekey>.pem -in <signingrequest>.pem -out <certificate>.pem

リバースプロキシーを構成

  • Blazor Web Appに独立したサーバ名を与えておき、そこへのアクセスをアプリのポートに振り向けます。

設定ファイルを作成

bash
$ sudoedit /etc/nginx/sites-available/<project>.conf
/etc/nginx/sites-available/<project>.conf
map $http_connection $connection_upgrade {
  "~*Upgrade" $http_connection;
  default keep-alive;
}

server {
  listen 80;
  listen [::]:80;
  server_name <servername>.<domainname>;
  return 302 https://$host$request_uri;
}

server {
  listen 443 ssl;
  listen [::]:443 ssl;
  server_name <servername>.<domainname>;
  ssl_certificate     <certificate>.pem;
  ssl_certificate_key <privatekey>.pem;
  location / {
      proxy_pass         http://127.0.0.1:5000;
      proxy_http_version 1.1;
      proxy_set_header   Upgrade $http_upgrade;
      proxy_set_header   Connection $connection_upgrade;
      proxy_set_header   Host $http_host;
      proxy_cache_bypass $http_upgrade;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header   X-Forwarded-Proto $scheme;
  }
}
  • httpでのアクセスは、httpsへリダイレクトされます。
    • サーバー名ごとに、証明書ssl_certificateと秘密鍵ssl_certificate_keyの用意が必要です。
  • "server_name:listen"へのアクセスをproxy_passのBlazor Web Appへ転送します。
    • nginxと外部の接続がhttpsであるのに対して、アプリとの接続はhttpです。
    • アプリ側で以下の警告が出ます。
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware`
      Failed to determine the https port for redirect.
  • なお、下記ドキュメントでは、上記全体がhttp { ~ }で括られていますが、/etc/nginx/sites-enabledへ配置したものは、既にhttp { ~ }内とみなされるため、外しておかないとエラーします。
  • 同じく、proxy_set_header Host $host;だと、OAuthのリダイレクトURIからポート指定が欠落して、listenポートを標準(80/443)以外にした場合に認証に失敗するため、HTTPヘッダからHostを取得して使用します。
    • 標準ポートを使用する場合はHost $host;で支障ありません。

https://learn.microsoft.com/ja-jp/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-8.0&tabs=linux-ubuntu#configure-nginx

組み込み変数

  • $http_<name>とすることで、HTTPヘッダから任意のnameを取り出せます。
    • nameは、全て小文字にして、-_に置き換えた名前を指定します。
      • 例えば、User-Agentなら$http_user_agentとします。

https://nginx.org/en/docs/http/ngx_http_core_module.html#variables

有効化 (シンボリックリンクを配置)

bash
$ sudo ln -s /etc/nginx/sites-available/<project>.conf /etc/nginx/sites-enabled/

無効化 (シンボリックリンクを削除)

bash
$ sudo rm /etc/nginx/sites-enabled/<project>.conf

構成のテスト

bash
$ sudo nginx -t

再読み込み

bash
$ sudo nginx -s reload

状態の確認

bash
$ sudo systemctl status nginx

ログの確認

bash
$ ls -l /var/log/nginx/
$ sudo cat /var/log/nginx/access.log
$ sudo cat /var/log/nginx/error.log

再起動

bash
$ sudo systemctl restart nginx

https://docs.nginx.com/nginx/admin-guide/web-server/

https://nginx.org/en/docs/dirindex.html

特定サイトのキャッシュをクリア (Google Chrome)

  • 開発ツール(F12)を開いた状態で、リロードボタンを長押しして、出てきたメニューから「キャッシュの消去」を選ぶと、確認なしで消えます。

MariaDB

導入

bash
$ sudo apt install mariadb-server

初期設定

bash
$ sudo mysql_secure_installation
  • unix_socketにすることで、DBのrootパスワードを不要にできます。(OSのrootアカウントが使うと認証される)
  • 間違えた場合はやり直すことができますが、状況によって質問が変化するので、注意深く読んで答える必要があります。

稼働状況確認

bash
$ sudo systemctl status mariadb
● mariadb.service - MariaDB 10.6.12 database server
     Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2023-10-30 12:19:52 JST; 25min ago
#~ ~ ~

バージョン確認

bash
$ mariadb --version

アカウントと権限

bash
$ sudo mariadb

アカウント一覧

bash
MariaDB [(none)]> select Host,User from mysql.user;

新規作成

MariaDB
MariaDB [(none)]> create user <username>@<hostname> identified by '<password>';

削除

MariaDB
MariaDB [(none)]> drop user <username>@<hostname>;

パスワード変更

MariaDB
MariaDB [(none)]> set password for <username>@<hostname> = '<password>'

権限確認

MariaDB
MariaDB [(none)]> show grants for <username>@<hostname>;

権限付与

MariaDB
MariaDB [(none)]> grant <privileges> on <database>.<table> to <username>@<hostname>;

privileges

all previleges == create,drop,delete,sinsert,select,update,grant option

database.table

*.* all tables of all database
<database>.* all tables of the database

剥奪

MariaDB
MariaDB [(none)]> revoke <privileges> on <database>.<table> from <username>@<hostname>;

権限反映

MariaDB
MariaDB [(none)]> flush privileges;

終了

MariaDB
MariaDB [(none)]> exit;

その他確認など

データベース一覧

MariaDB
MariaDB [(none)]> show databases;

データベース切り替え

bash
MariaDB [(none)]> use <database>;

テーブル一覧

MariaDB
MariaDB [(database)]> show tables;

テーブルスキーマ

MariaDB
MariaDB [(database)]> show create table <table>;

カラム一覧

MariaDB
MariaDB [(database)]> show columns from <table>;

プロセス一覧

MariaDB
MariaDB [(none)]> show processlist;
  • kill Id;できます。

変数一覧

MariaDB
MariaDB [(database)]> show valiables;

設定

MariaDB
MariaDB [(database)]> set <valiablename>=<value>;

MariaDB
MariaDB [(none)]> show variables like 'collation%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb3_general_ci |
| collation_database   | utf8mb4_general_ci |
| collation_server     | utf8mb4_general_ci |
+----------------------+--------------------+
3 rows in set (0.002 sec)
MariaDB [(none)]> set collation_server=utf8mb4_bin;
Query OK, 0 rows affected (0.000 sec)
MariaDB [(none)]> set collation_database=utf8mb4_bin;
Query OK, 0 rows affected (0.000 sec)
MariaDB [(none)]> show variables like 'collation%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb3_general_ci |
| collation_database   | utf8mb4_bin        |
| collation_server     | utf8mb4_bin        |
+----------------------+--------------------+
3 rows in set (0.002 sec)
  • この変更はセッション内でのみ有効です。
    • set global ~(またはset @@global.~)とすれば、大域化できます。
  • 恒久化する場合は、例えば以下のようにします。
/etc/mysql/mariadb.cnf
[client]
default-character-set = 'utf8mb4'

[mysqld]
character-set-server = 'utf8mb4'
collation-server = 'utf8mb4_bin'
bash-MariaDB
$ sudo systemctl restart mariadb
$ sudo mariadb
MariaDB [(none)]> show variables like 'character%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8mb3                    |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.001 sec)
MariaDB [(none)]> show variables like 'collation%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb4_general_ci |
| collation_database   | utf8mb4_bin        |
| collation_server     | utf8mb4_bin        |
+----------------------+--------------------+
3 rows in set (0.001 sec)
  • MySQLの場合は、set @@persist.~で恒久化できるみたいです。

ダンプ

スキーマのみ

bash
$ sudo mariadb-dump --no-data <database> > <sqlfile>

データ込み (バイナリを含む)

bash
$ sudo mariadb-dump --hex-blob <database> > <sqlfile>

復元 (SQLファイルの実行)

bash
$ sudo mariadb <database> < <sqlfile>
  • ERROR: ASCII '\0' appeared in the statement, but this is not allowed unless option --binary-mode is enabled and mysql is run in non-interactive mode.のようなエラーが出る場合は、読み込ませたファイルがUTF-8であることを確認してください。
    • 一部に、UTF-16を出力するアプリがあります。
  • 同様に、改行文字が原因でエラーになる場合があります。
    • 一部に、CHAR(13)だけを出力するアプリがあります。

設定編集

bash
$ sudoedit /etc/mysql/mariadb.conf.d/50-server.cnf

リモートアクセスを許可

/etc/mysql/mariadb.conf.d/50-server.cnf
  # localhost which is more compatible and is not less secure.
- bind-address            = 127.0.0.1
+ #bind-address            = 127.0.0.1
  • SSHトンネルを使えば、この許可は不要です。

https://mariadb.com/

管理クライアント

当初は、phpMyAdminを使いましたが、以下に切り替えました。

HeidiSQL

https://zenn.dev/tetr4lab/articles/c0a18c07bab0df

dotnet アプリのデプロイ

発行

  • パブリッシュを実施すると、構成Releaseが使われます。
  • Blazer Web App / Serverは、<project>\bin\Release\net<version>\pulish\に出力されます。
  • Blazor WebAssemblyは、<project>\bin\Release\net<version>\browser-wasm\publish\に出力されます。
    • 静的サイトの場合は、<project>\bin\Release\net<version>\wwwroot\を使います。

展開

  • パブリッシュしたフォルダをサーバに転送します。
    • アプリをオペレータのアカウントで実行する前提で、プロジェクトディレクトリをオペレータのホーム(/home/<user>/<project>)に配置します。
コマンドプロンプト
>scp -i "%USERPROFILE%\.ssh\<keyfile>" -r "<project>\bin\Release\net<version>\pulish\*" <user>@<address>:"~/<project>"
  • フォルダには一部に含まれないデータがあります。
    • 例えば、sqlite.dbは含まれないので、appsettings.json"Data Source=Data\\project.db"と設定されている場合、Data\project.dbをコピーして、プロジェクトディレクトリに'Data\project.db'というファイル名で配置することになります。
      • Windows 11 のscpは、quoteしても、リモートパス中の\/に置換してしまうようなので、いったんproject.dbを転送してからリモートでリネームするのが無難です。
    • ちなみに、project.dbファイルが無いことで、なぜか、wwwroot/css/site.cssが404エラーになったりします。

https://learn.microsoft.com/ja-jp/aspnet/core/blazor/host-and-deploy/?view=aspnetcore-8.0&tabs=visual-studio

展開の自動化

  • Visual Studioを前提に「発行」の処理を拡張します。
    • CLIで入力できないので、前述のscpを使う方法だと、パスワードの手入力ができません。手入力するには、GUIアプリが必要になります。
  • WinSCPを起動して、「ログイン」ウィンドウの「新しいサイト」から、scpのセッションを登録します。
    • ディレクトリの設定を/home/<user>/<project>にしておきます。
  • Visual Studioのプロジェクトファイル<project>.csprojに発行後ターゲットを追加します。
    • コマンド名をWinSCP.exeにすることでGUIで起動します。WinSCPWinSCP.comだとコンソールモードで起動します。
    • 別プロセスとして起動することで、転送の完了を待たずに発行が完了します。
<project>.csproj
<Target Name="CustomAfterPublish" AfterTargets="Publish">
    <Exec Command="powershell -File $(ProjectDir)deploy.ps1" Condition="Exists('$(ProjectDir)deploy.ps1')" />
</Target>
deploy.ps1
$command = "$env:LOCALAPPDATA\Programs\WinSCP\WinSCP.exe"
$arguments = '<SessionName> /upload "<ProjectPath>\bin\Release\net8.0\publish"'
Start-Process -FilePath $command -ArgumentList $arguments
  • 展開先ディレクトリが/home/<user>/<project>/publish/になるので、起動ディレクトリもそれに合わせます。

https://winscp.net/eng/docs/lang:jp

https://learn.microsoft.com/ja-jp/visualstudio/msbuild/how-to-extend-the-visual-studio-build-process?view=vs-2022

手動起動

bash
$ cd <projectdir>
$ dotnet <project>.dll
  • カレントディレクトリで起動する必要があることに留意してください。
  • サービスとして登録済みの場合は、あらかじめ、サービスの停止・抹消が必要です。

ポート指定

bash
$ cd <projectdir>
$ dotnet <project>.dll --urls http://+:<port>

自動起動 (アプリのサービス化)

サービスを構成

bash
$ sudoedit /lib/systemd/system/<project>.service
/lib/systemd/system/<project>.service
[Unit]
Description=<title> .NET Web API App running on Debian

[Service]
WorkingDirectory=/home/<user>/<projectdir>
ExecStart=/bin/dotnet <project>.dll
Type=simple
Restart=always

# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=<project>
User=<user>
Environment=ASPNETCORE_HTTP_PORTS=<port>;<port...>
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target
  • ExecStart.dllファイル名はパスを指定しません。
    • パスは上のWorkingDirectoryに書きます。
    • アプリは、カレントディレクトリで起動する必要があります。
  • Environment=<key>='<value>'で、環境変数を定義します。
  • 再起動するように構成されているので、構成に誤りがあると無限に再起動し続けます。
  • 再編集の際は、あらかじめ、サービスの停止・抹消が必要です。
    • あるいは、sudo systemctl daemon-reloadで再読み込みします。
  • 下記ドキュメントでは、User=www-dataを設定しています。
    • 設定を「プロジェクトディレクトリ全体を所有するユーザ」に変更するか、「プロジェクトディレクトリ全体の所有者とグループ」をwww-dataにする必要があります。
    • 設定をコメントアウトするとrootが指定されたことになり、プロジェクトディレクトリの所有者を気にしなくて良くなります。(セキュリティ的な問題は不詳)
    • なお、nginxwww-dataのプロセスです。

https://learn.microsoft.com/ja-jp/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/2-3-configure-aspnet-core-application-start-automatically#sample-service-file-for-aspnet-core-applications

サービスの操作

プロセスの確認

bash
$ ps auxf

サービスを登録

bash
$ sudo systemctl enable <project>

サービスを開始

bash
$ sudo systemctl start <project>

サービスを停止

bash
$ sudo systemctl stop <project>

サービスの状態を確認

  • 直近のログが一部表示されます。
bash
$ sudo systemctl status <project>
起動からの全てのログを表示
bash
$ journalctl -b

サービスを抹消

bash
$ sudo systemctl disable <project>

サービスを確認

bash
$ systemctl list-units *.service

複数アプリの構成

https://zenn.dev/tetr4lab/articles/3fb1d4e8e7ff21

バックアップ

timeshift

導入

bash
$ sudo apt install timeshift

ディスクの空きをチェック

bash
$ df -h
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           1.6G  1.7M  1.6G   1% /run
/dev/sdc2       146G   11G  128G   8% /
tmpfs           7.8G  8.0K  7.8G   1% /dev/shm
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
/dev/sdb1       229G   19M  217G   1% /mnt/hdd1
tmpfs           1.6G   44K  1.6G   1% /run/user/1000
tmpfs           1.6G   56K  1.6G   1% /run/user/131

構成の確認 (初回実行)

bash
$ sudo timeshift --check
First run mode (config file not found)
Selected default snapshot type: RSYNC
Mounted '/dev/sdb1' at '/run/timeshift/backup'
Selected default snapshot device: /dev/sdb1
Scheduled snapshots are disabled - Nothing to do!
------------------------------------------------------------------------------
  • 条件が成立したファイルがあると削除されます。
  • /dev/sdb1(/dev/sdb1)が自動的にバックアップ先として登録されました。

自動生成された構成

bash
$ cat /etc/timeshift/timeshift.json
/etc/timeshift/timeshift.json
{
  "backup_device_uuid" : "",
  "parent_device_uuid" : "",
  "do_first_run" : "true",
  "btrfs_mode" : "false",
  "include_btrfs_home" : "false",
  "stop_cron_emails" : "true",
  "schedule_monthly" : "false",
  "schedule_weekly" : "false",
  "schedule_daily" : "false",
  "schedule_hourly" : "false",
  "schedule_boot" : "false",
  "count_monthly" : "2",
  "count_weekly" : "3",
  "count_daily" : "5",
  "count_hourly" : "6",
  "count_boot" : "5",
  "snapshot_size" : "0",
  "snapshot_count" : "0",
  "exclude" : [
  ],
  "exclude-apps" : [
  ]
}

最初のスナップショット

bash
$ sudo timeshift --create --comments "First Snapshot" [--snapshot-device <path>]
  • バックアップ先の指定を省略すると、/run/timeshift/backupになります。

自動更新された構成

/etc/timeshift/timeshift.json
{
  "backup_device_uuid" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "parent_device_uuid" : "",
  "do_first_run" : "false",
  "btrfs_mode" : "false",
  "include_btrfs_home_for_backup" : "false",
  "include_btrfs_home_for_restore" : "false",
  "stop_cron_emails" : "true",
  "btrfs_use_qgroup" : "true",
  "schedule_monthly" : "false",
  "schedule_weekly" : "false",
  "schedule_daily" : "false",
  "schedule_hourly" : "false",
  "schedule_boot" : "false",
  "count_monthly" : "2",
  "count_weekly" : "3",
  "count_daily" : "5",
  "count_hourly" : "6",
  "count_boot" : "5",
  "snapshot_size" : "12443299415",
  "snapshot_count" : "178824",
  "date_format" : "%Y-%m-%d %H:%M:%S",
  "exclude" : [
    "/root/**",
    "/home/<user>/**"
  ],
  "exclude-apps" : []
}
特定ユーザのホームフォルダをバックアップする
/etc/timeshift/timeshift.json
    "exclude" : [
      "/root/**",
-     "/home/<user>/**"
+     "+ /home/<user>/**"
    ],

スケジュールを有効化

bash
$ sudoedit /etc/timeshift/timeshift.json
/etc/timeshift/timeshift.json
-  "schedule_monthly" : "false",
+  "schedule_monthly" : "true",
-  "schedule_weekly" : "false",
+  "schedule_weekly" : "true",
-  "schedule_daily" : "false",
+  "schedule_daily" : "true",
-  "schedule_hourly" : "false",
+  "schedule_hourly" : "true",
-  "schedule_boot" : "false",
+  "schedule_boot" : "true",

構成確認

bash
$ sudo timeshift --check

/dev/sdb1 is mounted at: /run/timeshift/backup, options: rw,relatime

Boot snapshots are enabled
Last boot snapshot not found
Tagged snapshot '2023-11-02_18-26-03': boot
Hourly snapshots are enabled
Last hourly snapshot not found
Tagged snapshot '2023-11-02_18-26-03': hourly
Daily snapshots are enabled
Last daily snapshot not found
Tagged snapshot '2023-11-02_18-26-03': daily
Weekly snapshots are enabled
Last weekly snapshot not found
Tagged snapshot '2023-11-02_18-26-03': weekly
Monthly snapshot are enabled
Last monthly snapshot not found
Tagged snapshot '2023-11-02_18-26-03': monthly
------------------------------------------------------------------------------
Added cron task: /etc/cron.d/timeshift-hourly
Added cron task: /etc/cron.d/timeshift-boot
  • 条件が成立したファイルがあると削除されます。
  • cronでスケジュールされています。

スナップショット確認

bash
$ sudo timeshift --list
[sudo] password for <user>:

/dev/sdb1 is mounted at: /run/timeshift/backup, options: rw,relatime

Device : /dev/sdb1
UUID   : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Path   : /run/timeshift/backup
Mode   : RSYNC
Status : OK
3 snapshots, 236.5 GB free

Num     Name                 Tags         Description
------------------------------------------------------------------------------
0    >  2023-11-02_18-26-03  O B H D W M  First Snapshot
1    >  2023-11-02_19-00-01  B
2    >  2023-11-02_19-01-04  B

https://github.com/linuxmint/timeshift/issues/150

メールレポート

cron のメール送信を停止しない

timeshiftは、デフォルトでcronのメール送信を止めていました。

bash
$ sudoedit /etc/timeshift/timeshift.json
/etc/timeshift/timeshift.json
-  "stop_cron_emails" : "true",
+  "stop_cron_emails" : "false",

cron メール設定

この時点で、この宛先を指定する行は存在しませんでした。

bash
$ sudoedit /etc/crontab
/etc/crontab
+ MAILTO=root

mariabackup

導入

bash
$ sudo apt install mariadb-backup

基本のバックアップ

フルバックアップ

bash
$ sudo mariabackup --backup --target-dir=<full backup directory> --user=root [--password=<password>]

増分バックアップ

bash
$ sudo mariabackup --backup --incremental-basedir=<prev backup directory> --target-dir=<new backup directory> --user=root [--password=<password>]

https://mariadb.com/kb/en/mariabackup/

スケジュールの構成例

  • 毎時フルバックアップを行い、時間毎に24時間、日毎に7日、月毎に365日分を残します。

機構

  • 月毎、日毎、時間毎のディレクトリを用意します。
  • 時間毎にバックアップを作成します。
  • バックアップは、いずれかひとつのディレクトリへ格納されます。
    • 毎日0時のバックアップは、日毎に格納されるので、他には存在しません。
    • 毎月1日0時のバックアップは、付き毎に格納されるので、他には存在しません。
  • ディレクトリ毎に期限切れのバックアップを削除します。(世代管理)

シェルスクリプト

  • /mnt/hdd1/mariabackupをバックアップ先とします。
bash
$ sudo mkdir /etc/mariabackup
$ sudoedit /etc/mariabackup/backup.sh
/etc/mariabackup/backup.sh
#!/bin/bash
MINUTES_TO_KEEP=60
HOURS_TO_KEEP=24
DAYS_TO_KEEP=7
MONTHS_TO_KEEP=12
BACKUP_DIR="/mnt/hdd1/mariabackup"
LOG_FILE="$BACKUP_DIR/backup.log"
LOG_LINES=100
# Logging function
log () {
    echo "$LOGGED_DATETIME $FREQUENCY $1" >> "$LOG_FILE"
    mv "$LOG_FILE" "$LOG_FILE.tmp"
    tail -n $LOG_LINES "$LOG_FILE.tmp" > "$LOG_FILE"
    rm "$LOG_FILE.tmp"
}
# Removing founds function
remove () {
    find "$BACKUP_DIR/$1" -mindepth 1 -maxdepth 1 $2 $3 -exec rm -rf "{}" \;
}
# Checking directory function
notanydir () {
    for dir in $@; do if [[ -d "${dir}" ]]; then return 1; else return 0; fi; done
}
# Run every hour
LOGGED_DATETIME=$(date +"%Y-%m-%d %H:%M:%S")
TARGET_DIR=$(date +%Y%m%d%H%M%S -d "$LOGGED_DATETIME")
# Determine frequency
if notanydir "$BACKUP_DIR/monthly/${TARGET_DIR:0:6}*"; then
    FREQUENCY="monthly"
elif notanydir "$BACKUP_DIR/daily/${TARGET_DIR:0:8}*"; then
    FREQUENCY="daily"
elif notanydir "$BACKUP_DIR/hourly/${TARGET_DIR:0:10}*"; then
    FREQUENCY="hourly"
else
    FREQUENCY="temporary"
fi
# Backup
if mariabackup --backup --user=root --target-dir="$BACKUP_DIR/$FREQUENCY/$TARGET_DIR"; then
    # Remove expired
    remove temporary -cmin +$MINUTES_TO_KEEP
    remove hourly -cmin +$(($HOURS_TO_KEEP * 60 - 30))
    remove daily -ctime +$DAYS_TO_KEEP
    remove monthly -ctime +$(($MONTHS_TO_KEEP * 31 - 15))
    log "success"
else
    log "error"
fi
bash
$ sudo chmod 700 /etc/mariabackup/backup.sh
$ sudo mkdir -p /mnt/hdd1/mariabackup/temporary
$ sudo mkdir /mnt/hdd1/mariabackup/hourly
$ sudo mkdir /mnt/hdd1/mariabackup/daily
$ sudo mkdir /mnt/hdd1/mariabackup/monthly

スケジューリング

bash
$ sudo crontab -e
crontab
#~ ~ ~
# m h  dom mon dow   command
0 * * * * /etc/mariabackup/backup.sh

運用

ログの確認
bash
$ cat /mnt/hdd1/mariabackup/backup.log
バックアップの確認
bash
$ tree -L 2 /mnt/hdd1/mariabackup/
$ sudo mariabackup --prepare --target-dir /mnt/hdd1/mariabackup/<targetdatetime>
臨時バックアップ
bash
$ sudo /etc/mariabackup/backup.sh
リストア
bash
$ sudo systemctl stop mariadb
$ sudo mariabackup --copy-back --target-dir /mnt/hdd1/mariabackup/<targetdatetime>
$ sudo chown -R mysql:mysql /var/lib/mysql/
$ sudo systemctl start mariadb

関連記事

https://zenn.dev/tetr4lab/articles/bbf4e486038da5

https://zenn.dev/tetr4lab/articles/c0a18c07bab0df

おわりに

お読みいただきありがとうございました。
未完成の記事ですが、細かなtypoから根本に関わる思い違いまで、あるいは説明不足や蛇足など、お気づきの点があればコメントしていただけると助かります。
よろしくお願いいたします。

Discussion