§まえがき

この文書は、2024年時点の作業内容をまとめたものです。

§サーバで提供される機能一覧

§1. 公開鍵認証での SSH 接続

パスワード認証でのログインを禁止します。

§2. Emacs の導入

Emacs を利用した手順書になっています。

§3. RDBMS の導入

MySQL 最新版を使える状態にします。

§4. 各種プログラミング言語の導入

C, C++, Java, Perl, PHP, Ruby, Python, Go, Node を使える状態にします。

§5. Apache - HTTP/HTTPS の応答

Apache 最新版で HTTP/HTTPS を応答します。

VirtualHost でサブドメインに対応する URL の展開を容易にします。

SNI で複数のドメイン名に対する HTTPS の提供を可能にします。

§6. Let's Encrypt / tinydns の導入

Let's Encrypt の SSL サーバ証明書を自動発行できるようにします。

tinydns で権威 DNS サーバを構築し、DNS-01 チャレンジを可能にします。

ワイルドカード証明書の自動発行が可能になります。

§7. Postfix の導入

メールの送信処理に対応します。

独自ドメイン名でのメールの受信(転送)処理に対応します。

§DNS RRs の設定

host type value
example.org A 203.0.113.123
*.example.org A 203.0.113.123
example.org MX 10 mx.example.org
example.org TXT v=spf1 +mx -all
example.org CAA 0 issue "letsencrypt.org"
0 issuewild "letsencrypt.org"
0 iodef "mailto:yourmail@example.jp"
_acme-challenge.example.org NS ns.example.org

§OS再インストール

www99999ui.vs.sakura.ne.jp

§OSのインストール操作

§秘密鍵と公開鍵の作成

ssh-keygen -t ed25519 -C 'yourmail@example.jp'

§サーバ起動

§ssh アクセスする

ssh ubuntu@203.0.113.123
// sudo のパスワードを通しておく
sudo ls
passwd

§ユーザ作成

sudo adduser yourname
// input password

sudo gpasswd -a yourname sudo
sudo gpasswd -a yourname adm
Host yourname
  HostName 203.0.113.123
  User yourname
  IdentityFile ~/.ssh/id_ed25519

§公開鍵認証の設定

sudo cp -r /home/ubuntu/.ssh /home/yourname/.ssh
sudo chown -R yourname:yourname /home/yourname/.ssh

§apt 更新

sudo apt update
sudo apt list --upgradable
sudo apt upgrade
sudo reboot

§apt コマンド

// パッケージ検索
apt search PACKAGE

// インストール済みパッケージ検索
dpkg -l

// インストール
apt -y install PACKAGE

// アンインストール(purge は環境設定、auto-remove は依存パッケージ含む)
apt purge --auto-remove PACKAGE

§emacs 設定

sudo apt -y install emacs
mkdir ~/.emacs.d
curl -fsSL https://server.lavoscore.org/file/emacs/init.el -o ~/.emacs.d/init.el

