T-CREATOR

Shell Script でインフラ初期構築の自動化:ユーザー作成・SSH 設定・FW ルール

Shell Script でインフラ初期構築の自動化:ユーザー作成・SSH 設定・FW ルール

新しいサーバーを立ち上げるたびに、ユーザーを作成して、SSH の鍵を設定して、ファイアウォールのルールを追加して……と、同じ作業を繰り返していませんか。

インフラの初期構築は、セキュリティや運用の基盤を作る重要な工程です。しかし手作業で行うと、設定ミスや作業漏れのリスクが高まりますし、複数台のサーバーを構築する際には時間もかかってしまいます。そこで今回は、Shell Script を使ってインフラ初期構築を自動化する方法をご紹介します。

本記事では、ユーザー作成・SSH 設定・ファイアウォールルールの設定という、インフラ構築の基本的な 3 つの作業を自動化するスクリプトを、段階的に解説していきますね。

背景

サーバー初期構築で行う基本作業

サーバーを新規に立ち上げた際、セキュリティと運用のために必ず行うべき初期設定があります。

まず、root ユーザーでの直接ログインを避けるため、管理用の一般ユーザーを作成します。次に、パスワード認証ではなく公開鍵認証で SSH 接続できるように設定し、セキュリティを強化する必要があります。そして、不要なポートへのアクセスを遮断するため、ファイアウォールのルールを設定します。

これらの作業は、1 台のサーバーであれば手作業でも対応できるでしょう。しかし、開発環境・ステージング環境・本番環境と複数のサーバーを構築する場合や、スケールアウトで頻繁にサーバーを追加する場合には、手作業では効率が悪くなってしまいます。

インフラ構築の基本フロー

以下の図は、サーバー初期構築で行う基本的な作業の流れを示しています。

mermaidflowchart TD
  start["サーバー起動"] --> user["管理ユーザー作成"]
  user --> sudo["sudo 権限付与"]
  sudo --> ssh["SSH 公開鍵設定"]
  ssh --> sshd["SSH 設定変更<br/>(パスワード認証無効化)"]
  sshd --> fw["ファイアウォール<br/>ルール設定"]
  fw --> service["サービス有効化"]
  service --> done["構築完了"]

この図からもわかるように、初期構築には複数のステップがあり、それぞれが依存関係を持っています。

作業の順序を間違えると、SSH 接続ができなくなったり、セキュリティホールが生まれたりする可能性があります。そのため、確実に同じ手順を実行できる仕組みが求められるのです。

課題

手作業によるインフラ構築の問題点

手作業でインフラを構築する場合、以下のような課題が発生します。

設定ミスのリスクが最も大きな問題でしょう。コマンドの打ち間違いや設定ファイルの編集ミスは、誰にでも起こり得ます。特に SSH の設定を誤ると、サーバーにログインできなくなってしまうこともあります。

作業時間の増大も無視できません。1 台あたり 30 分の作業でも、10 台のサーバーを構築すれば 5 時間かかります。その間、エンジニアは単純作業に時間を取られてしまいますね。

再現性の欠如も深刻です。作業者が異なると設定内容にばらつきが生まれ、「開発環境では動くのに本番環境では動かない」といった問題が発生しやすくなります。

ドキュメントの陳腐化にも注意が必要です。手順書を作成しても、実際の作業と乖離していくことがよくあります。手順書の更新を忘れたり、作業者が独自の判断で手順を変更したりすると、ドキュメントの信頼性が失われてしまうでしょう。

手作業と自動化の比較

以下の図は、手作業と自動化のアプローチを比較したものです。

mermaidflowchart LR
  subgraph manual["手作業のアプローチ"]
    m1["手順書を見る"] --> m2["コマンド入力"]
    m2 --> m3["設定ファイル編集"]
    m3 --> m4["動作確認"]
    m4 -->|エラー| m2
    m4 -->|成功| m5["次のサーバーへ"]
  end

  subgraph auto["自動化のアプローチ"]
    a1["スクリプト実行"] --> a2["自動で全設定"]
    a2 --> a3["ログ記録"]
    a3 --> a4["完了"]
  end

