タグ: Ubuntu Page 1 of 23

サーバのネットワーク情報を一覧で見るためのワンライナー。(RHEL系/Ubuntu系)

設計書を書く際に面倒な「サーバの設定値の抜き出し」を楽にするためのコマンドです。

RHEL系

  • Red Hat Enterprise
  • Rocky
  • Alma

など、dnfで管理するタイプのコマンドです。

{ echo -e "| インタフェース | IPv4 アドレス | ゲートウェイ | DNS |"; echo -e "| --- | --- | --- | --- |"; nmcli -t -f GENERAL.DEVICE,IP4.ADDRESS,IP4.GATEWAY,IP4.DNS device show | awk -F: '/^GENERAL.DEVICE/ {if (dev) printf "| %s | %s | %s | %s |\n", dev, addr, gw, dns; dev=$2; addr=gw=dns="-"; next} /^IP4.ADDRESS/ {addr=$2; next} /^IP4.GATEWAY/ {gw=$2; next} /^IP4.DNS/ {dns=(dns=="-" ? $2 : dns ", " $2); next} END {if (dev) printf "| %s | %s | %s | %s |\n", dev, addr, gw, dns}'; }

| インタフェース | IPv4 アドレス | ゲートウェイ | DNS |

実行と同時に、こういうマークダウンができあがります。(IPはダミーです)

インタフェースIPv4 アドレスゲートウェイDNS
ens192192.0.2.10/24192.0.2.18.8.8.8, 8.8.4.4
ens224198.51.100.50/24198.51.100.11.1.1.1
virbr0192.168.122.1/24--
docker0172.16.0.1/16--
lo127.0.0.1/8--

Ubuntu系

  • Debian
  • Ubuntu
  • LinuxMint

など、aptを用いるLinuxディストリビューションです。

Ubuntuはnmcliを用いないので、同じようにいきません。

{
  echo "| インタフェース | IPv4 アドレス | ゲートウェイ | DNS |"
  echo "| --- | --- | --- | --- |"
  nmcli -t -f GENERAL.DEVICE,IP4.ADDRESS,IP4.GATEWAY,IP4.DNS device show | \
  awk -F: '/^GENERAL.DEVICE/ {if (dev) printf "| %s | %s | %s | %s |\n", dev, addr, gw, dns; dev=$2; addr=gw=dns="-"; next} 
           /^IP4.ADDRESS/ {addr=$2; next} 
           /^IP4.GATEWAY/ {gw=$2; next} 
           /^IP4.DNS/ {dns=(dns=="-" ? $2 : dns ", " $2); next} 
           END {if (dev) printf "| %s | %s | %s | %s |\n", dev, addr, gw, dns}'
}

これの実行結果は

インタフェースIPv4 アドレスゲートウェイDNS
br-dummy0110.0.0.1/16-(br-dummy01):
docker0172.16.0.1/16-(docker0):
eth0192.0.2.15/24192.0.2.1(eth0):, 8.8.8.8, 1.1.1.1
veth_abc123--(veth_abc123):
veth_def456--(veth_def456):
veth_ghi789--(veth_ghi789):

これをどっかに仕込んでおくだけでも管理は楽になります。

クリスマス防衛戦。(ipsetによるDDoS対策)

自分のサーバに組み込んでいるWebセキュリティシステム(と言ってもスクリプトと設定の組み合わせ) 『ONE OUTS』システム。こちらの弱点を見越した追加設定が効力を発揮しました。

何が起きたか?

「自分のvpsがDDoSを喰らったので、カーネルレベルで対処して沈静化」した時のメモです。

状況確認

Redmine サービスダウン

話は2025年12月25日7:40JST。筆者が管理しているサーバにて、サービスダウンを確認。

  1. SSH接続:OK
  2. Growi:OK
  3. BookStack:OK
  4. Redmine:アクセスしようとして「too much connections」

そこで、状況を調べます。

自作ツール「top-procs」にて

--- CPU Consumers (Top 10) ---
%CPU   %MEM   PID      USER         UNIT                           COMMAND
------------------------------------------------------------------------------------------
85.5   5.1    45114    www-data     apache2.service                 Passenger RubyApp: /home/www-data/app (production)

と、CPU利用率85%以上を確認。

確定:DDoS

netstat -tan を実行すると、以下のようなコネクションが大量に表示されました。

tcp6       0  34498 192.0.2.1:443       198.51.100.15:29862     LAST_ACK   
tcp6       0      0 192.0.2.1:443       203.0.113.84:47044      TIME_WAIT  
tcp6       0      0 192.0.2.1:443       192.0.2.55:38844        TIME_WAIT  
tcp6       0      0 192.0.2.1:443       198.51.100.200:57934    ESTABLISHED
...(中略)...
tcp6       0  34081 192.0.2.1:443       203.0.113.120:27327     LAST_ACK   

総計700行にも及ぶコネクション。これは確実に「DDoS」攻撃です。

  • Mod_Security
  • シェルスクリプト
  • Apache設定

で構成されたONE_OUTSシステムはアクセスログを主体としてL7層(アプリケーション層)での防御を行うもの。なので

  • SYNフラッド攻撃
  • アクセスログに残らないレベルの低レイヤー攻撃

