debian に SqlServer for linux をインストールしようとしてハマった話（解決済み）

Linux でデータベースといえば、MariaDB や PostgreSQL があるのですが、それでもわざわざ SqlServer を入れたくなったので、

[クイック スタート:Ubuntu に SQL Server をインストールし、データベースを作成する](
https://learn.microsoft.com/ja-jp/sql/linux/quickstart-install-connect-ubuntu?view=sql-server-ver16)

を見ながらインストールしようとしたのですが、インストール後のセットアップで謎のエラーでハマってしまったものの、なんとか解決したので、備忘録として書いておきます。

# どこでハマったか

apt-get によるバイナリのインストールは終わりました。
その後の setup を行うコマンドを実行した際です。

```bash:セットアップのためのコマンド
root# sudo /opt/mssql/bin/mssql-conf setup
```

で、表示されたエラーが次の通り。

```bash: エラー
Warning: could not create log file for mssql-conf at /var/opt/mssql/log/mssql-conf/mssql-conf.log.
```

確認したところ、記載されている mssql-conf ディレクトリは存在してましたし、所有者も mssql ユーザになっていました。

# トラブルシュートサイトを確認する

Microsoft 社のサイトに、トラブルシュートのページがありました。

[Python モジュールを使用した mssql-conf セットアップの "モジュール オブジェクトに属性の実行がありません" というエラー](https://learn.microsoft.com/ja-jp/troubleshoot/sql/linux/mssql-conf-setup-fails-with-python-module-error)

Microsoft SQL Server 2019 の話ですが、似ているので読んでみます。
Python 3 が必要だそうですが、自分の環境には入っていました。

Python 2.x も入っているので、念のため優先順位を設定しておきました。

```bash:
root# update-alternatives  --install /usr/bin/python python /usr/bin/python2.7 1
root# update-alternatives  --install /usr/bin/python python /usr/bin/python3 2
root# update-alternatives --config python
alternative python (/usr/bin/python を提供) には 2 個の選択肢があります。

  選択肢    パス              優先度  状態
------------------------------------------------------------
* 0            /usr/bin/python3     2         自動モード
  1            /usr/bin/python2.7   1         手動モード
  2            /usr/bin/python3     2         手動モード
```

しかしエラーは消えません。
次にソースコードを追いかけます。

# インストーラでは何が実行されているのか

mssql-conf の中身を見てみます。

```txt:/opt/mssql/bin/mssql-conf
#!/bin/bash
# Don't generate .pyc files
export PYTHONDONTWRITEBYTECODE=1

CMD="exec $(dirname $0)/../lib/mssql-conf/mssql-conf.py $@"

# Handle all permissions checks and file creations internally
# This allows for all error messages to be localized
/bin/bash -c "$CMD"
```

/opt/lib/mssql-conf/mssql-conf.py を呼んでいるようです。こちらを見てみます。
結構、長いのでエラーの箇所を調べます。

```python:mssql-conf.py
def initializeLoggingFile(filename=defaultLogFile, logLevel=defaultLogLevel):
    """ Sets up the python logging framework to output logs to the defaultLogFile.
        These logs are rotated at 2MB with the 10 latest of these logs kept on disk.
        Any logging that is done before this function is called will likely not be captured anywhere.
        Unlike initializeLogging(), this function does not require args to be setup initialized.
        It is recommended to use this function for unit tests since it can specify an alternative file for logging.
    """
    filename = os.path.abspath(filename)
    rootLogger = logging.getLogger()
    rootLogger.setLevel(logLevel)

    # This is necessary mainly for tests when multiple test
    # modules are run in a single python instance.
    #
    if filename in logFileHandlers:
        # This will actually turn off the disable that we may have turned on earlier
        # since we pass NOTSET.
        #
        logging.disable(logging.NOTSET)
        fileHandler = logFileHandlers[filename]
        fileHandler.setLevel(logLevel)
        logger.info("Not initializing [%s] as log file because it is already initialized.", filename)
        return

    try:
        ensureLogFileExists(filename)
        fileHandler = logging.handlers.RotatingFileHandler(filename, maxBytes=2 * 1000 * 1000, backupCount=2)
        fileHandler.setFormatter(defaultLogFormatter)
        fileHandler.setLevel(logLevel)
        rootLogger.addHandler(fileHandler)
        logging.disable(logging.NOTSET)
        logFileHandlers[filename] = fileHandler
    except (OSError, LogFileAbsentException):
        logger.exception("Logging to file resulted in exception")
        print(_("Warning: could not create log file for mssql-conf at %s.") % filename)
```

ここの try: からの行のどこかでエラーが出ているようです。１行ずつコメントアウトをして反応をみると、どうも ensureLogFileExists 関数で例外が出ているようです。

すぐ下で定義されていました。

```ptyhon:mssql-conf.py
def ensureLogFileExists(fullFileName=defaultLogFile):
    """ Checks to see if the log file exists yet or not as well as doing the same for the mssql-conf log directory.
        However, if we cannot ensure this (either due to an exception or if /var/opt/mssql/log doesn't exist),
        LogFileAbsentException will be thrown.
    """
    try:
        # Check to see that mssql-server is installed. This is necessary because those contain the parent
        # directories for the mssql-conf log file. Don't run the checkInstall check though if testing since
        # we use a local file in that case.
        #
        if fullFileName == defaultLogFile and not mssqlconfhelper.checkInstall(runAsRoot=mssqlconfhelper.checkSudo()):
            raise LogFileAbsentException("mssql-server is not correctly installed.")
        directory = os.path.dirname(fullFileName)
        mssqlLogDir = os.path.dirname(directory)

        if not os.path.isdir(mssqlLogDir):
            raise LogFileAbsentException("The parent directory of the mssql-conf logs, {0}, does not exist.".format(mssqlLogDir))
        elif os.path.exists(directory) and not os.path.isdir(directory):
            raise LogFileAbsentException("Expected {0} to be a directory but it already exists and is NOT a directory.".format(directory))
        elif os.path.exists(fullFileName) and not os.path.isfile(fullFileName):
            raise LogFileAbsentException("Expected {0} to be a log file but it already exists and is NOT a normal file.".format(fullFileName))

        if not os.path.exists(directory):
            # When we create the directory, it is not guaranteed that permissions will be
            # set by os.mkdir. Therefore, follow it up with a chmod to get into a good state.
            # It is not worth setting file ownership at this step since we do that elsewhere
            # and that does not have the same security implications as having too lenient of
            # permissions.
            #
            os.mkdir(directory, logFileDirectoryPermissions)
            os.chmod(directory, logFileDirectoryPermissions)

        if not os.path.exists(fullFileName):
            pathObj = pathlib.Path(fullFileName)
            pathObj.touch(logFilePermissions)
    except Exception as e:
        if isinstance(e, LogFileAbsentException):
            raise
        else:
            raise LogFileAbsentException("Unexpected error occurred while ensuring log file's existence.") from e
```

また １行ずつコメントアウトして様子をみると、mssqlconfhelper.checkInstall(runAsRoot=mssqlconfhelper.checkSudo() でエラーが出ているようです。追いかけます。mssqlconfhelper.py で定義されているようです。

```python:mssqlconfhelper.py
def checkInstall(runAsRoot=True):
    """Checks installation of SQL Server

    Returns:
        True if there are no problems, False otherwise
    """

    return runScript(checkInstallScript, runAsRoot) == 0
```

ここでもチェックしていきます。どうやら、checkInstallScript というファイルを実行する際に失敗しているようです。

このcheckInstallScriptは、ファイル前半で定義されていました。

```python:mssqlconfhelper.py
checkInstallScript = directoryOfScript + "/checkinstall.sh"
```

どうやら、/opt/mssql/lib/mssql-conf/checkinstall.sh というファイルがそれのようです。

ためしに、直接 checkinstall.sh をたたいてみます。

```bash
root# /opt/mssql/lib/mssql-conf/checkinstall.sh
./lib/mssql-conf/checkinstall.sh: 行 40: usermod: コマンドが見つかりません
```

おっと。エラーがでました。usermod が必要のようです。
みたところ /usr/sbin/usermod としてインストールはされていましたが、PATH は通っていませんでした。
※root で直でログインしていたら実行できてたのです。

# そして解決へ

/etc/profile にある PATH の設定に /usr/sbin を足します。

```:/etc/profile 抜粋
if [ "`id -u`" -eq 0 ]; then
  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
else
  # PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games" こちらをコメントアウト
  PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin" # こちらを追加
fi
export PATH
```

/etc/profile を source コマンドで有効にして、あらためて setup コマンドを実行します。

```bash
root# source /etc/profile
root# /opt/mssql/bin/mssql-conf setup
SQL Server のエディションを選択します:
  1) Evaluation (無料、製品使用権なし、期限 180 日間)
  2) Developer (無料、製品使用権なし)
  3) Express (無料)
  4) Web (有料)
  5) Standard (有料)
  6) Enterprise (有料) - CPU core utilization restricted to 20 physical/40 hyperthreaded
  7) Enterprise Core (有料) - CPU core utilization up to Operating System Maximum
  8) 小売販売チャネルを介してライセンスを購入し、入力するプロダクト キーを持っています。
  9) Standard (Billed through Azure) - Use pay-as-you-go billing through Azure.
 10) Enterprise Core (Billed through Azure) - Use pay-as-you-go billing through Azure.

エディションの詳細については、以下を参照してください
https://go.microsoft.com/fwlink/?LinkId=2109348&clcid=0x411

このソフトウェアの有料エディションを使用するには、個別のライセンスを以下から取得する必要があります
Microsoft ボリューム ライセンス プログラム。
有料エディションを選択することは、
このソフトウェアをインストールおよび実行するための適切な数のライセンスがあることを確認していることになります。
By choosing an edition billed Pay-As-You-Go through Azure, you are verifying
that the server and SQL Server will be connected to Azure by installing the
management agent and Azure extension for SQL Server.

エディションを入力してください(1-10):
```

setup コマンドが起動しました！