手作業では各ステップで人間の判断と入力が必要になりますが、自動化すればスクリプト実行だけで全ての設定が完了します。

この違いは、複数のサーバーを構築する際に特に顕著になるのです。

解決策

Shell Script による自動化のメリット

Shell Script を使った自動化には、以下のようなメリットがあります。

冪等性の確保が可能になります。スクリプトを何度実行しても同じ結果が得られるため、安心して再実行できますね。

作業の標準化も実現できます。誰が実行しても同じ設定が適用されるため、環境ごとの差異がなくなります。

時間の大幅な削減も期待できるでしょう。30 分かかっていた作業が数分で完了するようになります。

ドキュメントとしての役割も果たします。スクリプト自体が最新の手順書となるため、ドキュメントの陳腐化を防げます。

自動化スクリプトの処理フロー

本記事で作成する自動化スクリプトの全体的な処理フローを示します。

mermaidflowchart TD
  start["スクリプト開始"] --> check["実行環境チェック<br/>(root 権限確認)"]
  check -->|権限なし| error["エラー終了"]
  check -->|権限あり| var["変数・設定読み込み"]
  var --> createUser["ユーザー作成処理"]
  createUser --> setupSSH["SSH 設定処理"]
  setupSSH --> setupFW["FW ルール設定処理"]
  setupFW --> verify["設定確認"]
  verify --> log["ログ出力"]
  log --> finish["完了"]

各処理は独立した関数として実装し、エラーが発生した場合は適切にハンドリングします。

このような構造にすることで、保守性が高く、拡張しやすいスクリプトになるのです。

スクリプト設計の基本方針

自動化スクリプトを作成する際には、以下の方針で設計します。

#方針理由
1関数単位で処理を分割可読性と再利用性の向上
2エラーハンドリングの徹底異常終了時の原因特定が容易
3ログ出力の充実トラブルシューティングが可能
4冪等性の確保何度実行しても安全
5変数による設定の外部化環境ごとのカスタマイズが容易

これらの方針に従うことで、運用環境でも安心して使えるスクリプトが作成できます。

具体例

全体構成とディレクトリ構造

まず、スクリプトのディレクトリ構造を以下のように構成します。

bash# ディレクトリ構造の例
/opt/server-setup/
├── setup.sh              # メインスクリプト
├── config.sh             # 設定ファイル
├── functions/
│   ├── user.sh          # ユーザー作成関数
│   ├── ssh.sh           # SSH 設定関数
│   └── firewall.sh      # FW 設定関数
└── logs/                 # ログ出力ディレクトリ

このような構成にすることで、各機能を独立して管理でき、メンテナンスが容易になります。

機能ごとにファイルを分割することで、特定の機能だけを修正したい場合にも対応しやすくなりますね。

設定ファイルの作成

まず、環境ごとに変更が必要な設定値を外部化するための設定ファイルを作成します。

bash#!/bin/bash
# config.sh - 設定ファイル

# 作成するユーザー名
ADMIN_USER="admin"

# ユーザーの初期パスワード(初回ログイン後に変更を促す)
INITIAL_PASSWORD="ChangeMe123!"

ユーザー名と初期パスワードを変数として定義します。

初期パスワードは強制的に変更させる設定を後ほど追加しますので、一時的なものとして扱います。

bash# SSH 公開鍵(実際の公開鍵に置き換えてください)
SSH_PUBLIC_KEY="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... user@example.com"

# SSH のポート番号
SSH_PORT="22"

# 許可する SSH 接続元 IP(空の場合は制限なし)
ALLOWED_SSH_IP=""

SSH の公開鍵は、実際に使用する鍵に置き換える必要があります。

