タグ: シェルスクリプト Page 1 of 7

NextcloudアップデートとDBバックアップを行うBashスクリプトの解説

Nextcloudのアップデートは、それほど頻繁に行う作業ではありません。しかし、だからこそ毎回「あれ、このコマンド実行したっけ?」となりがちです。

筆者自身も、メンテナンスモードの切り替えやデータベースのバックアップなど、手順を一つひとつ確認しながら作業していました。そこで「毎回やることはスクリプトに任せよう」と考え、更新前のバックアップから updater.phar の実行までを一通り補助する Bash スクリプトを作成しました。

今回は、そのスクリプトの仕組みを紹介します。

動作環境

動作を想定している環境は以下の通りです。

  • Ubuntu 20.04 LTS / 22.04 LTS / 24.04 LTS
  • MySQL または MariaDB
  • PHP / Bash
  • root 権限(sudo)
  • Apache実行ユーザ: www-data

Ubuntu系で一般的な構成であれば、そのまま利用できるようになっています。

スクリプト本体

最初に環境に合わせて、スクリプト冒頭の変数だけ変更します。

NC_DIR="/var/www/html/nextcloud"
NC_USER="www-data"

DB_NAME="nextcloud"
DB_USER="nextcloud"

BACKUP_DIR="/var/backups/nextcloud"

続いて実行権限を付与します。

chmod +x nextcloud_update.sh

最後に sudo で起動します。

sudo ./nextcloud_update.sh

途中では

  • データベースパスワード
  • 各ステップへ進むかどうか

を対話形式で確認します。

確認しながら進められるので、完全自動化というより「操作ミスを減らすための半自動化」という位置付けです。

Bashで自動化というと「全部ワンコマンドで終わらせる」方向へ行きがちですが、個人的にはサーバーの更新作業はそこまで割り切らない方が安心だと考えています。

特に Nextcloud のアップデートは、途中で状況を確認したくなる場面も少なくありません。

そのため、このスクリプトでは各工程ごとに確認を挟み、「人が判断するところ」と「機械に任せるところ」を分ける構成にしました。

毎回同じ手順を思い出しながら実行するより、確認すべきポイントだけに集中できるので、更新作業はかなり気楽になります。

以下がスクリプト全文です。

#!/bin/bash
# ==============================================================================
# Nextcloud Update & Database Backup Script
# ==============================================================================

set -euo pipefail

# --- 変数定義(環境に合わせて変更してください) ---
NC_DIR="/var/www/html/nextcloud"
NC_USER="www-data"
PHP_BIN="php"

# DB設定
DB_NAME="nextcloud"
DB_USER="nextcloud"
DB_HOST="localhost"
BACKUP_DIR="/var/backups/nextcloud"

# --- ユーティリティ関数 ---
log_info() {
    echo -e "\n\e[34m[INFO]\e[0m $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_warn() {
    echo -e "\e[33m[WARN]\e[0m $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo -e "\e[31m[ERROR]\e[0m $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}

ask_proceed() {
    local prompt_msg="${1:-次のステップに進みますか?}"
    while true; do
        read -rp "👉 ${prompt_msg} [y/N]: " answer
        case "${answer}" in
            [yY]|[yY][eE][sS])
                return 0
                ;;
            [nN]|[nN][oS]|"")
                log_warn "ユーザーにより処理が中断されました。"
                exit 1
                ;;
            *)
                echo "'y' または 'n' を入力してください。"
                ;;
        esac
    done
}

# --- 前提チェック ---
# root 権限チェック(sudo 経由を含む root 以外は即時終了)
if [ "${EUID}" -ne 0 ]; then
    log_error "このスクリプトは root 権限で実行する必要があります。(例: sudo $0)"
    exit 1
fi

log_info "前提条件と実行権限を確認しています..."

if [ ! -d "${NC_DIR}" ]; then
    log_error "Nextcloudディレクトリが見つかりません: ${NC_DIR}"
    exit 1
fi

if ! command -v mysqldump &> /dev/null && ! command -v mariadb-dump &> /dev/null; then
    log_error "mysqldump または mariadb-dump コマンドが見つかりません。"
    exit 1
fi

mkdir -p "${BACKUP_DIR}"

# ==============================================================================
# Step 1: データベースパスワードの取得と認証確認
# ==============================================================================
log_info "【Step 1】データベース情報の入力"
read -rsp "🔑 DBユーザー (${DB_USER}) のパスワードを入力してください: " DB_PASS
echo

# パスワードの疎通確認
if ! MYSQL_PWD="${DB_PASS}" mysqladmin -h "${DB_HOST}" -u "${DB_USER}" ping &>/dev/null; then
    log_error "データベースへの接続に失敗しました。パスワードまたはホスト設定を確認してください。"
    exit 1
fi
log_info "データベースへの接続を確認しました。"

# ==============================================================================
# Step 2: メンテナンスモードの有効化
# ==============================================================================
log_info "【Step 2】メンテナンスモードの有効化"
ask_proceed "メンテナンスモードをONにしますか?"

sudo -u "${NC_USER}" "${PHP_BIN}" "${NC_DIR}/occ" maintenance:mode --on
log_info "メンテナンスモードを有効化しました。"

# スクリプト異常終了時にメンテナンスモード解除を促すトラップを設定
cleanup() {
    if [ $? -ne 0 ]; then
        log_warn "エラーが発生したため処理を停止しました。"
        log_warn "必要に応じてメンテナンスモードを解除してください:"
        log_warn "sudo -u ${NC_USER} ${PHP_BIN} ${NC_DIR}/occ maintenance:mode --off"
    fi
}
trap cleanup EXIT

# ==============================================================================
# Step 3: DBバックアップの実行(インラインパスワード処理)
# ==============================================================================
log_info "【Step 3】DBバックアップの実行"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_backup_${TIMESTAMP}.sql.gz"

ask_proceed "DBバックアップを開始しますか? (保存先: ${BACKUP_FILE})"

# dumpコマンドの判定(MariaDB/MySQL互換)
DUMP_CMD="mysqldump"
command -v mariadb-dump &> /dev/null && DUMP_CMD="mariadb-dump"

# MYSQL_PWD環境変数を使用することで、psコマンド等へのパスワード露出を防止
if MYSQL_PWD="${DB_PASS}" ${DUMP_CMD} -h "${DB_HOST}" -u "${DB_USER}" --single-transaction --quick "${DB_NAME}" | gzip > "${BACKUP_FILE}"; then
    log_info "バックアップが正常に完了しました: ${BACKUP_FILE}"
else
    log_error "バックアップ処理中にエラーが発生しました。"
    exit 1
fi

# ==============================================================================
# Step 4: updater.phar によるアップデート実行
# ==============================================================================
log_info "【Step 4】updater.phar の実行"
log_warn "updater自体の対話プロンプトが表示されます。画面の指示に従って操作してください。"
ask_proceed "updater.phar を起動しますか?"

cd "${NC_DIR}"
sudo -u "${NC_USER}" "${PHP_BIN}" "${NC_DIR}/updater/updater.phar"

# ==============================================================================
# Step 5: 状態の確認とメンテナンスモードの調整
# ==============================================================================
log_info "【Step 5】最終確認とメンテナンスモードの解除"

current_mode=$(sudo -u "${NC_USER}" "${PHP_BIN}" "${NC_DIR}/occ" maintenance:mode)
echo "現在の状態: ${current_mode}"