と言った「そもそもログに残らない」「ページの閲覧など関係ない」相手には無意味です。(実際、上記をONE OUTSシステムに組み込んでもアクセスログ(という名の執拗なRedmineへのアクセス)が止まりません。

しかも、DDoSというものは実に厄介です。

  • 超・大量のIPから同時にアクセスしてくるためufw/iptablesで制御したとしても遅延が大きい
  • それでマシンパワーを喰う

という、「物理の力でごり押しする破壊行為」です。

漫画『ドリフターズ』にもある

「こりゃ堕とせんと思ったら
その時から目的は変わるのよ
占領からいやがらせに変わる」

この、いやがらせ目的のため、自分のサーバのリソースが奪われるという状況は見過ごせません。

防衛機構:piertotum locomotor

「こんなこともあろうかと」前もって用意していた「ipset」の設定をフルに使いました。

  • ルールを「集合(set)」として管理。
  • 「このIP群をブロック」というリストを一つのルールとしてまとめられる。
  • 内部的にはハッシュテーブル等を利用しており、検索がほぼ定数時間で完了するため非常に高速。

第2オクテット(/16)どころか、悪質なレンジに対しては「第1オクテット(/8)」すら一括でブロックする運用です。

上記リンクの通り

  1. ipsetコマンドをインストールします。
  2. ブロックリストの設定を行います。
  3. ipsetコマンドでSYNフラッド攻撃を行う攻撃者をレンジごとブロックします。

が事前準備です。

このipsetが有効であるかを確認

実はこれがハマった点でした。

 sudo iptables -L ufw-before-input -n --line-numbers | head -n 5

として、

Chain ufw-before-input (1 references)
num  target     prot opt source               destination         
1    DROP       0    --  0.0.0.0/0            0.0.0.0/0            match-set ufw-blocklist src
2    ACCEPT     0    --  0.0.0.0/0            0.0.0.0/0           
3    ACCEPT     0    --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED

と、match-set ufw-blocklist srcが記されていることを確認します。(私はこれに記述違いがあり、後で修正する羽目になりました)

執拗に攻撃してくるIP/NWの正規表現化

これにはAIの力を借りました。netstat -tanの結果やアクセスログを元に

  • 執拗にアクセスを試みるIP群
  • それが属するASN

を第1オクテット/第2オクテットで抜き出してもらいます。

シェルスクリプトで一括登録

#!/bin/bash

# 1. ターゲットのipset名
SET_NAME="ufw-blocklist"

# 2. DDoS主犯格リスト (CIDR表記)
BAN_LIST=(
  "xx.0.0.0/8"
  "yy.0.0.0/8"
  "zzz.0.0.0/8"

  # 執拗な個体 (CIDRではなく単一IPも登録可能)
  "abc.def.0.0/16" 
)

echo "Hogwarts is threatened!: ${SET_NAME}..."

# 3. ループ処理で注入
for ip_range in "${BAN_LIST[@]}"; do
  # -exist オプションをつけることで、既に登録済みでもエラーにせずスキップさせる
  sudo ipset add ${SET_NAME} ${ip_range} -exist
  if [ $? -eq 0 ]; then
      echo "  Checking... ${ip_range} -> Loaded."
  else
      echo "  Error adding ${ip_range}"
  fi
done

echo "Man the boundaries, protest us, do your duty to our school!"
sudo ipset save ${SET_NAME} -f /etc/ufw/ipsets.save

echo "I've always wanted to use that spell!"

というシェルスクリプトで一気に登録しました。(処理中のechoは『ハリー・ポッターと死の秘宝 part2』屈指の名シーンです)

そもそも、私のvpsは

  • 広告を置いていません
  • アフィリエイトもありません

大嫌いだからです。 主目的は

「私が後で閲覧するときのメモ帳」です。なので、私がアクセスしてこないようなアクセス元のブロックは一切の躊躇を行いません。そのため、\/8で切ることに躊躇はしません。

確認

cat /etc/ufw/ipsets.save

で、

cat /etc/ufw/ipsets.save
create ufw-blocklist hash:net family inet hashsize 1024 maxelem 65536 bucketsize 12 initval 0xcce80b68
add ufw-blocklist xxx.0.0.0/8

などと表示されればブロック成功です。

沈静化

効果は覿面でした。見られなかったRedmineサイトは無事に表示され、

--- CPU Consumers (Top 10) ---
%CPU   %MEM   PID      USER         UNIT                           COMMAND
------------------------------------------------------------------------------------------
19.3   5.1    45114    www-data     apache2.service                 Passenger RubyApp: /home/www-data/app (production)

CPU利用率も正常に用いました。

まとめ

今回、迅速に対処できたのは、以下の確信があったからです。

  • 「アプリケーション層の防御を突破できない攻撃者は、より原始的なレイヤーでの攻撃に切り替えてくる」
  • 「そして、運用側の注意が散漫になるタイミングを狙ってくる」

事前に「OSの負荷を抑えつつ高速にブロックできる仕組み」を構築していたことが功を奏しました。クリスマスというタイミングを狙ったのは、ホリデーシーズンによる対応の遅れを期待した計画的なものだったのでしょう。

連合艦隊解散の辞にある、

古人曰ク勝ツテ兜ノ緒ヲ締メヨト。

という言葉の重みを再認識する出来事でした。

Growi v7.1.x/v7.2.x→v7.4.0以降へのアップデート

概要

Growi 7.2.x → Growi 7.4.0にアップデートする 手順です。

Growi7.3.3より前のバージョンは脆弱性が存在します。Growiをインターネットで公開している方は、こちらの手順で上げましょう。

注意点

  • Growi 7.4.xはElasticSearchがv9でなければ動きません。
  • また、mongodbの最新版は、古いCPUでは動きません。
  • 上記理由のためGrowiをインターネット環境で動かしている場合は以下を十分検討ください。
    • WAFなどで防御する。
    • ハードウェア環境を最新のmongodb / Elasticsearchが動くものへとアップグレードする。
    • インターネット公開を諦める。

前提

  • 既にgrowi v7.1.x/v7.2.xをインストールしていること。
  • 管理画面トップやトップページ右下からバージョンが7.1.xまたは7.2.xであることを再確認します。
  • systemdによってサービス化されていること。
  • 具体的な手順はhttps://atelier.reisalin.com/projects/zettel/knowledgebase/articles/105
  • 最新版や安定版がリリースされていることを以下のサイトで確認していること。
  • https://github.com/growilabs/growi/releases
  • ※設定ファイルの変更やパッケージインストールの変更、nodeのバージョンアップの必要等があれば、それも事前に済ませます。

さっくりはならない手順

  1. Growiをメンテナンスモードにします。
  2. Growi・Elasticsearchのサービスを停止します。
  3. バックアップを取ります。
  4. gitコマンドで最新版をcheckoutします。
  5. アップグレードを行います。
  6. Elasticsearch・Growiのサービスを再開します。
  7. Growiのメンテナンスモードを解除します。
  8. アップグレードされたことを確認します。

メンテナンスモード有効化

  1. Growiに管理者権限でログインします。
  2. 管理トップ>アプリ設定に進み、「メンテナンスモードを開始する」をクリックします。
  3. トップページに戻り「メンテナンスモード」が表示されていることを確認します。

バックアップ

以下をバックアップします。

  • mongodbの格納データ
cat /etc/mongod.conf |grep dbPath

として、ここのディレクトリ一式を控えます。(筆者環境 /home/mongodb)

このディレクトリを任意の方法でバックアップします。

  • Growiの添付ファイル一式が納められているディレクトリ(ファイルアップロード先をlocalにしている場合のみ)
/growi/root/directory/apps/app/public

(筆者環境 /home/www-data/growi/apps/app/public)ここも念のためバックアップします。

※ 添付ファイルのアップロード先をAWSやAzureなどにしている場合は不要です

  • vpsや仮想ゲストの場合はシステム全体:推奨

スナップショット機能などでシステム全体をバックアップした方が確実で安心です。

ElasticsearchとGrowiの停止

  • Elasticsearchサービス停止
sudo systemctl stop elasticsearch.service
  • サービス停止確認
systemctl status elasticsearch.service

inactive(dead)を確認します。

  • Growiサービス停止
sudo systemctl stop growi.service
  • サービス停止確認
systemctl status growi.service

inactive(dead)を確認します。

作業前バックアップ

  • データディレクトリを丸ごとコピー (-aオプションでパーミッションを維持)
sudo cp -a /var/lib/elasticsearch/ /path/to/backup/dir/elastic_bk.$(date +%Y%m%d)

自分の環境に合わせます。

  • バックアップ確認
sudo ls -l /path/to/backup/dir/elastic_bk.$(date +%Y%m%d)

バックアップした内容があることを確認します。(※管理者権限でないとこのディレクトリを見ることはできません)

リポジトリ設定ファイル名をv9用に変更

Elasticsearchのバージョンを指定するリポジトリをv9に変更します。

  • 現行のリポジトリリストをバックアップ
sudo cp -pi /etc/apt/sources.list.d/elastic-8.x.list /path/to/backup/dir/elastic-8.x.list.$(date +%Y%m%d)
  • リポジトリリストのバックアップ確認
diff -u /path/to/backup/dir/elastic-8.x.list.$(date +%Y%m%d) /etc/apt/sources.list.d/elastic-8.x.list
  • リポジトリリストの名前変更
sudo mv /etc/apt/sources.list.d/elastic-8.x.list /etc/apt/sources.list.d/elastic-9.x.list
  • リポジトリリストの名前変更確認
ls -l /etc/apt/sources.list.d/elastic-9.x.list

ファイルがあることを確認します。

sedコマンドでファイル内の参照先を8.xから9.xに書き換え

sudo sed -i 's/8.x/9.x/g' /etc/apt/sources.list.d/elastic-9.x.list

Elasticsearchのアップグレード

  • パッケージ全体のバックアップ
sudo aptitude update

好みでaptitudeを用いています。必要に応じてaptを用いてください。

  • Elasticsearchのアップグレード
sudo aptitude upgrade elasticsearch

※ Growiインストール時、/etc/elasticsearch/jvm.optionsファイルなどの設定変更を行っているため、アップグレード時の設定ファイルを残すかどうかの確認では、必ずN(残す)を選択します。

  • プラグインのアンインストール

Growiに必要なElasticsearchのプラグインは自動更新されません。この処置を執らないとせっかくアップグレードしたのに起動しないという事態が発生します。

sudo /usr/share/elasticsearch/bin/elasticsearch-plugin remove analysis-icu
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin remove analysis-kuromoji
  • プラグインの再インストール
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-icu
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji

growiディレクトリに移動します

cd /home/www-data/growi && pwd

自分の環境に合わせます。(筆者環境/home/www-data/growi)

リリースタグを確認します。

  • リリースタグ取得
sudo git fetch --tags
  • リリースタグ確認
sudo git tag -l

スペースで確認していき、上記リリースサイトと同じバージョンがあることを確認します。

チェックアウトとインストールを行います。

  • 変更を一時的に退避
sudo git stash
  • チェックアウト
sudo git checkout 【バージョン】

リリースタグは再確認しましょう。今回は 2025/12/24 リリースされたv7.4.0を選択しました。

  • pnpm install
sudo pnpm i
  • ビルド
sudo npm run app:build

ElasticsearchとGrowiの再開

  • Elasticsearchサービス開始
sudo systemctl restart elasticsearch.service
  • サービス開始確認
systemctl status elasticsearch.service

active(running)を確認します。

  • バージョンアップ確認
curl -X GET "localhost:9200"

"number" : "9.1.3",など、9系にアップグレードされていることを確認します。

  • Growiサービス開始
sudo systemctl restart growi.service
  • サービス開始確認
systemctl status growi.service

active(running)を確認します。

メンテナンスモード無効化

  1. Growiに管理者権限でログインします。
  2. 管理トップ>アプリ設定に進み、「メンテナンスモードを終了する」をクリックします。
  3. トップページに戻り「メンテナンスモード」が表示されていないことを確認します。

バージョンアップを確認します。

  1. 画面下部にあるバージョンがチェックアウトしたバージョン(v7.4.x)であることを確認します。
  2. 各種機能(ページ閲覧や編集)などが正常に行えるかを確認します。

バージョンアップ後の作業

必要に応じてバックアップしたファイル一式やスナップショットを削除します。

Linuxサーバ、稼働中のサービスのバージョンを確認するワンライナー。

概要

Linuxサーバの構築時に非常に面倒で厄介な「動いているサービス(systemctl status hoge.service でenabledになっているもの)だけを取り出し、表に転記するという作業が

  • 限りなく単調で
  • とてもミスが多い

重箱の隅をつつくような作業を一発で解消するワンライナーの紹介です。

前提

RHEL系Linuxで動きます。(筆者環境Rocky Linux)

ワンライナー

※これは

sudo su 

としてから入力した方が無難です。(パッケージによっては一般的権限では見られないため)

 { echo -e "| ソフトウェア名 | バージョン |\n| --- | --- |"; systemctl list-unit-files --type=service --no-legend | awk '{print $1}' | grep -v '@\.service$' | xargs -r systemctl show -p FragmentPath | sed 's/^FragmentPath=//' | grep '^/' | sort -u | xargs -I {} sh -c 'if rpm -qf "{}" >/dev/null 2>&1; then rpm -qf "{}" --qf "| %{NAME} | %{VERSION}-%{RELEASE} |\n"; else echo "| $(basename "{}") | (not owned by any package) |"; fi' | sort -u; } 

スクリプトの解説

このコマンドは大きく分けて「ヘッダーの出力」「サービスファイルのパス特定」「パッケージ情報の取得と整形」の3つのフェーズで動いています。

1. 表のヘッダーを作成

  • { echo -e "| ソフトウェア名 | バージョン |\n| --- | --- |"; ... }
    • 最初にMarkdown形式の表の1行目(項目名)と2行目(区切り線)を出力します。
    • 全体を { } で囲むことで、ヘッダーと後の実行結果を一つの出力ストリームとしてまとめています。

2. サービス一覧からファイルパスを特定

ここからがメインのパイプラインです。

  • systemctl list-unit-files --type=service --no-legend
    • システム上の全サービスユニットを表示します。--no-legend でヘッダー行を省きます。
  • awk '{print $1}'
    • 出力結果から1列目(サービス名)だけを抜き出します(例: sshd.service)。
  • grep -v '@\.service$'
    • インスタンス化されたユニット(user@.service など)を除外します。これらは実ファイルが少し特殊なためです。
  • xargs -r systemctl show -p FragmentPath
    • 各サービスに対して、その定義ファイル(ユニットファイル)がディスク上のどこにあるか(FragmentPath)を取得します。
  • sed 's/^FragmentPath=//'
    • 出力に含まれる FragmentPath= という文字列を削除し、純粋なパス名だけにします。
  • grep '^/'
    • 空行や無効なパスを除外し、/ から始まる絶対パスのみを残します。
  • sort -u
    • 重複したパスを削除します。

3. RPMによるパッケージ照会と整形

特定されたファイルパスを一つずつ RPM データベースと照合します。

  • xargs -I {} sh -c '...'
    • 各パス({})に対して、シェルスクリプトを実行します。
  • if rpm -qf "{}" >/dev/null 2>&1; then ...
    • そのファイルが RPM パッケージによって管理されているかを判定します。
    • 管理されている場合: rpm -qf を使い、パッケージ名とバージョンを Markdown の行形式 | 名前 | バージョン | で出力します。
    • 管理されていない場合: (手動で作成したサービスなど)「(not owned by any package)」と出力します。
  • sort -u(最後)
    • 最終的なリストをソートし、重複を排除して綺麗に並べます。

実行結果のイメージ

コマンドを実行すると、以下のような結果が得られます。

ソフトウェア名バージョン
chrony4.1-3.el9
openssh-server8.7p1-10.el9
my-custom-script.service(not owned by any package)
systemd250-6.el9

このワンライナーの利点

  • 徹底した合理性。
    • なにせ「目視確認」という手段が排除されます。一切のヒューマンエラーがなくなります。
  • 一覧性と整合性。
    • 「最新の情報を全て表示せよ」という要求に対してもコマンドを叩くだけで済みます。
  • 再利用性
    • これが一番大きいです。2020年代ITで最も使いやすいMarkdownのテーブル形式。そのため、
      • Redmine
      • VS Code
      • Notion
        などにいくらでも反映可能。そして、一度テーブルにしてしまえばExcelへの転記も一発です。

まとめ

「ウサギとカメの寓話」は、コツコツやる者が強いパターンではありますが、「60分の道を頑張って55分に縮めるよりも、30分かけて『1分で終わる手段を考える』」方が、本当の「タイムパフォーマンス」だと思った次第です。

備考

Ubuntuサーバでも、このようにすれば動きます。

{
  echo -e "| ソフトウェア名 | バージョン |\n| --- | --- |"
  # 1. サービスファイルのパス一覧を取得
  systemctl list-unit-files --type=service --no-legend | \
  awk '{print $1}' | \
  grep -v '@\.service$' | \
  xargs -r systemctl show -p FragmentPath | \
  sed 's/^FragmentPath=//' | \
  grep '^/' | \
  sort -u | \
  # 2. 1行ずつ読み込んで処理(whileループでシェル起動を最小化)
  while read -r path; do
    # dpkg-query -S は dpkg -S より高速で安定しています
    res=$(dpkg-query -S "$path" 2>/dev/null)
    if [ $? -eq 0 ]; then
      pkg=$(echo "$res" | cut -d: -f1)
      # 複数のパッケージがヒットする場合があるため、最初の1つを取得して詳細表示
      dpkg-query -W -f="| \${Package} | \${Version} |\n" "${pkg%%,*}"
    else
      echo "| $(basename "$path") | (not owned by any package) |"
    fi
  done | sort -u
}

CPU/メモリの利用状況を確認するスクリプト.part2

Webサーバのみならず、サーバ運用において「どのプロセスがCPU/メモリを喰っているか」というボトルネックの把握は重要です。

それを把握するためのスクリプトのご紹介です。

なぜボトルネックの把握が重要なのか

以下の3点が主な理由です:

  1. リソースの最適化と安定運用
     高負荷プロセスを特定することで、不要な消費を抑え、他のサービスへの影響を防げます。
  2. 障害予防と早期対応
     異常なリソース使用は障害の前兆であることが多く、早期発見によりダウンタイムを回避できます。
  3. 攻撃予兆への対応
     DDoS/執拗な攻撃などはリソース寮にダイレクトに現れます。

把握するためのシェルスクリプト

といっても、topwコマンドなどでは煩雑な情報が多いため、シンプルに

  1. CPUを多く使っているプロセス
  2. メモリを多く使っているプロセス

に絞り込みを行います。というのも、プロセスの暴走は先に示したとおり、CPU/メモリを多く使うからです。

それをより分かりやすく視覚化するスクリプト例が以下の通り。

top-procs.sh等の名前で、任意の場所に作成します。

#!/bin/bash

#================================================================
# System Resource Monitor (Refined by Riza)
#================================================================

# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color

# --- Defaults ---
TOP_N=5
MODE="all" # all, cpu, mem

# --- Usage ---
usage() {
    echo -e "${CYAN}Usage: $(basename "$0") [-c] [-m] [-n NUM] [-h]${NC}"
    echo "  -c      : CPU使用率順で表示"
    echo "  -m      : メモリ使用率順で表示"
    echo "  -n NUM  : 表示する行数を指定 (Default: ${TOP_N})"
    echo "  -h      : ヘルプ表示"
    exit 0
}

# --- Argument Parsing (getopts is standard) ---
while getopts "cmn:h" opt; do
  case $opt in
    c) MODE="cpu" ;;
    m) MODE="mem" ;;
    n) TOP_N="$OPTARG" ;;
    h) usage ;;
    \?) usage ;;
  esac