接続元 IP を指定することで、特定の IP からのみ SSH 接続を許可できるようになります。

bash# ファイアウォールで許可するポート
ALLOWED_PORTS=("22" "80" "443")

# ログファイルのパス
LOG_DIR="/opt/server-setup/logs"
LOG_FILE="${LOG_DIR}/setup_$(date +%Y%m%d_%H%M%S).log"

許可するポートは配列で定義し、後から追加や削除が簡単にできるようにします。

ログファイルには日時を含めることで、複数回実行した際のログを区別できるようにしています。

ユーザー作成スクリプト

次に、管理ユーザーを作成する関数を実装します。

bash#!/bin/bash
# functions/user.sh - ユーザー作成関数

# ユーザーが存在するかチェックする関数
user_exists() {
    local username=$1
    id "${username}" &>/dev/null
    return $?
}

この関数は、指定されたユーザーが既に存在するかをチェックします。

id コマンドの終了コードで判定し、存在すれば 0、存在しなければ 1 を返しますね。

bash# 管理ユーザーを作成する関数
create_admin_user() {
    local username=$1
    local password=$2

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ユーザー作成を開始: ${username}"

    # ユーザーが既に存在する場合はスキップ
    if user_exists "${username}"; then
        echo "[INFO] ユーザー ${username} は既に存在します"
        return 0
    fi

ユーザー作成関数では、まず既存ユーザーのチェックを行います。

既に存在する場合はスキップすることで、冪等性を確保しています。

bash    # ユーザー作成(ホームディレクトリも作成)
    if useradd -m -s /bin/bash "${username}"; then
        echo "[SUCCESS] ユーザー ${username} を作成しました"
    else
        echo "[ERROR] ユーザー作成に失敗: ${username}"
        return 1
    fi

useradd コマンドでユーザーを作成します。

-m オプションでホームディレクトリを作成し、-s ​/​bin​/​bash でデフォルトシェルを bash に設定していますね。

bash    # パスワード設定
    echo "${username}:${password}" | chpasswd

    # 初回ログイン時にパスワード変更を強制
    passwd -e "${username}"

    echo "[SUCCESS] パスワードを設定しました(初回ログイン時に変更が必要)"
}

パスワードは chpasswd コマンドで設定し、passwd -e で初回ログイン時の変更を強制します。

これにより、デフォルトパスワードのままで運用されるリスクを回避できます。

bash# sudo 権限を付与する関数
grant_sudo_access() {
    local username=$1
    local sudoers_file="/etc/sudoers.d/${username}"

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] sudo 権限付与を開始: ${username}"

    # sudoers ファイルが既に存在する場合はスキップ
    if [[ -f "${sudoers_file}" ]]; then
        echo "[INFO] ${username} の sudo 設定は既に存在します"
        return 0
    fi

sudo 権限を付与する関数では、​/​etc​/​sudoers.d​/​ ディレクトリを使用します。

既存のファイルをチェックすることで、重複実行を防ぎます。

bash    # sudo 権限を付与(パスワードなしで sudo 実行可能)
    echo "${username} ALL=(ALL) NOPASSWD:ALL" > "${sudoers_file}"

    # ファイルのパーミッションを設定(440 は必須)
    chmod 440 "${sudoers_file}"

    # 設定ファイルの構文チェック
    if visudo -c -f "${sudoers_file}"; then
        echo "[SUCCESS] sudo 権限を付与しました"
        return 0
    else
        echo "[ERROR] sudoers ファイルの構文エラー"
        rm -f "${sudoers_file}"
        return 1
    fi
}

sudoers ファイルを作成し、パーミッションを 440 に設定します。

visudo -c で構文チェックを行い、エラーがあれば作成したファイルを削除することで、システムの安全性を保っていますね。

SSH 設定スクリプト

SSH の設定を行う関数を実装します。

bash#!/bin/bash
# functions/ssh.sh - SSH 設定関数

