OmniAICreator/Qiita-1.07M huggingface dataset:
【Wordpress】有料テーマは何回でもインポートしてOKなのか？複数サイトで使い回しして良いのか...

# 概要
* Wordpressの有料テーマを使っていて、別のサイトでも同じ有料テーマを使いまわして良いのかと思ってふと調べてみました。

## 結論
* 利用する有料テーマの「利用規約」を読むのが一番です。
* 一般的にWordPressテーマには、GPL（General Public License）というライセンス形態が多く採用されているようです。
* GPLとは?
    * 「General Public License」の略で、ソフトウェアに対してのオープンソースライセンスの1つです。GPLライセンスは、ソフトウェアの複製、改変、配布を自由に行うことができるように規定しています。自由ソフトウェアのライセンスの1つで、自由に使い、研究し、配布し、修正することを認めています。
* もし購入したテーマがGPLライセンスの下でリリースされたソフトウェアであれば、何回インストールしても問題ないということになります。
* ただし、それ以外のライセンス形態を採用しているテーマもありますので、ライセンスについては有料テーマのページや利用規約で確認する必要があります。

## 自分が使っているテーマの利用規約をみたところ...
* 利用中のテーマの利用規約をみたところ、以下のように記載がありました。

> 「当テーマのPHP部分についてはGNU General Public License（以下「GPL」といいます）が本規約に優先して適用されます」

お、ということは使い回し可能ということか!?
と思ったら一つ下には以下のように記載されています。

> 当テーマに含まれる画像、HTML、CSS、Javascript等にかかる著作権を含む知的財産権は、加工の有無を問わず、すべて当社または当社に使用許諾を与える第三者に帰属します。

横には以下のような記載もありました。

> 当テーマではいわゆるスプリットライセンスを採用しています。当テーマに含まれるPHPファイルにはGPLが適用されますが、それ以外についてはGPLは適用されません。

* いわゆる、スプリットライセンスというやつです。ググるとGPLとの違いみたいな記事がたくさん出てきます。
* 画像、HTML、CSS、Javascriptについては、GPLが適用されるので使いまわしOKですが、画像、HTML、CSS、JavascriptなどはGPL適用されないので、これらのファイルに関してはテーマ作成者側が使用制限を設けることができる、という意味です。そのため、テーマを使用する前に、利用規約やライセンスなどをよく確認し、適切に使用することが大事です。
* 上記の利用規約では、特定の条件下でのファイルの再配布は許可されているようです。例えば、同じドメイン名を持つ複数のサイトで使用する場合や、WordPressのサイトの所有権を継承する場合は、再配布が許可されます。ただし、利用規約の条項を遵守しなければ、著作権侵害のリスクがあることを忘れてはいけません。利用規約をよく読み、遵守することが重要でしょう。
【備忘録】SSL証明書、更新の手順（Let's Encrypt）

# はじめに
- 本記事は、備忘録としてまとめたものになります。
- 他の方の参考になる可能性も踏まえて、一般公開も行なっております。
- また記載内容はすべて、正しい内容が記載されているとは限りません。
- 誤った内容を見つけた場合は、ご指摘をお願いいたします。

# この記事は誰向けの記事か？
- Let's Encryptの証明書の有効期限が切れてしまい、更新したい管理者
- Let's Encryptを使用したアプリケーションへアクセスできない状態になっている管理者またはユーザー

# 背景
- Techcampにて作成したアプリをSSL化するために、Let's Encryptを使用した。
- しかし突然、アクセスすることができなくなってしまったため、調査と対処を行なった。

# 運用環境
以下はアプリへアクセスする際に使用していたブラウザの詳細になります。
- OS：macOS 13.1（22C65）
- ブラウザ：Google Chrome バージョン: 111.0.5563.146（Official Build） （arm64）

以下はアプリ/サーバー環境の情報になります。
- 開発言語：Ruby on Rails
- 開発環境：AWS(EC2、Route53）、Nginx
- その他：Let's Encrypt

# 調査
アプリへアクセスすると、以下のような状況となっていたことを確認した。
エラー内容：この接続ではプライバシーが保護されません
![スクリーンショット 2023-04-04 14.58.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/9126ee99-2bc6-5c6f-958c-b6b63c57497f.png)

通信内容を確認すると、以下のような状況となっていた。
エラー内容：このサイトへの接続は保護されていません 証明書が無効です
![スクリーンショット 2023-04-04 14.59.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/6973b86c-2d7a-0d2f-e8f8-48ed8d7667ef.png)

原因：証明書を確認すると、有効期限が日時を経過してしまっていた。
![スクリーンショット 2023-04-04 14.59.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/4072bfe6-0719-9df1-a378-ae6054519786.png)

# この問題に対しての対処法
- クライアント側
対処する方法はありません。サーバー管理者に問い合わせて、証明書を更新してもらえるまで待つしかない。
- サーバー管理者側
証明書を更新しなければならない。

# 対処
対処するため、Let's Encryptの証明書を以下の手順で更新した。

## EC2へログインする

## CRONの内容を確認し、自動更新の設定を確認する
結果としては、1時と13時にルート権限で自動更新する設定がされていた。
しかしなぜ自動更新されていないのかがわからないため、これについては別途調査が必要。
```
$ sudo crontab -e
```
または
```
$ sudo crontab -l
```
![スクリーンショット 2023-04-04 19.30.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/990a4e2f-b60e-2952-9046-5fd8c97fb91c.png)
![スクリーンショット 2023-04-04 19.28.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/1b25df33-caa8-cbf7-5cbc-98d93b219af8.png)

## 手動にて更新を行う

### SSL証明書の有効期限の確認する
```
$ certbot certificates
```
または
```
$ sudo certbot certificates
```

### 更新前にチェックをする
この段階でエラーが出るようであれば、更新は成功しない。
```
$ sudo certbot renew --dry-run
```

### 更新する
以下のコマンドを実行することで、SSL証明書の更新が行われる。
```
$ sudo certbot renew
```

### アクセスできるかを確認する
その結果、アプリケーションへアクセスすることに成功し、SSL証明書が更新されていることを確認できた。
![スクリーンショット 2023-04-04 19.48.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/70c6a088-8a67-11e4-5d22-137a94248ef1.png)


# 自動更新されていない問題について

## CRONをリスタートしてみる
リスタートし、直して様子見をすることとなった。
```
$ sudo systemctl restart crond
```
既に作成済みのS3バケットに一括でSSL通信Onlyを設定する

## Security Hubのアラーム多いな
Security HubでPCI DSSやCIS AWS Foundations Benchmarkのセキュリティ標準を有効化した場合に、S3バケットに対してSSL通信のみの設定をするよう求められる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3303491/1de7d7cf-59ae-1acd-270c-8b7baf7aeed6.png)

## どんな構成が必要なのか
具体的にはバケットのStatementに以下の記載をすることでアラームが解消される。
このStatementはS3にオブジェクトを操作する際に、SSL通信（つまりはHTTPS）でないと拒否Denyする設定となります。
```json
{
    "Sid": "AllowSSLRequestsOnly",
    "Effect": "Deny",
    "Principal": "*",
    "Action": "s3:*",
    "Resource": [
        "arn:aws:s3:::<bucket_name>",
        "arn:aws:s3:::<bucket_name>"
    ],
    "Condition": {
        "Bool": {
            "aws:SecureTransport": "false"
        }
    }
}
```

## もう一杯S3作っちゃったよ
Security Hubを有効化した際に全てのS3が走査され、このセキュリティ基準に準拠しないものがすべて通知されます。
数十あるバケットに対して手作業でこのポリシーを追記する（バケット名も転記する）ことは、面倒だと思いますのでpythonスクリプトを作成しました。

## pythonスクリプト
このスクリプトでは boto3とjsonを利用しているため、`pip install boto3`と`pip install json`をしてください。
動作としては、
1. 全てのバケットを走査します。
2. バケットポリシーが有効化されている（何かしらバケットポリシーが記載されている）バケットに対して、
` "aws:SecureTransport": "false"`の記載があるかを確認します。
バケットポリシーになにも記載がないバケットはスキップするようにしていますが、お好みでバケットポリシーを新規作成する形に変更していただければと思います。
3. 記載がなければPolicyを追記し、反映します。

ログ表示
- `Statement exists` = SSL通信Onlyの記載が既にあるため、スキップ
- `No Bucket Policy` = バケットポリシーが有効化されていないため、スキップ
- `Bucket Policy changed` = バケットポリシーにSSL通信Onlyを設定した