if echo "${current_mode}" | grep -q "enabled"; then
    read -rp "👉 メンテナンスモードをOFF(解除)にしますか? [y/N]: " unfreeze
    if [[ "${unfreeze}" =~ ^[yY]$ ]]; then
        sudo -u "${NC_USER}" "${PHP_BIN}" "${NC_DIR}/occ" maintenance:mode --off
        log_info "メンテナンスモードを解除しました。"
    else
        log_warn "メンテナンスモードを維持したまま終了します。"
    fi
fi

# ==============================================================================
# Step 6: バージョン確認
# ==============================================================================
log_info "【Step 6】バージョンの確認"
sudo -u "${NC_USER}" "${PHP_BIN}" "${NC_DIR}/occ" status

log_info "すべての更新手順が完了しました。"
trap - EXIT

このスクリプトで重視したこと

単にコマンドを並べるだけではなく、「途中で失敗しても致命傷にならないこと」を意識しています。

set -euo pipefail

Bashスクリプトでは定番ですが、非常に重要です。

  • コマンドが失敗した
  • 未定義変数を参照した
  • パイプラインの途中でエラーが発生した

このような場合は、その時点で処理を停止します。

「バックアップに失敗したのに、そのままアップデートが続く」といった事故を防ぐためです。

root権限の確認

EUID を利用して、root 権限以外では実行できないようにしています。

sudo ./nextcloud_update.sh

以外の実行方法では即座に終了します。

パスワードをコマンドラインへ残さない

昔ながらの

mysqldump -pPASSWORD

という書き方は、ps コマンドなどからパスワードが見えてしまいます。

そこで本スクリプトでは

MYSQL_PWD

環境変数を利用しています。

賛否のある方法ではありますが、少なくともコマンドライン引数へ平文パスワードを残すより安全です。(なお筆者は別スクリプトを利用していますが、それはまた別のお話)

trapによる復旧案内

一番怖いのは

  • メンテナンスモードON
  • エラー発生
  • そのまま終了

というパターンです。

そこで trap cleanup EXIT を利用し、異常終了した場合は

occ maintenance:mode --off

を案内するようにしています。

「解除方法を忘れた」という事態を防ぐための保険です。

実行の流れ

処理は次の順番で進みます。

1. 前提条件の確認

まずは

  • root権限
  • Nextcloudディレクトリ
  • mysqldump または mariadb-dump

の存在を確認します。

ここで問題があれば、それ以上処理は進みません。

2. データベース接続の確認

パスワードを入力したあと、

mysqladmin ping

で接続テストを行います。

アップデート直前になって認証エラーが発覚するより、最初に確認してしまった方が安心です。

3. メンテナンスモードを有効化

occ maintenance:mode --on

を実行します。この操作の前にも確認プロンプトを表示するため、誤操作を防げます。

4. データベースをバックアップ

バックアップには

  • mysqldump
  • mariadb-dump

のどちらか利用可能な方を自動で選択します。

取得したSQLは、そのまま gzip 圧縮して保存します。

nextcloud_backup_20260821_153000.sql.gz

のように日時付きファイルになるため、履歴も管理しやすくしています。

5. updater.phar を起動

Nextcloud標準の

updater.phar

を実行します。ここから先はNextcloud側の対話画面になりますので、画面の案内に従って更新を進めます。

6. 最終確認

更新後は

  • メンテナンスモードの状態確認
  • 必要ならOFFへ変更
  • occ status

まで実行します。最後に現在のバージョンが表示されれば、一通りの更新は完了です。

さっくりとしたまとめ

サーバー運用では「バックアップを取ってから更新する」は当たり前ですが、人間は当たり前ほど慣れてしまうものです。だから私は、忘れない仕組みを作るようにしています。

「運用者は呼吸するかのようにできる。だが、第三者に任せられるか? すっごく焦ってるときに緊急アップデートをした時は?」のためにも、確実に動く仕組みは大切というお話でした。

改良版:Linuxサーバのプロセスを見るためのスクリプト。

自分のサーバでその場で障害状況を見るための道具を洗練させています。

#!/bin/bash

# --- カラー定義 ---
GREEN='\033[0;32m'  # 緑:正常・低負荷
RED='\033[0;31m'    # 赤:高負荷・警告
BLUE='\033[0;34m'   # 青:ヘッダ・情報
YELLOW='\033[1;33m' # 黄:中負荷・注意
NC='\033[0m'        
BOLD='\033[1m'

# --- デフォルト設定 ---
TOP_N=10
MODE="all"

# --- ヘルプ表示 ---
usage() {
    echo -e "${BLUE}Usage: $0 [-c] [-m] [-n NUM] [-i]${NC}"
    echo "  -c    : CPU使用率順で表示"
    echo "  -m    : メモリ使用率順で表示"
    echo "  -n    : 表示行数 (Default: ${TOP_N})"
    echo "  -i    : 対話式モード (Interactive)"
    exit 0
}

# --- 対話モード (Interactive Mode) ---
interactive_mode() {
    echo -e "${BLUE}${BOLD}=== 対話設定モード ===${NC}"
    
    # モード選択
    echo -e "表示モードを選択してください:"
    echo "  1) 全て表示 (デフォルト: Enter)"
    echo "  2) CPUのみ"
    echo "  3) メモリのみ"
    read -r -p "選択 [1-3]: " mode_choice
    case "$mode_choice" in
        2) MODE="cpu" ;;
        3) MODE="mem" ;;
        *) MODE="all" ;;
    esac

    # 行数指定
    read -r -p "表示行数を入力してください (デフォルト: 10): " input_n
    if [[ "$input_n" =~ ^[0-9]+$ ]]; then
        TOP_N="$input_n"
    else
        TOP_N=10
    fi
    echo ""
}

# --- CPU情報の表示 ---
show_cpu_info() {
    echo -e "${BLUE}${BOLD}--- 💻 CPU Information ---${NC}"
    if [ -f /proc/cpuinfo ]; then
        # メーカー名 / モデル名
        local model_name
        model_name=$(grep -m 1 "model name" /proc/cpuinfo | cut -d: -f2 | sed 's/^[ \t]*//')
        # 物理コア数 (無い場合はソケット数等からフォールバック)
        local cores
        cores=$(grep -c "^processor" /proc/cpuinfo)
        # 周波数 (MHz から GHz に変換して見やすく)
        local cpu_mhz
        cpu_mhz=$(grep -m 1 "cpu MHz" /proc/cpuinfo | cut -d: -f2 | sed 's/^[ \t]*//')
        
        echo -e "  ${YELLOW}Model:${NC} ${model_name:-Unknown}"
        if [ -n "$cpu_mhz" ]; then
            # 簡易的にGHz変換(小数点以下2桁)
            local cpu_ghz
            cpu_ghz=$(awk -v mhz="$cpu_mhz" 'BEGIN {printf "%.2f", mhz/1000}')
            echo -e "  ${YELLOW}Speed:${NC} ${cpu_ghz} GHz (${cpu_mhz} MHz)"
        fi
        echo -e "  ${YELLOW}Cores:${NC} ${cores} threads"
    else
        echo -e "${RED}/proc/cpuinfo が見つかりません。${NC}"
    fi
    echo ""
}

# --- Memory & zRAM ---
show_memory_status() {
    echo -e "${BLUE}${BOLD}--- 🧠 Memory & zRAM Status ---${NC}"
    
    # free -h の表示
    echo -e "${YELLOW}[Physical Memory]${NC}"
    free -h | awk 'NR==1{print "              " $0} NR>1{print $0}'
    
    echo ""
    # zramctl の表示
    if command -v zramctl > /dev/null; then
        echo -e "${YELLOW}[zRAM Compression Status]${NC}"
        zramctl --output-all
    else
        echo -e "${RED}zramctlはこの環境にはありません。${NC}"
    fi
    echo ""
}