# SSH ディレクトリと authorized_keys を設定する関数
setup_ssh_key() {
    local username=$1
    local public_key=$2
    local user_home=$(eval echo ~${username})
    local ssh_dir="${user_home}/.ssh"
    local auth_keys="${ssh_dir}/authorized_keys"

SSH 鍵を設定する関数では、まずユーザーのホームディレクトリと SSH ディレクトリのパスを取得します。

eval echo ~${username} を使うことで、任意のユーザーのホームディレクトリを正確に取得できます。

bash    echo "[$(date '+%Y-%m-%d %H:%M:%S')] SSH 鍵設定を開始: ${username}"

    # .ssh ディレクトリが存在しない場合は作成
    if [[ ! -d "${ssh_dir}" ]]; then
        mkdir -p "${ssh_dir}"
        echo "[INFO] .ssh ディレクトリを作成しました"
    fi

    # 適切なパーミッションを設定(700 は必須)
    chmod 700 "${ssh_dir}"
    chown "${username}:${username}" "${ssh_dir}"

.ssh ディレクトリを作成し、パーミッションを 700 に設定します。

このパーミッション設定は SSH のセキュリティ要件として必須ですので、必ず実行する必要がありますね。

bash    # 公開鍵が既に登録されているかチェック
    if [[ -f "${auth_keys}" ]] && grep -q "${public_key}" "${auth_keys}"; then
        echo "[INFO] 公開鍵は既に登録されています"
        return 0
    fi

    # 公開鍵を追加
    echo "${public_key}" >> "${auth_keys}"

    # authorized_keys のパーミッション設定(600 は必須)
    chmod 600 "${auth_keys}"
    chown "${username}:${username}" "${auth_keys}"

    echo "[SUCCESS] SSH 公開鍵を設定しました"
}

公開鍵の重複登録を防ぎ、authorized_keys ファイルのパーミッションを 600 に設定します。

所有者も適切に設定することで、他のユーザーから読み取られることを防いでいます。

bash# sshd_config を設定する関数
configure_sshd() {
    local ssh_port=$1
    local sshd_config="/etc/ssh/sshd_config"
    local sshd_config_backup="${sshd_config}.backup_$(date +%Y%m%d_%H%M%S)"

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] SSH デーモン設定を開始"

    # 設定ファイルのバックアップ
    cp "${sshd_config}" "${sshd_config_backup}"
    echo "[INFO] 設定ファイルをバックアップしました: ${sshd_config_backup}"

SSH デーモンの設定を変更する前に、必ずバックアップを取ります。

何か問題が発生した場合に元の状態に戻せるようにすることは、とても重要ですね。

bash    # パスワード認証を無効化
    sed -i 's/^#\?PasswordAuthentication yes/PasswordAuthentication no/' "${sshd_config}"
    sed -i 's/^#\?PasswordAuthentication no/PasswordAuthentication no/' "${sshd_config}"

    # root ログインを無効化
    sed -i 's/^#\?PermitRootLogin yes/PermitRootLogin no/' "${sshd_config}"
    sed -i 's/^#\?PermitRootLogin prohibit-password/PermitRootLogin no/' "${sshd_config}"

sed コマンドで設定ファイルを書き換えます。

パスワード認証と root ログインを無効化することで、セキュリティを大幅に向上させています。

bash    # 公開鍵認証を有効化
    sed -i 's/^#\?PubkeyAuthentication no/PubkeyAuthentication yes/' "${sshd_config}"

    # SSH ポート番号の設定(デフォルトの 22 以外の場合のみ)
    if [[ "${ssh_port}" != "22" ]]; then
        sed -i "s/^#\?Port 22/Port ${ssh_port}/" "${sshd_config}"
        echo "[INFO] SSH ポートを ${ssh_port} に変更しました"
    fi

公開鍵認証を有効にし、必要に応じてポート番号も変更します。

デフォルトポートを変更することで、自動化された攻撃のリスクを軽減できるでしょう。