```python
import boto3
import json

def check_sslonly(bucket_name, json):
  for statement in json['Statement']:
    try:
      statement['Condition']['Bool']['aws:SecureTransport']
      print(bucket_name + ' : Statement exists')
      return True
    except KeyError:
      continue
  print(bucket_name + ' : Statement not exists')
  return False

secure_transport_obj = {
  "Sid": "AllowSSLRequestsOnly",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": [],
  "Condition": {
    "Bool": {
      "aws:SecureTransport": "false"
    }
  }
}

def add_secure_transport_obj(bucket_name, bucket_policy_obj):
  secure_transport_obj['Resource'] = [
    "arn:aws:s3:::" + bucket_name,
    "arn:aws:s3:::" + bucket_name + '/*'
  ]
  bucket_policy_obj['Statement'].append(secure_transport_obj)
  return

s3 = boto3.client('s3')
buckets = s3.list_buckets()
bucket_policy = None

for bucket in buckets['Buckets']:
  try:
    bucket_policy = s3.get_bucket_policy(Bucket=bucket['Name'])
  except s3.exceptions.from_code('NoSuchBucketPolicy'):
    print(bucket['Name'] + ' : No Bucket Policy')
    continue

  bucket_policy_obj = json.loads(bucket_policy['Policy'])

  if(check_sslonly(bucket['Name'], bucket_policy_obj) == False):
    add_secure_transport_obj(bucket['Name'], bucket_policy_obj)
    s3.put_bucket_policy(
      Bucket=bucket['Name'],
      Policy=json.dumps(bucket_policy_obj)
    )
    print(bucket['Name'] + ' : Bucket Policy changed')
```
【SwiftUI】MapKitで地図を表示させる

# MapKitとは

- Appple純正の地図情報を実装するフレームワーク

- UIKit、SwiftUIのどちらでも利用可能

- Xcodeには既に組み込まれているので、「import MapKit」と記述するだけで使用可能
###
# MapKitでできること

- ジオコーディング…判読可能な住所を地理座標に変換、また逆の変換もできる

- 検索…付近にある関連の高い場所を検索したり、検索条件に基づいて自動検索補完する

- Look Around…インタラクティブなパノラマでストリートビューを提供する

- 選択可能なマップ機能…検索結果をフィルタして、場所に関する詳細を提供することで、ユーザーに関連性の高い地図を表示できる

- 注釈…特定の場所をカスタマイズした注釈で目立たせたり、わかりやすい説明を追加したり、複数の場所をまとめたりすることができる

- 経路…入力した開始地点と終了地点に基づき、経路と移動時間を表示できる

- オーバーレイ…地図上の区域や経路をハイライトするオーバーレイやブレンドモードを追加できる

###
# SwiftUIで使用できるMap
SwiftUIでは、

- MapKitのMap(UIViewRepresentableを使用しない)
- UIKitのMKMapView(UIViewRepresentableを使用する)

どちらかを使用してMapを表示することができる。

UIViewRepresentableプロトコルを使用しなくてもMap機能を実装できるが、まだ十分な機能が提供されていないので、
今回MapKitのViewを作成するために、UIViewRepresentableプロトコルを使う。

###
# Mapを表示させる手順

### １，MapKitをインポート
```swift
import MapKit
```

MapKit は、アプリ内に地図や衛星画像の表示、地図にピンを配置、住所から地図座標の検索をすることができるフレームワーク。

###
### ２，UIViewRepresentableプロトコルを使用

```swift
struct MapView: UIViewRepresentable {
// (省略)
} 
```

「UIViewRepresentable」は、UIKitのViewや機能をSwiftUIでも使えるようにするプロトコル。

SwiftUIはUI部品が構造体（struct）で定義されているのに対し、
MapKitのUI部品はクラス（class）で定義されている。
構造体とクラスではデータ型が異なるため、そのまま利用できない。
また前述したとおり、SwiftUIはまだまだ実装できる機能が少ない。

このことから、「UIKit」と「SwiftUI」の両フレームワークの機能を橋渡しするためのプロトコル「UIViewRepresentable」を使用する。

###
### ３，makeUIViewメソッドを使用

```swift
// 表示するViewを作成するときに実行
func makeUIView(context: Context) -> MKMapView {
// MKMapViewのインスタンスを生成
MKMapView()
} 
```

makeUIViewメソッドはUIViewRepresentableプロトコルで必須のメソッド。
makeUIViewメソッドは、画面に表示する前に一度だけ実行され、表示に必要なViewを作成する。
引数contextにはUIKitのView作成と更新に使用するシステムの状態に関するコンテキスト（文脈）情報が渡される。
MKMapViewで初期状態のマップを作成。

###
### ４，updateUIViewメソッドを使用

```swift
// 表示した View が更新される度に実行
func updateUIView(_ uiView: MKMapView, context: Context) {
// (省略)
} 
```

updateUIViewメソッドもUIViewRepresentableプロトコルで必須のメソッド。
updateUIViewメソッドは、表示するViewの状態が更新される度に呼び出され実行される。
最初の引数uiViewはUIView型のインスタンスで、makeUIViewメソッドで作成したMKMapViewが設定される。
※updateUIviewメソッドで使っている「_ uiView: MKMapView」の「 _ 」は、ラベルの省略を表す。

# 終わり
コード：https://github.com/Yuchi-SaitoYuya/MapKit.git
vscodeでcypressのintellisenseが見れない問題

# テスト環境
* node: 18.15.0
* yarn: 4.0.0-rc.42
* cypress: 12.9.0
* vscode: 1.77.0
* ubuntu: 20.04.6 LTS
* volta: 1.1.1