done

# --- Core Function ---
# Arguments: 
# 1: Sort Key (e.g., -pcpu or -pmem)
# 2: Title
# 3: Format String for ps
show_top() {
    local sort_key="$1"
    local title="$2"

    echo -e "\n${BOLD}${CYAN}--- ${title} (Top ${TOP_N}) ---${NC}"

    # Header format specifically tailored for clarity
    printf "${YELLOW}%-6s %-6s %-8s %-20s %s${NC}\n" "%CPU" "%MEM" "PID" "USER" "COMMAND"
    echo "---------------------------------------------------------"

    # ps command explanation:
    # -e : Select all processes
    # -o : User-defined format
    # --sort : Internal sorting (descending with -)
    # head : Limit output

    ps -e -o pcpu,pmem,pid,user,args --sort="${sort_key}" | \
    head -n "$((TOP_N + 1))" | tail -n "$TOP_N" | \
    awk '{
        # Highlighting logic
        cpu=$1; mem=$2;

        # Colorize CPU if > 50% or MEM > 50% (Arbitrary threshold for visual aid)
        color="'"${NC}"'";
        if (cpu > 50.0 || mem > 50.0) color="'"${RED}"'";
        else if (cpu > 10.0 || mem > 10.0) color="'"${GREEN}"'";

        # Reconstruct the line with printf for alignment
        # $1=CPU, $2=MEM, $3=PID, $4=USER, $5...=COMMAND

        # Capture the full command (args) which starts from $5
        cmd=""; for(i=5;i<=NF;i++) cmd=cmd" "$i;

        printf "%s%-6s %-6s %-8s %-20s %s%s\n", color, $1, $2, $3, $4, cmd, "'"${NC}"'"
    }'
}

# --- Main Logic ---

case $MODE in
    cpu)
        show_top "-pcpu" "CPU Consumers"
        ;;
    mem)
        show_top "-pmem" "Memory Consumers"
        ;;
    all)
        show_top "-pcpu" "CPU Consumers"
        show_top "-pmem" "Memory Consumers"
        ;;
esac

echo ""

以前の改良点

  1. ps --sort の活用: これが最大の改良点よです。ps aux | sort -k ... はデータをテキストとして処理するから遅いのですが、ps --sort=-pcpuはカーネルから情報を取得する段階でソートします。
  2. -n オプションの追加: ./top-procs.sh -n 10 と打てばトップ10が見られるようにしています。
  3. しきい値による色付け: awk の中で、CPUやメモリを激しく消費しているプロセス(例えば50%以上)を赤色で表示するようにしています。
./top-procs.sh

を実行することで、

--- CPU使用率 (上位 5 プロセス) ---
 %CPU    PID  COMMAND
-----------------------------------------
 52.10%  12345  ruby_app_server: /var/www/webapp1 (production)
  9.40%   1086  /usr/sbin/database_server [...]
  3.80%  42162  /usr/sbin/web_server -k start
  1.50%  42161  /usr/sbin/web_server -k start
  0.90%   7978  nodejs_process /path/to/nodejs_app/server.js