# --- ディスク使用量 (Storage Status) ---
show_disk_status() {
    echo -e "${BLUE}${BOLD}--- 💾 Disk Usage Status ---${NC}"
    # 主要なファイルシステム(tmpfs等を除く実ディスク)を抽出して表示
    df -h -x tmpfs -x devtmpfs -x squashfs 2>/dev/null || df -h
    echo ""
}

# --- プロセス監視 (Process Tracking) ---
show_top() {
    local sort_key="$1"
    local title="$2"
    
    echo -e "${BLUE}${BOLD}--- ${title} (Top ${TOP_N}) ---${NC}"
    
    # Header (見出し列の幅を調整してズレを防止)
    printf "${BLUE}%-6s %-6s %-8s %-12s %-20s %s${NC}\n" "%CPU" "%MEM" "PID" "USER" "UNIT" "COMMAND"
    echo "------------------------------------------------------------------------------------------"

    # ps コマンド。head/tailのロジックを整理し、空文字による awk の挙動エラーを防止
    ps -e -o pcpu,pmem,pid,user,unit:20,args --sort="${sort_key}" | \
    tail -n +2 | head -n "$TOP_N" | \
    awk -v red="$RED" -v green="$GREEN" -v yellow="$YELLOW" -v nc="$NC" '
    {
        cpu=$1; mem=$2; pid=$3; user=$4; unit=$5;
        
        # カラー判定
        color=nc;
        if (cpu > 50.0 || mem > 50.0) color=red;
        else if (cpu > 10.0 || mem > 10.0) color=yellow;
        else color=green;

        # 6列目以降のコマンド引数を結合
        cmd=""; for(i=6;i<=NF;i++) cmd=cmd" "$i;
        # 先頭の余分なスペースを削除
        sub(/^ /, "", cmd);
        
        printf "%s%-6s %-6s %-8s %-12s %-20s %-50s%s\n", color, cpu, mem, pid, user, unit, substr(cmd,1,50), nc
    }'
}

# --- 引数解析 ---
# オプション指定がない場合はデフォルトで対話モードを起動
if [ $# -eq 0 ]; then
    interactive_mode
else
    while getopts "cmn:ih" opt; do
        case $opt in
            c) MODE="cpu" ;;
            m) MODE="mem" ;;
            n) TOP_N="$OPTARG" ;;
            i) interactive_mode ;;
            h|*) usage ;;
        esac
    done
fi

# --- 実行 ---
clear
echo -e "${GREEN}監視を開始します。${NC}\n"

# システム情報の表示 (常に表示)
show_cpu_info
show_memory_status
show_disk_status

# プロセス情報の表示
case $MODE in
    cpu) show_top "-pcpu" "CPU Consumers" ;;
    mem) show_top "-pmem" "Memory Consumers" ;;
    all) 
        show_top "-pcpu" "CPU Consumers"
        echo ""
        show_top "-pmem" "Memory Consumers"
        ;;
esac

echo -e "\n${GREEN}プロセスが表示されました${NC}"

全体の動きのシナリオ

  1. 初期設定: 出力する色や、デフォルトの表示件数(10件)などを決めます。
  2. 引数のチェック(モード切り替え): * スクリプト実行時にオプション(-c-m など)が指定された場合は、その設定に従います。
  • 何も指定せず実行した場合は「対話モード」が起動し、「どの情報を」「何件表示するか」を画面上で質問されます。
  1. 画面のクリア: ターミナルを一度綺麗にしてから測定結果を表示します。
  2. ハードウェア情報の表示: CPUの型番やコア数、メモリの使用量、ディスクの空き容量を順番に表示します。
  3. 高負荷プロセスの表示: 設定されたモード(CPU順、メモリ順、または両方)に合わせて、リソースを多く消費しているプロセスをランキング形式で色付き表示します。

📦 各関数の役割と解説

1. usage(ヘルプ表示)

使い方を間違えた時や、-h オプションを付けた時に、コマンドのオプション一覧を表示してスクリプトを終了します。

2. interactive_mode(対話設定モード)

引数なしで実行された時に呼ばれます。read コマンドを使ってユーザーに入力を促し、表示したいモード(全て/CPUのみ/メモリのみ)や、ランキングを何件表示するか(デフォルト10件)を動的に決定します。

3. show_cpu_info(CPU情報の表示)

Linuxのシステムファイル /proc/cpuinfo を読み込んで解析します。

  • CPUのモデル名、スレッド数(コア数)を取得します。
  • 周波数(MHz)を取得し、awk を使って計算し GHz 単位に変換して見やすく表示します。

4. show_memory_status(メモリとzRAMの状況)

  • free -h コマンドで、人間が見やすい単位(GBやMB)で物理メモリの空き状況を表示します。
  • zramctl コマンドがある環境(メモリ圧縮技術 zRAM が有効な環境)では、その圧縮ステータスも同時に表示します。

5. show_disk_status(ディスク使用量)

  • df -h コマンドでストレージの残量を表示します。
  • その際、-x tmpfs などを指定することで、メモリ上の仮想ファイルシステムを除外した「実際の物理ディスク(SSDやHDD)だけ」を狙って表示する工夫がされています。

6. show_top(プロセス監視のコア処理)

  • ps コマンドで現在動いているプロセスの一覧を、指定されたリソース(CPUまたはメモリ)の消費量が多い順に並び替えて取得します。
  • tailhead を使って、指定された件数(デフォルト10件)だけを切り出します。
  • awk を使ってデータを1行ずつ処理し、CPUまたはメモリの使用率が50%を超えていれば「赤」、10%を超えていれば「黄」、それ以下なら「緑」に文字色をリアルタイムに変化させて出力します。

スクリプトの使い方

実行する際のオプションによって、動きを切り替えることができます。

オプション実行例動き
(なし)./script.sh対話モード。画面の指示に従って入力する。
-c./script.sh -cCPU使用率の高いプロセスをトップ10で表示する。
-m./script.sh -mメモリ使用率の高いプロセスをトップ10で表示する。
-n [数値]./script.sh -c -n 5CPUの高いプロセスを上位5件だけ表示する。
-h./script.sh -hヘルプ(使い方の説明)を表示する。

LinuxサーバーやPCの健康状態を、コマンド一つでパッと色鮮やかに確認できる非常に実用的かつ便利なものに仕上げた自負があります。

BBC Newsの見出しを表示するシェルスクリプトの改修(メニューつき)

BBC NewsのRSSフィードを取得するスクリプトを改修しました。

改修した点

  • -m フラグの処理:
    • 引数に -m が指定された場合、インタラクティブモード(対話式)の関数を呼び出します。
  • セクションの番号選択:
    • 統合された全セクションを1から順にナンバリングして表示し、ユーザーが数字で選べるようにしました。
  • 件数の入力:
    • デフォルト値を提示しつつ、エンターを押すだけでデフォルト値(3件)が適用される親切設計にしています。

改修版スクリプト

#!/bin/bash

# デフォルト値の設定
default_section="world"
default_count=3

# メインセクションのリスト
main_sections=("world" "uk" "business" "politics" "health" "education" "science_and_environment" "technology" "entertainment_and_arts")

# グローバルセクションのリスト
global_sections=("africa" "asia" "europe" "latin_america" "middle_east" "us_and_canada")

# 全セクションのリストを統合
all_sections=("${main_sections[@]}" "${global_sections[@]}")

