🍥

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

2023/12/10に公開

はじめに

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

概要

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

前提

  • Windows 11 Pro 22H2
  • Debian 12.2.0
    • ASP.NET Core 7.0.14, 8.0.0
    • nginx 1.22.1
    • MariaDB 10.11.4-MariaDB-1
    • PHP 8.2.7
    • phpMyAdmin 5.2.1deb1
  • 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
  • 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
PasswordAuthentication no

USBメモリからの取り出し

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 の設定

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

永続的環境変数の設定

~/.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

パッケージ管理

一覧の更新

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>

.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

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

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)を開いた状態で、リロードボタンを長押しして、出てきたメニューから「キャッシュの消去」を選ぶと、確認なしで消えます。

RDBMS/PHP

mariadb

導入

bash
$ sudo apt install mariadb-server

初期設定

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

バージョン確認

bash
$ mariadb --version

稼働状況確認

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
$ sudo mariadb
#~ ~ ~
MariaDB [(none)]> show grants;

アカウントの権限変更

付与

bash
MariaDB [(none)]> grant ALL on *.* to '<username>'@localhost;

確認

bash
MariaDB [(none)]> show grants for '<username>'@localhost;

新規作成

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

パスワード変更

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

終了

bash
MariaDB [(none)]> exit;

https://mariadb.com/

php

導入

bash
$ sudo apt install php

バージョン確認

bash
$ php -v

https://www.php.net/manual/ja/

php-fpm

導入

bash
$ sudo apt install php-fpm

参照

bash
$ tail /etc/php/8.2/fpm/php-fpm.conf
/etc/php/8.2/fpm/php-fpm.conf
#~ ~ ~
include=/etc/php/8.2/fpm/pool.d/*.conf
bash
$ ls /etc/php/8.2/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/www.conf
bash
$ cat /etc/php/8.2/fpm/pool.d/www.conf
/etc/php/8.2/fpm/pool.d/www.conf
#~ ~ ~
listen = /run/php/php8.2-fpm.sock
#~ ~ ~

Basic認証

導入

bash
$ sudo apt install apache2-utils

パスワード生成

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

phpMyAdmin

導入

bash
$ sudo apt install phpmyadmin
  • Webサーバの種別を尋ねられた際は、選択肢にnginxがないので、何もチェックしません。
  • ここで尋ねられるパスワードは、phpMyAdminのものです。
  • 間違った際は、いったん(DBの消去を含め)removeして、再度installすることで、やり直すことができます。

設定

アカウントの権限変更

bash
$ sudo mariadb
#~ ~ ~
MariaDB [(none)]> grant all on *.* to phpmyadmin@localhost;
#~ ~ ~
MariaDB [(none)]> show grants for phpmyadmin@localhost;
#~ ~ ~
MariaDB [(none)]> exit;

nginxのルートディレクトリに配置

bash
$ sudo ln -s /usr/share/phpmyadmin /var/www/html/<path>

https://www.phpmyadmin.net/

nginxへ設定

bash
$ sudoedit /etc/nginx/sites-available/default
/etc/nginx/sites-available/default
#~ ~ ~
  index index.php index.html index.htm index.nginx-debian.html;
#~ ~ ~
  location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    # Basic Authentication
    auth_basic "private area";
    auth_basic_user_file /var/www/html/<path>/.htpasswd;
  }
#~ ~ ~

https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html

リロード

bash
$ sudo nginx -s reload

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

手動起動

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

複数アプリの構成

アプリの識別手法

  • ひとつのサイトで、複数の独立したアプリをホストする場合の手法を模索します。
  • 基本的には、nginxで仮想サイトを作り、アプリ(サービス)ごとに待ち受けポートを別にして、リバースプロキシで振り分けます。

サブディレクトリ

  • サービス名を使って、仮想サイトにサブディレクトリを割り当てます。
  • アプリに関連付けた名前が使えるので利用の際に分かり易いです。
  • App.razor<base href="/<directory>" />を設定することで、https://<domain>/<directory>/に配置できます。
    • ビルドの時点で識別子を確定させる必要があるので、変更時には再ビルドが必要です。
  • ただし、これだけでは、一部のファイルをhttps://<domain>/から取得しようとするため、課題が生じます。
  • なお、baseはルートのままで、仮想サイトのURLをrewriteする方法も考えられますが、サイト内のリンクにサブディレクトリが付かないため破綻します。

ポート

  • アプリ毎に仮想サイトとサービスのポートを用意し、仮想サイトをhttps://<domain>:<port>/に配置して、サービスのポートに転送します。
  • サービスのユニットファイル(systemd)毎に、環境変数を使ってポートを指定します。
    • サービス開始の時点まで番号を不確定にでき、変更が容易です。
  • この方法であれば、コンテンツがアプリ毎に完全に分離できます。
    • アドレスバーの見た目が悪いです。
    • 「番号」では、アプリと結びつけづらいです。
      • ブックマーク(ショートカット)を作ることで緩和できます。
  • 当プロジェクトでは、現時点ではこちらを採用しています。

バックアップ

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" : "481df198-449c-438a-b0a9-ef94d22efc55",
  "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" : []
}

スケジュールを有効化

bash
$ sudoedit /etc/timeshift/timeshift.json
/etc/timeshift/timeshift.json
{
//~ ~ ~
  "schedule_monthly" : "true",
  "schedule_weekly" : "true",
  "schedule_daily" : "true",
  "schedule_hourly" : "true",
  "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   : 481df198-449c-438a-b0a9-ef94d22efc55
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

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時のバックアップは、付き毎に格納されるので、他には存在しません。
  • ディレクトリ毎に期限切れのバックアップを削除します。

シェルスクリプト

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
$ 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/
臨時バックアップ
bash
$ sudo /etc/mariabackup/backup.sh

おわりに

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

Discussion