--- メモリ使用率 (上位 5 プロセス) ---
 %MEM    PID  COMMAND
-----------------------------------------
 13.10%   1984  /usr/bin/java -Xms256m -Xmx256m [...] search_engine -Des.path.home=/usr/share/search_engine [...]
 10.00%   1086  /usr/sbin/database_server [...]
  7.50%  12345  ruby_app_server: /var/www/webapp1 (production)
  3.90%  78630  ruby_app_server: /var/www/webapp2 (production)
  3.80%  76583  ruby_app_server: /var/www/webapp3 (production)

が出てきます。

この例では、rubyアプリが圧倒的にCPUを消費し、ElasticSearchがメモリを食っているというのが分かります。

そして、

  • -a / 引数無し : CPUとメモリの両方を表示
  • -c : CPU情報のみを表示
  • -m : メモリ情報のみを表示
  • -h : これら引数やスクリプトの内容を表示

と、目的に合わせた柔軟な表示も可能にしています。

ついでにコマンド化

こういった障害発生時のボトルネック判定時、いちいちスクリプトの場所を探すという悠長なことはできません。

なので、余裕がある(つまりこのスクリプトを作成した直後です)状況で、

sudo ln -sf /path/to/script/top-procs.sh /usr/local/bin/top-procs

として、どこからでもコマンドを呼び出せるようにします。(スクリプトの場所は自分がこれを保存した絶対パスを指定してください)

which top-procs

/usr/local/bin/top-procs

と表示されればコマンド化は完了。こうすることにより、どのユーザーでもこのコマンド一発で上記のボトルネック判定が可能になります。

Apache/PHP-FPMの構文確認&再起動スクリプト part.2

https://barrel.reisalin.com/books/950a4/page/apachephp-fpm

のスクリプトの改良版となります。

スクリプト特徴

  1. 管理者(root)権限で実行するかのチェック
  2. サービス再起動前に有効なサイトとドメイン名を事前に確認
  3. 構文にミスがないかを確認
  4. 再起動前の最終確認をy/nで行う
  5. PHP-FPMにも対応(インストールされていない場合はスキップ)
  6. 再起動後にサービスの状況を表示する
  7. -r オプションでreloadのみ実施

スクリプト内容

  • apache2-check.sh
#!/bin/bash

#================================================================
# Apache & PHP-FPM Management Script
#================================================================

# --- Colors for Output ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# --- Settings ---
SITES_DIR="/etc/apache2/sites-enabled"
# Default to 8.3, but allow override via ENV like: PHP_VERSION=8.2 ./script.sh
PHP_VERSION="${PHP_VERSION:-8.3}"

# --- Flags ---
AUTO_YES=false
RESTART_APACHE=true
RESTART_PHP=true
EXCLUSIVE_MODE=false
ACTION_TYPE="restart" # default action
ACTION_LABEL="再起動"

# --- Usage ---
usage() {
    echo -e "${CYAN}Usage: $(basename "$0") [-y] [-r] [-a] [-p] [-h]${NC}"
    echo "  -y : 確認をスキップ (Auto-Yes)"
    echo "  -r : restartの代わりに reload を使用 (設定反映のみの場合に推奨)"
    echo "  -a : Apacheのみ対象"
    echo "  -p : PHP-FPMのみ対象"
    echo "  -h : ヘルプ表示"
    exit 0
}

# --- Argument Parsing ---
while getopts "yraph" opt; do
  case $opt in
    y) AUTO_YES=true ;;
    r) 
       ACTION_TYPE="reload"
       ACTION_LABEL="リロード(設定読込)"
       ;;
    a)
      if ! $EXCLUSIVE_MODE; then
          RESTART_APACHE=false; RESTART_PHP=false; EXCLUSIVE_MODE=true
      fi
      RESTART_APACHE=true
      ;;
    p)
      if ! $EXCLUSIVE_MODE; then
          RESTART_APACHE=false; RESTART_PHP=false; EXCLUSIVE_MODE=true
      fi
      RESTART_PHP=true
      ;;
    h) usage ;;
    \?) usage ;;
  esac
done

PHP_FPM_SERVICE="php${PHP_VERSION}-fpm"
PHP_FPM_COMMAND="php-fpm${PHP_VERSION}"

# --- Function: Action & Check ---
manage_service() {
    local service_name="$1"
    local service_label="$2"
    local confirm_action="n"

    # PHP-FPM doesn't support 'reload' gracefully in all versions/configs,
    # but systemd handles it usually. If not, fallback or stick to restart.
    # For this script, we assume systemctl reload works or fails safely.

    if [ "$AUTO_YES" = true ]; then
        confirm_action="y"
        echo -e "${YELLOW}${service_label} を ${ACTION_LABEL} します... (-y)${NC}"
    else
        read -p "${service_label} を ${ACTION_LABEL} しますか? (y/n): " confirm_action
    fi

    if [[ "$confirm_action" =~ ^[Yy]$ ]]; then
        if ! systemctl "$ACTION_TYPE" "$service_name"; then
            echo -e "${RED}エラー: ${service_label} の ${ACTION_LABEL} に失敗しました。${NC}"
            # On failure, show status immediately
            systemctl status "$service_name" --no-pager
        else
            echo -e "${GREEN}${service_label} が正常に ${ACTION_LABEL} されました。${NC}"
            echo "---- ステータス ----"
            systemctl is-active "$service_name"
            echo "--------------------"
        fi
    else
        echo -e "${CYAN}${service_label} の処理はキャンセルされました。${NC}"
    fi
    echo
}

# --- Root Check ---
if [ "$EUID" -ne 0 ]; then
    echo -e "${RED}エラー: root権限が必要です。sudoしてください。${NC}"
    exit 1
fi

# --- 1. Display Sites ---
echo -e "${CYAN}==== 有効なサイト設定 (VHosts) ====${NC}"
if [ -z "$(ls -A "$SITES_DIR" 2>/dev/null)" ]; then
    echo "サイト設定が存在しません。"
else
    shopt -s nullglob
    for site in "$SITES_DIR"/*; do
        echo -e "${YELLOW}File: $(basename "$site")${NC}"
        # Parse Logic (Kept your logic, it works well)
        grep -hi -E "^\s*(ServerName|ServerAlias)\s+" "$site" | sed -E 's/^[[:blank:]]+//;s/[[:blank:]]*#.*//' | awk '{
            orig=$1; dir=tolower(orig);
            proper=(dir=="servername"?"ServerName":(dir=="serveralias"?"ServerAlias":orig));
            for(i=2;i<=NF;i++){
                d=tolower($i); sub(/[;,]*$/,"",d); gsub(/^[[:blank:]]+|[[:blank:]]+$/,"",d);
                if(d) printf "  %s %s\n", proper, d
            }
        }' | sort -u
    done
    shopt -u nullglob
fi
echo -e "${CYAN}==================================${NC}\n"

# --- 2. Syntax Check (Revelio) ---
echo -e "${CYAN}--- 構文チェック (Revelio) ---${NC}"
SYNTAX_OK=true

if [ "$RESTART_APACHE" = true ]; then
    echo -n "Apache: "
    if apachectl configtest 2>&1 | grep -q "Syntax OK"; then
        echo -e "${GREEN}Syntax OK${NC}"
    else
        echo -e "${RED}Syntax Error Detected!${NC}"
        apachectl configtest
        SYNTAX_OK=false
    fi
fi

PHP_FPM_ENABLED=false
if [ "$RESTART_PHP" = true ]; then
    # Simple check for binary existence
    if command -v "$PHP_FPM_COMMAND" &>/dev/null; then
        PHP_FPM_ENABLED=true
        echo -n "${PHP_FPM_SERVICE}: "
        if "$PHP_FPM_COMMAND" -t 2>&1 | grep -q "test is successful"; then
            echo -e "${GREEN}Syntax OK${NC}"
        else
            echo -e "${RED}Syntax Error Detected!${NC}"
            "$PHP_FPM_COMMAND" -t
            SYNTAX_OK=false
        fi
    else
        echo -e "${YELLOW}Warning: ${PHP_FPM_COMMAND} not found. Skipping PHP check.${NC}"
        RESTART_PHP=false
    fi
fi

if [ "$SYNTAX_OK" = false ]; then
    echo -e "\n${RED}構文エラーがあるため、処理を中断します (Protego)。${NC}"
    exit 1
fi
echo

# --- 3. Execute Action ---
if [ "$RESTART_APACHE" = false ] && [ "$RESTART_PHP" = false ]; then
    echo "対象サービスなし。終了します。"
    exit 0
fi

if [ "$RESTART_APACHE" = true ]; then
    manage_service "apache2" "Apache"
fi

if [ "$RESTART_PHP" = true ] && [ "$PHP_FPM_ENABLED" = true ]; then
    manage_service "$PHP_FPM_SERVICE" "$PHP_FPM_SERVICE"
fi

echo -e "${CYAN}👏 Finito!${NC}"

主な改良点

  • -r (Reload) オプションの追加: プロセスを殺さずに設定を読み直す。
  • カラー出力: 重要なメッセージを強調。
  • PHPバージョンの柔軟性: 環境変数でも渡せるように変更。

スクリプトのコマンド化

このスクリプトをコマンドとして実行できるようにします。

sudo ln -sf /path/to/script/apache2-check.sh /usr/local/bin/apache2-check
  • コマンド確認
which apache2-check