# --- [新規] 対話式メニュー関数 ---
interactive_menu() {
    echo "=== BBC News RSS セクション選択 ==="
    # セクション一覧を番号付きで表示
    for i in "${!all_sections[@]}"; do
        printf "%2d) %s\n" "$((i+1))" "${all_sections[$i]}"
    done

    # セクションの入力受付
    while true; do
        read -p "セクションの番号を選択してください (1-${#all_sections[@]}): " sec_num
        if [[ "$sec_num" =~ ^[0-9]+$ ]] && [ "$sec_num" -ge 1 ] && [ "$sec_num" -le "${#all_sections[@]}" ]; then
            section="${all_sections[$((sec_num-1))]}"
            break
        else
            echo "無効な入力です。正しい番号を入力してください。"
        fi
    done

    # 件数の入力受付
    read -p "表示する件数を入力してください (デフォルト: ${default_count}): " input_count
    if [[ "$input_count" =~ ^[0-9]+$ ]] && [ "$input_count" -gt 0 ]; then
        count=$input_count
    else
        count=$default_count
    fi
    echo "--------------------------------------------------"
}

# --- 引数の処理 ---
if [[ "$1" == "-m" ]]; then
    # 対話モードの起動
    interactive_menu
elif [[ "$1" =~ ^[0-9]+$ ]]; then
    section=$default_section
    count=$1
else
    section=${1:-$default_section} # 引数1が指定されていない場合はデフォルト値を使用
    count=${2:-$default_count}     # 引数2が指定されていない場合はデフォルト値を使用
fi

# 引数の短縮形を対応する正式名に変換 (通常モード用)
case "$section" in
    "usa" | "n-usa") section="us_and_canada" ;;
    "me") section="middle_east" ;;
    "latam" | "la") section="latin_america" ;;
    "eu") section="europe" ;;
    "science") section="science_and_environment" ;;
    "entertainment") section="entertainment_and_arts" ;;
    *) section=$section ;; # その他はそのまま
esac

# セクションの検証
if [[ ! " ${all_sections[@]} " =~ " ${section} " ]]; then
    echo "Error: Invalid section '${section}'. Valid sections are: ${all_sections[*]}"
    exit 1
fi

# URLの構築
if [[ " ${main_sections[@]} " =~ " ${section} " ]]; then
    url="https://feeds.bbci.co.uk/news/${section}/rss.xml"
else
    url="https://feeds.bbci.co.uk/news/world/${section}/rss.xml"
fi

# 最初に一度だけRSSフィードをダウンロードし、変数に格納する
xml_content=$(curl -s "$url")

# コンテンツが取得できなかった場合はエラー終了
if [ -z "$xml_content" ]; then
    echo "Error: No headlines found for section '${section}'. Please check the section name or try again later."
    exit 1
fi

# フィードの最終更新日時を取得し、フォーマットする
feed_date_raw=$(echo "$xml_content" | xmllint --xpath "string(//channel/lastBuildDate)" - 2>/dev/null)
if [ -n "$feed_date_raw" ]; then
    # JSTに変換して表示フォーマットを整える
    feed_date_formatted=$(date -d "$feed_date_raw" '+%Y/%m/%d %H:%M:%S %Z')
fi

# 見出しを取得
headlines=$(echo "$xml_content" | xmllint --xpath "//item/title/text()" - 2>/dev/null | sed -e 's/<!\[CDATA\[//g' -e 's/\]\]>//g' | head -n "$count")

# 見出しの表示
echo "BBC News - ${section} section (${count} headlines)"
# 取得した日付を表示
if [ -n "$feed_date_formatted" ]; then
    echo "As of: ${feed_date_formatted}"
fi
echo "--------------------------------------------------" #区切り線
echo "$headlines"

使い方

  1. 通常モード(従来通り)

引数をそのまま渡せば、セクションごとに件数を表示してくれます。

./bbc_headline.sh business 5
  1. 対話式メニューモード(新機能)

-m を付けて実行します。

./bbc_headline.sh -m

実行イメージ:

=== BBC News RSS セクション選択 ==
 1) world
 2) uk
...
15) us_and_canada
セクションの番号を選択してください (1-15): 3
表示する件数を入力してください (デフォルト: 3): 5
--------------------------------------------------
BBC News - business section (5 headlines)
...

graph TD A("スクリプト開始") --> B{"引数の判定"} %% 引数の分岐 B -->|"-m"| C["対話式メニュー関数 interactive_menu"] B -->|"数字"| D["セクション: デフォルト<br>件数: 引数1の数字"] B -->|"その他"| E["セクション: 引数1<br>件数: 引数2"] %% 対話モードの詳細 subgraph "対話モードの内部処理" C --> C1["セクション一覧を番号付きで表示"] C1 --> C2["ユーザーが番号を入力"] C2 --> C3{"有効な番号か?"} C3 -->|"No"| C2 C3 -->|"Yes"| C4["セクション名を確定"] C4 --> C5["ユーザーが表示件数を入力"] C5 --> C6{"数字が入力されたか?"} C6 -->|"Yes"| C7["入力された件数を採用"] C6 -->|"No"| C8["デフォルトの3件を採用"] end %% 合流後の共通処理 C7 --> F["短縮名の変換ケース文"] C8 --> F D --> F E --> F F --> G{"有効なセクション名か?"} G -->|"No"| H("エラーを表示して終了") G -->|"Yes"| I{"メインかグローバルか?"} %% URL構築 I -->|"メイン"| J["URL: news/セクション/rss.xml"] I -->|"グローバル"| K["URL: news/world/セクション/rss.xml"] %% 取得と出力 J --> L["curl で RSS XML をダウンロード"] K --> L L --> M{"データが空でないか?"} M -->|"No"| N("エラーを表示して終了") M -->|"Yes"| O["xmllint で最終更新日時を取得・JST変換"] O --> P["xmllint と head で見出しを抽出"] P --> Q["結果を画面に出力"] Q --> R("スクリプト終了") %% スタイルの調整 style C fill:#f9f,stroke:#333,stroke-width:2px style C1 fill:#fff2cc,stroke:#d6b656 style C2 fill:#fff2cc,stroke:#d6b656 style C5 fill:#fff2cc,stroke:#d6b656

このスクリプトは役に立つのか?

私が便利だと思ったから乗せています。実際、/etc/update-motdに仕込むことで、「今はこういう出来事が起こっている」をリアルタイムで知ることができるからです。

改良版・SSLの有効期限をチェックするRubyスクリプト。

サイトの命綱であるSSL証明書を簡単にチェックするRubyスクリプトを更に改良しました。

スクリプト内容

このRubyスクリプトの動きを解説してください

#!/usr/bin/env ruby

require 'openssl'
require 'socket'
require 'date'
require 'uri'
require 'timeout'
require 'net/http'

# ANSIカラーコード
COLORS = {
  red:    "\e[31m",
  yellow: "\e[33m",
  green:  "\e[32m",
  reset:  "\e[0m"
}.freeze