# 解決方法
* [IDE Integration (Cypress公式ドキュメント)](https://docs.cypress.io/guides/tooling/IDE-integration#Triple-slash-directives)
    * この方法だとうまくいかなかったので、以下のようにする

* `/// <reference path="../../.yarn/unplugged/cypress-npm-12.9.0-0c9e067ccc/node_modules/cypress/types/index.d.ts" />`を一行目に入れる
    * pathは適宜変更すること`node_modules/cypress/types/index.d.ts`があってればよい

# 原因
* `types="Cypress"`は`node_module/@cypress`に頼った方法だが、`yarn`では`node_module`フォルダが生成されないので、この方法は使えない
* 直接、型定義ファイルである`index.d.ts`へのパスを指定してあげる

# その他
* これはnode_moduleを生成しないyarnで発生する問題であり、npmでは問題なくintellisenseが使える
* `/// <reference ... />`はTypeScriptのものらしい
    * [Triple-Slash Directives (TypeScript)](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)

# 再現手順
1. 以下のコマンドを実行
    ```first.sh
    yarn create react-app my-app
    cd my-app
    volta pin node@18.15.0
    volta pin yarn@4.0.0-rc.42
    yarn add cypress --dev
    yarn run cypress open
    ```
2. Cypressの初回セットアップを行う
    1. 左の「E2E Testing」を選択
    2. Configuration filesの画面ですべてにチェックが入っていることを確認して、Continueをクリック
    3. Choose a browserの画面でChromeを選択
    4. Create your first specの画面で「Create new spec」を選択
    5. ファイル名はデフォルトのまま「Create spec」をクリック
    6. 「Great! The spec was successfully added」が表示されたら、いったんブラウザを閉じてCypressのサーバーを閉じる
3. 以下のコマンドで新規ファイルを作成して、vscodeを開く
    ```second.sh
    touch cypress/e2e/first.cy.js
    code .
    ```
4. describeと打ってみてもintellisense（予測変換みたいなもの）が出てこないのが確認できる
5. 公式ドキュメントに従って、一行目に`/// <reference types="Cypress" />`を入れる
6. describeと打ってみてもやはりintellisenseが表示されない

# 参考サイト
* [create-react-app](https://create-react-app.dev/docs/getting-started/)
    * Creating an Appを参考にしました
* [Installing Cypress](https://docs.cypress.io/guides/getting-started/installing-cypress)
    * yarn addを参考にしました
* [Opening the App (Cypress)](https://docs.cypress.io/guides/getting-started/opening-the-app)
    * cypress openを参考にしました
*
Windows の docker run のボリューム指定(-v)でカレントディレクトリを指定する方法

ちょっとハマったのでここにTIPSとして残そう。

`docker run` コマンドで、`-v`オプションでディレクトリをマウントする場合は、ホスト側は絶対パスで書く必要があります。


### Mac/Linuxの場合の例

カレントディレクトリ内のサブフォルダを指定する時、いちいちフルパスを書くのはだるいので、Linux や Mac では、下記例のように `pwd` コマンドを活用するでしょう。

```bash:Linuxの場合の例
docker run -it -v $(pwd)/bocchi:/home/bocchi somestarry/somekessokuband:latest
```

### Windowsの場合

じゃあ Windows ではどうすればいいか。
（WSL2を導入していない環境で、コマンドプロンプトでの実行を想定しています。[^whynowsl2]）

[^whynowsl2]: そういう環境でコンテナ導入の必要があったので。

こうです。

```bash:Windowsの場合の例
docker run -it -v %cd%¥bocchi:/home/bocchi somestarry/somekessokuband:latest
```

環境変数 `cd` にカレントディレクトリ情報が保持されます。そしてWindowsで環境変数を参照する場合 `%` で囲みます。
p5.jsドキュメント日本語翻訳プロジェクトへようこそ！

![p5js-website-ja-jp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60052/1ce8b7bb-9ffe-a127-c129-0a677db39197.png)

# p5.jsウェブサイト日本語翻訳プロジェクトへようこそ！
このプロジェクトでは、p5.jsの公式ウェブサイト（ https://p5js.org ）を日本語に翻訳し、より多くの日本語話者がp5.jsを利用しやすくすることを目指しています。
プロジェクトの成果物は、公式ウェブサイトにマージされることをゴールにしています。

まずは、現在の最新成果物をご覧ください！

https://p5js-i18n-ja.pages.dev/ja/

# プロジェクト立ち上げの背景

p5.jsは、プログラミング教育やGenerativeArtの作成、さらにはNFTアートやAI関連の分野でも注目を集めています。
これらをきっかけにp5.jsでプログラミングをしてみたい方が増えつつあります！

いざp5.jsの日本語ドキュメント（特にプログラミングに大事なAPIリファレンスやサンプル）のようなものを探してみると、更新が止まり、情報がバラバラになっている状況となっておりました。
これらは個々の有志の方々によって作成・管理されていたのですが、時間の経過や分量の多さもありますので、個人での対応の限界もありますし致し方ない流れだと認識しております。

とはいえ、この状況は新たに学習を進めていく方々にとっては不安要素となっているでしょう。

ですので、このプロジェクトを通じて、状況の改善（完全な翻訳の実施と、永続的な更新）をしたいと考えました。

#### ※p5js.jpとの違いについて
すでに公式サイトから日本語用の外部ウェブサイトとして https://p5js.jp があるのでは？
はい。管理者様との状況確認や擦り合わせの結果、現時点では当プロジェクトの成果やメンバーが合流するタイミングではないとのことでした。
ですので、現時点では当プロジェクトは別プロジェクトとして、下記の目標で進めています！
もちろんjpへの貢献も可能になったタイミングで何かできると良いですね！

# プロジェクトのゴール
翻訳したファイル群が**公式ウェブサイト(org)にマージされること**を目指します。
 
## ゴール実現のための目標
- p5.js公式ウェブサイトの最新版に追従し、継続的に翻訳を行えるコミュニティを構築
- p5.js公式ウェブサイトの国際化対応に不足があれば、改善貢献

## 直近の目標
- https://p5js.org の最新版を追従しながら、必要なページをすべて日本語翻訳します
- p5.js公式ウェブサイトの国際化対応に不足があれば、対応します
- 公式本体の改善提案をします。（以下は実績です）

https://github.com/processing/p5.js-website/pull/1342

https://github.com/processing/p5.js-website/pull/1346

# ぜひご参加・ご協力ください！
目標に向けて走りゴールを迎えるために、このプロジェクトのコンセプトに賛同いただけるメンバーを探しています！
フラットなコミュニティで、ぜひ一緒にご活動いただければと！

## 参加者が得られるメリット

- オープンソースへの貢献ができます。p5.js-website公式のコントリビュータになれます。
- 翻訳作業にあたって p5.js とじっくり向き合う機会も増え、説明のための動作検証などをしていくうちに、技術向上ができます。
- プログラミング技術自体の向上も伴います。
- チームでのgit運用や、gitの使い方、プルリクエストなどの経験ができます。
- 翻訳のベースは ChatGPT を使っていきます。プロンプトの工夫も学べるかも！？

## 参加方法
どのような形でも構いません。
コントリビュータとして運営や、翻訳者として翻訳作業に参加するだけでなく、レビューアとしてチェックを行ったり、SNSでの会話や応援、いいね！など、どんな形でも歓迎です。
この記事のシェアだけでも大変ありがたいです！

一緒にp5.jsウェブサイトの日本語翻訳プロジェクトを盛り上げていきましょう！

### ~~Discordコミュニティ~~ (〜2024.06、GitHubに移行しました) 
~~**p5.js i18n-ja**~~ 
~~Discordコミュニティって外から見えずらいので、ちょっと覗いてみるのを躊躇されるかもしれませんが；~~
~~ご興味ある方は気軽に入ってみてください。合わなければ、すぐに抜けていただいても全然構いません！~~

~~https://discord.gg/eAypr2BaMU~~

### コミュニティ GitHub (2024.06〜)
**GitHub p5js-i18n-ja** 
Issue, Projectにて、やりとりをして行きたいと考えております。
[p5js-i18n-ja GitHub](https://github.com/p5js-i18n-ja/p5.js-website/)
また下記Xでもお気軽にお声がけください。

### X.com
**p5js_i18n_ja** 
進捗なども流そうかと思っています。気軽にフォローください！いいね！リポスト大歓迎です！
テストサイトを見て、おかしなところをメンションいただくだけでも、ありがたいです！
ご要望などもお気軽にお聞かせくださいませ。
[@p5js_i18n_ja X.com](https://x.com/p5js_i18n_ja)

## コントリビュート
### Gitリポジトリへのプルリクエスト
- **Gitリポジトリ**

https://github.com/p5js-i18n-ja/p5.js-website/tree/i18n/ja

- **ブランチ:** i18n/ja

- **詳細はWikiをご覧ください** https://github.com/p5js-i18n-ja/p5.js-website/wiki

取り急ぎのご質問などはコミュニティ、または、Xへお問い合わせくださいませ。
- [プロジェクト X.com](https://x.com/p5js_i18n_ja)
ホラー漫画の美少女を使ってStable Diffusionのファインチューニングで遊んでみた

リアルな美少女を生成させて話題になっているStable Diffusion。

自分も好きな画像を生成させて遊んでみたいなと思いました。

# 環境
Google Colabで実行。
Colab Proだとメモリ不足になってまったので、Colab+ Proに入りました。

# ソース

Hugging Faceの学習済みモデルを使用し、「Dream Booth」という数枚の画像でファインチューニングして、画像を生成できる技術を用いました。

参考にした記事・ソース

https://www.12-technology.com/2022/10/dreambooth-stable-diffusion.html

## 流れ

以下のように一部のソースコードを編集し学習させました。

学習させたい画像をGoogle Driveのフォルダに入れる
↓
https://www.12-technology.com/2022/10/dreambooth-stable-diffusion.html より
https://colab.research.google.com/github/kaz12tech/ai_demos/blob/master/Dreambooth_demo.ipynb を開く
↓ソースコード編集
Google Driveをマウントさせて、フォルダのパスを設定
↓
手動で保存するのがめんどいので、Google Driveのフォルダに生成画像のGridを保存

# 生成画像が真っ暗になる問題

https://zenn.dev/platina/scraps/3259add3d014c9

この記事で解決できた。
推論のコードを以下のように変える。

```python
pipe = StableDiffusionPipeline.from_pretrained(
        "<学習モデル>",
        torch_dtype=torch.float16,
    )
# 以下追加↓
def dummy(images, **kwargs): return images, False 
pipe.safety_checker = dummy
pipe = pipe.to("cuda")
```


# 求めている画像を生成させるポイント

- ディープラーニングのようにたくさん画像を用意する必要はない。求めているような画像を数枚用意すればOK

たくさん画像を用意した場合...
いろんな種類の画像が生成されてしまい、求めている画像が生成される頻度が少ないです

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ホラー漫画の美少女「富江」で<br>Stable Diffusionのファインチューニングをさせてみた結果、、 <a href="https://t.co/U0In72k4WN">pic.twitter.com/U0In72k4WN</a></p>&mdash; あんはる@エンジニアFlutter Go React Rust (@_anharu) <a href="https://twitter.com/_anharu/status/1643102579071782912?ref_src=twsrc%5Etfw">April 4, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

好きな画像だけピックアップして学習させて場合...
求めているような美しい美少女が生成されました


<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ホラー漫画の美少女「富江」でStable Diffusionのファインチューニングをしてみた結果、、Part2！！<br><br>富江だ、、、、、 <a href="https://t.co/GDOrVdIG9G">pic.twitter.com/GDOrVdIG9G</a></p>&mdash; あんはる@エンジニアFlutter Go React Rust (@_anharu) <a href="https://twitter.com/_anharu/status/1643182133002002432?ref_src=twsrc%5Etfw">April 4, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 生成された画像集

https://twitter.com/_anharu/status/1643198137010647040
【Error】SQLSTATE[42S01]: Base table or view already exists

環境
Laravel9
Vue3
MySQL

# エラー

php artisan migtate実行時に下記のエラーが発生しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2784589/8ef35ca3-9600-61b8-14f6-98cfa3addcba.png)

# 原因

テーブル作成ファイルが同名の物が存在していた

# 解決策

対象のテーブル作成ファイルを片方、削除して再度コマンド入力
```
php artisan migrate:refresh
```

これで解決しました
AWS Cloud Practitioner Essentialsモジュール1 受講メモ

# AWS Cloud Practitioner Essentials モジュール1まとめ

## はじめに
AWSの勉強のために↓を受講したので、そのメモを備忘録として残す
https://explore.skillbuilder.aws/learn/course/1875/aws-cloud-practitioner-essentials-japanese-na-ri-ben-yu-shi-xie-ban


## AWSのサービス製品

AWSにはのような様々な分野のサービスがある

- ストレージ
- ネットワークセキュリティ
- ブロックチェーン
- 機械学習
- 人工知能
- etc..

## クラウドの定義

<aside>
💡 従量課金制によるインターネット経由でのITリソースのオンデマンド配信

</aside>

## クラウドコンピューティングのメリット

- ボタンワンクリックでインスタンスを増やしたり、ストレージを増やしたりできるのでオンプレミスでやる場合に加えてインフラ管理のコストが大幅に減る

- MySQLのようなストレージエンジンをインストールしたりするなどの、とても重要ではあるが手間暇がかかりどこの会社でも共通して行うような作業 → 画一的で面倒なITの作業
    
    上記のようなタスクをクラウドコンピューティングを用いてサービスとして提供することで、顧客は本当に重要なドメインモデルの研鑽などの作業に時間を使える
    

## クラウドコンピューティングのデプロイモデル

- クラウドベース
    - アプリケーションの全てをクラウド上で実行する
- オンプレミス
    - プライベートデプロイとも言う。
    - 仮想化ツールとリソース管理ツールを使用して、リソースをプライベートなコンピュータにデプロイする
    - アプリケーション管理と仮想化技術を使用して、リソースの使用率を向上させる
- ハイブリッド
    - クラウドベースのリソースを、オンプレミスで管理していたインフラストラクチャ(コンピューター群)に接続する
    - クラウドベースのリソースを、従来のITアプリケーションと統合する
[AWS] CloudFrontでCache-Controlのレスポンスヘッダーを返す

## 概要
AWS CloudFrontでレスポンスヘッダーにCache-Controlを返す方法を記載します。

## 経緯
画像をS3に置いて、CloudFrontを通してサイトに配信しています。

その際対象のサイトを `PageSpeed Insights`で計測をすると、CloudFront経由で配信しているS3の画像が「キャッシュの設定をしてくだいさい」的な感じで警告になる場合がありました。

原因はCloudFrontのキャッシュにはヒットしているのですが、レスポンスにCache-Controlが無いため、PageSpeed Insightsではブラウザキャッシュの設定をしてくださいと出ているようだと思いました。

本方法と別方法で、S3の画像にカスタムヘッダーでCache-Controleをつければ解決する方法もあるようですが、画像アップロードのたびにカスタムヘッダー情報を付けたりする方法が少し面倒でCloudFrontの設定で付けれないか試してみたのがきっかけになります。

> 参考
https://www.aws-room.com/entry/cloudfront-s3

## 設定

### CloudFrontのレスポンスヘッダーを新規作成
1. CloudFrontのレスポンスヘッダーの新規作成画面をひらきます
![](https://storage.googleapis.com/zenn-user-upload/b4678be6791c-20230303.jpg)

1. 名前・説明を入れ、カスタムヘッダーを追加します。それ以外はデフォルトのままです。
![](https://storage.googleapis.com/zenn-user-upload/5d7817fad38b-20230303.jpg)

### ビヘイビアの設定に追加する
1. CloudFrontのビヘイビア編集画面に入ります
2. レスポンスヘッダーポリシーで先程作成したポリシーを選択し、変更を保存します
![](https://storage.googleapis.com/zenn-user-upload/b9deca4614de-20230303.jpg)

## 確認
1. PageSpeed Insightsで確認する
2. ブラウザで開発ツールなどでResponse Headersを確認する
（下記画像はChrome > デベロッパーツール > ネットワークタブ > 任意の画像を選択）
![](https://storage.googleapis.com/zenn-user-upload/dab009b97859-20230404.jpg)

以上になります。
inやnot in の書き方



```diff_python
<要素> not in <集合>
not (<要素> in <集合>)
式はこんな感じ

a = [1,2,3,4,5]

print(0 not in a)
print(not(0 in a))

#1がリストに存在しているかを判別するif
if 1 in a:
    print("1が入っている！")

# not inの方が可動性が高くpep8にも沿っている
- if not 1 in a:
-   print("1は入っていません")
+ if 1 not in a:
+   print("1は入っていません")


#余談
#リストから該当する文字を含むものを抽出する
a = ["202202","202203"]

b = [i for i in a if i in "202202"]

#文頭に202202を見つける
b = [i for i in a if i.startswith("202202")]

print(b)
#->[202202]

#ただし無駄な文字が入っていた場合
a = ["hoge202202","202203hoge"]

b = [i for i in a if i in "202202"]
#=>[]
# これダメなんですね。inを使えば文字を"含むもの"を抽出してくれると思っていたので、
この場合はあまりinを使う場面ではないかもしれない。

b = [i for i in a if i.startswith("202203")]
#=>["202203"]
print(b)

```

参考：
startwith
https://itsakura.com/python-startswith
C#のタブコントロールの外観を変更する

# 1. はじめに
* C#のWindownsフォームアプリのタブコントロールの外観を変更したい

    * 変更前
      ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/5c306cdb-a5fa-6378-bd11-4479f29f050d.png)

    * 変更後
     ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/94531c01-b83a-8039-83c8-83c9bb7d7097.png)

# 2. 開発環境
* C#
* .NET 6 (Windowsフォームアプリ)
* Visual Studio 2022
* Windows 11

# 3. タブコントロールの配置
* ツールボックスからFormにTabContorlをドラッグ&ドロップする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/a5195581-7392-261d-38e2-28406429c5c5.png)

* TabControlのDockをFillに変更する(タブコントロールをフォーム全体に表示)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/d3e6f3bf-3c25-6bd0-e54c-37026cddc7d7.png)

# 4. タブコントロールのコード
* コンストラクタにタブのサイズ、横幅固定の記述を追加する
```csharp
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        // タブのサイズを変更
        this.tabControl1.ItemSize = new Size(240, 40);

        // タブの横幅を固定
        this.tabControl1.SizeMode = TabSizeMode.Fixed;
    }
}
```

# 5. 動作確認
* デフォルトのタブより外観が少し良くなった
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/be18f411-b045-ea70-558a-441daae448bb.png)

# 6. 参考文献

https://hensa40.cutegirl.jp/archives/4070


#
MySQLのJSONデータ型カラムを検索する方法

MySQLにはJSONデータ型というものがあります。

> MySQL は、JSON (JavaScript Object Notation) ドキュメント内のデータへの効率的なアクセスを可能にする、RFC 7159 によって定義されたネイティブ JSON データ型をサポートしています。
> [MySQL :: MySQL 8.0 リファレンスマニュアル :: 11.5 JSON データ型](https://dev.mysql.com/doc/refman/8.0/ja/json.html)

JSONデータ型は検索用の関数があります。何も知らず・・・likeで検索してしまいました。そんな無知を改善するため「JSONデータ型カラムを検索する方法」を書き溜めます。

# 配列内の要素の値で検索
検索条件にはJSON_CONTAINSを使います。

> **JSON_CONTAINS(target, candidate[, path])**
> 特定の candidate JSON ドキュメントが target JSON ドキュメント内に含まれているかどうか、または path 引数が指定されているかどうか (候補がターゲット内の特定のパスで見つかったかどうか) を 1 または 0 を返して示します。
> [MySQL :: MySQL 8.0 リファレンスマニュアル :: 12.18.3 JSON 値を検索する関数](https://dev.mysql.com/doc/refman/8.0/ja/json-search-functions.html)

「-> 演算子」を使ってJSONの要素を指定し、配列の中身は「[*]」で参照します。

> **column->path**
> -> 演算子は、左側のカラム識別子と JSON ドキュメント (カラム値) に対して評価される右側の JSON パスの 2 つの引数で使用される JSON_EXTRACT() 関数のエイリアスとして機能します。 
> [MySQL :: MySQL 8.0 リファレンスマニュアル :: 12.18.3 JSON 値を検索する関数](https://dev.mysql.com/doc/refman/8.0/ja/json-search-functions.html)


検索結果の列にはJSON_EXTRACTを使って見たい要素を抜き出します。

> **JSON_EXTRACT(json_doc, path[, path] ...)**
> path 引数に一致するドキュメントの一部から選択された JSON ドキュメントからデータを返します。
> [MySQL :: MySQL 8.0 リファレンスマニュアル :: 12.18.3 JSON 値を検索する関数](https://dev.mysql.com/doc/refman/8.0/ja/json-search-functions.html)

```json:[json]というjsonデータ型のカラムにこんなJSONが格納されている
{
    "no": "09xxxxx00000",
    "syurui": [
        {
            "seq": 1,
            "name": "001",
            "syuruiCd": "030"
        },
        {
            "seq": 1,
            "name": "004",
            "syuruiCd": "010"
        },
        {
            "seq": 1,
            "name": "006",
            "syuruiCd": "090"
        }
    ]
}
```

```sql:配列内のsyuruiCdが「030」を検索するSQL
-- 検索値の「'"030"'」を「'030'」にするとエラーになるのでご注意ください(Invalid JSON text in argument 2 to function json_contains: "The document root must not follow by other values." at position 1.)
select JSON_EXTRACT(json,'$.syurui[*].syuruiCd') from table_name where JSON_CONTAINS(json->'$.syurui[*].syuruiCd','"030"');
```

検索結果は以下のようになります。

|$.syurui[*].syuruiCd|
|:--|
|["002", "002", "011", "030", "030"]|
JAVA学習の記録⑨【拡張for文でリストを繰り返し処理】

前回までの学び[JAVA学習の記録⑧【コレクションフレームワーク】](https://qiita.com/rion0726ittoti/items/ff539e25f39efe075bbe)

# リストを拡張for文を使って処理する
リストと繰り返し処理を組み合わせて複数のデータを一括して処理することができる。
拡張for文の構文
```java
for (型 変数名(取り出した要素を) : リストを入れた変数名) {
    繰り返したい処理
}
```
拡張for文は、通常のfor文と違って条件式がないため、Listなどのすべての要素に対して処理を行います。

```java
import java.util.ArrayList;
import java.util.List;

public class ListLoopSample {
  public static void main(String[] args) {
    List<String> animalsList = new ArrayList<>();

    animalsList.add("犬");
    animalsList.add("猫");
    animalsList.add("猿");
    
    for (String animals : animalsList) { //リストから要素を取り出す
      System.out.println(animals);
    }
  }
}

```
出力結果
```
犬
猫
猿
```
参考文献・サイト
「いちばんやさしいJAVAの教本」著者　石井真
https://www.sejuku.net/blog/14825
[有料級]　Rails7(Turbo), Docker, Bootstrap5を使ったtodoリストの作り方第1章-1 (Todoリスト準備編)

## 前回のあらすじ
前回はBootstrap5導入編でBootstrap5の導入を行いました。
[[有料級]　Rails7(Turbo), Docker, Bootstrap5を使ったtodoリストの作り方第0章-2(Bootstrap5導入編)](https://qiita.com/streasure0426/items/fe3ae095ab1920dd3e56)
今回は本当にBootstrapが導入できているか確かめるところから始めたいと思います。

## 事前準備
Dockerを一度止めている方はこちらのコマンドでDockerを起動し、コンテナに入りましょう。
``` bash
docker-compose up -d
docker-compose exec app bash
```

## こちらの記事をスキップしたい方へ
これからTodoリストを作るのですが、作り方がわかる方はこちらのコマンドを打ってすぐに終わらせましょう！
``` bash
rails g scaffold Todo title:string description:text
rails db:migrate
```

こちらのコマンドを打つとこのように基本的なtodoリストを作ってくれます
![スクリーンショット 2023-04-04 18.15.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982962/1ada66cc-fde6-20e7-868a-61703cc43b1d.png)

## モデルの作成
まずはTodoモデルから作成していきます。
``` bash
rails g model Todo
```
コマンドを入力すると次のようなファイルは作成されます
``` bash
      invoke  active_record
      create    db/migrate/20230404091949_create_todos.rb
      create    app/models/todo.rb
      invoke    test_unit
      create      test/models/todo_test.rb
      create      test/fixtures/todos.yml
```

こちらで作成したマイグレーションファイルをデータベースに反映させます
``` bash
rails db:migrate
```

これでモデルの作成は完了です。

## コントローラーの作成
次にコントローラーを作成していきます
下記のコマンドを入力することでコントローラーが作成されます
``` bash
rails g controller todos
```

コマンドを入力すると次のようなファイルは作成されます
``` bash
      create  app/controllers/todos_controller.rb
      invoke  erb
      create    app/views/todos
      invoke  test_unit
      create    test/controllers/todos_controller_test.rb
      invoke  helper
      create    app/helpers/todos_helper.rb
      invoke    test_unit
```

まずはtodoリストの一覧表示から作りたいのでコントローラーでindexアクションを定義していきます。
``` ruby:app/controllers/todos_controller.rb
class TodosController < ApplicationController
  def index
  end
end

```
これでコントローラーの作成は完了です。

## ルーティングの設定
今回はtodoリストを作りたいのでtodoリストに必要なルートを一括で設定したいと思います。
``` ruby:config/routes.rb
Rails.application.routes.draw do
  root to: 'todos#index'
  resources :todos
end
```
`resources :todos`とすることによってtodoリストに必要なルートを作成してくれます。
`root to: 'todos#index'`とすることによってrailsを起動した際に一番最初にtodoのindexアクションがよばれるようになります。

## Viewファイルの作成
まずこちらのコマンドでtodosディレクトリの中にindexのファイルを作成します
``` bash
touch app/views/todos/index.html.erb
```

ファイルが一通り作成できたので、`index.html.erb`が表示されるか検証してみましょう。
`index.html.erb`に以下のコードを書いてみます。

```erb:index.html.erb
<h1>Indexのページです</h1>
```

書き終わったら
http://127.0.0.1:3050
こちらのURLにアクセスしてみましょう。
![スクリーンショット 2023-04-04 18.45.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982962/b9b3ee75-8428-bfe8-9a39-178212d88098.png)
こちらの画像のようなページが表示されたら成功です。

## Bootstrapの検証
前回導入したBootastrapが導入されているか検証してみます。
先ほど作成した`index.html.erb`にbootstrapのコードを追加してみます。
```erb:index.html.erb
<h1>Indexのページです</h1>
<h1 class="text-danger">Textの色が赤くなります</h1>
```
書き終わったら
http://127.0.0.1:3050
こちらのURLにアクセスしてみましょう。
![スクリーンショット 2023-04-04 18.51.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982962/4d2ef088-2595-bdb7-2d48-e84f9c752ed3.png)
こちらの画像のようにTextの色が赤くなったらboostrapが導入されています。

今回はここまで！
続けて読んでもらうとありがたいです！

[[有料級]　Rails7(Turbo), Docker, Bootstrap5を使ったtodoリストの作り方第1章-2(Todoリスト一覧表示編)](https://qiita.com/streasure0426/items/180e0954a2af402b4a8c)
CPLEXでC#のプログラムからLPファイルなどを書き出す。

CPLEXのC#のAPIでInteractive Optimizerで利用できるLPファイルなどを書き出すことができます。
Interactive Optimizerを使うとC#本体のプログラムを実行しなくても現象を再現できるため、問題判別に有効です。

https://www.ibm.com/docs/ja/icos/20.1.0?topic=applications-using-interactive-optimizer-debugging

* テスト環境
    * CPLEX 22.1.1
    * Microsoft Visual Studio Community 2022 Version 17.4.4
    * Windows 11 64bit

# LPファイルなどの書出し


以下のチュートリアルのソースコードを書き換えて、LPファイルなどを書き出します。

https://github.com/hkwd/230404cplex/blob/master/230404cs_sample/Dietlesson.cs

具体的にはL189-200を書き換えました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/45be73bc-8de9-a2a5-b85f-d16ccdf0e866.png)

以下は１つ解を見つけたら終了するパラメータを指定しています。
```cs:SetParam
cplex.SetParam(Cplex.Param.MIP.Limits.Solutions, 1);
```
#### 参考:MIP 整数解の制限
https://www.ibm.com/docs/ja/icos/20.1.0?topic=parameters-mip-integer-solution-limit

以下はExportModelで最適化モデル出力しています。モデルファイルの形式はlpとsavがあり、どちらも同じ内容ですが、lpはテキストで、savはバイナリです。lpはテキストエディターで中身を確認できます。savの方が読込が速いです。
WriteParamはパラメーター情報をファイルに出力します。
Solveで求解します。Cplex.Param.MIP.Limits.Solutions, 1を設定したので最初に解が一つ見つかったら終了するようにしています。

```cs:ExportModel
cplex.ExportModel(lpFileName + ".lp");
cplex.ExportModel(lpFileName + ".sav");
cplex.WriteParam(lpFileName + "1.prm");
cplex.Solve();
```

次に最初に見つかった解をWriteMIPStartsでmstファイルに保存しています。このファイルがあると、この解を初期解にして求解をするMIPStartが可能です。

```cs:WriteMIPStarts
cplex.WriteMIPStarts(lpFileName + ".mst");
```

#### 参考:MST ファイル・フォーマット: MIP 開始
https://www.ibm.com/docs/ja/icos/20.1.0?topic=cplex-mst-file-format-mip-starts

以下は解を1000個見つけたら終了するパラメータを指定しています。

```cs:SetParam
cplex.SetParam(Cplex.Param.MIP.Limits.Solutions, 1000);
cplex.WriteParam(lpFileName + "2.prm");
```

WriteSolutionは解をXML形式のファイルで書き出します。

```cs:WriteSolution
cplex.WriteSolution(lpFileName + ".sol");
```

完成したソースコードは以下になります。

https://github.com/hkwd/230404cplex/blob/master/230404cs_interactive/DietlessonLp.cs

実行すると以下のようにlpファイルなどが出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/dc7d8d11-bd37-3aab-31a5-661d4f8a9ca2.png)

実際に出力されたファイルは以下に置きました。

https://github.com/hkwd/230404cplex/tree/master/230404cs_interactive
Power Appsから作成したPower BIのダッシュボードを表示したいが、「タイル」が接続できない場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/524a1711-8323-8330-bb8c-5a5973259f3c.png)
ご利用のPower BIタイルはまだ接続されていません。「接続先となるワークスペース、ダッシュボード、タイルを選択します。」というメッセージが出力されています。

この、メッセージが出力される前に以下の手順を行いました。:cry:
【Power　Appsで作業】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/1890fd0a-6e2a-14ac-bbad-dd72aebd87ee.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/a5630760-3305-9e89-ea65-a8aa4b362aa0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/037143bf-d6a3-282d-8c2a-038a7d50d264.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/1c118c54-1191-c189-3800-756be0230d4f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/d769be4d-402e-7c25-9611-0da6e4222c49.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/91d2faf9-aa19-af3b-b9a7-d46b8156fa8a.png)
Power BIで作成したダッシュボードをPower Apps上、表示したいと思い、
＋挿入＞[グラフ]タブ配下のPower BIタイルを押下し、「データ」windowから
ワークスペースとダッシュボードは選択できましたが、タイルが選択できない状態になっています。
（タイルを選択したいですが、非活性になっていますよ？！:fearful:）

Power BIタイルが接続されていないため、以下の方法で解消できました。
【Power BI proで作業】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/46836d9f-a507-3531-3ce6-6d39e7d2691c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/7ea05324-14d1-3cf1-cd73-116b7c4b2800.png)
【Power　Appsで作業】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114135/ca0a7eb6-e467-e8cf-0d54-fe22547f0747.png)

これで、作成したPower BI　proのダッシュボードをPower Apps上埋め込むすることができました！:relaxed::v:

＋参考のため、URL形式をここにもう一回記入します。Power BI proから取得したURLのダッシュボードID、タイルIDをタイルURL形式で利用します。:writing_hand_tone1:
【Power BI proから取得したURL形式】（【Power BI proで作業】の④の手順で登場します。）
"https://app.powerbi.com/groups/グループID/dashboards/ダッシュボードID/tiles/タイルID/infocus"

【Power Appsで利用するタイルURL形式】（【Power　Appsで作業】の⑥の手順で登場します。）
"https://msit.powerbi.com/embed?dashboardId=ダッシュボードID&tileId=タイルID"
[CPS変換]継続ってパイプライン的な何かだなと思ったらなんかわかってきた4

まとめ： **callCCも、もっと簡単に書ける!** うれしい!...けど?

[前の記事](https://qiita.com/ttatsf/items/7d80827b2cfa59327edd)で、もっと簡単にパイプラインの中で分岐できるってのが分かったんだけど、振り返ってみたら、これってcallCCももっと簡単にできるってことだよなあ、って気付いたんでやってみます。
[前の前の記事](https://qiita.com/ttatsf/items/ea4e75b0882bd728f1ec)の焼き直しです。


```js:
const ret = x =>
  k => k(x)

const evalCont = x => x

const callCC = f =>
  m => f( jumpTo(m) )( m )

const jumpTo = m =>
  x => k => k !== m ? jumpTo(m)(x) : m(x) 
```
独自の pipe 関数も、 bind も定義しません。 よく考えてみれば、そもそも 継続自体がパイプラインであり、bind なんじゃないの?って気づいちゃったんで。

jumpTo のところが変っています。これで callCCの中では kを無視して、外に出たら普通に戻るようになります。


どうなるか使ってみます。

```js:
const g = x =>
  callCC(exit => (x === 0 ? exit(x): ret(x))
         (x => ret(x + 1))
         (x => ret(x + 1))
        )
  (x => ret(x + 1))
  (evalCont)

g(0)  // 1
g(1)  // 4
```
ちゃんと動いた!

フィボナッチ数列もやってみます。

```js:
const fibCPS = n =>
  callCC(exit => (n < 2 ? exit(1) : fibCPS(n - 2))
                  (a => fibCPS(n - 1)
                        (b => ret(a + b))
                  )
        )

fibCPS(5)(evalCont) // 8
```
ちゃんと動いてる! やったね。

# と思ったんだけど...
落しあながあります。callCCの中と外で同じ関数を使いまわすとちゃんと動きません。

```js:
const incCPS = x =>ret(x +1)
const g = x =>
  callCC(exit => (x === 0 ? exit(0): ret(x))
         (incCPS)
         (incCPS)
        )
  (incCPS)
  (evalCont)

g(0)  // 1 になってほしいのに 3 になっちゃう...
g(1)  // 4
```
jumpToで関数が同じかどうかで判断してるから、そりゃそうなるわ。いい考えだと思ったんだけどなあ...

# 何とかしてみる
泥縄式ですが対策はないことはないです。何か他とかぶらないもので callCC の出口を教えてやればよい。
Symbol を使ってみます。

```js:
const Extract = Symbol("extract")

const ret = x => 
  k => k === Extract ? m => m(x) : k(x)

const evalCont = 
      x => x

const callCC = c => 
  c(Extract)

const exit = x => 
  k => k === Extract ?  m => m(x) : exit(x) 
```
何かかえってすっきりしたかも?

まず、callCC の 終わりを教える Extract を定義しました。
callCC は 継続のいれもの内の値 を (本来継続ではない)Extract に渡そうとします。
ret と exit は、Extract を受けとったら、Extract は捨てて、その次の継続に値を渡そうとします。
jumpTo は、もう callCC の継続を束縛しなくてもよくなったので、 直接 exit として定義します。

今度はうまく行くかな?

```js:
const incCPS = x =>ret(x + 1)

const g = x =>
  callCC((x === 0 ? exit(0) : ret(x))
         (incCPS)
         (incCPS)
        )
  (incCPS)
  (evalCont)

g(0)  // 1 
g(1)  // 4
```
うん、ちゃんと動いてる。やったね!

# もうちょっと何とかしてみる

この見た目なら人類が普通にcallCCを使う日も近いんじゃないでしょうか?
基本これで、ちょい改良します。
このシステムで始終使うret の中で比較をしてるので速度がだいぶ落ちるってのがあります。
ret は元に戻して他の部分を工夫します。

```js:
const SkipOut = x => ret(x)  

const ret = x => 
  k => k(x)

const evalCont = 
      x => x

const callCC = c => 
  c(SkipOut)

const exit = x => 
  k => k === SkipOut ? ret(x) : exit(x) 
```
Symbolだったところを関数にして、独自性だけが欲しかったところを機能も使うようにしました。

ret(x) は SkipOut の関数の機能を使って、ret(x) を返します。
exit(x) は SkipOut かどうかを判断して ret(x) か exit(x) を返します。

これもちゃんと動くようです。

```js:
const incCPS = x =>ret(x + 1)

const g = x =>
  callCC((x === 0 ? exit(0) : ret(x))
         (incCPS)
         (incCPS)
        )
  (incCPS)
  (evalCont)

g(0)  // 1 
g(1)  // 4
```
これがいいかな?
Python環境構築



# Python 環境構築

### Homebrewインストール

HomebrewはmacOS上で動作するパッケージ管理ツールで、このHomebrewを用いて様々なパッケージをインストールすることができます。

[こちら](https://brew.sh/index_ja)からインストール可能

以下を実行すればOK

```
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

### (1) pyenvのインストール


**pyenv** : Pythonのバージョン管理を行なうコマンドラインツールで、複数のバージョンのPythonのインストールや切り替えを容易に行うことができます。

シェルの確認
※シェル : 簡単に言うと私たち人間が入力した情報をコンピュータさんに伝えてくれるプログラム。bashrcはbashを起動した時に読み込まれる設定ファイル


```
$ echo $SHELL
/bin/bash
```

bashの方

```bash
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc 
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc 
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc
$ source ~/.bashrc
```

zshの方
※最近のMacはzshがデフォルト。ライセンス周りでbash→ zshの流れになった？

```bash
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc 
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc 
$ echo 'eval "$(pyenv init -)"' >> ~/.zshrc
$ source ~/.zshrc
```



##### インストールできるパッケージ一覧表示

```bash
$ pyenv install -l
or
$ pyenv install --list
```

##### Pythonのインストール

最新のパッケージだと安定していない可能性があるので少し古い方が良いかと。

```bash
$ pyenv install 3.10.0
```

##### インストールされているバージョンの確認

```
$ pyenv versions
```

##### バージョンの切り替え

```
$ pyenv global 3.10.0
```

### (2) 仮想環境


**virtualenv** : 一つのシステムの中に分離されたPythonの仮想環境を作ることができるソフトウェアです。コマンドで仮想環境を簡単に作成できます。


##### 仮想環境作成
```
$ python -m venv venv
```


##### 仮想環境の有効化

```
$ source venv/bin/activate
```

##### 仮想環境の無効化

```
$ (venv) deactivate
```
Httpie: ChatGPT Speech to text の使い方

こちらと同じことを Httpie で行いました。
[Python3: ChatGPT Speech to text の使い方](https://qiita.com/ekzemplaro/items/835e6e1621e8c9123e8e)
音源も同じです。

## スクリプト

```bash:httpie_text.sh
API_KEY="sk-11**********************"
http --multipart POST \
  https://api.openai.com/v1/audio/transcriptions \
  'Authorization: Bearer '$API_KEY \
  file@'./yonendowa1_08_rappa_niimi.mp3' \
  model=whisper-1 > out01.json
```

実行方法

```bash
./httpie_text.sh
```

## 実行結果

```json:out01.json
{"text":"LibriVox.orgのために録音されました。 ラッパ おじいさんは田舎に一人ぼっちで暮らしていました。 だんだん耳が遠くなったので、 都にいる息子のところへ 耳が遠くなったから よく聞こえる機械を買って送ってくれ と言ってやりました。 息子はいろいろ探したが あまり良い機械がないので ラッパを買って これを逆さまにして耳につけて ラッパの尻から聞きなさい と言ってやりました。 おじいさんはそのラッパを喜んで使っていましたが 耳はどんどん遠くなって とうとうつんぼになりました。 それでもおじいさんは あのラッパでは聞こえなくなったから もっとよく聞こえるのを 送ってくれと息子に手紙をやりました。 息子はいろいろ考えてから また前のラッパと同じラッパを送って このラッパを耳につけると 私の声が聞こえます と言ってやりました。 おじいさんはそのラッパを耳に当ててみました。 そして ああ、聞こえる、聞こえる 懐かしい息子の声が聞こえる と言って喜びました。 けれど、それはおじいさんの耳が聞いたのではありません 心が聞いたのです。 ラッパ終わり この録音はパブリックドメインです。"}
```
Next.jsとAnt Designを一緒に使うとFOUC(読み込み時の表示崩れ)が発生する場合の対処法

# Next.jsとAnt Designを一緒に使うとFOUCが発生する場合の対処法

GitHubのIssueにも上がっています｡

https://github.com/ant-design/ant-design/issues/39891

以下の`_document.tsx`に書き換えることで直せます｡

https://github.com/sagemathinc/cocalc/blob/master/src/packages/next/pages/_document.tsx

```tsx
/*
This custom document is needed to workaround this bug in antd + nextjs:

    https://github.com/ant-design/ant-design/issues/38767

The actual fix -- i.e., this entire file -- comes from

    https://github.com/ant-design/ant-design/issues/38767#issuecomment-1350362026

which is for a different bug in antd + nextjs, but it happens to fix
the same problem, and fortunately also works with the older nextjs 12.x, which
we are currently stuck with.

See also the discussion at https://github.com/ant-design/ant-design/issues/39891
*/

import type { DocumentContext, DocumentInitialProps } from "next/document";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs";

export default class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    const cache = createCache();
    const originalRenderPage = ctx.renderPage;

    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App) => (props) =>
          (
            <StyleProvider cache={cache}>
              <App {...props} />
            </StyleProvider>
          ),
      });

    const initialProps = await Document.getInitialProps(ctx);

    return {
      ...initialProps,
      styles: (
        <>
          {initialProps.styles}
          {/* This is hack, `extractStyle` does not currently support returning JSX or related data. */}
          <script
            dangerouslySetInnerHTML={{
              __html: `</script>${extractStyle(cache)}<script>`,
            }}
          />
        </>
      ),
    };
  }
  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}
```
C#でタブレット向け在庫管理システムを構築する

# 在庫管理システムの考え方
今回は簡単なシステムが作れれば良いので、定期的に在庫確認をし、入力するだけです．

# 苦労した点
操作性を良くするため、該当のボタンを押すとテンキーを表示し、入力された数字をボタンに表示する仕様にしました．
複数のボタンが必要となるため、ボタンをループで自動生成しておりますが、自動生成したボタンにイベントを持たせる点に苦労しました．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3087800/d70101dd-8e47-8721-d0ac-6bd905703b46.png" width="400">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3087800/7927706c-e2e0-aa04-f3e0-9483787349fe.png" width="533">

# 動作
スクロールさせると動作が重たくなりアプリが落ちることがあったため、1画面に表示できるアイテムの上限を設け、ページを切り替える方式にしました．
背面のPanelにbackground imageを設定しているので、それが重くなる原因かと思われます．

# データベース
Excelに保存し、ClosedXMLを使用して読み書きをしています．
いつかはSQLを使いたいところです...

# コントロールの自動生成(例)

labelとButtonを自動生成し、Buttonにクリックイベントを持たせます．
(イベントハンドラ=KeypadShow_Click)

```csharp
using ClosedXML.Excel;

XLWorkbook workbook = new XLWorkbook("Inventory.xlsx");
IXLWorksheet worksheet = workbook.Worksheet(1);
int lastRow = worksheet.LastRowUsed().RowNumber();

AUTOlabel = new Label[lastRow];
AUTObutton = new Button[lastRow];

for (i = 1; i <= lastRow; i++)
{
    AUTOlabel[i] = new Label();
    AUTOlabel[i].Name = "AUTOlabel" + j;
    AUTOlabel[i].BackColor = Color.Transparent;
    AUTOlabel[i].ForeColor = Color.Black;
    AUTOlabel[i].Font = new Font("MS UI Gothic", 13, FontStyle.Bold);
    AUTOlabel[i].TextAlign = ContentAlignment.MiddleLeft;
    AUTOlabel[i].Text = i.ToString();
    AUTOlabel[i].Location = new Point(3, i * 57 - 54);
    AUTOlabel[i].Size = new Size(291, 53);
    AUTOlabel[i].Visible = true;

    AUTObutton[i] = new Button();
    AUTObutton[i].Name = "AUTObutton" + j;
    AUTObutton[i].BackColor = Color.DarkGreen;
    AUTObutton[i].ForeColor = Color.White;
    AUTObutton[i].Font = new Font("MS UI Gothic", 13, FontStyle.Bold);
    AUTObutton[i].TextAlign = ContentAlignment.MiddleCenter;
    AUTObutton[i].Text = i.ToString();
    AUTObutton[i].Location = new Point(300, i * 57 - 54);
    AUTObutton[i].Size = new Size(99, 53);
    AUTObutton[i].FlatStyle = FlatStyle.Popup;
    AUTObutton[i].Visible = true;
    AUTObutton[i].Click += new EventHandler(KeypadShow_Click);
    AUTObutton[i].Tag = AUTObutton[j].Name;
}
```

クリックイベントの記述です．
変数"tagNum"にクリックされたボタンのタグが代入されます．
これにより、ボタンごとの処理を変えることができます．

```csharp
string select;
private void KeypadShow_Click(object sender, EventArgs e){
    var tagNum = ((Button)sender).Tag;
}
```
pip3 install pyaudio時にエラー（`src/pyaudio/device_api.h:7:10: fatal error: Python.h: そのようなファイルやディレクトリはありません`）

pip3 install pyaudio時にエラー（`src/pyaudio/device_api.h:7:10: fatal error: Python.h: そのようなファイルやディレクトリはありません`）

```
$ pip3 install pyaudio
Collecting pyaudio
  Using cached PyAudio-0.2.13.tar.gz (46 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: pyaudio
  Building wheel for pyaudio (pyproject.toml) ... error
  error: subprocess-exited-with-error
  
  × Building wheel for pyaudio (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [19 lines of output]
      running bdist_wheel
      running build
      running build_py
      creating build
      creating build/lib.linux-x86_64-cpython-310
      creating build/lib.linux-x86_64-cpython-310/pyaudio
      copying src/pyaudio/__init__.py -> build/lib.linux-x86_64-cpython-310/pyaudio
      running build_ext
      building 'pyaudio._portaudio' extension
      creating build/temp.linux-x86_64-cpython-310
      creating build/temp.linux-x86_64-cpython-310/src
      creating build/temp.linux-x86_64-cpython-310/src/pyaudio
      x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -fPIC -I/usr/local/include -I/usr/include -I/home/ubuntu/venv/python3.10-test/include -I/usr/include/python3.10 -c src/pyaudio/device_api.c -o build/temp.linux-x86_64-cpython-310/src/pyaudio/device_api.o
      In file included from src/pyaudio/device_api.c:1:
      src/pyaudio/device_api.h:7:10: fatal error: Python.h: そのようなファイルやディレクトリはありません
          7 | #include "Python.h"
            |          ^~~~~~~~~~
      compilation terminated.
      error: command '/usr/bin/x86_64-linux-gnu-gcc' failed with exit code 1
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for pyaudio
Failed to build pyaudio
ERROR: Could not build wheels for pyaudio, which is required to install pyproject.toml-based projects
```

### 解決策

ビルド時に`Python.h`が足りないということで、どこかから`Python.h`を持ってきて無理やりビルドして解決。（したが、`pyaudio`が一見すると正しく使える状態にはなったが、少々怪しいと思うのでこの方法の正式な解決方法をどなたか伝授頂けますと幸いです。。）
Cloud9からGithubへのpush方法の備忘録

# Cloud9でcloneしたローカルリポジトリからGithub上のリモートリポジトリへのpush方法  
  
## ①経緯  
スクールの課題の中で、「GitHub上で作成したリポジトリをCloud9にcloneしてファイルを作成し、pushしましょう。」という内容のものがありました。今までIntelliJで作成したローカルリポジトリをGitHubに共有する手法を用いてきたので、cloneはあまり使用してきませんでした。そんな中でpushがうまくいかずに積みまくったので自分用に備忘録として書いておきます。  
  
## ②問題のポイント  
まずは問題のポイントまで順を追って説明します。 
ここの手順は失敗に終わりますので③まで飛ばしてもOKです。 

### 2-1.リポジトリの作成  
まずはGitHub上でリポジトリを作成します。  
「Your repositories」の「New」から作成しましょう。  
特にこれといった操作は必要ないですが、今回は「Public」のリポジトリを作成します。  

### 2-2.リポジトリのclone→push  
作成したリポジトリの「HTTPS の url」をコピーしましょう。  
コピーしたurlをCloud9上のターミナルでcloneします。  
```  
$ git clone < HTTPS の url >
```  
今回はGitHubの指定通りの方法でpushまで進めてみましょう。  
```  
echo "# test" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin < HTTPS の url >
git push -u origin main
```  
上記のコマンドはREADME.mdを作成し最初のコミットを行うのが目的なのですが、おそらく最後のgit pushを行った際に、名前とパスワードの入力が求められます。  
私の場合、ここで名前とパスワードを入力してもpushが拒否されました。エラーメッセージを見てみると、「GitHubでは2021年8月13日以降、パスワード認証はサポートされていません。代わりに、アクセストークンを使用してリモートリポジトリに認証する必要があります。」的な文章が表示されました。ここで色々と試してみましたが結局pushすることはかないませんでした。もちろん方法はあると思いますが、私の場合うまくいかな過ぎて「よし、最初からやろう！」となりました。  

## ③解決の方法  
結論からいうとSSHキーを作成する方法がすんなりいけたような気がします。  
以下で順に操作を書いていきます。  

### 3-1.SSHキー作成の準備  
```  
cd ~/.ssh //SSH鍵を保存するためのフォルダーに移動します。
ssh-keygen //SSH鍵を生成するためのコマンドを実行します。途中でパスフレーズを入力するよう求められますが、パスフレーズを使用しない場合はそのままEnterキーを押してください。
cat ~/.ssh/id_rsa.pub //作成したSSH鍵の公開鍵を表示するためのコマンドを実行します。  
```  
最後のコマンドで表示される長い文字列がSSHキーを作成するうえで必要になります。先頭から文末から1つ目の空白までをコピーしましょう。この公開鍵をGitHub等のリモートリポジトリに登録することで、SSH接続によるリモートリポジトリへのアクセスが可能になります。  
  
### 3-2.SSHキーの作成  
GitHub上でSSHキーを作成しましょう。  
アイコンの「Settings」→「SSH and GPG keys」へと移動し、「New SSH key」を選択しましょう。「title」には自分がわかりやすい名前を付けましょう。「Key type」は特に設定は行いませんでした。「Key」の箇所に先ほど発行した長い文字列を入力しましょう。ここまで入力して「Add SSh key」を押せばSSHキーの完成です。  
  
### 3-3.リポジトリのclone→push(リベンジ)  
SSHキーを作成したので再度cloneまで行っていきましょう！...といっても手順は上記の2-2.と基本的には変わりません。  
```  
$ git clone < SSH の url >
```  
そう、使用するurlがSSH用に変わっただけです。  
```  
echo "# test" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin < SSH の url >
git push -u origin main
``` 
今回は問題なくpushが成功すると思われます。　　

## ④まとめ  
今回はSSHキーの発行による認証についてまとめてみました。  
あくまで自分の場合このようにすればうまくいった、という感じなので「もっと簡単な方法あるよ！」とか「間違ってるよ！」などあればご指摘いただけると幸いです。
Ubuntu 22.04 LTS Server + VirtualBox 備忘録

ちょっとした実験に使う VirtualBox + Ubuntu 22 Server LTS の備忘録。

## VirtualBox 側設定

* 現在 VirtualBox 7 で普通に USB が使えないので注意: https://forums.virtualbox.org/viewtopic.php?f=8&t=107333
    * Intel Mac なら VirtualBox 6.1 を使う。
    * M1 なら `sudo virtualbox` すると良いらしい。
* VirtualBox インストール後 Security & Privacy で Oracle を許可した後リブートが必要。
    * driver not installed rc=-1908 のエラーが消えないなら更にもう一度リブートしたらなぜか直った。
* インストール時にストレージが全部割り当てられないので、インストーラで割り当て直す。
    * Storage Configuration > USED DEVICES > EDIT
* イントールし終わったら、ネットワーク設定を変更
    * ホスト側の VPN を使いたい時があるので次のように設定している。参考: https://qiita.com/propella/items/39818d8e59e0163775d7
        * Adapter 1: NAT
        * Adapter 2: Bridged Adapter
* VirtualBox Guest Additions のインストール
    * Devices > Insert Guest Additions CD Image ...

    ```shell
    sudo mount /dev/cdrom /media
    sudo apt update
    sudo apt install -y libxt6 libxmu6 build-essential linux-headers-$(uname -r)
    sudo /media/VBoxLinuxAdditions.run
    ```

## Ubuntu ネットワーク設定

ネットワークインターフェース名の確認

    ip link

２つ目のネットワーク設定追加。本当は DHCP したかったが VirtualBox 6.1 では動かなかった。7 だと動くかも。

```
$ sudo vi /etc/netplan/00-installer-config.yaml 

network:
  ethernets:
    enp0s3:
      dhcp4: true
    enp0s8:
      dhcp4: false
      addresses: [192.168.100.200/24]
      routes:
        - to: default
          via: 192.168.100.1
      nameservers:
        addresses: [8.8.8.8,8.8.4.4]
  version: 2
```

適用

    sudo netplan apply

netplan が /run/systemd/network/ にある設定を更新して、systemd-networkd によってネットワーク状態が変化する。逆に設定を削除した時は netplan apply だけで反映されない時は systemd-networkd を再起動する。

    sudo systemctl restart systemd-networkd


## ホスト名と mDNS の設定

インストール後ホスト名を付け直す最近の方法は hostnamectl らしい。`/etc/hostname` も連動して変わる。この時ホスト名に . (ドット) など mDNS に使えない文字を入れないようにする。

    hostnamectl hostname ホスト名

IP が変わっても名前でアクセスできるように mDNS も設定する。

```shell
sudo apt install -f avahi-daemon avahi-utils
sudo systemctl restart avahi-daemon
```

mDNS の設定は `/etc/avahi/avahi-daemon.conf` にあるが、無設定で勝手に `/etc/hostname` を読んで ホスト名.local という名前で解決できるようになる。

参考

* [Multicast DNS Names](https://datatracker.ietf.org/doc/html/rfc6762#section-3)
* [Ubuntu Manpage: hostnamectl - Control the system hostname](https://manpages.ubuntu.com/manpages/jammy/man1/hostnamectl.1.html)


## SSH パスワード認証の無効化

`cp ~/.ssh/id_ed25519.pub 192.168.xxx.yyy:.ssh/authorized_keys` で公開鍵を置いた後、パスワード認証を無効にする。

```
$ sudo vi /etc/ssh/sshd_config.d/50-cloud-init.conf

PasswordAuthentication no
ChallengeResponseAuthentication no

$ sudo systemctl restart sshd
```

ここで cloud-init というのに気づいた。YAML で Linux を設定出来るツールらしいが今回は深堀りしない。

## トラブルシューティング

DHCP が本当に動いているか不安な時に、DHCP クライアント動作確認を行う。

    dhclient -d enp0s8

DHCP が動かない時、VirtualBox の再インストールで直る時がある。