Postfixのrelayhostにsmtp.gmail.comを使用する

Googleアカウント側の設定

smtp.gmail.comを使用するにはGoogleアカウントが必要になる。Googleアカウントの設定を開き、セキュリティにあるアプリパスワードをクリックする。

アプリはメールを選択する。

デバイスはその他を選択し、わかりやすい名前を入力する。


生成ボタンをクリックすると下記の黄色の枠内にアプリパスワードが表示されるので、Postfix側の設定に使用する。

Postfix側の設定

Postfixをインストールして、SMTP認証を有効にする。

sudo apt install postfix

sudo postconf -e "relayhost = [smtp.gmail.com]:587"
sudo postconf -e "smtp_sasl_auth_enable = yes" / 
"smtp_sasl_security_options = noanonymous" / 
"smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd"

sudo postconf -e "smtp_use_tls = yes" /
"smtp_tls_loglevel = 1" /
"smtp_tls_security_level = encrypt" /
"smtp_tls_note_starttls_offer = yes" /
"smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt"

sudo postconf -e "mynetworks = 127.0.0.0/8"

Googleアカウント側の設定を参照し、smtp.gmail.comのログインID、パスワードを設定する。

sudo vi /etc/postfix/sasl_passwd
[smtp.gmail.com]:587 Gmailアドレス:アプリパスワード

sudo chmod 600 /etc/postfix/sasl_passwd
sudo postmap hash:/etc/postfix/sasl_passwd
sudo service postfix restart

テストメールを送信する。

echo 'Hellow World!' | mail -s test toメールアドレス

無料のSSL証明書をインストールする(Ubuntu 22.04版)

No-IPドメインでLet’s Encryptの証明書を使うことが出来る。

事前確認

・ドメイン名を取得していること(今回はNo-IPを使用する)
・80、443ポートが外部に開放されていること

Certbotをインストールする

sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

SSL証明書の取得とApacheへの設定の追加

sudo certbot --apache

取得と設定に成功すれば、下記の通り表示される。

Successfully deployed certificate forドメイン名 to /etc/apache2/sites-available/000-default-le-ssl.conf
Congratulations! You have successfully enabled HTTPS on https://ドメイン名

自動更新のタスクもこの処理で設定されるので、特にこれ以上することはないが、念のため下記のコマンドで自動更新のテストが可能となっている。

sudo certbot renew --dry-run

Ubuntu Server 22.04 LTSをインストールする

目的

開発用のサーバーをUbuntu Server 22.04で再構築する。

Ubuntu Serverのインストール

下記からISOファイルをダウンロードして、USBフラッシュドライブに書き込む。
https://jp.ubuntu.com/download

SSHのインストールのチェックを入れるのを忘れずに、他はインストーラーの指示通りに入力する。

MariaDBのインストール

ここではインストールのみ行い、設定は後程行う。

sudo apt install mariadb-server
sudo mysql_secure_installation

Apache、phpのインストール

phpを使用することを前提としたインストールと、設定を行う。

sudo apt install apache2
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2ensite default-ssl
sudo apt install php libapache2-mod-php php-fpm php-common php-mbstring php-xmlrpc php-gd php-xml php-mysql php-cli php-zip php-curl php-imagick php-intl

.htaccesによる、設定の上書きを有効にする。

sudo vi /etc/apache2/apache2.conf

<Directory /var/www/>
	Options Indexes FollowSymLinks
	AllowOverride all
	Require all granted
</Directory>

sudo service apache2 restart

Webminのインストール

sudo apt install wget apt-transport-https software-properties-common

wget https://download.webmin.com/jcameron-key.asc
cat jcameron-key.asc | gpg --dearmor >jcameron-key.gpg
sudo cp jcameron-key.gpg /etc/apt/trusted.gpg.d/jcameron-key.gpg

sudo apt update
sudo apt install webmin
sudo /usr/share/webmin/changepass.pl /etc/webmin ユーザー名 パスワード

日本語設定

sudo apt install language-pack-ja
localectl list-locales | grep ja
sudo localectl set-locale LANG=ja_JP.UTF-8

Tomcat 9のインストール

sudo apt install tomcat9 tomcat9-admin

Jenkins運用のため、/etc/tomcat9/context.xmlに下記を追記する。

    <Resources cacheMaxSize="102400" />

/etc/tomcat9/server.xmlのAJP/1.3の設定を下記の通り変更する。

    <Connector protocol="AJP/1.3"
               address="localhost"
               port="8009"
               redirectPort="8443"
               secretRequired="false" />