# 1. 引数のURL/ドメインを適切にパースするメソッド
def parse_to_uri(input)
  clean_input = input.strip
  url = clean_input.match?(%r{\Ahttps?://}) ? clean_input : "https://#{clean_input}"
  URI.parse(url)
rescue URI::InvalidURIError
  nil
end

# 2. リダイレクトを追跡して最終的なURIを返すメソッド
def fetch_effective_uri(uri, limit = 5)
  return nil if limit == 0

  response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
    http.open_timeout = 5
    http.read_timeout = 5
    http.head(uri.path.empty? ? '/' : uri.path)
  end

  case response
  when Net::HTTPRedirection
    redirect_uri = URI.join(uri, response['location'])
    fetch_effective_uri(redirect_uri, limit - 1)
  else
    uri
  end
rescue => e
  nil
end

# 3. 証明書の有効期限を取得するメソッド(エラー箇所を修正)
def fetch_certificate_expiry(uri)
  Timeout.timeout(5) do
    # ポートが明示されていない場合は、スキーマがhttpsなら443、それ以外なら80にする
    port = uri.port || (uri.scheme == 'https' ? 443 : 80)

    # TCPSocket.open はブロックを渡すと自動クローズしてくれます
    TCPSocket.open(uri.host, port) do |tcp_socket|
      ctx = OpenSSL::SSL::SSLContext.new
      
      # .open ではなく .new を使用し、ブロックの代わりに ensure でクローズする形に修正
      ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ctx)
      ssl_socket.hostname = uri.host
      
      begin
        ssl_socket.connect
        cert = ssl_socket.peer_cert
        
        expiration_date = cert.not_after.to_date
        days_remaining = (expiration_date - Date.today).to_i

        [expiration_date, days_remaining]
      ensure
        ssl_socket.close rescue nil
      end
    end
  end
rescue Timeout::Error
  [nil, "サーバーへの接続がタイムアウトしました。"]
rescue => e
  [nil, e.message]
end

# 4. 結果を表示するメソッド
def print_result(uri, expiration_date, days_remaining)
  if expiration_date
    formatted_date = expiration_date.strftime("%Y/%m/%d")
    
    color = case days_remaining
            when ...14 then COLORS[:red]
            when ...30 then COLORS[:yellow]
            else            COLORS[:green]
            end

    puts "サイト #{uri} の有効期限は #{formatted_date} です。#{color}残り #{days_remaining} 日です。#{COLORS[:reset]}"
  else
    puts "#{COLORS[:red]}サイト #{uri} の証明書取得に失敗しました: #{days_remaining}#{COLORS[:reset]}"
  end
end

# メイン処理
def main
  inputs = ARGV.empty? ? [print("チェックしたいサイト(ドメイン/URL)を入力してください: "), gets].last.to_s : ARGV

  inputs.each do |input|
    next if input.strip.empty?

    uri = parse_to_uri(input)
    if uri.nil?
      puts "#{COLORS[:red]}無効な入力です: #{input}#{COLORS[:reset]}"
      next
    end

    puts "Checking: #{uri} ..."
    
    final_uri = fetch_effective_uri(uri)

    if final_uri.nil?
      puts "#{COLORS[:red]}サイト #{uri} にアクセスできませんでした。#{COLORS[:reset]}"
      next
    end

    unless final_uri.scheme == 'https'
      puts "#{COLORS[:yellow]}最終遷移先がHTTPSではないため、スキップします: #{final_uri}#{COLORS[:reset]}"
      next
    end

    expiration_date, days_remaining = fetch_certificate_expiry(final_uri)
    print_result(final_uri, expiration_date, days_remaining)
  end
end

main if __FILE__ == $PROGRAM_NAME

スクリプトの使い方

このスクリプトを実行するには、Rubyがインストールされた環境(MacのターミナルやLinux、Windowsのコマンドプロンプトなど)が必要です。

ステップ1: ファイルに保存する

上記のコードをコピーし、任意の場所にファイルとして保存します。

  • ファイル名(例): check_cert.rb

ステップ2: スクリプトを実行する

使い方は「引数でまとめて指定する」か「実行後に入力する」の2パターンあります。

  • パターンA:引数でドメインを指定して実行(おすすめ)

調べたいサイトをスペース区切りで並べて一気に実行できます。URLでもドメインだけでもOKです。

ruby check_cert.rb google.com github.com https://rubygems.org
  • パターンB:対話形式で実行

引数を何も渡さずに実行すると、画面上で入力を求められます。

ruby check_cert.rb

実行すると チェックしたいサイト(ドメイン/URL)を入力してください: と表示されるので、そこに yahoo.co.jp などを入力してEnterを押します。

実際の動き(出力のイメージ)

スクリプトを実行すると、内部で通信が行われ、結果がターミナルに以下のように表示されます。残り日数に応じて自動で文字に色がつくため、一目で状況がわかります。

Checking: https://google.com ...
サイト https://google.com の有効期限は 2026/07/15 です。残り 50 日です。(←緑色で表示)

Checking: https://example.com ...
サイト https://example.com の有効期限は 2026/06/05 です。残り 10 日です。(←赤色で表示)

Checking: http://http-only-site.com ...
最終遷移先がHTTPSではないため、スキップします: http://http-only-site.com (←黄色で表示)

Checking: https://invalid-domain-xyz.com ...
サイト https://invalid-domain-xyz.com の証明書取得に失敗しました: Failed to open TCP connection to invalid-domain-xyz.com:443(←赤色で表示)

エラーハンドリングの動き

  • リダイレクトがある場合:
    • http://github.com と入力しても、自動的に https://github.com へ転送(リダイレクト)されたことを検知し、最終的なHTTPSのページに対して証明書をチェックします。
  • 接続できない場合:
    • ドメインが間違っていたり、サーバーがダウンしている場合は、プログラムがクラッシュせずに「Failed to open TCP connection(接続に失敗しました)」とエラー理由を教えてくれます。

まとめというか本題

筆者はLinuxターミナルの/etc/update-motd.dに仕込むことで自サイトの証明書更新のタイミングを計っています。何せ、Let's Encryptはわずか90日。更に短くなるというのもほぼ確定しましていますから。

そして、筆者が、ここまで執拗にこれをチェックするのは「私怨」が本質。

自分をかなり追い詰めていき、体と心を大きく崩してしまった前の職場。その元職場が

  • 2020年代であるにもかかわらず常時SSL化していない

という、IT系会社にあるまじき状態を鼻で笑うためというのもこのスクリプト作成のきっかけ。

  • しかも、最近、屋台骨であるWebサービスの証明書が失効してタイムアウトになっている

という更にお察し状態なのはまた別のお話。

IPSetを効率運用するためのスクリプト

こちらのipsetによる防御。これを運用して分かったのが

  • /16や/8で切る狂気
  • 自分自身がロックアウトされるという恐怖

の2点。そこで、これを回避するためのスクリプトを改良しました。

#!/bin/bash

# セット名の定義
SET_NAME="ufw-blocklist"
SAVE_FILE="/etc/ufw/ipsets.save"

# 1. root権限チェック
if [[ $EUID -ne 0 ]]; then
   echo "エラー: このスクリプトは sudo または root 権限で実行してください。"
   exit 1
fi

# 保存用関数
save_ipset() {
    read -p "設定をファイルに保存しますか? (y/n): " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
        ipset save "$SET_NAME" -f "$SAVE_FILE"
        echo "保存完了: $SAVE_FILE"
    else
        echo "保存をスキップしました(メモリ上の設定のみ更新)。"
    fi
}

# ipsetが存在しない場合に作成
if ! ipset list "$SET_NAME" > /dev/null 2>&1; then
    echo "情報: $SET_NAME が見つからないため新規作成します。"
    if ipset create "$SET_NAME" hash:net; then
        echo "作成成功"
    else
        echo "エラー: ipsetの作成に失敗しました。"
        exit 1
    fi
fi

# フェイルセーフ関数:接続中の自分のIP(SSHポータ22)を遮断しようとしていないかチェック
is_ssh_connected_ip() {
    local target_ip=$1
    
    # 現在22番ポートでサスペンド/エスタブリッシュしている接続元のIPをすべて取得
    # (awkで不要なヘッダーやコロン以降のポート番号を削る)
    local my_ssh_ips=$(ss -nt state established sport = :22 | awk 'NR>1 {print $5}' | sed 's/:[^:]*$//' | sort -u)
    
    # テスト対象のターゲットからサブネット部分(/24など)を除外してIPのみにする
    local clean_target=$(echo "$target_ip" | cut -d'/' -f1)

    for ssh_ip in $my_ssh_ips; do
        # 1. 完全一致チェック
        if [[ "$ssh_ip" == "$clean_target" ]]; then
            return 0 # ロックアウトの危険あり
        fi

        # 2. サブネットマスクごとの巻き込みチェック
        if [[ "$target_ip" == */24 ]]; then
            local target_net=$(echo "$clean_target" | cut -d'.' -f1-3)
            local ssh_net=$(echo "$ssh_ip" | cut -d'.' -f1-3)
            if [[ "$target_net" == "$ssh_net" ]]; then
                return 0
            fi
        elif [[ "$target_ip" == */16 ]]; then
            local target_net=$(echo "$clean_target" | cut -d'.' -f1-2)
            local ssh_net=$(echo "$ssh_ip" | cut -d'.' -f1-2)
            if [[ "$target_net" == "$ssh_net" ]]; then
                return 0
            fi
        fi
    done

    return 1 # 安全
}

# IP/NWの形式を整形する関数
format_target() {
    local ip=$1
    echo "=== サブネット選択 ==="
    echo "1) そのまま登録 (${ip})"
    echo "2) /24 で登録 (${ip%.*}.0/24)"
    echo "3) /16 で登録 (${ip%.*.*}.0.0/16)"
    read -p "選択肢を選んでください [1-3]: " subnet_choice
    
    case $subnet_choice in
        2) echo "${ip%.*}.0/24" ;;
        3) echo "${ip%.*.*}.0.0/16" ;;
        *) echo "$ip" ;;
    esac
}