bash    # 設定ファイルの構文チェック
    if sshd -t; then
        echo "[SUCCESS] SSH 設定が完了しました"
        # SSH サービスを再起動(設定を反映)
        systemctl restart sshd
        echo "[INFO] SSH サービスを再起動しました"
        return 0
    else
        echo "[ERROR] SSH 設定ファイルに構文エラーがあります"
        # バックアップから復元
        cp "${sshd_config_backup}" "${sshd_config}"
        echo "[INFO] 設定ファイルをバックアップから復元しました"
        return 1
    fi
}

設定変更後は必ず sshd -t で構文チェックを行います。

エラーがあればバックアップから復元することで、SSH 接続ができなくなる事態を防いでいます。

ファイアウォール設定スクリプト

ファイアウォールのルールを設定する関数を実装します。この例では ufw(Uncomplicated Firewall)を使用します。

bash#!/bin/bash
# functions/firewall.sh - ファイアウォール設定関数

# ufw がインストールされているかチェックする関数
check_ufw_installed() {
    if ! command -v ufw &>/dev/null; then
        echo "[ERROR] ufw がインストールされていません"
        echo "[INFO] インストールコマンド: apt install ufw"
        return 1
    fi
    return 0
}

まず、ufw がインストールされているかをチェックします。

コマンドが見つからない場合は、インストール方法を表示してエラー終了しますね。

bash# ファイアウォールの基本設定を行う関数
setup_firewall_defaults() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ファイアウォール基本設定を開始"

    # デフォルトポリシーを設定(受信拒否、送信許可)
    ufw default deny incoming
    ufw default allow outgoing

    echo "[SUCCESS] デフォルトポリシーを設定しました"
}

ファイアウォールのデフォルトポリシーを設定します。

受信は拒否、送信は許可というのが一般的なセキュリティポリシーです。

bash# 指定されたポートを許可する関数
allow_ports() {
    local ports=("$@")

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ポート許可設定を開始"

    for port in "${ports[@]}"; do
        # 既に許可されているかチェック
        if ufw status | grep -q "${port}"; then
            echo "[INFO] ポート ${port} は既に許可されています"
            continue
        fi

        # ポートを許可
        ufw allow "${port}"
        echo "[SUCCESS] ポート ${port} を許可しました"
    done
}

指定されたポートを順番に許可していきます。

既に許可されているポートはスキップすることで、冪等性を確保しています。

bash# 特定 IP からの SSH 接続のみ許可する関数
restrict_ssh_by_ip() {
    local ssh_port=$1
    local allowed_ip=$2

    # IP 指定がない場合は制限なし
    if [[ -z "${allowed_ip}" ]]; then
        echo "[INFO] SSH 接続元 IP の制限は設定されません"
        return 0
    fi

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] SSH 接続元 IP 制限を設定"

    # 特定 IP からの SSH 接続を許可
    ufw allow from "${allowed_ip}" to any port "${ssh_port}" proto tcp

    echo "[SUCCESS] ${allowed_ip} からの SSH 接続を許可しました"
}

SSH 接続を特定の IP アドレスからのみ許可する設定です。

IP が指定されていない場合は制限を設けず、柔軟に対応できるようにしています。

bash# ファイアウォールを有効化する関数
enable_firewall() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ファイアウォール有効化を開始"

    # ufw が既に有効かチェック
    if ufw status | grep -q "Status: active"; then
        echo "[INFO] ファイアウォールは既に有効です"
        return 0
    fi

    # 確認なしで有効化(--force オプション)
    ufw --force enable

    # サービスの自動起動を有効化
    systemctl enable ufw

    echo "[SUCCESS] ファイアウォールを有効化しました"
}

ファイアウォールを有効化し、システム起動時に自動的に起動するよう設定します。

--force オプションを使うことで、対話式の確認をスキップして自動化できますね。

