- 投稿日:
adiaryをnginxの上で動作させつつ開発する方法をメモしておく。
前提条件
動作URLとしてはhttps://hoge.example.com/adiary
のようなサブドメイン付きのサブディレクトリとする。
adiaryはネイティブでfcgiをサポートしているらしいが、そこは無視して一般的な環境で起動するのがゴール。
SSLで動かす設定を書いているため、nginxでローカル開発環境向けのhttps環境を作るも参考にすること。
特別な設定は不要で、サブドメインモードにしなくとも動く。
確認環境
Env | Ver |
---|---|
OS | Ubuntu 22.04.3 LTS |
nginx | 1.24.0 |
fcgiwrap | 1.1.0-12 amd64 |
adiary | 3.50n |
手順
nginxはインストールされており、既に稼働している状態として話を進める
# adiaryを格納するディレクトリを作成する
sudo mkdir -p /usr/share/nginx/html/sites
# 既にどっかに展開済みのadiaryをディレクトリに突っ込む
sudo cp -R adiary /usr/share/nginx/html/sites
cd /usr/share/nginx/html/sites
# 所有権とパーミッションの調整
sudo chwon -R $USER:$USER adiary/
cd adiary/
sudo chmod -R 777 __cache/
sudo chmod -R 777 data/
sudo chmod -R 777 pub/
# Image::Magickを入れる
sudo apt install -y libimage-magick-perl
# nginxでCGIを動かすために、fcgiwrapをインストールする
sudo apt install fcgiwrap
# /etc/nginx/conf.dに設定ファイルを作成
cat <<'EOF' | sudo tee -a /etc/nginx/conf.d/hoge.example.com.conf
server {
listen 443 ssl;
server_name hoge.example.com;
ssl_certificate ssl/hoge.example.com.pem;
ssl_certificate_key ssl/hoge.example.com-key.pem;
access_log /var/log/nginx/hoge.example.com.access.log;
error_log /var/log/nginx/hoge.example.com.error.log;
# マルチテナント用にこう書いている
root /usr/share/nginx/html/sites;
# /usr/share/nginx/html/sites/adiaryにadiary一式が入っている想定
location / {
index index.html;
}
location /adiary {
index adiary.cgi;
}
# fastcgi用
location ~ \.cgi$ {
gzip off;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
EOF
# nginxの設定を再読み込みする
sudo service nginx restart
トラブルシューティング
403エラーで起動せず、エラーログにパスがないと出る
以下のコマンドを叩き、パスが整合するまで設定を調整する
sudo strace -f -e trace=file -p $(pidof fcgiwrap)
- 投稿日:
インストール
https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script
wget https://dot.net/v1/dotnet-install.sh
chmod 755 dotnet-install.sh
./dotnet-install.sh
echo 'export DOTNET_ROOT=$HOME/.dotnet' >> ~/.zshrc
echo 'export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools' >> ~/.zshrc
Hello world
https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet
dotnet new console -o myapp
dotnet build myapp/myapp.csproj
./myapp/bin/Debug/net6.0/myapp
- 投稿日:
公式のドキュメントには書かれていない気がするがDockerだけあればセットアップ可能。WSL2でのセットアップが面倒なVargantは不要
本記事ではWSL2内のUbuntuの中でDockerとDocker Composeを使った構築法について記述する。Windows向けのDocker Desktopは使用しない
前提
- WSL2のUbuntuにVSCodeで接続して作業
- 起動してログイン、トゥート、設定と言った最低限の画面操作が可能な程度まで
確認環境
Env | ver |
---|---|
Mastodon | v3.5.3 |
Docker | 20.10.21, build baeda1f |
Docker Compose | v2.12.2 |
Ubuntu | 20.04.5 LTS |
WSL2 | 1.0.3.0 |
DevContainer | v0.266.1 |
VSCode | 1.73.0 |
手順
git clone https://github.com/mastodon/mastodon.git
- 最新版などバージョン指定が必要な場合は追加で
tag
をチェックアウトする
- 最新版などバージョン指定が必要な場合は追加で
- VSCodeでClone先を開く
- Reopen Containerする
- セットアップが走るので暫く待つ
foreman start
を流す- http://localhost:3000/ へアクセス
操作方法
ユーザーを作ったりしたい場合、Using the admin CLIが参考になる
一例としてユーザー作成は次の書式で行ける
./bin/tootctl accounts create foo --emaill foo@example.com --confirmed
トラブルシュート
DB接続エラーが出る
ActiveRecord::ConnectionNotEstablished at /
could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/var/run/postgresql//.s.PGSQL.5432"?
上記のエラーが出る場合、ビルドしたDockerイメージが壊れてるので以下のコマンドで全て消して作り直すとうまく行く
docker rm -f `docker ps -a -q`
docker rmi `docker images -q`
docker system prune
他のMastodonインスタンスが動かない
複数のMastodonインスタンスのDocker環境を同居させると多分コンフリクトする
- 投稿日:
ローカル環境用の開発ドメインをhttps化する時に使えるやつ
例えばローカル環境に複数のサービスがいて、それぞれをhttps://*.example.com/
のようなドメインで管理したい時に使える
確認環境
同じことをすればLinuxとかでも応用できると思う
Env | Ver |
---|---|
nginx | 1.19.8 |
mkcert | 1.4.3 |
Windows 10 Pro | 19043.1415 |
手順
- mkcert の導入とワイルドカード証明書の作成
choco install mkcert # mkcertを認証局として登録 mkcert -install # 証明書を作成するドメインを列挙 mkcert example.test *.example.test mv _wildcard.example.com+1.* C:/nginx/conf/.ssl/
nginxの設定に証明書を記載
server { server_name dev.example.com; listen 443 ssl; ssl_certificate ssl/_wildcard.example.com+1.pem; ssl_certificate_key ssl/_wildcard.example.com+1-key.pem; ... }
- 投稿日:
別にReactに限った話でもないのですが、実務で悩ましいコードにあたって頭を抱えてる内にハゲてきたので、ハゲがこれ以上進まない事を祈り書きました。いやいっそのこと全ハゲになりたいですが、それはさておき
とりあえず個人的には以下2点を意識すれば起きないとは思うので、Next.jsを使った簡単なサンプルを交えながら3例ほどケーススタディ形式で紹介していきます
- 密結合にしない
- シンプルに実装する
挙げている事例については例示用にフルスクラッチで書いています(主題と関係ないコードは端折ってます
ここはこうした方がより良いのでは?などのご意見があれば是非コメントいただけると嬉しいです
コンポーネントのOptional引数のオーバーライド
コンポーネント引数をコンポーネント側で書き換えるような実装はどうかなと思います
問題点
- 親の預かり知らぬところで値が設定されている
- 誰かが知らずに設定値をすり替えたりしたら不具合が起きそうです
- 型のコメントと設定値が異なる
- 型のコメントと実装が異なるので、誰かが直すかもしれません
- すると、コンポーネントを参照している全コンポーネントに影響が波及します
- そもそも型と実装は本質的に関係ないので、こういった運用はNG
- 型のコメントと実装が異なるので、誰かが直すかもしれません
一例
type AccountContainerProps = {
email: string;
// デフォルトtrue
isUpdate?: boolean;
// デフォルトfalse
gotNotify?: boolean;
};
export const AccountContainer = ({
isUpdate = true,
gotNotify = true,
...props
}: AccountContainerProps) => {
...
}
改善案
呼び元で明示的に指定するように変更しています
これによって親は子の実装を知る必要がなくなり、責務がそこで閉じるようになりました
改善点
- 親の預かり知らぬところで値が設定されなくなった
- これで子コンポーネントで値が変わることに怯える必要はなくなりました
- 型の初期値コメントを削除できた
- 実装と一致する保証がないコメントは削除するべきでしょう
type AccountContainerProps = {
email: string;
isUpdate: boolean;
gotNotify: boolean;
};
export const AccountContainer = (props: AccountContainerProps) => {
...
}
コンポーネントのOptional引数の多重オーバーライド
コンポーネントのOptional引数のオーバーライドが多重化されている上に、なんか途中で更に書き換えられているとかいう地獄
どうしてそんなことをするのか…
一例
type AccountTemplateProps = {
email: string;
// デフォルトtrue
isUpdate?: boolean;
// デフォルトfalse
gotNotify?: boolean;
from: 'register' | 'update';
};
export const AccountTemplate = ({
isUpdate = false,
gotNotify = true,
...props
}: AccountTemplateProps) => {
if (props.from === 'register') isUpdate = false;
return <AccountContainer {...props} />;
};
type AccountContainerProps = {
email: string;
// デフォルトtrue
isUpdate?: boolean;
// デフォルトfalse
gotNotify?: boolean;
};
export const AccountContainer = ({
isUpdate = true,
gotNotify = true,
...props
}: AccountContainerProps) => {
...
}
改善案
前項のように明示的に値を渡してあげるようにしましょう
直列に分散されたコンポーネント
コンポーネントの中にコンポーネントがネストされ続けてるパターンです
一見して何をしているのか分かりづらい上、StateやらHookやら色んな処理が各コンポーネントに分散配置されていることもあります
問題点
- 親からみると子が何をしているのかが分かりづらい
- コンポーネント名が意味をなしていない(責務が別れていない)
- 子コンポーネントが状態を持っているため、親コンポーネントでハンドリングができない
- 子コンポーネントのロジック変更が参照している全コンポーネントのロジックに波及する
一例
親コンポーネント
RegisterHeader
なる物が差し込まれていることだけがわかります
このコンポーネントが何をするのかはパッと見ではよくわかりません
const AccountUpdatePage = () => {
return <Register header={<RegisterHeader />} />;
};
ラッピングしているコンポーネント
ラッパーなのでこのコンポーネントそのものは何をしているのかわかりません
type RegisterProps = {
header: JSX.Element;
};
export const Register = (props: RegisterProps) => {
return <>{props.header}</>;
};
差し込まれているコンポーネントの中身
どうやらRegisterHeader
はRegisterContent
を含むようです
なんでヘッダーの中にコンテンツがあるのでしょうか…
export const RegisterHeader = () => {
return (
<>
<p>head</p>
<RegisterContent />
</>
);
};
差し込まれているコンポーネントの子
現在のパスに応じて叩くAPIを変えるような実装がされていますが、もしパスが変わったり増えたりしたらこのコンポーネントの実装を知らない限り面倒なことになります
export const RegisterContent = () => {
const rt = useRouter();
const currentPath = rt.pathname;
const url =
currentPath === 'register'
? 'https://example.com/kaiin/touroku'
: 'https://example.com/kaiin/koshin';
const [username, setUsername] = useState<string | undefined>(undefined);
const onSubmit = (ev: React.FormEvent<HTMLFormElement>) => {
ev.preventDefault();
axios.post(url, {
username,
});
};
return (
<>
<form onSubmit={onSubmit}>
<input
type="text"
value={username}
onChange={(ev) => setUsername(ev.target.value)}
/>
<button>送信</button>
</form>
<RegisterFooter />
</>
);
};
export const RegisterFooter = () => {
return <p>foot</p>;
};
改善案
改善点
- 親から子への見通しが改善された
- コンポーネント名が名前の通りの意味を持つようになった
- ヘッダーはヘッダー、フォームはフォーム、フッターはフッターの責務だけに集中できます
Register
とか言う謎コンポーネントも姿を消しました
- 状態をすべて親に集約した
- 子コンポーネントのロジック変更が起きる確率が減った
- 子コンポーネント側のロジックを減らし、親から渡されるコールバックで行うようにしたため、子コンポーネントのロジック変更が他に影響する確率が減りました
親コンポーネント
- ひとまずヘッダーと入力フォーム、フッターがあるんだなという事が解るようにはなったと思います
- 状態を親に集約したのでAPI叩く時のURIも態々パスから判断しなくて良くなったのでコードの複雑性が減っています
const usePageState = () => {
const [username, setUsername] = useState('');
return {
username,
setUsername,
};
};
const onSubmit = (username: string) => {
axios.post('https://example.com/kaiin/koshin', {
username,
});
};
const AccountUpdatePage = () => {
const ps = usePageState();
return (
<>
<RegisterHeader />
<RegisterForm
onChangeUsername={ps.setUsername}
username={ps.username}
onSubmit={() => onSubmit(ps.username)}
/>
<RegisterFooter />
</>
);
};
ヘッダー
export const RegisterHeader = () => {
return <p>head</p>;
};
入力フォーム
type RegisterFormProps = {
username: string;
onChangeUsername: (value: string) => void;
onSubmit: () => void;
};
const onChangeUsername = (
ev: React.ChangeEvent<HTMLInputElement>,
setState: (value: string) => void
) => {
const value = ev.target.value;
setState(value);
};
const onSubmit = (
ev: React.FormEvent<HTMLFormElement>,
onSubmit: () => void
) => {
ev.preventDefault();
onSubmit();
};
export const RegisterForm = (props: RegisterFormProps) => {
return (
<>
<form onSubmit={(ev) => onSubmit(ev, props.onSubmit)}>
<input
type="text"
value={props.username}
onChange={(ev) => onChangeUsername(ev, props.onChangeUsername)}
/>
<button>送信</button>
</form>
</>
);
};
フッター
export const RegisterFooter = () => {
return <p>foot</p>;
};