/etc/tomcat9/tomcat-users.xmlに下記を追記する。

  <role rolename="manager-script"/>
  <user username="ユーザー名" password="パスワード" roles="manager-script"/>

/usr/lib/systemd/system/tomcat9.serviceに下記を追記する。

Environment="CATALINA_OPTS=-DJENKINS_HOME=/opt/jenkins/"
ReadWritePaths=/opt/jenkins/

/etc/tomcat9/policy.d/50local.policyに下記を追記する。

grant codeBase "file:${catalina.base}/webapps/jenkins/-" {
    permission java.security.AllPermission;
};
grant codeBase "file:/opt/jenkins/-" {
    permission java.security.AllPermission;
};
sudo systemctl daemon-reload
sudo service tomcat9 restart

Jenkinsのインストール

/opt/jenkinsフォルダを作成し、フォルダにTomcatから書き込み可能な権限を付与する。

jenkins.warファイルをダウンロードし、/var/lib/tomcat9/webapps/に配置する。

ApacheとTomcatの連携の設定

sudo a2enmod proxy
sudo a2enmod proxy_ajp

/etc/apache2/conf-enabled/tomcat.confを作成し、下記の通り記載する。

ProxyPass /jenkins/ ajp://localhost:8009/jenkins/
ProxyPassReverse /jenkins/ ajp://localhost:8009/jenkins/
sudo service apache2 restart

Postfixのインストール

こちらのページにインストールと設定方法を記載した。

Vue Composition APIを試す

Composition APIを使用すれば、同じ論理的な関心事に関連するコードを並べることが出来る?可読性が良くなる?良くわからないけど、前にVue 2で書いたコードを書き換えてみる。

このドキュメントでは実際にどのように変更したかをメインに記載する。

ログイン画面(Login.vue)の例

・Vue 2
一見シンプルに見えるけれども、thisで参照するものが多すぎて、何が使えて、何が使えないのか、後から見るとわかりづらい。

<script>
import {AUTH_REQUEST} from '@/store/actions/auth'

export default {
  data () {
    return {
      username: '',
      password: '',
    }
  },
  methods: {
    login: function () {
      const { username, password } = this
      this.$store.dispatch(AUTH_REQUEST, { username, password }).then(() => {
        this.$router.push(this.$route.query.redirect || '/')
      }).catch(error => {
        this.$message({
          showClose: true,
          message: error,
          type: 'error'
        })
      })
    }
  }
}
</script>

・Vue 3.2
Vue 2で必要だった独特な書き方がなくなって、一般的なJavaScriptの書き方になっている。インポート、宣言部、関数とわかれていて、わかりやすくなっているように感じる。

<script setup>
import { reactive } from 'vue'
import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { AUTH_REQUEST } from '@/store/actions/auth'

const store = useStore()
const router = useRouter()
const route = useRoute()

const state = reactive({
  username: '',
  password: ''
})

function login() {
  const { username, password } = state
  store.dispatch(AUTH_REQUEST, { username, password }).then(() => {
    router.push(route.query.redirect || '/')
  }).catch(error => {
    ElMessage({
      message: error,
      grouping: true,
      type: 'error'
    })
  })
}
</script>

Raspberry Pi 4 Model Bを設定する

やりたいこと

Raspberry Pi OS動かして、デスクトップ画面を表示する。

Raspberry Pi 4を購入する

今は品薄で、なかなか手が入らない。仕方がないので必要なものが揃っている下記を購入した。

Raspberry Pi OSをインストールする

下記からRaspberry Pi Imagerをダウンロードしてインストールする。
https://www.raspberrypi.com/software/

SDカードを挿入し、Raspberry Pi Imagerを起動して、OS選択し下記の通り実行する。

OSをインストールしたSDカードをRaspberry Piに挿入し電源を入れる。

※4/21追記
コンパクトなキーボードとマウスが欲しかったので下記を購入した。USBレシーバーも付属しているので、互換性を気にしなくて良く、すぐ使用できる点が気に入った。

Unreal Engine 5をインストールしてCity Sampleを見てみたい

Unreal Engine 5のCity Sampleとは何か

The Matrix Awakens: An Unreal Engine 5 Experienceの技術デモはPlayStation 5 と Xbox Series X/Sでしか公開されていなかったが、Unreal Engine 5が先日リリースされ、この都市シーンをPC上で動かしてみることが出来るようになった。(ただし、Matrix関係のコンテンツは含まれない)