/usr/local/bin/apache2-check と表示されることを確認します。

後は、

sudo apache2-check

を実行すればOKです。

引数によるオプション

また、このコマンドは以下の引数での柔軟な処理も特徴です。

  • -y 確認プロンプトを全てスキップし、全てyで応答。(cronなどで威力を発揮)
  • -a Apacheのみを対象。PHP-FPMを組み込んでいないとき、変更対象がApacheのみの場合。
  • -p PHP-FPMのみを対象。PHP-FPMの設定のみを変更した場合。
  • -r Reload。設定変更のみを対象。
  • 引数無し : デフォルトでApacheとPHP-FPMを確認プロンプト込みで再起動確認。
  • -h この引数を表示。

Nextcloud高性能バックエンドサーバ (Signaling Server) 構築メモ。

概要:

Nextcloudアップデート後に出るようになった

高性能バックエンドが構成されていません - 高性能バックエンドなしでNextcloud Talkを実行すると、非常に小規模な通話 (最大2~3人の参加者)でのみ利用できます。複数の参加者との通話がシームレスに機能するようにするためには高性能バックエンドを設定してください。

このメッセージを対処するため、「高性能バックエンドサーバ」とやらをインストールすることにします。

当初はこれは考慮していませんでした(個人用のファイルサーバとして使っていたため)が、

「自分の信条を曲げてまでDockerを入れてしまった以上、こいつもDockerで入れる」

と“それはそれ、これはこれ/That's another matter entirely, chaps."の精神でインストールしていきます。

これの導入により、何が変わるのか?

接続の安定化・高速化です。

これまでPHP(Nextcloud本体)が行っていた「誰と誰をつなぐか」という重い処理(シグナリング)を、専用のGo言語プログラム(高性能バックエンド)が肩代わりします。これにより、通話開始までの時間が短縮され、サーバー全体の負荷が劇的に下がります。

環境

  • Nextcloud 32.0.3
  • PHP 8.3
    • PHP-FPM 8.3
  • MySQL 8.0
  • Apache 2.4

さっくりとした手順

  1. 【コマンドライン】(オプション)docker-composeをインストールします。
  2. 【コマンドライン】レット文字列を生成します。
  3. 【コマンドライン】Dockerファイルを作成します。
  4. 【コマンドライン】コンテナを起動します。
  5. 【コマンドライン】Apache設定ファイルを編集します。
  6. 【Webブラウザ】動作を確認します。
  7. 【Webブラウザ】Nextcloud管理画面を設定します。

(オプション)docker-composeのインストール

sudo aptitude install docker-compose

筆者の好みでaptitudeを用いています。

シークレット文字列を生成します。

openssl rand -hex 16

4accc25d95464f00a9537dc956bd5414といった文字列が出ます。これを以下「共有シークレット」と呼びます。

Docker設定ファイルを作成します。

任意のコンテナ設定ファイル群に以下を作成します。

mkdir -p /path/to/container/directory/nextcloud-signaling
cd /path/to/container/directory/nextcloud-signaling && pwd

このディレクトリに入り、ファイルを作っていきます。

※ドメイン名などは自分の環境に合わせましょう。※

  • server.conf
[http]
listen = 0.0.0.0:8080

[app]

debug = false

[sessions]

# 手順1で生成した文字列を使用 hashkey = [共有シークレット] blockkey = [共有シークレット]

[backend]

# Nextcloud本体のURL backend = https://hoge.example.com # Docker内部からホストへのSSL検証をスキップ (接続エラー回避のため必須) allowall = true # 手順1で生成した文字列を使用 secret = [共有シークレット]

[nats]

url = nats://nats:4222

[mcu]

# 現時点ではJanus(MCU)は使用しないため無効化 # type = janus # url = ws://127.0.0.1:8188

※ これらシークレット文字列は、別個にしておいた方がより安全です。

-docker-compose.yml

version: '3.6'

services:
  nats:
    image: nats:2.9
    restart: always

  signaling:
    image: strukturag/nextcloud-spreed-signaling:latest
    restart: always
    ports:
      - "127.0.0.1:8080:8080" # ホストのApacheからのみアクセス許可
    volumes:
      - ./server.conf:/config/server.conf
    depends_on:
      - nats
    extra_hosts:
      # Docker内部から hoge.example.com を解決するためにホストIPを指定
      - "hoge.example.com:172.17.0.1"

※重要: extra_hosts には ip addr show docker0 で確認したホストIP (例: 172.17.0.1) を記述します。

Dockerファイルを起動します。

cd /path/to/container/directory/nextcloud-signaling && pwd

念のため、先ほど作成したディレクトリにいることを確認してください。

  • コンテナ起動
sudo docker-compose up -d
  • コンテナ起動確認
sudo docker ps

STATUS が UPになっていれば問題なく起動できています。

Nextcloudのバーチャルサイトの編集

  • バーチャルファイルのバックアップ
sudo cp -pi /etc/apache2/sites-available/nextcloud.conf /path/to/backup/directory/nextcloud.conf.$(date +%Y%m%d)

.confやバックアップディレクトリは自分の環境に合わせます。

  • バーチャルファイルのバックアップ確認
diff -u /path/to/backup/directory/nextcloud.conf.$(date +%Y%m%d) /etc/apache2/sites-available/nextcloud.conf

差分が無いことでバックアップを確認します。

  • ファイル編集

/etc/apache2/sites-available/nextcloud.conf を、任意の方法で編集します。(エディタという宗教問題が絡むため)

他のリダイレクト設定やセキュリティ設定(RewriteRuleなど)に干渉されないよう、<VirtualHost *:443> ブロック内のなるべく上の方に記述するのがコツです。

<VirtualHost *:443>
    # (その他既存の設定...)

    # ====================================================
    # Signaling Server 設定
    # ====================================================

    # 1. バックエンド(Docker)に正しいホスト名とプロトコルを伝える
    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Proto "https"

    # 2. プロキシ設定
    # "upgrade=websocket" オプションにより、http/ws を自動判別してヘッダーを渡す
    ProxyPass "/standalone-signaling/" "http://127.0.0.1:8080/" upgrade=websocket
    ProxyPassReverse "/standalone-signaling/" "http://127.0.0.1:8080/"

    # ====================================================

    # ... (これ以降にDocumentRootやセキュリティ設定が続く) ...
  • 編集後の差分確認
diff -u /path/to/backup/directory/nextcloud.conf.$(date +%Y%m%d) /etc/apache2/sites-available/nextcloud.conf

以下の差分を確認します。

+# ====================================================
+    # Signaling Server 設定
+    # ====================================================
+    # 1. バックエンド(Docker)に正しいホスト名とプロトコルを伝える
+    ProxyPreserveHost On
+    RequestHeader set X-Forwarded-Proto "https"
+
+    # 2. プロキシ設定
+    # "upgrade=websocket" オプションにより、http/ws を自動判別してヘッダーを渡す
+    ProxyPass "/standalone-signaling/" "http://127.0.0.1:8080/" upgrade=websocket
+    ProxyPassReverse "/standalone-signaling/" "http://127.0.0.1:8080/"
+    # ====================================================
+

設定反映

  • 整合性確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • apache再開前確認
systemctl status apache2.service

active(running)を確認します

  • apache再開
sudo systemctl restart apache2.service
  • apache再開確認
systemctl status apache2.service

active(runnning)を確認します。

動作確認

ブラウザで、

https://hoge.example.com/standalone-signaling/api/v1/welcome

にアクセスし、{"nextcloud-spreed-signaling":"Welcome", ...}の表示が出ていればOKです。

Nextcoloud管理画面設定

Nextcloudに管理者ログインし、[管理設定] > [Talk] > [高性能バックエンド] を設定します。

  • 高性能バックエンドURL: https://hoge.example.com/standalone-signaling/
  • 共有シークレット: 手順1で生成した文字列
  • SSL証明書を検証する: チェックを入れる

設定後、「保存」ボタンをクリックします。 「OK: 実行中のバージョン: x.x.x~docker」 と表示されれば完了です。

FAQ

Q. Dockerを入れることでサーバそのものが高負荷になるということは?

A. むしろ逆で、サーバー全体の負荷は「劇的に下がります」。

  • これまで(高性能バックエンドなし):
    • Nextcloudの画面を開いている間、ブラウザは数秒おきに「着信はありますか?」「新しいチャットはありますか?」とサーバー(Apache/PHP)に聞きに行きます(ポーリング方式)。 そのたびに Apacheが動き、PHPが起動し、データベースに接続し… という重い処理が走っていました。これがサーバーを高負荷にする原因です。
  • これから(高性能バックエンドあり):
    • Docker(Go言語)が、ブラウザと「1本のパイプ(WebSocket)」を繋ぎっぱなしにします。 情報はパイプを通ってスッと届くため、「着信確認」のための無駄な処理がゼロになります。

Q. メモリは食いますか?

A. メモリは食いますが、CPUは休まります。

  • Dockerコンテナが常駐するため、約50MB〜100MB程度のメモリ を常に確保(占有)します。これは増えます。
  • しかし、上記の「無駄な確認作業」がなくなるため、CPUの利用率はガクンと下がります。 サーバーにとって一番きついのは「メモリを確保すること」ではなく「CPUをブン回すこと」なので、トータルではサーバーが楽になります。