// 一度 Emacs を開く
emacs ~/.emacs.d/init.el
// Emacs を抜ける
;; UTF-8 として扱う
;; prefer-coding-system は使わないことにする
;; https://github.com/takueof/.emacs.d/blob/master/init.el
; (prefer-coding-system 'utf-8)
; (setq coding-system-for-read 'utf-8)
; (setq coding-system-for-write 'utf-8)
(set-language-environment "Japanese")
(set-coding-system-priority 'utf-8)
(setq-default buffer-file-coding-system 'utf-8-unix)
;; macOS ONLY
(when (member system-type '(darwin))
  (set-keyboard-coding-system 'utf-8-unix)
  (set-selection-coding-system 'utf-8)
  (set-terminal-coding-system 'utf-8)
  (setq-default default-process-coding-system '(utf-8 . utf-8)))

;; emacs の自動バックアップによる ~ 付きのファイル生成を止める
(setq backup-inhibited t)
(setq delete-auto-save-files t)

;; インデントをソフトタブにする
(setq-default indent-tabs-mode nil)
(setq lisp-indent-offset 2)

;; シンボリックリンクの読み込みを許可
(setq vc-follow-symlinks t)
;; シンボリックリンク先のVCS内で更新が入った場合にバッファを自動更新
(setq auto-revert-check-vc-info t)

;; 空白類文字の可視化
(require 'whitespace)
(set-face-foreground 'whitespace-space "#ccc")
(set-face-background 'whitespace-space nil)
(set-face-bold-p 'whitespace-space t)
(set-face-foreground 'whitespace-tab "#333")
(set-face-background 'whitespace-tab nil)
(set-face-underline  'whitespace-tab t)
(setq whitespace-style '(face tabs tab-mark spaces space-mark))
(setq whitespace-space-regexp "\\(\x3000+\\)")
(setq whitespace-display-mappings
  '((space-mark ?\x3000 [?\□])
   (tab-mark ?\t [?\xBB ?\t])
   ))
(global-whitespace-mode 1) ;; 全角スペースを常に表示
(setq-default show-trailing-whitespace t) ;; 末尾のスペースを表示

;; emacs に行番号を表示する
(require 'linum)
(global-linum-mode t)
(setq linum-format "%4d: ")
(column-number-mode t)
(line-number-mode t)

;; f6 で行番号表示をトグルする
(global-set-key (kbd "<f6>") 'linum-mode)

;; [Ctrl + q] <tab> でハードタブを入力できるようにする
(global-set-key (kbd "<backtab>") (kbd "C-q <tab>"))

;; [Ctrl + z] で矩形選択編集を可能にする
(cua-mode t)
(setq cua-enable-cua-keys nil)
(global-set-key (kbd "C-z") 'cua-set-rectangle-mark)

;; M-x package-list-packages
;; M-x package-install PACKAGE_NAME
;; https://stackoverflow.com/questions/10092322/how-to-automatically-install-emacs-packages-by-specifying-a-list-of-package-name#10093312
;; list the repositories containing them
(setq package-archives '(("melpa-stable" . "https://stable.melpa.org/packages/")))
;; activate all the packages (in particular autoloads)
(package-initialize)

;; list the packages you want
(setq package-list '(editorconfig web-mode))
;; fetch the list of packages available
(unless package-archive-contents
  (package-refresh-contents))
;; install the missing packages
(dolist (package package-list)
  (unless (package-installed-p package)
    (package-install package)))

;; M-x package-install editorconfig
(require 'editorconfig)
(editorconfig-mode 1)

;; M-x package-install web-mode
(require 'web-mode)
(add-to-list 'auto-mode-alist '("\\.php\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))

;; web-mode で混在する言語のインデントを調整する
(defun my-web-mode-hook ()
  "Hooks for Web mode."
  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-css-indent-offset 2)
  (setq web-mode-code-indent-offset 2)
  (setq web-mode-attr-indent-offset 0)
  (setq web-mode-block-padding 0)
  )
(add-hook 'web-mode-hook  'my-web-mode-hook)

;; php-mode のインデントを調整する
(add-hook 'php-mode-hook
  (lambda ()
    (c-set-offset 'case-label' c-basic-offset)
    (c-set-offset 'arglist-intro' c-basic-offset)
    (c-set-offset 'arglist-cont-nonempty' c-basic-offset)
    (c-set-offset 'arglist-close' 0)
    ))

;; web-mode でのカラーリング
(custom-set-faces
  ;; custom-set-faces was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
  '(web-mode-comment-face ((t (:foreground "#ff0000"))))
  '(web-mode-doctype-face ((t (:foreground "#808099"))))
  '(web-mode-function-call-face ((t (:foreground "#60ccff"))))
  '(web-mode-function-name-face ((t (:foreground "#60ccff"))))
  '(web-mode-html-attr-equal-face ((t (:foreground "#ffffff"))))
  '(web-mode-html-attr-name-face ((t (:foreground "#b07000"))))
  '(web-mode-html-attr-value-face ((t (:foreground "#ff0066"))))
  '(web-mode-html-tag-bracket-face ((t (:foreground "#ffffff"))))
  '(web-mode-html-tag-face ((t (:foreground "#60ccff")))))

(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(package-selected-packages '(editorconfig web-mode)))
// rootにも設置する
sudo -i
mkdir ~/.emacs.d
ln -s /home/yourname/.emacs.d/init.el ~/.emacs.d/

// 一度 Emacs を開く
emacs ~/.emacs.d/init.el
// Emacs を抜ける

// root を抜ける
exit
sudo update-alternatives --config editor
// 3 (emacs) など好きなエディタを選択する

§.editorconfig の設置

sudo curl -fsSL https://server.lavoscore.org/file/editorconfig.txt -o /.editorconfig
# 上位ディレクトリの EditorConfig を無視する場合は true
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.html]
indent_style = space
indent_size = 2

[*.php]
indent_style = space
indent_size = 2

[*.{css,scss}]
indent_style = space
indent_size = 2

[*.js]
indent_style = space
indent_size = 2

§.profile 記述

emacs ~/.profile

// ファイル末尾に以下の内容を追記する
# [dir => Cyan, ln => Yellow, or => Blink-Red, mi => Blink-Red]
# or = Orphaned symlink (symlinkFrom), mi = Missing file (symlinkTo; displayed by `ls -l`)
# refs: `dircolors` or `man dir_colors`
export LS_COLORS="${LS_COLORS}di=01;36:ln=01;33:or=01;05;37;41:mi=01;05;37;41:"

#   "\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "
PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;35m\]\w\[\033[00m\]\$ "

PATH="$HOME/bin:$HOME/.local/bin:$PATH"

alias s='ls'
alias sl='ls'
alias qls='ls'
alias qll='ll'
alias qgit='git'
alias emasc='emacs'

# https://orangeclover.hatenablog.com/entry/20110201/1296511181
alias clear2="echo -e '\026\033c'"
// 一度ターミナルを抜けて、入り直す
exit

§git 設定

emacs ~/.gitconfig
[user]
        name = yourname
        email = yourname@example.org
[color]
        diff = auto
        status = auto
        branch = auto
        interactive = auto
        ui = auto
[alias]
        co           = checkout
        st           = status
        di           = diff
        diw          = diff --color-words='[[:alnum:]]+|[^[:space:]]'
        push-reset   = push -f origin HEAD:master
        amend        = commit --amend
        pick         = cherry-pick
        dic          = diff --cached
        ls           = ls-files
        up-assume    = update-index --assume-unchanged
        up-no-assume = update-index --no-assume-unchanged
        up-skip      = update-index --skip-worktree
        up-no-skip   = update-index --no-skip-worktree
[core]
        pager = less -+S
[push]
        default = simple
// root にも設置する
sudo ln -s /home/yourname/.gitconfig /root
cd /etc

sudo emacs .gitignore

// 以下の内容を .gitignore ファイルに記述する
.gitignore
/letsencrypt/
/tinydns/
/_tinydns/
/_ucspi-tcp/
sudo git init .
sudo git add .
sudo git commit -m "first commit"

§システムロケール

sudo apt -y install language-pack-ja manpages-ja
locale -a
C
C.UTF-8
POSIX
ja_JP.utf8
locale
LANG=C.UTF-8
LANGUAGE=
LC_CTYPE="C.UTF-8"
LC_NUMERIC="C.UTF-8"
LC_TIME="C.UTF-8"
LC_COLLATE="C.UTF-8"
LC_MONETARY="C.UTF-8"
LC_MESSAGES="C.UTF-8"
LC_PAPER="C.UTF-8"
LC_NAME="C.UTF-8"
LC_ADDRESS="C.UTF-8"
LC_TELEPHONE="C.UTF-8"
LC_MEASUREMENT="C.UTF-8"
LC_IDENTIFICATION="C.UTF-8"
LC_ALL=

§/etc/hostname 設定

sudo emacs /etc/hostname
example
sudo emacs /etc/hosts

// 以下の 3 行目を追加する
127.0.0.1 localhost
127.0.1.1 www99999ui.vs.sakura.ne.jp www99999ui
203.0.113.123 example.org example

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
cd /etc
sudo git add .
sudo git commit -m "/etc/host*"
sudo reboot

§公開鍵認証を可能にする

クライアントマシン側で生成した公開鍵を ~/.ssh/authorized_keys に設置すれば公開鍵認証が可能になる。

クライアント側では次のようなコマンドで秘密鍵と公開鍵を生成できる。

ssh-keygen -t ed25519 -C 'yourmail@example.jp'

RSA 2048 bit 鍵でもよいが、Ed25519 鍵が使える環境であれば Ed25519 鍵を使いたい。

クライアント側では ~/.ssh/id_rsa~/.ssh/id_ed25519 などがあれば自動的にそれが使われるが、明示的に使いたい場合は ssh -i /path/to/id_ed25519 のようにオプションを付与して秘密鍵のパスを指定するか、次のように ~/.ssh/config ファイルを記述すればよい。

Host example.org
    HostName        203.0.113.123
    User            yourname
    IdentityFile    ~/.ssh/id_ed25519

§ファイアウォールを有効にする

sudo ufw status numbered
sudo ufw default deny
sudo ufw allow ftp
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw allow domain
sudo ufw enable

// enable really? [y/n]

sudo ufw status numbered
cd /etc
sudo git add .
sudo git commit -m "ufw"

§ミドルウェア

sudo apt -y install apache2
sudo apt -y install mysql-server
sudo apt -y install build-essential libssl-dev libreadline-dev zlib1g-dev libffi-dev libyaml-dev

sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt -y install php8.3
sudo apt -y install php8.3-mysql php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-intl php8.3-imagick php8.3-zip
sudo apt -y install silversearcher-ag tree imagemagick zip
sudo apt list --upgradable
sudo apt upgrade

sudo reboot
cd /etc
sudo git add .
sudo git commit -m "middleware installed"
cd ~
emacs getcomposer.sh
#!/bin/sh

EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"

if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
then
    >&2 echo 'ERROR: Invalid installer checksum'
    rm composer-setup.php
    exit 1
fi

php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT
chmod +x getcomposer.sh
./getcomposer.sh
rm getcomposer.sh

mkdir ~/bin
mv composer.phar ~/bin/composer

§Java

sudo apt install openjdk-21-jdk

cd /etc
sudo git add .
sudo git commit -m "java"

§Ruby

git clone https://github.com/rbenv/rbenv.git ~/.rbenv
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

cd ~
emacs .profile
# https://qiita.com/kerupani129/items/77dd1e3390b53f4e97b2
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
// 一度ターミナルを抜けて、入り直す
exit
// インストール可能な Ruby のバージョンを確認
rbenv install -l

// メモリが十分あれば、2分程度で最新版がインストールできる
// 4GB あれば十分だが、もしかしたら 2GB でも足りるかもしれない
rbenv install 3.x.y
rbenv global 3.x.y
rbenv rehash

// 以下は、さくら 1G で Ubuntu 24.04 に ruby をインストールする手順
// 3.2.x, 3.3.x はメモリ不足でインストールに失敗するので
// 3.1.x をインストールする(インストールには 50 分近くかかるので注意)
// メモリが少ないので -j1 で並列処理をさせないようにしておく
// --disable-install-doc でドキュメントの生成をしないようにする
// ログは /tmp/ruby-build.20240630232944.38582.log のようなファイルで確認できる
MAKEOPTS="-j1" CONFIGURE_OPTS="--disable-install-doc" time rbenv install 3.1.x
rbenv global 3.1.x
rbenv rehash

§gem

gem update --system

§python

ln -s /usr/bin/python3 ~/bin/python

§go

git clone https://github.com/syndbg/goenv.git ~/.goenv
emacs ~/.profile
# https://qiita.com/locomotive/items/1c9461dc059b663b022f
# https://github.com/sonots/lltsv
export GOENV_ROOT=$HOME/.goenv
export PATH=$GOENV_ROOT/bin:$PATH
eval "$(goenv init -)"
// 一度ターミナルを抜けて、入り直す
exit
goenv install -l
goenv install 1.xx.yy
goenv versions
goenv global 1.xx.yy
goenv rehash
go install github.com/sonots/lltsv@latest

§node

git clone https://github.com/nodenv/nodenv.git ~/.nodenv
emacs ~/.profile
# https://qiita.com/masakuni-ito/items/1ea015eea03ef4b95c72
# https://hodalog.com/nodenv-install-no-such-command/
export PATH="$HOME/.nodenv/bin:$PATH"
eval "$(nodenv init -)"
// 一度ターミナルを抜けて、入り直す
exit
// このままでは nodenv install コマンドがないので node-build を入れる
mkdir -p "$(nodenv root)"/plugins
git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build

nodenv install -l
nodenv install ***
nodenv versions
nodenv global ***
nodenv rehash

§php.ini の設定

sudo emacs /etc/php/8.3/apache2/php.ini
 ; Maximum amount of memory a script may consume
 ; https://php.net/memory-limit
-memory_limit = 128M
+memory_limit = 256M ;;changed

 ; Its value may be 0 to disable the limit. It is ignored if POST data reading
 ; is disabled through enable_post_data_reading.
 ; https://php.net/post-max-size
-post_max_size = 8M
+post_max_size = 64M ;;changed

 ; Maximum allowed size for uploaded files.
 ; https://php.net/upload-max-filesize
-upload_max_filesize = 2M
+upload_max_filesize = 64M ;;changed

 ; Maximum number of files that can be uploaded via a single request
-max_file_uploads = 20
+max_file_uploads = 128 ;;changed

 [Date]
 ; Defines the default timezone used by the date functions
 ; https://php.net/date.timezone
-;date.timezone =
+date.timezone = Asia/Tokyo ;;changed
sudo emacs /etc/php/8.3/cli/php.ini
 [Date]
 ; Defines the default timezone used by the date functions
 ; https://php.net/date.timezone
-;date.timezone =
+date.timezone = Asia/Tokyo ;;changed
cd /etc
sudo git add .
sudo git commit -m "php.ini"

§MySQL

§MySQL 実行時のメモリ使用量を制限する

sudo emacs /etc/mysql/mysql.conf.d/mysqld.cnf
 # The following values assume you have at least 32M ram

 [mysqld]
+table_definition_cache=400
+
 #
 # * Basic Settings
 #
cd /etc
sudo git add .
sudo git commit -m "mysqld.cnf"

§MySQL を起動する

mysql --version

sudo systemctl enable mysql
systemctl status mysql

sudo mysql -u root

§MySQL 一般ユーザを作成する

// ユーザを作成する
mysql> CREATE USER mysqluser@localhost IDENTIFIED by 'YOUR_DB_PASSWORD';

// 権限を与える
mysql> GRANT ALL PRIVILEGES ON *.* TO mysqluser@localhost;

// MySQL を抜ける
mysql> quit

§権限を外す場合

// 権限を外す
mysql> REVOKE ALL PRIVILEGES ON *.* FROM mysqluser@localhost;

// 特定の DB のみに権限を与える場合
// この例では proj_ を接頭辞に持ち、その後ろに少なくとも 1 文字以上を持つ DB への権限が与えられる
// LIKE 句と同様に、ワイルドカード _ は任意の1文字であり % は任意の 0 文字以上の意味を持つ
mysql> GRANT ALL PRIVILEGES ON `proj\__%`.* TO mysqluser@localhost;

§存在するユーザの権限確認

mysql> SELECT Host, User FROM mysql.user;
mysql> SELECT * FROM mysql.user\G
mysql> SELECT * FROM mysql.db\G

§全データベースのバックアップとリストア

mysqldump -u mysqluser -p -x --all-databases > dbdump.sql
mysql -u mysqluser -p < dbdump.sql

§文字コード

mysql> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8mb3                    |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

§Web 公開用ディレクトリを作成する

// gweb グループを作成
// 自身を gweb に追加
// www-data (Apache) を gweb に追加(www-data は envvar で設定されている)

sudo groupadd gweb
sudo gpasswd -a yourname gweb
sudo gpasswd -a www-data gweb
// 一度ターミナルを抜けて、入り直す
exit
cd /var/www/
sudo mkdir hosts
cd hosts/

// hosts/ のグループを gweb に変更する
// グループに書き込み権限を与える
// グループに setgid を与える

sudo chgrp gweb .
sudo chmod g+w .
sudo chmod g+s .
mkdir web
cd /var/www/hosts/web/
mkdir -p www/public_html
emacs www/public_html/index.html

// hello とでも打って保存しておく

§Apache

ここでは以下の内容について作業する。

§モジュールの有効化

sudo apache2ctl -M
sudo a2enmod rewrite
sudo a2enmod vhost_alias
sudo a2enmod authz_groupfile
sudo a2enmod headers
sudo a2enmod include
sudo a2enmod expires
sudo a2enmod ssl
sudo a2enmod cgi

§security.conf の設定

sudo emacs /etc/apache2/conf-available/security.conf
 # Set to one of:  Full | OS | Minimal | Minor | Major | Prod
 # where Full conveys the most information, and Prod the least.
 #ServerTokens Minimal
-ServerTokens OS
+#ServerTokens OS
 #ServerTokens Full
+ServerTokens Prod

 # Set to "EMail" to also include a mailto: link to the ServerAdmin.
 # Set to one of:  On | Off | EMail
-#ServerSignature Off
-ServerSignature On
+ServerSignature Off
+#ServerSignature On

§dir.conf の設定

sudo emacs /etc/apache2/mods-enabled/dir.conf
 <IfModule mod_dir.c>
-       DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm
+       DirectoryIndex index.php index.cgi index.rb index.html
 </IfModule>

§envvars の設定

sudo emacs /etc/apache2/envvars
-export LANG=C
+# export LANG=C
+export LANG=ja_JP.UTF-8
 # Uncomment the following line to use the system default locale instead:
 #. /etc/default/locale

 export LANG
# CUSTOMIZE: set 002 to permit g+w for setgid directory
umask 002

§httpd.conf の設定をする - HTTP/HTTPS

sudo apt -y install cronolog

§includes 用ファイルを作成する

cd /etc/apache2/
sudo mkdir includes
cd includes/
sudo emacs make_includes.sh
#!/bin/bash

DOMAIN_APEX_LIST=(
    example.org
)

echo '  # Enable this if your want HSTS (recommended)
  # * 15768000 = 60 * 60 * 24 * 365 / 2 (= 0.5 year)
  Header always set Strict-Transport-Security "max-age=15768000"' > hsts.conf;

echo '  # Enable this if your want HSTS (recommended)
  # * 15768000 = 60 * 60 * 24 * 365 / 2 (= 0.5 year)
  Header always set Strict-Transport-Security "max-age=0"' > hsts_zero.conf;

echo '  <Directory /var/www/hosts/web>
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
  </Directory>' > http2https.conf

echo '  UseCanonicalName Off' > http.conf

for DOMAIN_APEX in ${DOMAIN_APEX_LIST[@]}; do
    echo "  UseCanonicalName Off
  # refs. https://wiki.mozilla.org/Security/Server_Side_TLS
  # refs. https://mozilla.github.io/server-side-tls/ssl-config-generator/
  SSLEngine on
  # SSLCertificateFile    /etc/ssl/certs/ssl-cert-snakeoil.pem
  # SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
  SSLCertificateFile      /etc/ssl/hosts/${DOMAIN_APEX}/cert.pem
  SSLCertificateKeyFile   /etc/ssl/hosts/${DOMAIN_APEX}/privkey.pem
  SSLCertificateChainFile /etc/ssl/hosts/${DOMAIN_APEX}/chain.pem
  # Intermediate configuration, tweak to your needs
  SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
  # SSLCipherSuite FIPS@STRENGTH:!aNULL:!eNULL
  SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256
  SSLHonorCipherOrder on
  SSLCompression off
  # 'SSLSessionTickets' directive (Available in httpd 2.4.11 and later)
  SSLSessionTickets off
  # OCSP Stapling, only in httpd 2.3.3 and later
  SSLUseStapling on
  SSLStaplingResponderTimeout 5
  SSLStaplingReturnResponderErrors off
  # Enable this if your want HSTS (recommended)
  # * 15768000 = 60 * 60 * 24 * 365 / 2 (= 0.5 year)
  # Header always set Strict-Transport-Security 'max-age=15768000'
  # * To disable HSTS. We are using not only HTTPS but HTTP." > "https_${DOMAIN_APEX}.conf"
done

echo '  LogLevel warn
  # ErrorLog ${APACHE_LOG_DIR}/error.log
  # CustomLog ${APACHE_LOG_DIR}/access.log ltsv
  ErrorLog "| /usr/bin/cronolog /var/log/apache2/logs/error_%Y_%m.log"
  CustomLog "| /usr/bin/cronolog /var/log/apache2/logs/access_%Y_%m.log" ltsv
  LogFormat "host:%h\ttime:%t\treq:%r\tstatus:%>s\tsize:%b\treferer:%{Referer}i\tua:%{User-Agent}i\ttaken:%D\tisbot:%{Isbot}e\tdos:%{SuspectDoS}e\tharddos:%{SuspectHardDoS}ecache:%{X-Cache}o" ltsv_legacy
  LogFormat "opt_time:%{%Y/%m/%d %H:%M:%S}t\tserver:%A\tdomain:%V\tpath:%U%q\thttps:%{HTTPS}e\tmethod:%m\tstatus:%>s\tprotocol:%H\tuser:%u\tident:%l\tsize:%b\tresponse_time:%D\tcookie:%{cookie}i\tset_cookie:%{Set-Cookie}o\ttime:%{%d/%b/%Y:%H:%M:%S %z}t\treferer:%{Referer}i\tagent:%{User-Agent}i\thost:%h" ltsv' > log.conf
sudo chmod +x make_includes.sh
sudo ./make_includes.sh

§httpd.conf を記述する

cd /etc/apache2/

sudo a2dissite 000-default
sudo emacs /etc/apache2/sites-available/001-original.conf
SSLStrictSNIVHostCheck off

############################################################
# default rule (for IP or default host request)
############################################################
<VirtualHost *:80>
  ServerName 203.0.113.123
  ServerAlias www99999ui.vs.sakura.ne.jp
  DocumentRoot /var/www/hosts/web/www/public_html
  Include includes/http.conf
  Include includes/log.conf
  <Location />
    Require all denied
  </Location>
</VirtualHost>

<VirtualHost *:443>
  ServerName 203.0.113.123
  ServerAlias www99999ui.vs.sakura.ne.jp
  DocumentRoot /var/www/hosts/web/www/public_html
  Include includes/https_example.org.conf
  Include includes/log.conf
  <Location />
    Require all denied
  </Location>
</VirtualHost>

############################################################
# ssl.example.org
############################################################
<VirtualHost *:80>
  ServerName ssl.example.org
  VirtualDocumentRoot /var/www/hosts/web/%-3+/public_html
  Redirect / https://ssl.example.org/
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName ssl.example.org
  VirtualDocumentRoot /var/www/hosts/web/%-3+/public_html
  Include includes/hsts.conf
  Include includes/https_example.org.conf
  Include includes/log.conf
</VirtualHost>

############################################################
# www.example.org, *.example.org
############################################################
<VirtualHost *:80>
  ServerName www.example.org
  ServerAlias *.example.org
  VirtualDocumentRoot /var/www/hosts/web/%-3+/public_html
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName www.example.org
  ServerAlias *.example.org
  VirtualDocumentRoot /var/www/hosts/web/%-3+/public_html
  Include includes/https_example.org.conf
  Include includes/log.conf
</VirtualHost>

############################################################
# example.org
############################################################
<VirtualHost *:80>
  ServerName example.org
  VirtualDocumentRoot /var/www/hosts/web/www/public_html
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName example.org
  VirtualDocumentRoot /var/www/hosts/web/www/public_html
  Include includes/https_example.org.conf
  Include includes/log.conf
</VirtualHost>

############################################################
# others
############################################################

# SSLStaplingCache
SSLStaplingCache shmcb:/var/run/ocsp(128000)

# Access denied to all directories (/var/www)
<Directory /var/www>
  Options FollowSymLinks
  AllowOverride None
  Require all denied
  # Access denied to dot-files such as .git/*, but excepts .well-known/*
  RedirectMatch 403 /\.(?!well-known/)
</Directory>

# Access granted to hosts directory (/var/www/hosts)
<Directory /var/www/hosts>
  Options FollowSymLinks
  AllowOverride All
  Require all granted
  # 403 Forbidden for non-existing sub-domains.
  RewriteEngine On
  RewriteCond %{DOCUMENT_ROOT} !-d
  RewriteRule ^ - [F]
</Directory>
sudo a2ensite 001-original
sudo apache2ctl configtest
// SSL サーバ証明書を用意していないのでここではエラーになる
cd /etc
sudo git add .
sudo git commit -m "apache"

§.htaccess への記述

# authAccess
#   NOTE: Execute the following command to create .htpasswd
#     $ htpasswd -c .htpasswd username
AuthUserFile /var/www/hosts/web/DIR_NAME/.htpasswd
AuthName "Authorization Required"
AuthType Basic
Require valid-user
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

§Let's Encrypt 証明書と tinydns の導入

§certbot インストール

sudo apt -y install snapd

sudo snap install --classic certbot

type certbot
certbot --version
cd /etc
sudo git add .
sudo git commit -m "certbot"

§tinydns インストール

cd /etc
sudo mkdir _tinydns
cd _tinydns

sudo apt -y install daemontools

sudo wget http://cr.yp.to/djbdns/djbdns-1.05.tar.gz
sudo tar xzf djbdns-1.05.tar.gz
cd djbdns-1.05/

sudo sh -c "echo 'gcc -O2 -include /usr/include/errno.h' > conf-cc"
sudo perl -i -pe "BEGIN{undef $/;} s@-d300000@-d400000@smg" tinydns-conf.c

sudo make
sudo make setup check

ls -la /usr/local/bin/ | grep tinydns

§tinydns 起動

sudo apt -y install svtools daemontools-run
sudo useradd tinydns
sudo useradd axfrdns
sudo useradd dnslog

sudo tinydns-conf tinydns dnslog /etc/tinydns $(hostname -I | ag -o '\d+\.\d+\.\d+\.\d+')

sudo ln -s /etc/tinydns /etc/service
sleep 5

sudo svstat /etc/service/tinydns

cd /etc/service/tinydns/root
sudo emacs data
._acme-challenge.example.org::ns.example.org:60
'_acme-challenge.example.org:tinydns-install-succeeded:60
sudo make

dig _acme-challenge.example.org txt +norec @$(hostname -I | ag -o '\d+\.\d+\.\d+\.\d+')
cd /etc
sudo git add .
sudo git commit -m "tinydns"

§Let's Encrypt 自動更新

cd /etc/ssl

sudo emacs make_scripts.sh
#/bin/sh

if [ $# -ne 2 ]; then
    echo "$0 example.org yourmail@example.jp"
    exit 1
fi

echo '#!/bin/sh -eu

certbot renew' >  will_update_letsencrypt.sh

echo '#!/bin/bash -eu

DOMAIN_APEX_LIST=(
    '"$1"'
)
MAIL_ADDRESS="'"$2"'"

SCRIPT_DIR="/etc/ssl"
AUTH_HOOK_PATH="${SCRIPT_DIR}/certbot-auth-hook.sh"
CLEANUP_HOOK_PATH="${SCRIPT_DIR}/certbot-cleanup-hook.sh"

for DOMAIN_APEX in ${DOMAIN_APEX_LIST[@]}; do
    certbot certonly --agree-tos --manual --preferred-challenges dns-01 --force-renewal \
        -d "*.${DOMAIN_APEX}" -d "$DOMAIN_APEX" -m "$MAIL_ADDRESS" \
        --manual-auth-hook "$AUTH_HOOK_PATH" --manual-cleanup-hook "$CLEANUP_HOOK_PATH"
done' > update_letsencrypt.sh

echo '#!/bin/sh

service apache2 restart
# service vsftpd restart
service postfix restart' > restart_ssl_services.sh

echo '#!/bin/sh

/etc/ssl/update_letsencrypt.sh
/etc/ssl/restart_ssl_services.sh' > update_letsencrypt_and_postprocess.sh

echo '#!/bin/sh

DATA_PARENT_DIR="/etc/service/tinydns/root"
DATA_PATH="${DATA_PARENT_DIR}/data"

DOMAIN="_acme-challenge.${CERTBOT_DOMAIN}"

TXT_RECORD="'\''${DOMAIN}:${CERTBOT_VALIDATION}:60"

echo "${TXT_RECORD}" >> ${DATA_PATH}
make -C ${DATA_PARENT_DIR}
sleep 1' > certbot-auth-hook.sh

echo '#!/bin/sh

DATA_PARENT_DIR="/etc/service/tinydns/root"
DATA_PATH="${DATA_PARENT_DIR}/data"

DOMAIN="_acme-challenge.${CERTBOT_DOMAIN}"

TARGET_RECORD="'\''${DOMAIN}:${CERTBOT_VALIDATION}:"

sed -i.bak -e "/${TARGET_RECORD}/d" ${DATA_PATH}
make -C ${DATA_PARENT_DIR}' > certbot-cleanup-hook.sh

chmod +x *.sh
sudo chmod +x make_scripts.sh
// ドメイン名と通常利用のメールアドレスを引数にする
sudo ./make_scripts.sh example.org yourmail@example.jp
cd /etc
sudo git add .
sudo git commit -m "/etc/ssl"

§cron 記述とスクリプト作成

§/etc/crontab

sudo emacs /etc/crontab
# Update sslcert (Let's Encrypt)
10 6    1 * *   root    /etc/ssl/update_letsencrypt_and_postprocess.sh
cd /etc
sudo git add .
sudo git commit -m "crontab"

§証明書作成 DNS-01 チャレンジ

cd /etc/ssl/

sudo ./update_letsencrypt.sh
sudo ln -nfs /etc/letsencrypt/live /etc/ssl/hosts
sudo apache2ctl configtest

sudo service apache2 restart
cd /etc
sudo git add .
sudo git commit -m "letsencrypt"

§証明書作成 HTTP-01 チャレンジ

sudo certbot certonly --apache -d foo.example.org -d bar.example.org

§証明書失効

sudo mkdir /etc/ssl/ocsp-stapling
sudo cp -r /etc/letsencrypt/archive/foo.example.org /etc/ssl/ocsp-stapling
sudo certbot revoke --cert-path /etc/letsencrypt/live/foo.example.org/cert.pem

§OS のパスワードポリシーを設定する

cat /etc/pam.d/common-password
# here are the per-package modules (the "Primary" block)
password    [success=1 default=ignore]  pam_unix.so obscure yescrypt
# here's the fallback if no module succeeds
password    requisite           pam_deny.so
# prime the stack with a positive return value if there isn't one already;
# this avoids us returning an error just because nothing sets a success code
# since the modules above will each just jump around
password    required            pam_permit.so
# and here are more per-package modules (the "Additional" block)
# end of pam-auth-update config
sudo apt -y install libpam-pwquality
 # here are the per-package modules (the "Primary" block)
-password       [success=1 default=ignore]      pam_unix.so obscure yescrypt
+password       requisite                       pam_pwquality.so retry=3
+password       [success=1 default=ignore]      pam_unix.so obscure use_authtok try_first_pass yescrypt

この状態でも辞書にあるフレーズが設定できないなど多少は設定できるパスワードを制限できるが、このままでは最低 4 文字のパスワードを設定することが可能である。ここでは、次のように変更しておく。

sudo emacs /etc/pam.d/common-password
 # here are the per-package modules (the "Primary" block)
-password       requisite                       pam_pwquality.so retry=3
+password       requisite                       pam_pwquality.so retry=3 minlen=10 difok=1 dcredit=0 ucredit=0 lcredit=0 ocredit=0
 password       [success=1 default=ignore]      pam_unix.so obscure use_authtok try_first_pass sha512
cd /etc
sudo git add .
sudo git commit -m "common-password"

§FTP (FTPS/SFTP) を設定する

FTP 機能を提供したい場合、このセクションの操作を行う。

// ユーザを新規作成し、ホームディレクトリも生成する
sudo adduser YOUR_ANOTHER_NAME

// gweb に追加する
sudo gpasswd -a YOUR_ANOTHER_NAME gweb
sudo apt -y install vsftpd
sudo service vsftpd stop
cd /etc
sudo git add .
sudo git commit -m "vsftpd"
emacs ~/vsftpd.patch
diff --git a/vsftpd.conf b/vsftpd.conf
index 0f199c0..84e4361 100644
--- a/vsftpd.conf
+++ b/vsftpd.conf
@@ -11,7 +11,7 @@
 #
 # Run standalone?  vsftpd can run either from an inetd or as a standalone
 # daemon started from an initscript.
-listen=NO
+listen=YES
 #
 # This directive enables listening on IPv6 sockets. By default, listening
 # on the IPv6 "any" address (::) will accept connections from both IPv6
@@ -19,7 +19,7 @@ listen=NO
 # sockets. If you want that (perhaps because you want to listen on specific
 # addresses) then you must run two copies of vsftpd with two configuration
 # files.
-listen_ipv6=YES
+#listen_ipv6=YES
 #
 # Allow anonymous FTP? (Disabled by default).
 anonymous_enable=NO
@@ -28,11 +28,11 @@ anonymous_enable=NO
 local_enable=YES
 #
 # Uncomment this to enable any form of FTP write command.
-#write_enable=YES
+write_enable=YES
 #
 # Default umask for local users is 077. You may wish to change this to 022,
 # if your users expect that (022 is used by most other ftpd's)
-#local_umask=022
+local_umask=002
 #
 # Uncomment this to allow the anonymous FTP user to upload files. This only
 # has an effect if the above global write enable is activated. Also, you will
@@ -55,6 +55,10 @@ use_localtime=YES
 #
 # Activate logging of uploads/downloads.
 xferlog_enable=YES
+xferlog_file=/var/log/xferlog
+xferlog_std_format=NO
+log_ftp_protocol=YES
+dual_log_enable=YES
 #
 # Make sure PORT transfer connections originate from port 20 (ftp-data).
 connect_from_port_20=YES
@@ -96,8 +100,8 @@ connect_from_port_20=YES
 # predicted this attack and has always been safe, reporting the size of the
 # raw file.
 # ASCII mangling is a horrible feature of the protocol.
-#ascii_upload_enable=YES
-#ascii_download_enable=YES
+ascii_upload_enable=YES
+ascii_download_enable=YES
 #
 # You may fully customise the login banner string:
 #ftpd_banner=Welcome to blah FTP service.
@@ -119,16 +123,16 @@ connect_from_port_20=YES
 # (Warning! chroot'ing can be very dangerous. If using chroot, make sure that
 # the user does not have write access to the top level directory within the
 # chroot)
-#chroot_local_user=YES
-#chroot_list_enable=YES
+chroot_local_user=YES
+chroot_list_enable=YES
 # (default follows)
-#chroot_list_file=/etc/vsftpd.chroot_list
+chroot_list_file=/etc/vsftpd.chroot_list
 #
 # You may activate the "-R" option to the builtin ls. This is disabled by
 # default to avoid remote users being able to cause excessive I/O on large
 # sites. However, some broken FTP clients such as "ncftp" and "mirror" assume
 # the presence of the "-R" option, so there is a strong case for enabling it.
-#ls_recurse_enable=YES
+ls_recurse_enable=YES
 #
 # Customization
 #
@@ -146,9 +150,24 @@ pam_service_name=vsftpd
 #
 # This option specifies the location of the RSA certificate to use for SSL
 # encrypted connections.
-rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
-rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
-ssl_enable=NO
+#rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
+#rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
+rsa_cert_file=/etc/ssl/hosts/example.org/cert.pem
+rsa_private_key_file=/etc/ssl/hosts/example.org/privkey.pem
+ssl_enable=YES
+ssl_sslv2=NO
+ssl_sslv3=NO
+ssl_tlsv1=YES
+ssl_ciphers=HIGH
+force_local_data_ssl=YES
+force_local_logins_ssl=YES
+require_ssl_reuse=NO
+seccomp_sandbox=NO
+pasv_max_port=60099
+pasv_min_port=60000
+allow_writeable_chroot=YES
+local_root=/var/www/hosts
+user_config_dir=/etc/vsftpd/vsftpd_user_conf

 #
 # Uncomment this to indicate that vsftpd use a utf8 filesystem.
cd /etc
sudo patch < ~/vsftpd.patch
rm ~/vsftpd.patch
sudo touch /etc/vsftpd.chroot_list
sudo mkdir -p /etc/vsftpd/vsftpd_user_conf
sudo ufw allow 60000:60099/tcp
sudo ufw reload

sudo service vsftpd restart
cd /etc
sudo git add .
sudo git commit -m "update vsftpd"

§SFTP を使用不可にする

/etc/ssh/sshd_config の差分

-Subsystem sftp /usr/lib/openssh/sftp-server
+# Subsystem sftp /usr/lib/openssh/sftp-server
sudo systemctl restart ssh

§SSH ログインを不可にする

/etc/ssh/sshd_config の差分(末尾に追記)

+DenyUsers YOUR_ANOTHER_NAME
sudo systemctl restart ssh

§FTP ルートディレクトリを変更する

sudo emacs /etc/vsftpd/vsftpd_user_conf/YOUR_ANOTHER_NAME
local_root=/path/to/
sudo service vsftpd restart

§postfix をインストールする

sudo apt -y install postfix
cd /etc
sudo git add .
sudo git commit -m "postfix"
sudo emacs /etc/postfix/main.cf
-smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
-smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
+#smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
+#smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
+smtpd_tls_cert_file=/etc/ssl/hosts/example.org/cert.pem
+smtpd_tls_key_file=/etc/ssl/hosts/example.org/privkey.pem

さらに末尾に以下を追記する。

# CatchAll
local_recipient_maps =
luser_relay = yourname
sudo service postfix restart
cd /etc
sudo git add .
sudo git commit -m "update postfix"
sudo emacs /etc/aliases
# See man 5 aliases for format
postmaster:    root
yourname:    yourmail@example.jp
cd /etc
sudo newaliases
sudo ufw allow smtp
sudo ufw allow smtps
sudo ufw reload
sudo service postfix restart
cd /etc
sudo git add .
sudo git commit -m "aliases"

§ドメイン名を追加する

以下は example.net を追加で使えるようにする場合の手順です。

§DNS RRs

host type value
_acme-challenge.example.net NS ns.example.net
ns.example.net A 203.0.113.123

§tinydns

cd /etc/service/tinydns/root
sudo emacs data
// 以下を追記する
._acme-challenge.example.net::ns.example.net:60
'_acme-challenge.example.net:tinydns-install-succeeded:60

§/etc/ssl

sudo emacs /etc/ssl/update_letsencrypt.sh
 DOMAIN_APEX_LIST=(
     example.org
+    example.net
 )
sudo /etc/ssl/update_letsencrypt.sh

§Apache

cd /etc/apache2/includes
sudo emacs make_includes.sh
 DOMAIN_APEX_LIST=(
     example.org
+    example.net
 )
sudo ./make_includes.sh
sudo emacs /etc/apache2/sites-enabled/001-original.conf
############################################################
# www.example.net, *.example.net
############################################################
<VirtualHost *:80>
  ServerName www.example.net
  ServerAlias *.example.net
  VirtualDocumentRoot /var/www/hosts/example.net/%-3+/public_html
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName www.example.net
  ServerAlias *.example.net
  VirtualDocumentRoot /var/www/hosts/example.net/%-3+/public_html
  Include includes/https_example.net.conf
  Include includes/log.conf
</VirtualHost>

############################################################
# example.net
############################################################
<VirtualHost *:80>
  ServerName example.net
  VirtualDocumentRoot /var/www/hosts/example.net/www/public_html
  Include includes/http.conf
  Include includes/log.conf
</VirtualHost>

<VirtualHost *:443>
  ServerName example.net
  VirtualDocumentRoot /var/www/hosts/example.net/www/public_html
  Include includes/https_example.net.conf
  Include includes/log.conf
</VirtualHost>
sudo apache2ctl configtest

sudo /etc/ssl/restart_ssl_services.sh

§既存サーバーのデータを取得する

§データを保存する

sudo -i

cd ~
mkdir server_data
cd server_data

mysqldump -u mysqluser -p -x --all-databases > dbdump.sql
cp -a /home/coeurl home_dir
cp -a /var/www/hosts var_www_hosts
cp /etc/crontab .

cd ~
tar czf server_data.tgz server_data

§データを復元する

// server_data のファイルを設置した後...

cd /var/www/hosts
sudo chgrp gweb -R .
find . -type d | xargs sudo chmod g+s