City Sample をインストールする

Epic Gamesから、Unreal Engine 5をダウンロードし、インストールする。(Epic Games Launcherが必要になる)

Unreal EngineのマーケットプレイスからCityサンプルを探して入手する。

ライブラリのマイダウンロードにあるCityサンプルから、プロジェクトを作成する。

Cityサンプルの容量は約93GB程あるので、ダウンロードに時間がかかる。

Unreal Engineは全く触ったことがないので、サンプル起動後Small Cityをロードしてみた。

プレビューで動かすことは出来たけど、この後パッケージするにはどうしたら良いのだろう。

※追記:パッケージ化出来るようになった。
1.勝手に日本語化されていたタイトルの2バイト文字を1バイト文字に変更(Cityサンプル→CitySample)。
2.下記から.NET Core 3.1 ランタイムをダウンロードしてインストール。
https://aka.ms/dotnet-core-applaunch?framework=Microsoft.WindowsDesktop.App&framework_version=3.1.0&arch=x64&rid=win10-x64

Spring Boot 2.6でHibernate Search 6を動かす

依存関係

implementation 'org.hibernate.search:hibernate-search-mapper-orm:6.1.4.Final'
implementation 'org.hibernate.search:hibernate-search-backend-lucene:6.1.4.Final'

実装

Entityクラスに@Indexed、メソッドに@FullTextFieldを追加する。

@Entity
@Table(name = "novel")
@Indexed
public class Novel implements Serializable {

...

    @FullTextField
    private String title;

Serviceクラスに検索処理のロジックを追加する。

@Service
public class NovelService {

...

    @Transactional
    public Stream<Novel> searchIndex(final String searchParameters) {
        SearchSession searchSession = Search.session(entityManager);

        SearchResult<Novel> result = searchSession.search(Novel.class)
                .where(f -> f.bool()
                        .must(f.match()
                                .field("title")
                                .matching("異世界")))
                .fetchAll();
        return result.hits().stream();
    }

実際に動くコードはGitHubに公開予定。公開完了したら、下記に追記する。

※4/22追記
NovelService.java – github

public Stream<Novel> searchIndex(final String searchParameters) {
    SearchSession searchSession = Search.session(entityManager);
    String operationSetExper = String.join("|", SearchOperation.SIMPLE_OPERATION_SET);
    Pattern pattern = Pattern.compile(
            "(\\p{Punct}?)(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),",
            Pattern.UNICODE_CHARACTER_CLASS);
    Matcher matcher = pattern.matcher(searchParameters + ",");

    SearchResult<Novel> result = searchSession.search(Novel.class)
            .where(f -> f.bool(b -> {
                b.must(f.matchAll());
                while (matcher.find()) {
                    b.must(f.match().field(matcher.group(KEY))
                            .matching(matcher.group(VALUE)));
                }
            }))
            .fetchAll();

    return result.hits().stream();
}

Spring BootでLuceneAnalysisConfigurerがうまく動かなかった件

Spring Boot 2.6はHibernate Search 6に対応している。そこで、日本語の検索も出来るようにするため、lucene-analyzers-kuromojiのAnalyzerをLuceneAnalysisConfigurerで設定しようとした。しかし、公式のガイド通りやってもうまくいかなかったため、メモを残しておく。

依存関係

implementation 'org.apache.lucene:lucene-analyzers-kuromoji:8.11.1'
implementation 'org.hibernate.search:hibernate-search-mapper-orm:6.1.4.Final'
implementation 'org.hibernate.search:hibernate-search-backend-lucene:6.1.4.Final'

LuceneAnalysisConfigurerの実装

@Component("customLuceneAnalysisConfigurer")
public class CustomLuceneAnalysisConfigurer implements LuceneAnalysisConfigurer {