# メインループ
while true; do
    echo "------------------------------------------"
    echo " ipset 管理メニュー ($SET_NAME)"
    echo "------------------------------------------"
    echo "1) IP/ネットワークを追加"
    echo "2) IP/ネットワークを削除"
    echo "3) ステータス表示 (list)"
    echo "q) 終了 (Exit)"
    echo "------------------------------------------"
    read -p "番号を選択してください: " choice

    case $choice in
        1)
            echo "--- 追加モード ---"
            local added_any=false
            while true; do
                read -p "追加する IPアドレス: " raw_ip
                if [ -n "$raw_ip" ]; then
                    # サブネットの選択
                    target=$(format_target "$raw_ip")
                    
                    # フェイルセーフ発動チェック
                    if is_ssh_connected_ip "$target"; then
                        echo "【警告】現在あなたが接続しているSSH元IPが含まれているため、登録をブロックしました! ($target)"
                    else
                        if ipset add "$SET_NAME" "$target" 2>/dev/null; then
                            echo "追加成功: $target"
                            added_any=true
                        else
                            echo "エラー: 追加に失敗したか、既に登録されています。 ($target)"
                        fi
                    fi
                fi
                
                # 続けて入力するか確認
                read -p "続けて他のIPを入力しますか? (y/n): " continue_add
                if [[ ! "$continue_add" =~ ^[Yy]$ ]]; then
                    break
                fi
                echo "------------------------------------------"
            done
            
            # 追加が1件でもあれば一括で保存確認
            if [ "$added_any" = true ]; then
                save_ipset
            fi
            ;;
            
        2)
            echo "--- 削除(解除)モード ---"
            read -p "削除する IPアドレス: " raw_ip
            if [ -n "$raw_ip" ]; then
                target=$(format_target "$raw_ip")
                
                # 削除時も念のためセーフティ(既存の接続を誤って切らないようにするため)
                if is_ssh_connected_ip "$target"; then
                    echo "【警告】現在接続中のSSHセッションが含まれています。削除を中止します。 ($target)"
                else
                    if ipset del "$SET_NAME" "$target" 2>/dev/null; then
                        echo "削除成功: $target"
                        save_ipset
                    else
                        echo "エラー: 削除に失敗したか、リストに存在しません。 ($target)"
                    fi
                fi
            fi
            ;;
            
        3)
            echo "--- 現在のリスト ---"
            ipset list "$SET_NAME"
            ;;
            
        q)
            echo "終了します。"
            exit 0
            ;;
            
        *)
            echo "無効な選択です。"
            ;;
    esac
    echo ""
done

ステップ 1:スクリプトの配置と実行権限の付与

まずはスクリプトをサーバーに保存し、実行できるように権限を付与します。

  • スクリプトファイルの作成: 任意のディレクトリ
  • 任意の場所にファイルを作成します(例: blocklist-mgr.sh)。
  • 実行権限の付与: chmod コマンド
  • sudo chmod +x blocklist-mgr.sh を実行して、スクリプトに実行権限を与えます。
  • root権限で起動: sudo 必須
  • sudo ./blocklist-mgr.sh で起動します。

ステップ 2:メニュー画面の操作

起動すると、以下のような対話型メニューが表示されます。