もっとさっくり言うと:

  • 1. 以前(高性能バックエンドなし)
    • 動作: ブラウザが「ねえ、着信ある?」「ねえ、メッセージ来た?」と、数秒おきにApacheを叩き起こしていました。
    • 負荷: そのたびに、数十MBのメモリを使うPHPプロセス が起動し、データベースに接続し、確認して終了する…という「重い開け閉め」を繰り返していました。
  • 2. 現在(高性能バックエンドあり)
    • 動作: 今回導入した signaling コンテナ(7MB/筆者環境)が、ブラウザと細い糸電話(WebSocket)を繋ぎっぱなしにします。
    • 負荷: 何もなければただ待っているだけ(CPU 0.02%/筆者環境)。着信があった時だけ、「来たよ!」と一瞬で伝えます。

重たいApacheやPHPは、本当に必要な画面表示の時だけ働けば良くなったので、サーバー全体が静かになり、反応速度(レスポンス)が向上します。

BookStackのカスタム404ページを更に変更。

以前のこのページを更に差し替え。

/path/to/BookStack/resources/views/errors/

の`404.blade.php`の内容を、

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 Not Found: The Final Problem</title>
<style>
  @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700;900&display=swap');

  body {
    background-color: #000000;
    color: #d4d4d4;
    font-family: 'Noto Sans JP', sans-serif;
    margin: 0;
    padding: 20px;
    line-height: 1.6;
    overflow-x: hidden;
  }

  .container {
    max-width: 800px;
    margin: 0 auto;
    border: 2px solid #ff0055; /* Neon Pink Border */
    box-shadow: 0 0 15px #ff0055, inset 0 0 20px rgba(255, 0, 85, 0.2);
    padding: 40px;
    position: relative;
    background: repeating-linear-gradient(
      0deg,
      #0a0a0a,
      #0a0a0a 2px,
      #000000 2px,
      #000000 4px
    ); /* Scanline effect */
  }

  h1 {
    color: #ff0055;
    font-size: 3rem;
    text-align: center;
    text-transform: uppercase;
    text-shadow: 2px 2px 0px #ffffff;
    margin-bottom: 10px;
    letter-spacing: 0.1em;
    font-weight: 900;
  }
  
  .sub-header {
    color: #00ffcc; /* Cyber Green */
    text-align: center;
    font-size: 1.2rem;
    margin-bottom: 40px;
    font-family: monospace;
  }

  h2 {
    color: #ffff00; /* Warning Yellow */
    border-left: 5px solid #ff0055;
    padding-left: 15px;
    margin-top: 40px;
    margin-bottom: 20px;
    font-size: 1.5rem;
    text-shadow: 0 0 5px rgba(255, 255, 0, 0.5);
  }

  p {
    margin-bottom: 1.5em;
    font-size: 1.1rem;
  }

  .shout {
    color: #ff0055;
    font-weight: bold;
    font-size: 1.2rem;
  }

  .emphasis {
    color: #00ffcc;
    font-weight: bold;
  }

  .quote-box {
    border: 1px dashed #00ffcc;
    padding: 15px;
    margin: 20px 0;
    background-color: rgba(0, 255, 204, 0.05);
    font-style: italic;
  }

  .footer-nav {
    margin-top: 50px;
    text-align: center;
    border-top: 1px solid #333;
    padding-top: 20px;
  }

  .btn {
    display: inline-block;
    background-color: #ff0055;
    color: #000;
    padding: 10px 25px;
    text-decoration: none;
    font-weight: bold;
    font-size: 1.2rem;
    border: 2px solid #fff;
    transition: all 0.3s ease;
    box-shadow: 5px 5px 0px #00ffcc;
    transform: skew(-10deg);
  }

  .btn:hover {
    background-color: #000;
    color: #ff0055;
    border-color: #ff0055;
    box-shadow: 5px 5px 0px #fff;
    transform: skew(-10deg) translate(-2px, -2px);
  }

  hr {
    border: 0;
    height: 1px;
    background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(255, 0, 85, 0.75), rgba(0, 0, 0, 0));
    margin: 30px 0;
  }

  /* Glitch Effect Animation for H1 */
  @keyframes glitch {
    2%, 64% { transform: translate(2px,0) skew(0deg); }
    4%, 60% { transform: translate(-2px,0) skew(0deg); }
    62% { transform: translate(0,0) skew(5deg); }
  }
  h1:hover {
    animation: glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite;
    color: #00ffcc;
  }

</style>
</head>
<body>

<div class="container">
  <h1>404 Not Found</h1>
  <div class="sub-header">ERROR: PAGE ANNIHILATED / ページはアナイアレーションされた</div>

  <h2>💀 第一章:クリエイターの恐怖、ドイル=サンの葛藤</h2>
  <p>時はマッポーの世、ヴィクトリア朝のヤミ。ドイル=サンは自らが生み出した、探偵という名の、知的なリアルニンジャ、シャーロック・ホームズ=サ
ンの存在に辟易していた。</p>
  
  <div class="quote-box">
    「アイエエエ! 私は医術というリアルなサイエンスを極めたいのに!ヘッズどもはホームズ=サンの狂気の物語しか求めない!」
  </div>

  <p>ドイル=サンにとってホームズ=サンは、ニューロンを支配し、創造性を吸い尽くす、邪悪なバイオ・ゴーストの如し! 彼は決断した。<span class="shout">「ホームズ=サンを殺す!完全に、そして、シメやかに、アナイアレーションする!」</span></p>
  
  <p>彼の脳内には既に、ホームズ=サンのデス・プランが構築されていた。ヘッズたちの熱狂、ストランズ・シンジケートの金と欲! これらを断ち切るには、究極のバイオレンス、すなわち、デスしかない!</p>
  <p>ドイル=サンは内心、自らが生み出した、巨大な、そして、熱狂的な読者層(ヘッズ)に恐れをなしていた。「アイエエエ! 彼らは私のロードではない!私が彼らのロードであるはずなのに!」</p>

  <hr>

  <h2>💥 第二章:ライヘンバッハのNRS! 滝壺のデス・ファイト</h2>
  <p>場所はスイス。ライヘンバッハの滝!</p>
  <p>ホームズ=サンの宿敵、モリアーティ=サンとの最終決戦。二人の、知的なリアルニンジャは激流の轟音の中で格闘!</p>
  
  <p class="shout">「イヤーッ!」「グワーッ!」</p>

  <p>ドイル=サンは冷静にペンを走らせる。二人の、そして、彼の分身の如き、ホームズ=サンは滝壺へと落ち、デス! 証拠は一切なし! 完全なアナイア
レーション!</p>
  <p>ストランズ・マガジンに掲載されたこの物語を目撃した読者、すなわち、ヘッズたちは一斉にNRSを発動!</p>
  
  <div class="quote-box">
    「アイエエエエ! ホームズ=サンが死んだナンデ!?」
  </div>

  <p>彼らのリアリティは滝壺へと引きずり込まれた! ホームズ=サンは架空の存在ではない! 彼らの日々の、そして、生きるための精神的な支えであった
のだ!</p>
  <p>ライヘンバッハの滝は単なる、景勝地ではなく、世界のニューロンが崩壊した地点となった。</p>

  <hr>

  <h2>🔥 第三章:IRCの大炎上! フジキドめいた絶望</h2>
  <p>ドイル=サンは勝利を確信! 「これで私は自由だ!」</p>
  <p>だが、彼の解放の喜びは一瞬で砕かれる! ヘッズたちの狂気がドイル=サンの私生活を襲ったのだ!</p>
  <p>電報、手紙、そして、ストランズ・シンジケートからの緊急のIRC通信がドイル=サンの屋敷を包囲! これぞIRCの大炎上!</p>
  
  <div class="quote-box">
    「貴様の殺意、許さん!」「ホームズを戻せ!」「私はドイルのロードをやめる!」
  </div>

  <p>女性のヘッズたちは喪章を付け、ストリートでデモ! マガジンの発行部数は暴落! ドイル=サンは現実のニンジャに襲われるが如く、四方八方からヘ
ッズの狂気に晒された!</p>
  <p>ドイル=サンは悟る。自らの手で生み出した、この物語の世界に囚われていることを!</p>
  <p>これはまるでニンジャスレイヤー、フジキド・ケンジ=サンの如し! 家族を殺され、復讐のソウルに憑依され、望まぬ、そして、終わりの見えない、デス・ファイトの世界に引きずり込まれた!</p>
  <p>ドイル=サンも同じ! 望まぬ、そして、辞めることの許されない、クリエイターとしてのデス・ゲームに囚われたのだ!</p>

  <hr>

  <h2>✨ 第四章:奇跡の生還! 読者の要求がニューロンを支配</h2>
  <p>ドイル=サンに選択肢はなかった。ホームズ=サンを殺すことで得た「自由」は幻想! 彼のリアリティはヘッズたちの狂気によって完全に、そして、シメやかに支配されていた。</p>
  
  <p class="emphasis">「奇跡の生還、これしか策なし!」</p>

  <p>10年の沈黙を経て、ドイル=サンは筆を執る。「空き家の冒険」!</p>
  <p><span class="shout">ゴウランガ!</span> ホームズ=サンは生存していた! モリアーティ=サンとの格闘の後、奇跡的に生還し、三年の間、世界のヤミで潜伏していたという狂気的な筋書き!</p>
  <p>読者、すなわち、ヘッズたちは歓喜! NRSは修復され、IRCの炎上は鎮火!</p>
  <p>ドイル=サンは再び、ホームズ=サンの物語を書き続けるという運命に逆戻り。フジキド=サンがニンジャをスレイする宿命から逃れられない如く、ド