    /**
     * {@inheritDoc}
     */
    @Override
    public void configure(LuceneAnalysisConfigurationContext context) {
        context.analyzer("japanese").instance(new JapaneseAnalyzer());
    }
}

CustomLuceneAnalysisConfigurerの設定

公式の例では、実装したクラスを直接crawlerapi.config.CustomLuceneAnalysisConfigurerのように指定していたのだが、上記のようにComponentとして登録してから、下記のように参照するようにしないとSpring Bootの起動でハングアップした。

spring:
  jpa:
    properties:
      hibernate:
        search.backend:
          analysis.configurer: customLuceneAnalysisConfigurer

設定したAnalyzerの使用方法

先ほど定義した名前で参照できる。

@FullTextField(analyzer = "japanese")

element-ui 2からelement-plus 2にアップグレードする

今まではelement-ui 2を使用していたが、今後はVue 3に対応しているelement-plus 2を使用する。変更点のメモを残しておく。

element-ui 2の依存関係

"element-ui": "^2.15.6",

element-plus 2の依存関係

"@element-plus/icons-vue": "^1.1.4",
"element-plus": "^2.1.8",

element-ui 2の初期設定

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import locale from 'element-ui/lib/locale/lang/ja'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI, {locale})

new Vue({
  render: h => h(App)
}).$mount('#app')

element-plus 2の初期設定

import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import locale from 'element-plus/lib/locale/lang/ja'
import 'element-plus/dist/index.css'

const app = createApp(App)

app.use(ElementPlus, {locale})

app.mount('#app')

vee-validate 3から4にアップグレードする

APIが完全に変わったため、マイグレーションガイドは存在しないらしい。変更点のメモを残しておく。

vee-validate 3の依存関係

"vee-validate": "^3.4.13",

vee-validate 4の依存関係

"@vee-validate/i18n": "^4.5.10",
"@vee-validate/rules": "^4.5.10",
"vee-validate": "^4.5.10",

vee-validate 3の初期設定

import Vue from "vue"
import { extend, localize } from "vee-validate"
import { required, max, email } from "vee-validate/dist/rules"
import en from "vee-validate/dist/locale/en.json"
import ja from "vee-validate/dist/locale/ja.json"
import enNames from '../locale/enNames.json'
import jaNames from '../locale/jaNames.json'

extend("required", required)
extend("max", max)
extend("email", email)

localize({
  en: {
    messages: en.messages,
    names: enNames
  },
  ja: {
    messages: ja.messages,
    names: jaNames
  }
})

let LOCALE = "ja"

Object.defineProperty(Vue.prototype, "locale", {
  get() {
    return LOCALE
  },
  set(val) {
    LOCALE = val
    localize(val)
  }
})

vee-validate 4の初期設定

import { defineRule, configure } from 'vee-validate';
import { required, max, email } from '@vee-validate/rules';
import { localize, setLocale } from '@vee-validate/i18n';
import en from '@vee-validate/i18n/dist/locale/en.json';
import ja from '@vee-validate/i18n/dist/locale/ja.json';
import enNames from '../locales/enNames.json'
import jaNames from '../locales/jaNames.json'

defineRule('required', required)
defineRule('max', max)
defineRule('email', email)

const customLocalize = localize({
  en: {
    messages: en.messages,
    names: enNames
  },
  ja: {
    messages: ja.messages,
    names: jaNames
  }
})

setLocale('ja')

configure({
  generateMessage: customLocalize
})

vee-validate 3の使用方法

<template>
  <ValidationObserver
    ref="observer"
    v-slot="{ passes }"
  >
    <el-form
      ref="form"
      class="login"
    >
      <h2>Sign up</h2>
      <ValidationProvider
        name="username"
        rules="required|max:16"
        v-slot="{ errors }"
      >
        <el-form-item
          :error="errors[0]"
          class="input-form-wrapper"
        >
          <el-input
            type="text"
            placeholder="Username"
            v-model="username"
          />
        </el-form-item>
      </ValidationProvider>
      <el-button
        type="primary"
        @click="passes(signup)"
      >Signup</el-button>
<script>
import { ValidationProvider, ValidationObserver } from "vee-validate"

export default {
  components: {
    ValidationProvider,
    ValidationObserver
  },
  methods: {
    signup: function () {
      const { username, password, email } = this

vee-validate 4の使用方法

※vee-validate以外にも、element-uiからelement-plusに変更している。element-plusの変更点については、こちらを参照のこと。

<template>
  <Form
    as="el-form"
    :validation-schema="schema"
    @submit="onSubmit"
  >
    <h2>Sign up</h2>
    <Field
      name="username"
      v-slot="{ value, field, errorMessage }"
    >
      <el-form-item
        :error="errorMessage"
        class="input-form-wrapper"
      >
        <el-input
          type="text"
          placeholder="Username"
          v-bind="field"
          :model-value="value"
        />
      </el-form-item>
    </Field>
<script setup>
import { Field, Form } from "vee-validate";

const schema = {
  username: 'required|max:16'
}

function onSubmit(values) {
  const { username, password, email } = values

記述量が減って良いと思う。