bash# ファイアウォールの状態を表示する関数
show_firewall_status() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ファイアウォールの状態"
    echo "================================"
    ufw status verbose
    echo "================================"
}

最後に、設定されたファイアウォールの状態を表示します。

この出力をログに記録することで、後から設定内容を確認できるようにしています。

メインスクリプト

各機能を統合したメインスクリプトを作成します。

bash#!/bin/bash
# setup.sh - インフラ初期構築メインスクリプト

set -e  # エラーが発生したら即座に終了
set -u  # 未定義の変数を使用したらエラー
set -o pipefail  # パイプ内のエラーも検出

# スクリプトのディレクトリを取得
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

メインスクリプトの冒頭で、エラー処理を厳格にする設定を行います。

set -e でエラー時に即座に終了し、set -u で未定義変数の使用を防ぎ、set -o pipefail でパイプ処理のエラーも検出します。

bash# 設定ファイルと関数ファイルを読み込み
source "${SCRIPT_DIR}/config.sh"
source "${SCRIPT_DIR}/functions/user.sh"
source "${SCRIPT_DIR}/functions/ssh.sh"
source "${SCRIPT_DIR}/functions/firewall.sh"

# ログディレクトリが存在しない場合は作成
if [[ ! -d "${LOG_DIR}" ]]; then
    mkdir -p "${LOG_DIR}"
fi

設定ファイルと各関数ファイルを読み込みます。

ログディレクトリが存在しない場合は作成することで、初回実行でもエラーにならないようにしていますね。

bash# ログファイルへの出力関数
log() {
    echo "$@" | tee -a "${LOG_FILE}"
}

# エラーメッセージ出力関数
error_exit() {
    log "[ERROR] $1"
    log "[ERROR] スクリプトを終了します"
    exit 1
}

ログ出力用の関数を定義します。

tee -a を使うことで、標準出力とログファイルの両方に出力できるようにしています。

bash# root 権限チェック
check_root_permission() {
    if [[ $EUID -ne 0 ]]; then
        error_exit "このスクリプトは root 権限で実行する必要があります"
    fi
    log "[INFO] root 権限を確認しました"
}

# メイン処理開始
main() {
    log "========================================"
    log "インフラ初期構築スクリプト開始"
    log "実行日時: $(date '+%Y-%m-%d %H:%M:%S')"
    log "========================================"

メイン処理の開始時に、ログにヘッダー情報を出力します。

実行日時を記録することで、複数のログファイルがあっても識別しやすくなります。

bash    # 1. root 権限チェック
    check_root_permission

    # 2. ユーザー作成
    log ""
    log "## ステップ 1: ユーザー作成"
    create_admin_user "${ADMIN_USER}" "${INITIAL_PASSWORD}" | tee -a "${LOG_FILE}"
    grant_sudo_access "${ADMIN_USER}" | tee -a "${LOG_FILE}"

ユーザー作成処理を実行します。

各ステップの開始時にログに見出しを出力することで、後からログを見たときにどの処理で問題が発生したかを特定しやすくしています。

bash    # 3. SSH 設定
    log ""
    log "## ステップ 2: SSH 設定"
    setup_ssh_key "${ADMIN_USER}" "${SSH_PUBLIC_KEY}" | tee -a "${LOG_FILE}"
    configure_sshd "${SSH_PORT}" | tee -a "${LOG_FILE}"

SSH の設定処理を実行します。

公開鍵の設定と SSH デーモンの設定を順番に行っていますね。

bash    # 4. ファイアウォール設定
    log ""
    log "## ステップ 3: ファイアウォール設定"

    # ufw のインストールチェック
    if ! check_ufw_installed; then
        error_exit "ufw をインストールしてから再実行してください"
    fi

    setup_firewall_defaults | tee -a "${LOG_FILE}"
    allow_ports "${ALLOWED_PORTS[@]}" | tee -a "${LOG_FILE}"
    restrict_ssh_by_ip "${SSH_PORT}" "${ALLOWED_SSH_IP}" | tee -a "${LOG_FILE}"
    enable_firewall | tee -a "${LOG_FILE}"
    show_firewall_status | tee -a "${LOG_FILE}"

ファイアウォールの設定を行います。

各関数を順番に呼び出し、最後に状態を表示することで、設定が正しく適用されたことを確認できます。

bash    # 5. 完了メッセージ
    log ""
    log "========================================"
    log "インフラ初期構築が完了しました"
    log "完了日時: $(date '+%Y-%m-%d %H:%M:%S')"
    log "========================================"
    log ""
    log "次のステップ:"
    log "1. 作成したユーザーで SSH 接続できることを確認してください"
    log "   ssh ${ADMIN_USER}@<サーバーIP>"
    log "2. 接続確認後、初回ログイン時にパスワード変更が求められます"
    log "3. ログファイル: ${LOG_FILE}"
}