イル=サンもホームズ=サンという知的なリアルニンジャの物語を書き続けるという宿命から逃れられなかったのだ!</p>
  <p>クリエイターとヘッズの関係が逆転した、アリエナイ・インシデント! これぞフィクションを超えた、リアルな、そして、ゴウランガな史実! <span class="shout">スゴイ!</span></p>

  <div class="footer-nav">
    <p>このリアル・ニンジャ・ゴトの文学的事件をさらに掘り下げるか? それとも、次の恐るべき史実を所望か?</p>
    <a href="/" class="btn">トップページへ戻る</a>
  </div>

</div>

</body>
</html>

に置き換え。

のような、マッポーめいた文章にしました。

この手の、遊び心を発揮できるというのは本当に自サイトの強みです。

『ONE OUTS』システム番外:ipsetによるUFWの効率化。

はじめに

筆者が用いているWebへの攻撃を防ぐ、MOD_SECURITYとApache設定、シェルスクリプトの連携「ONE OUTSシステム」。これは「実際にWebサイトにアクセスした者」への盾として機能していますが、「アクセス未満」での低レイヤーでの攻撃を仕掛ける者を防ぎきることができません。

例えば、SYNフラッド攻撃はWebサイトへの攻撃を仕掛けるわけではないのでログに残らず、じわじわとリソースを奪っていきます。

かといって、これらのSYNフラッド攻撃は極めて広範囲のIPレンジから仕掛けてくるので

sudo ufw insert 1 deny from xxx.xxx.xxx.xxx

とするには、ufwでのルールが広範になりすぎてメンテナンス性ならびにシステムパフォーマンスの低下を招きます。

これを解決するための手段を設けました。

環境

  • Ubuntu 24.04
  • ufw導入済み

行ったこと

  1. ipsetコマンドをインストールします。
  2. ブロックリストの設定を行います。
  3. ipsetコマンドでSYNフラッド攻撃を行う攻撃者をレンジごとブロックします。

事前注意

これは、カーネルメモリにufwのブロックリストを付与する、「破壊的アップデート」の可能性が発生します。

  • セキュリティポリシー
  • 明確な運用基準

組織単位 で行う必要があります。

手順

ipsetコマンドのインストール

※筆者の好みでaptitudeを用いています。環境に合わせてapt等に読み替えます。

  • パッケージアップデート
sudo aptitude update
  • ipsetインストール
sudo aptitude install ipset

※ 要注意 ※

ここでは、ipset-persistantコマンドを入れていません。なぜなら、ufwと競合する結果、パッケージ管理はufwを破壊する可能性があるからです。

  • ipsetインストール確認
ipset -v
ipset v7.19, protocol version: 7
ipset v7.19: Kernel error received: Operation not permitted

※この、not permittedは、root権限で実行していないため許可されていないというメッセージです。

ipsetのルール変更

  • ブロックしたいIPを格納するための「セット」をメモリ上に作成します。

これは再起動時に消えます

sudo ipset create ufw-blocklist hash:net

ufwにipsetを参照するルールを追加します。

  • ufwの前段ルールをバックアップ
sudo cp -pi /etc/ufw/before.rules /path/to/backup/before.rules.$(date +%Y%m%d)

→ バックアップ先は任意のものを指定します

  • バックアップ確認
sudo diff -u /path/to/backup/before.rules.$(date +%Y%m%d) /etc/ufw/before.rules 

※ここでsudoを付与します。なぜなら、これはroot権限でしか読み取りできないからです

  • ファイル修正

上記、/etc/ufw/before.rulesの内容を慎重に修正します。 *.filterのすぐ下の行です。

# ===================================================================
# "ufw-blocklist" セットに含まれるIPからの全パケットを破棄する
-A ufw-before-input -m set --match-set ufw-blocklist src -j DROP
# ===================================================================

# ok all existing rules
-A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
  • ファイル修正確認
sudo diff -u /path/to/backup/before.rules.$(date +%Y%m%d) /etc/ufw/before.rules 

以下の差分になっていることを確認します。

 *filter
+# ===================================================================
+# "ufw-blocklist" セットに含まれるIPからの全パケットを破棄する
+-A ufw-before-input -m set --match-set ufw-blocklist src -j DROP
+# ===================================================================
+
+# ok all existing rules

リロード前確認(最重要)

さて、ここまで手順通りに行えばufwのリロードは正常に完了します。しかし、ここで今一度

  • sudo ipset create ufw-blocklist hash:netを実行したか?
  • /etc/ufw/before.rulesを編集したか?
  • この編集は既存ファイルの内容を削除していない(追記のみ)か?

を確認しましょう。確認しなければ、この先に待ち構えているのは「失敗した場合の自分自身のロックアウト」にもつながります。

ufwリロード

よく深呼吸しましょう。実行前に何か飲み物を飲んでおいてもいいぐらいです。

sudo ufw reload

ファイアウォールを再読込しましたのメッセージが出れば成功です!

sudo ufw status

でも状態:アクティブを確認します。

ipset のリストを「永続化」する

ipset-persistent の代わりに、UFW自身の起動・停止スクリプトに、リストの保存・復元を組み込みます。

  • A. 保存用ファイルのパスを定義

まず、ipset のリストを保存するファイルを決めておきましょう。 ここではIPTABLES_IPSET_SAVE_FILE="/etc/ufw/ipsets.save"と定義します。

  • B. UFW起動時にリストを「復元」する設定

ufw が起動する前に、保存したリストを読み込むようにします。

sudo cp -pi /etc/ufw/before.init /path/to/backup/before.init.$(date +%Y%m%d)

でバックアップを取ります。(バックアップディレクトリを一括にしているならbefore.init.$(date +%Y%m%d)の前にbefore.rules.$(date +%Y%m%d)を作っているはず。「タブの補間はせず、確実にファイルのバックアップを取りましょう)

sudo diff -u /path/to/backup/before.init.$(date +%Y%m%d) /etc/ufw/before.init

で、バックアップが成功していることも確認します。※diffでもsudoを付与します。なぜなら、これはroot権限でしか読み取りできないからです

/etc/ufw/before.initを編集します。