```text

ipset 管理メニュー (ufw-blocklist)

1) IP/ネットワークを追加
2) IP/ネットワークを削除
3) ステータス表示 (list)

q) 終了 (Exit)

番号を選択してください:

1 IP/ネットワークを追加する場合(メニューで「1」を選択)

悪意のあるアクセス元IPを見つけたら、「1」を入力して追加モードに入ります。

  1. IPアドレスの入力: ブロックしたいIP(例: 192.168.1.50)を入力します。
  2. サブネット(範囲)の選択:以下のように範囲を聞かれるので、攻撃の規模に合わせて選択します。
    • 1(単一IPのみ)
    • 2(/24:周辺のIP 256台をまとめてブロック)
    • 3(/16:大規模なネットワークごとブロック)
  3. 連続入力: 「続けて他のIPを入力しますか? (y/n)」と聞かれるので、複数ある場合は y で続けて入力できます。
  4. ファイルへの永続化:

追加が終わると「設定をファイルに保存しますか? (y/n)」と聞かれます。ここで y を選ぶと、OS再起動後もブロックが有効になります。

もし間違えて「自分が今SSH接続しているIP(またはその所属サブネット)」を入力してしまった場合、スクリプトが自動でそれを検知し、

【警告】登録をブロックしました! と表示して処理を中断します。これにより、リモートワーク中にサーバーから締め出される悲劇を防ぎます。

2 ブロックを解除(削除)する場合(メニューで「2」を選択)

誤ってブロックしてしまったIPや、制限を解除したいIPがある場合は「2」を選択します。
追加時と同様にIPを入力し、サブネット範囲を選択すれば、リストから安全に削除されます。ここでも自分が接続中のIPを誤って削除(変更)しないようセーフティが働きます。

3 現在のブロックリストを確認する場合(メニューで「3」を選択)

「3」を選択すると、現在 ipset に登録されてリアルタイムでブロックされているIPの一覧がズラリと表示されます。正しく反映されているか確認したいときに便利です。

4 終了する場合(メニューで「q」を選択)

「q」を入力すると、安全にメニューを閉じます。

続:ipsetによるufwブロックの効率化スクリプト。

このスクリプトのver.2といったところです。

#!/bin/bash

# セット名の定義
SET_NAME="ufw-blocklist"
SAVE_FILE="/etc/ufw/ipsets.save"

# 1. root権限チェック
if [[ $EUID -ne 0 ]]; then
   echo "エラー: このスクリプトは sudo または root 権限で実行してください。"
   exit 1
fi

# 保存用関数 (変更があった場合のみ呼び出し)
confirm_and_save() {
    echo "------------------------------------------"
    read -p "変更をファイルに保存しますか? ($SAVE_FILE) (y/n): " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
        ipset save "$SET_NAME" -f "$SAVE_FILE"
        echo "保存完了しました。"
    else
        echo "保存をスキップしました。"
    fi
}

# ipsetが存在しない場合に作成
if ! ipset list "$SET_NAME" > /dev/null 2>&1; then
    echo "情報: $SET_NAME が見つからないため新規作成します。"
    ipset create "$SET_NAME" hash:net
fi

while true; do
    echo "=========================================="
    echo " ipset 管理メニュー ($SET_NAME)"
    echo "=========================================="
    echo "1) ネットワーク帯/IP を追加 (例: 192.0.2.0/24 203.0.113.5)"
    echo "2) ネットワーク帯/IP を削除"
    echo "3) 現在のリストを表示 (list)"
    echo "q) 終了"
    echo "------------------------------------------"
    read -p "番号を選択してください: " choice

    case $choice in
        1)
            echo "【追加モード】追加したいNW帯を入力してください(空エンターでメニューに戻る)"
            echo "例: 203.0.113.0/24 198.51.100.0/24"
            changed=false
            while true; do
                read -p "追加対象: " targets
                [ -z "$targets" ] && break
                
                for target in $targets; do
                    if ipset add "$SET_NAME" "$target" 2>/dev/null; then
                        echo "  [成功] $target を追加しました。"
                        changed=true
                    else
                        echo "  [失敗] $target は既にあるか、形式が不正です。"
                    fi
                done
            done
            [ "$changed" = true ] && confirm_and_save
            ;;
        2)
            echo "【削除モード】削除したいNW帯を入力してください(空エンターでメニューに戻る)"
            changed=false
            while true; do
                read -p "削除対象: " targets
                [ -z "$targets" ] && break
                
                for target in $targets; do
                    if ipset del "$SET_NAME" "$target" 2>/dev/null; then
                        echo "  [成功] $target を削除しました。"
                        changed=true
                    else
                        echo "  [失敗] $target が見つからないか、形式が不正です。"
                    fi
                done
            done
            [ "$changed" = true ] && confirm_and_save
            ;;
        3)
            echo "--- 現在の登録内容 ---"
            ipset list "$SET_NAME" | grep -A 100 "Members:"
            ;;
        q)
            echo "終了します。"
            exit 0
            ;;
        *)
            echo "無効な選択です。"
            ;;
    esac
    echo ""
done

試作版との違いは

  1. 連続入力に対応。ASNが持つNWを連続で断つことができます。
  2. 既にブロックされているNW/IPアドレスをチェックする機能を追加。

まだ詰められる感じなのでもう少し続けます。

試作版:ipsetによるufwブロックの効率化スクリプト。

攻撃者の種は尽きまじという形。

この、ufwの効率化を図るスクリプトを試作です。

前提条件

上記リンク先にあるとおり、ipsetをipset-persistant抜きで入れている場合。なので、相当、対象者は限られます。

スクリプト内容

#!/bin/bash

# セット名の定義
SET_NAME="ufw-blocklist"
SAVE_FILE="/etc/ufw/ipsets.save"

# 1. root権限チェック
if [[ $EUID -ne 0 ]]; then
   echo "エラー: このスクリプトは sudo または root 権限で実行してください。"
   exit 1
fi

# 保存用関数
save_ipset() {
    read -p "設定をファイルに保存しますか? (y/n): " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
        ipset save "$SET_NAME" -f "$SAVE_FILE"
        echo "保存完了: $SAVE_FILE"
    else
        echo "保存をスキップしました(メモリ上の設定のみ更新)。"
    fi
}

# ipsetが存在しない場合に作成
if ! ipset list "$SET_NAME" > /dev/null 2>&1; then
    echo "情報: $SET_NAME が見つからないため新規作成します。"
    ipset create "$SET_NAME" hash:net
fi

while true; do
    echo "------------------------------------------"
    echo " ipset 管理メニュー ($SET_NAME)"
    echo "------------------------------------------"
    echo "1) IP/ネットワークを追加 (例: 198.51.100.42, 203.0.113.0/24)"
    echo "2) IP/ネットワークを削除"
    echo "3) ステータス表示 (list)"
    echo "q) 終了"
    echo "------------------------------------------"
    read -p "番号を選択してください: " choice

    case $choice in
        1)
            echo "--- 追加モード ---"
            read -p "追加する IP/NW: " target
            if [ -n "$target" ]; then
                if ipset add "$SET_NAME" "$target"; then
                    echo "追加成功: $target"
                    save_ipset
                fi
            fi
            ;;
        2)
            echo "--- 削除モード ---"
            read -p "削除する IP/NW: " target
            if [ -n "$target" ]; then
                if ipset del "$SET_NAME" "$target"; then
                    echo "削除成功: $target"
                    save_ipset
                fi
            fi
            ;;
        3)
            echo "--- 現在のリスト ---"
            ipset list "$SET_NAME"
            ;;
        q)
            echo "終了します。"
            exit 0
            ;;
        *)
            echo "無効な選択です。"
            ;;
    esac
    echo ""
done

後は

sudo chmod 755 ipset-block.sh

等として実行権限を付与。

sudo bash ipset-block.sh

等で実行することで

------------------------------------------
 ipset 管理メニュー (ufw-blocklist)
------------------------------------------
1) IP/ネットワークを追加 (例: 198.51.100.42, 203.0.113.0/24)
2) IP/ネットワークを削除
3) ステータス表示 (list)
q) 終了
------------------------------------------

番号表示。

--- 追加モード ---
追加する IP/NW: 

で、IP/NWを設定すれば、後はセーブまでしてくれます。

今後の展望

  • 引数
  • ファイルからの一括入力
  • 既にあるリストとの突き合わせ

など。興味は尽きません。

GROWIスタックの運用を効率化するBashスクリプトの作成

1. はじめに

GROWI(Wikiシステム)をセルフホストする場合、本体の他にMongoDB、Elasticsearch、そしてリバースプロキシとしてのApacheなど、複数のサービスを管理する必要があります。これらを個別に手動で起動・停止するのは手間がかかり、手順ミスも起きやすくなります。

そこで、サービス間の依存関係を考慮し、安全かつ簡潔に管理するためのBashスクリプトを作成しました。

1-1. 筆者環境

  • Ubuntu 24.04
  • Growi (systemdにサービス登録済み)
  • Apacheのバーチャルホストでリバースプロキシ運用
  • mongodb
  • elasticsearch

2. スクリプトの主な特徴

  • 依存関係を考慮した起動順序:
    • DB → 検索エンジン(起動待ち含む) → Apache設定 → GROWI本体の順で確実に立ち上げます。
  • Apache設定の動的切り替え:
    • a2ensite / a2dissite を活用し、サービス停止時にはApache自体は止めず、GROWIへのアクセスパスのみをクリーンに遮断します。
  • 対話モードとコマンドライン引数の両対応:
    • 単体実行ではメニューが表示され、引数を渡せば自動実行が可能です。
  • エラーハンドリング: 各ステップで実行状態をチェックし、異常があれば即座に中断・通知します。

3. 管理スクリプト: manage-growi.sh

systemctlをいじるので、root権限で作っておきます。

#!/bin/bash

# ==============================================================================
# GROWIスタック管理スクリプト
# 対応サービス: MongoDB, Elasticsearch, Apache2, GROWI
# ==============================================================================

# --- [ 設定セクション ] ---
APACHE_CONF_NAME="growi.conf"
SITE_NAME="${APACHE_CONF_NAME%.conf}"
SERVICES=("mongod.service" "elasticsearch.service" "apache2.service" "growi.service")

# カラー出力設定
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# --- [ ユーティリティ関数 ] ---

log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_err()  { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
log_ok()   { echo -e "${GREEN}[SUCCESS]${NC} $1"; }

# 実行権限チェック
if [[ $EUID -ne 0 ]]; then
   log_err "このスクリプトの実行にはroot権限(sudo)が必要です。"
fi

# サービスの状態確認
is_active() {
    systemctl is-active --quiet "$1"
    return $?
}

# --- [ メイン機能 ] ---

start_services() {
    log_info "GROWIスタックの起動を開始します。"

    # 1. MongoDB
    log_info "データベース (MongoDB) を起動中..."
    systemctl start mongod.service
    is_active mongod.service || log_err "MongoDBの起動に失敗しました。"

    # 2. Elasticsearch
    log_info "検索エンジン (Elasticsearch) を起動中..."
    systemctl start elasticsearch.service
    # 応答待ち(最大75秒)
    for i in {1..15}; do
        if is_active elasticsearch.service; then break; fi
        [[ $i -eq 15 ]] && log_err "Elasticsearchの起動がタイムアウトしました。"
        sleep 5
    done

    # 3. Apache (設定の有効化)
    log_info "Apacheのリバースプロキシ設定を有効化しています..."
    if [ ! -L "/etc/apache2/sites-enabled/${APACHE_CONF_NAME}" ]; then
        a2ensite "${SITE_NAME}" > /dev/null
    fi
    systemctl reload apache2.service || systemctl start apache2.service

    # 4. GROWI 本体
    log_info "GROWI本体を起動中..."
    systemctl start growi.service
    sleep 3
    if is_active growi.service; then
        log_ok "すべてのサービスが正常に起動しました。"
    else
        log_err "GROWIの起動に失敗しました。ログを確認してください。"
    fi
}

stop_services() {
    log_info "GROWIスタックを停止します。"

    # サービス影響を最小限にするため、リバースプロキシから先に遮断
    log_info "GROWI本体を停止中..."
    systemctl stop growi.service

    log_info "Apacheの設定を無効化しています..."
    if [ -L "/etc/apache2/sites-enabled/${APACHE_CONF_NAME}" ]; then
        a2dissite "${SITE_NAME}" > /dev/null
        systemctl reload apache2.service
    fi

    log_info "バックエンドサービスを停止中..."
    systemctl stop elasticsearch.service
    systemctl stop mongod.service

    log_ok "すべての関連サービスが正常に停止しました。"
}

show_status() {
    echo -e "\n${YELLOW}--- サービス稼働状況 ---${NC}"
    printf "%-25s %s\n" "サービス名" "ステータス"
    printf "%-25s %s\n" "------------------------" "-------"
    for s in "${SERVICES[@]}"; do
        if is_active "$s"; then
            printf "%-25s ${GREEN}RUNNING${NC}\n" "$s"
        else
            printf "%-25s ${RED}STOPPED${NC}\n" "$s"
        fi
    done

    if [ -L "/etc/apache2/sites-enabled/${APACHE_CONF_NAME}" ]; then
        echo -e "Apache設定 (${APACHE_CONF_NAME}): ${GREEN}ENABLED${NC}"
    else
        echo -e "Apache設定 (${APACHE_CONF_NAME}): ${RED}DISABLED${NC}"
    fi
    echo ""
}

# --- [ エントリポイント ] ---
ACTION=${1:-"menu"}

case "$ACTION" in
    start)   start_services ;;
    stop)    stop_services ;;
    status)  show_status ;;
    restart) stop_services; start_services ;;
    menu)
        echo -e "${YELLOW}実行する操作を選択してください:${NC}"
        select opt in "Start" "Status" "Stop" "Restart" "Exit"; do
            case $opt in
                Start) start_services; break ;;
                Status) show_status; break ;;
                Stop) stop_services; break ;;
                Restart) stop_services; start_services; break ;;
                Exit) exit 0 ;;
                *) echo "無効な選択です。" ;;
            esac
        done
        ;;
    *)
        echo "Usage: $0 {start|stop|status|restart}"
        exit 1
        ;;
esac

4. 利用前の事前準備

スクリプトを使用するには、実行権限の付与と、システム上のファイル名の確認が必要です。

  1. 保存と実行権限の付与

ファイルを manage-growi.sh として保存した場合、以下のコマンドを実行します。

sudo chmod +x manage-growi.sh
  1. 設定の確認(重要)

スクリプト内の APACHE_CONF_NAME="growi.conf" が、実際に /etc/apache2/sites-available/ に存在するファイル名と一致しているか確認してください。

5.使い方(コマンド一覧)

このスクリプトは root権限(sudo) が必要です。

A. 対話型メニューで操作する

引数なしで実行すると、メニューが表示されます。

sudo ./growi-stack.sh

B. 直接コマンドを指定する

特定の操作を即座に実行したい場合に使用します。

  • 起動: sudo ./growi-stack.sh start
  • 停止: sudo ./growi-stack.sh stop
  • 状態確認: sudo ./growi-stack.sh status
  • 再起動: sudo ./growi-stack.sh restart

6. 表示・実行例

ステータス確認時の表示 (status)

現在の各サービスの稼働状況が一覧で表示されます。

--- サービス稼働状況 ---
サービス名                  ステータス
------------------------   -------
mongod.service             RUNNING
elasticsearch.service      RUNNING
apache2.service            RUNNING
growi.service              RUNNING
Apache設定 (growi.conf): ENABLED

起動処理の実行例 (start)

依存関係を考慮し、DBから順に起動していきます。

[INFO] GROWIスタックの起動を開始します。
[INFO] データベース (MongoDB) を起動中...
[INFO] 検索エンジン (Elasticsearch) を起動中...
[INFO] Apacheのリバースプロキシ設定を有効化しています...
[INFO] GROWI本体を起動中...
[SUCCESS] すべてのサービスが正常に起動しました。

7. スクリプトの主な特徴と処理順序

このスクリプトは、単なる一括起動ではなく、GROWIの依存関係を考慮した設計になっています。

順序起動時 (Start)停止時 (Stop)理由
1MongoDBGROWI本体DBがないとアプリが落ちるため / 停止は入り口から。
2ElasticsearchApache設定無効検索エンジンを準備。
3Apache設定有効Elasticsearchインフラが整ってから外部公開。
4GROWI本体MongoDB最後にアプリを立ち上げる。

8. 運用のヒント

  • トラブルシューティング: もし [ERROR] が出た場合は、個別のログを確認してください。
  • GROWIのログ: journalctl -u growi.service -f
  • Elasticsearchのログ: journalctl -u elasticsearch.service -f
  • 自動起動設定: サーバー再起動時に自動で立ち上げたい場合は、このスクリプトを使わず systemctl enable [サービス名] を各サービスに設定するのが一般的です。このスクリプトは、メンテナンス時の手動メンテナンス用として最適です。

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 この引数を表示。

Page 1 of 7

Powered by WordPress & Theme by Anders Norén