# メイン処理実行
main "$@"

メイン処理の最後に、次に行うべきアクションを表示します。

ユーザーが次に何をすべきかを明示することで、初期構築後の手順が明確になりますね。

スクリプトの実行方法

作成したスクリプトを実行する手順を説明します。

bash# 1. スクリプトディレクトリの作成
sudo mkdir -p /opt/server-setup/functions
sudo mkdir -p /opt/server-setup/logs

# 2. スクリプトファイルの配置
# 各ファイルを適切なディレクトリに配置します

まず、スクリプトを配置するディレクトリを作成します。

​/​opt ディレクトリは、サードパーティのソフトウェアやスクリプトを配置する標準的な場所です。

bash# 3. 実行権限の付与
sudo chmod +x /opt/server-setup/setup.sh
sudo chmod +x /opt/server-setup/functions/*.sh

# 4. 設定ファイルの編集
sudo vi /opt/server-setup/config.sh
# ユーザー名、SSH 公開鍵などを環境に合わせて編集

実行権限を付与し、設定ファイルを環境に合わせて編集します。

特に SSH 公開鍵は、実際に使用する鍵に必ず置き換える必要がありますね。

bash# 5. スクリプトの実行
sudo /opt/server-setup/setup.sh

# 6. ログの確認
sudo tail -f /opt/server-setup/logs/setup_*.log

スクリプトを実行し、ログをリアルタイムで確認します。

エラーが発生した場合は、ログファイルを確認して原因を特定できます。

エラー処理とトラブルシューティング

スクリプト実行時に発生する可能性のあるエラーと、その対処法を説明します。

Error 1: Permission denied

エラーコード: Permission denied

エラーメッセージ:

bash[ERROR] このスクリプトは root 権限で実行する必要があります

発生条件: スクリプトを root 権限なしで実行した場合

解決方法:

  1. sudo を付けてスクリプトを実行する
  2. または sudo su - で root ユーザーに切り替えてから実行する
bash# 解決方法 1: sudo を使用
sudo /opt/server-setup/setup.sh

# 解決方法 2: root に切り替え
sudo su -
/opt/server-setup/setup.sh

Error 2: SSH service restart failed

エラーコード: Job for sshd.service failed

エラーメッセージ:

bash[ERROR] SSH 設定ファイルに構文エラーがあります
Job for sshd.service failed because the control process exited with error code.

発生条件: sshd_config ファイルの構文エラー

解決方法:

  1. バックアップファイルから自動的に復元されます
  2. 手動で確認する場合は以下のコマンドを実行
bash# 構文チェック
sudo sshd -t

# エラー箇所の確認
sudo sshd -T

# バックアップから手動復元(必要な場合)
sudo cp /etc/ssh/sshd_config.backup_* /etc/ssh/sshd_config
sudo systemctl restart sshd

Error 3: UFW not found

エラーコード: Command not found: ufw

エラーメッセージ:

bash[ERROR] ufw がインストールされていません
[INFO] インストールコマンド: apt install ufw

発生条件: ufw がインストールされていないシステム

解決方法:

  1. Ubuntu/Debian 系の場合
bash# ufw のインストール
sudo apt update
sudo apt install ufw -y

# スクリプトの再実行
sudo /opt/server-setup/setup.sh
  1. CentOS/RHEL 系の場合は firewalld を使用
bash# firewalld のインストール
sudo yum install firewalld -y

# スクリプトの functions/firewall.sh を
# firewalld 用に書き換える必要があります

動作確認とテスト

スクリプトが正しく動作したかを確認する方法を説明します。

bash# 1. ユーザーが作成されたか確認
id admin

# 期待される出力例:
# uid=1001(admin) gid=1001(admin) groups=1001(admin)

ユーザーが正しく作成されているかを確認します。

uid と gid が表示されれば、ユーザー作成は成功していますね。

bash# 2. sudo 権限があるか確認
sudo -l -U admin

# 期待される出力例:
# User admin may run the following commands on hostname:
#     (ALL) NOPASSWD: ALL

sudo 権限が正しく付与されているかを確認します。

(ALL) NOPASSWD: ALL と表示されれば、パスワードなしで sudo が使用できます。

bash# 3. SSH 公開鍵が設定されているか確認
sudo cat /home/admin/.ssh/authorized_keys

# 4. SSH 設定ファイルを確認
sudo grep -E "PasswordAuthentication|PermitRootLogin|PubkeyAuthentication" /etc/ssh/sshd_config

# 期待される出力:
# PasswordAuthentication no
# PermitRootLogin no
# PubkeyAuthentication yes

SSH の設定が正しく適用されているかを確認します。

パスワード認証が無効化され、公開鍵認証が有効化されていることが重要ですね。

bash# 5. ファイアウォールの状態確認
sudo ufw status verbose

# 期待される出力例:
# Status: active
# Logging: on (low)
# Default: deny (incoming), allow (outgoing), disabled (routed)
# New profiles: skip
#
# To                         Action      From
# --                         ------      ----
# 22                         ALLOW IN    Anywhere
# 80                         ALLOW IN    Anywhere
# 443                        ALLOW IN    Anywhere

ファイアウォールのルールが正しく設定されているかを確認します。

指定したポートが許可されていることを確認しましょう。

bash# 6. 実際に SSH 接続をテスト(別のターミナルから)
ssh -i ~/.ssh/id_rsa admin@<サーバーIP>

# 接続できたら成功
# 初回ログイン時にパスワード変更が求められます

最後に、実際に SSH 接続できることを確認します。

接続テスト中は、元の SSH セッションを閉じないようにしてくださいね。接続できなかった場合に元に戻せなくなってしまいます。

まとめ

Shell Script を使ったインフラ初期構築の自動化について、ユーザー作成・SSH 設定・ファイアウォールルールの 3 つの観点から解説しました。

手作業で行っていた初期構築を自動化することで、設定ミスの防止作業時間の大幅な削減環境の標準化 が実現できます。特に複数のサーバーを管理する場合や、頻繁にサーバーを構築する環境では、その効果は絶大でしょう。

本記事で紹介したスクリプトは、以下の特徴を持っています。

#特徴効果
1冪等性の確保何度実行しても安全
2エラーハンドリング問題発生時に自動復旧
3詳細なログ出力トラブルシューティングが容易
4関数による構造化保守性と拡張性が高い
5設定の外部化環境ごとのカスタマイズが簡単

これらの自動化スクリプトは、そのまま使うこともできますし、環境に合わせてカスタマイズすることもできます。例えば、Docker のインストールや、監視エージェントのセットアップなど、他の初期構築作業も同じ構造で追加できますね。

インフラの初期構築を自動化することは、単なる作業効率化だけでなく、セキュリティの向上運用品質の安定化にもつながります。ぜひ、本記事のスクリプトを参考に、皆さんの環境に合わせた自動化を実現してみてください。

関連リンク