ファイルの一番上(#!/bin/sh の直後)に、以下の行を追記します。

IPTABLES_IPSET_SAVE_FILE="/etc/ufw/ipsets.save"

# 起動時に ipset リストをファイルから復元する
if [ -f "$IPTABLES_IPSET_SAVE_FILE" ]; then
    ipset restore -f "$IPTABLES_IPSET_SAVE_FILE"
fi
sudo diff -u /path/to/backup/before.init.$(date +%Y%m%d) /etc/ufw/before.init
 #!/bin/sh
+IPTABLES_IPSET_SAVE_FILE="/etc/ufw/ipsets.save"
+
+# 起動時に ipset リストをファイルから復元する
+if [ -f "$IPTABLES_IPSET_SAVE_FILE" ]; then
+    ipset restore -f "$IPTABLES_IPSET_SAVE_FILE"
+fi

を確認。

  • UFW停止時にリストを「保存」する設定

ufw が停止(またはリロード、シャットダウン)する後に、現在のリストをファイルに保存するようにします。

sudo cp -pi /etc/ufw/after.init /etc/conf_backup/after.init.$(date +%Y%m%d)
sudo diff -u /etc/conf_backup/after.init.$(date +%Y%m%d) /etc/ufw/after.init 

※diffでもsudoを付与します。なぜなら、これはroot権限でしか読み取りできないからです

/etc/ufw/after.initを管理者権限で編集します。 ファイルの一番上(#!/bin/sh の直後)に、以下の行を追記します。

IPTABLES_IPSET_SAVE_FILE="/etc/ufw/ipsets.save"

# 停止時に現在の ipset リストをファイルに保存する
ipset save -f "$IPTABLES_IPSET_SAVE_FILE"

追記後、

sudo diff -u /etc/conf_backup/after.init.$(date +%Y%m%d) /etc/ufw/after.init 

で差分を確認します。

 #!/bin/sh
+IPTABLES_IPSET_SAVE_FILE="/etc/ufw/ipsets.save"
+
+# 停止時に現在の ipset リストをファイルに保存する
+ipset save -f "$IPTABLES_IPSET_SAVE_FILE"
  • コマンド実行権付与
sudo chmod +x /etc/ufw/before.init /etc/ufw/after.init

→ コマンドを追記しているので重要です!

再度のufwリロード前の確認

ここでも

  • /etc/ufw/before.initを編集し、差分通りか?
  • etc/ufw/before.initを編集し、差分通りか?
  • この編集は既存ファイルの内容を削除していない(追記のみ)か?
  • /etc/ufw/before.init /etc/ufw/after.initに実行権限が付与されているか?

を確認しましょう。確認しなければ、こちらも自分自身のロックアウトにつながります。

ufwリロード

よく深呼吸しましょう。今度は手元にあればお茶菓子を食べてもいいぐらいです。

sudo ufw reload

ファイアウォールを再読込しましたのメッセージが出れば成功です!

sudo ufw status

でも状態:アクティブを確認します。

ここまで来たら:SYNフラッド攻撃への対処

これは、対象のIPアドレスをシャットアウトする「慈悲なき王」です。

ブロック対象は慎重に慎重を期します。

  • メモリ上のリストに即時追加
sudo ipset add ufw-blocklist IPアドレス・NWアドレス

→ 実際のIPアドレスを半角で入力しましょう。

  • メモリ上のリストをファイルに「永続化」
sudo ipset save ufw-blocklist -f /etc/ufw/ipsets.save

(sudo ipset save ではない点に注意してください)

  • 永続化確認
cat /etc/ufw/ipsets.save 

として

create ufw-blocklist hash:net family inet hashsize 1024 maxelem 65536 bucketsize 12 initval 0xcce80b68
add ufw-blocklist IPアドレス・NWアドレス

等と表示されれば成功です。

※この作業はufwリロード不要です。※

まとめ

「相手が回りくどい攻撃をしてきたら、更に回りくどい方法をとらなければならない」という形。

正直、この作業は二度とやりたくない部類に入ります。ですが、一度設定してしまえば

sudo ipset add ufw-blocklist IPアドレス・NWアドレス

で永続的に執拗な攻撃を仕掛ける攻撃者に対処することが可能です。

Linuxにおけるrootの特別感

  • なんとなくrootを使っている
  • そもそもrootしかアカウントを使っていない

と言う方への注意喚起としても書いている記事です。

Linuxにおける「root」の特別性:システムを司る絶対的な権限

Linuxサーバーを運用する上で、誰もが耳にする最も重要なアカウント、それが「root」ユーザーです。このアカウントは、単なる管理者権限を持つユーザーという枠を超え、システム全体を司る絶対的な権限を持つ存在として位置づけられています。

なぜrootはそれほどまでに特別か、というその問題提起を行います。

システム全体を制御する「神」の権限

Linuxは、セキュリティと安定性のために「権限分離(Privilege Separation)」の原則に基づいて設計されています。一般ユーザーは、自分のホームディレクトリや許可されたファイルに対してのみ操作が可能で、システムの中核に関わる重要な設定ファイルやディレクトリには基本的に触れることができません。

一方でrootユーザーは、この権限の壁を完全に超越します。

  • 全ファイルの読み書き・実行権限: システム内のすべてのファイルとディレクトリに対して、制限なくアクセス、変更、削除が可能です。
  • カーネル操作: システムの心臓部であるLinuxカーネルの設定変更や、重要なモジュールのロード/アンロードが可能です。
  • 全ユーザーの管理: どのユーザーのパスワードでも変更でき、プロセスを強制終了させ、システムを再起動・シャットダウンできます。
  • ポートの利用: 予約された特別なポート(1024未満)を利用したサービスを起動できます。

絶大な力と隣り合わせの危険性

この絶大な力は、システム管理者にとっては不可欠なツールですが、一歩間違えれば致命的な破壊に繋がりかねません。

例えば、一般ユーザーであれば誤って自分のファイルしか削除できませんが、rootユーザーが誤って / (ルートディレクトリ) で rm -rf のようなコマンドを実行すれば、システム全体が一瞬にして破壊されてしまいます。(システム権限に関する部分は維持されるでしょう。しかし、/homeディレクトリにあるユーザデータは消し飛びます。

そのため、システム管理者であっても、普段の作業は権限の制限された一般ユーザーとして行い、root権限は本当に必要な時だけ susudo コマンドで一時的に利用することが鉄則です。

ちょっとした具体例

例えば:

systemctl status apache2.service

は本当によく使うコマンドです。

同様に

exit

はコンソールから抜ける時にも使う初歩的なコマンドでしょう。

しかし、

systemctl 

を打った状態で電話があった、声をかけられ中座する必要があった場合。 (これは本当に頻発します)
「まぁいいや、そっちに集中したいからコンソールを抜けよう」と

systemctl exit

を実行すると

shutdown -h now

と全く同じ効果が得られます。これは笑い事ではない事実です。

systemctl exit一般権限のユーザで実行した場合は

==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
'exit.target'を開始するには認証が必要です。
Authenticating as:

と、「ん? なんか様子が違うぞ?」で思い直すことができますが、これがrootユーザーの場合は

システム停止を自然に、言葉通りに受け止め実行します。

話は戻しましょう。

「全能である」がゆえの危険性と不可欠な存在という側面が、rootユーザーを他のアカウントと一線を画す「特別」な存在にしているのです。

一般的にrootのプロンプトが「#」とされているのは、この 「システムを破壊する力」 を持っていることを常に意識し、コマンドの入力に細心の注意を払うための、管理者自身への視覚的な警告として機能しているのです。

それを防ぐためのちょっとしたTIPS

これを防ぐために私がLinuxサーバを設定時に真っ先に指定する記述がこれです。

/home/hoge/.bashrc

hogeは自分のログインユーザです。

ここの末尾に以下のように記します。

PS1="[\u@\H \W]\$ "

# 一般ユーザ向けのプロンプト設定
if [ "$PS1" ]; then
  if [ "$(id -u)" -eq 0 ]; then # rootユーザの場合
    PS1='\[\e[0;31m\][\u@\H \W]#\[\e[0m\] '
  else # 一般ユーザの場合
    PS1='\[\e[0;32m\][\u@\H \W]$\[\e[0m\] '
  fi
fi

プロンプト設定の意図解説

このPS1設定の主要な意図は、セキュリティの確保と作業効率の向上のために、ユーザーの種別(rootか一般ユーザーか)と現在のコンテキストを視覚的に明確化の措置です。

ユーザー種別による視覚的な区別

最も重要な意図は、誤操作を防ぐためにrootユーザー(管理者)と一般ユーザーの環境を瞬時に見分けられるようにすることです。

  • rootユーザーの場合 (id -u0)
    • 色: \e[0;31m は赤色を設定しています。絶大な権限を持つroot環境では、コマンド入力に細心の注意が必要であることを、視覚的な警告として管理者に促します。
    • プロンプト末尾: \#(通常は#に展開)は、伝統的にrootユーザーを示す記号です。
  • 一般ユーザーの場合:
    • 色: \e[0;32m は緑色を設定しています。通常作業を行う安全な環境であることを示します。
    • プロンプト末尾: $ は、伝統的に一般ユーザーを示す記号です。

コンテキストの明確化

ユーザーの種別に関わらず、プロンプトには以下の情報を含めることで、管理者が「誰として」「どのサーバーの」「どのディレクトリにいるか」を一目で把握できるようにしています。

記号意味
\u現在のユーザー名rootadmin
\Hホスト(サーバー)の完全な名称server.example.com
\W現在の作業ディレクトリのベース名/var/log/ の場合は log

設定スクリプトの技術的な解説

初期設定

PS1="[\u@\H \W]\$ "

これは、色の設定が機能しない場合や、条件分岐(if文)に入る前のデフォルトのプロンプトとして機能します。

条件分岐によるプロンプトの切り替え

if [ "$PS1" ]; then
    if [ "$(id -u)" -eq 0 ]; then # rootユーザの場合
        PS1='\[\e[0;31m\][\u@\H \W]#\[\e[0m\] '
    else # 一般ユーザの場合
        PS1='\[\e[0;32m\][\u@\H \W]$\[\e[0m\] '
    fi
fi
  • if [ "$(id -u)" -eq 0 ]; then: コマンドid -uを実行し、現在のユーザーIDを取得します。0はrootユーザーを意味します。このIDに基づいてプロンプトを切り替えます。
  • \[\]: これらは、エスケープシーケンス(色の設定部分)をbashに非表示文字として認識させるための重要な記号です。これがないと、bashがプロンプトの長さを誤認し、コマンド入力時の行がずれてしまう不具合が発生します。
  • \e[0m: これは色のリセットコードです。プロンプトの末尾にこれを記述することで、プロンプト以降に入力されるユーザーのコマンドや出力が色付きにならないように戻しています。

Ubuntu/Debian系での重要な留意点

このプロンプト設定は、ログインするユーザーの環境に適用される必要があります。

UbuntuやDebian系のシステムでは、rootユーザーとしてログイン(またはsu -で環境を切り替え)する際の.bashrcの扱いが、他のシステム(例:CentOS/RHEL)と異なる場合があります。

多くのシステムでは、rootユーザーのホームディレクトリは /root です。

  • 一般ユーザーのプロンプト設定は、/home/(ユーザー名)/.bashrc に記述します。
  • rootユーザーのプロンプト設定は、通常 /root/.bashrc に記述する必要があります。

この設定は、root環境にも適用。コンソールログインなどでrootでログインした場合にも備え、/root/.bashrcにも明記する必要があります。

補足:

/etc/bash.bashrc などのシステム全体の設定ファイルに記述することも可能ですが、特定のユーザーのみに設定を適用したい場合は、ユーザーごとの.bashrcファイルを使用するのが最も確実な方法です。

まとめ

RHEL系で最初にsudo su -をしたときに明示される

「大いなる力には大いなる責任が伴う」

をより明確にした言葉があります。『鉄人28号』でも高らかに歌われている

あるときは正義の味方
あるときは悪魔の手先
いいもわるいもリモコンしだい

この3行は、まさにLinuxのrootアカウントという「大いなる力」の二面性を表しています。

サーバ管理者は、この力が「悪魔の手先」とならないよう、使う一瞬一瞬に

  • 「自分は今、強大な力を得ているのか?」
  • 「この力は良き目的、必要な手段として用いるのか?」

を自問する必要があります。 その『意識のリモコン』を操作するために、筆者は明確にプロンプトで視覚化しているというお話でした。

Page 1 of 23

Powered by WordPress & Theme by Anders Norén