gemini://technicalsuwako.moe
テクニカル諏訪子
2023-12-11T00:00:00Z
テクニカル諏訪子
blog/digital-autonomy-076video-hosting.gmi
凛
【デジタル自主】076動画ホスティングの解決策
2023-12-11T00:00:00Z
<p>PeerTubeのホスティングと言えば、「高額」というイメージが浮かびます。<br />
しかし、デジタル自主を理解すれば、高額な費用は必要ありません。<br />
あたしはストレージやトラフィックの使用量に関わらず、毎月1,200円を支払っています。</p>
<h2 id="heading">必要な物</h2>
<ul>
<li>古いパソコン3台(秋葉原でのジャンク品を選ぶ事を勧めます)</li>
<li>2枚1 TiBのSSD(勧め: <a href="https://www.amazon.co.jp/gp/product/B0BYZFB8D6">https://www.amazon.co.jp/gp/product/B0BYZFB8D6</a> 残念ですが、最近はCRUCIAL MX-500のSSDが凄く高くなりましたので、もう勧めれません)</li>
<li>2枚の大きさがどうでも良いSSD</li>
<li>ルータ(勧め: <a href="https://www.amazon.co.jp/gp/product/B08MH4VLR3">https://www.amazon.co.jp/gp/product/B08MH4VLR3</a> )</li>
<li>KVMスイッチ(勧め: <a href="https://www.amazon.co.jp/gp/product/B094N5LWKZ">https://www.amazon.co.jp/gp/product/B094N5LWKZ</a> )</li>
<li>キーボード(勧め: <a href="https://www.amazon.co.jp/gp/product/B08M3BQ1TS">https://www.amazon.co.jp/gp/product/B08M3BQ1TS</a> )</li>
<li>VGAを対応するモニタ</li>
<li>インターリンク フレッツ接続ZOOT NEXT(固定IPの為)</li>
<li>(沢山サーバがあれば)ハブスイッチ(勧め: <a href="https://www.amazon.co.jp/gp/product/B08YKSR6R8">https://www.amazon.co.jp/gp/product/B08YKSR6R8</a> )</li>
<li>2つのUSBメモリ(一つはFreeBSDと、一つはOpenBSD)</li>
</ul>
<p>特にKVMスイッチが必要はありませんが、1つのモニタとキーボードを持ったら、とても便利です。\</p>
<p>OpenBSDを使う理由はセキュリティで、FreeBSDを使う理由はパフォーマンスです。<br />
一緒に完璧なコンビネーションとなります。</p>
<h2 id="usb">USBメモリを準備して、インストールして下さい</h2>
<p>FreeBSDとOpenBSDのイメージをダウンロードして下さい。<br />
<a href="https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.0/">FreeBSD 14.0-RELEASE</a><br />
<a href="https://cdn.openbsd.org/pub/OpenBSD/7.4/amd64/install74.img">OpenBSD 7.4</a></p>
<p>この記事の目的の為に、USBメモリのフラッシュとFreeBSD及びOpenBSDのインストール方法を既に知っていると仮定します。</p>
<h2 id="heading-1">あたしのセットアップ</h2>
<p>あたしのセットアップは下記のイメージです。
ネットワーク以内のIPは:</p>
<ul>
<li>192.168.0.1 = ルータ</li>
<li>192.168.0.104 = PeerTubeサーバ</li>
<li>192.168.0.106 = relaydサーバ</li>
<li>192.168.0.143 = NAS</li>
</ul>
<p><img src="https://ass.technicalsuwako.moe/DKp6BGXWTYu4JnBZ1biZPw.jpg" alt="" />
<img src="https://ass.technicalsuwako.moe/4CUzz0i5QI2FoYuKotvAAQ.jpg" alt="" />
<img src="https://ass.technicalsuwako.moe/JePPSZafQn25uCJxvFaKGw.jpg" alt="" />
<img src="https://ass.technicalsuwako.moe/5VLgmL3DSTyoItyvOILtRw.jpg" alt="" /></p>
<p>ちなみに、このネットワークのIPセグメントは下記のイメージです:</p>
<ul>
<li>1 = ルータ</li>
<li>2〜99 = DHCP</li>
<li>100 = メインパソコン</li>
<li>101〜120 = サーバ</li>
<li>121〜130 = ノートパソコン(イーサネット)</li>
<li>131〜140 = ノートパソコン(WiFi)</li>
<li>141〜160 = ネットワーク機器(NAS、WiFiアクセスポイント、防犯カメラ等)</li>
<li>161〜180 = ゲーム機(ニンテンドースイッチ、ニンテンドー3DS等)</li>
<li>181〜200 = スマホ、タブパソコン等</li>
<li>201〜254 = 何もない</li>
</ul>
<h2 id="relaydopenbsd">サーバ1:relaydサーバ(OpenBSD)</h2>
<p>このサーバでOpenBSDを使う理由はセキュリティです。<br />
ネットワーク外からの接続は全部このサーバに通じます。</p>
<h3 id="etcacme-clientconf">/etc/acme-client.conf</h3>
<pre><code lang="">#
# $OpenBSD: acme-client.conf,v 1.4 2020/09/17 09:13:06 florian Exp $
#
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
domain 076.moe {
alternative names {
www.076.moe,
stopsdgs.076.moe,
stopsmaho.076.moe,
mitra.076.moe,
mixi.076.moe,
video.076.moe,
imgproxy.076.moe
}
domain key "/etc/ssl/private/076.moe.key"
domain full chain certificate "/etc/ssl/076.moe.crt"
sign with letsencrypt
}
</code></pre><h3 id="etchttpdconfssl">/etc/httpd.conf(SSL証明書を受け取る為)</h3>
<pre><code lang=""># $OpenBSD: httpd.conf,v 1.22 2020/11/04 10:34:18 denis Exp $
server "default" {
listen on * port 80
root "/htdocs"
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
}
</code></pre><h3 id="etcrelaydconf">/etc/relayd.conf</h3>
<pre><code lang=""># $OpenBSD: relayd.conf,v 1.5 2018/05/06 20:56:55 benno Exp $
#
# Macros
#
relayd_addr="0.0.0.0"
insrv4_addr="192.168.0.104"
router_addr="192.168.0.106"
table <video> { $insrv4_addr }
http protocol reverse {
tcp { nodelay, sack }
tls ciphers "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"
tls keypair "076.moe"
match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
match request header append "X-Forwarded-Port" value "$REMOTE_PORT"
match response header set "X-Frame-Options" value "deny"
match response header set "X-XSS-Protection" value "1; mode=block"
match response header set "X-Content-Type-Options" value "nosniff"
match response header set "Strict-Transport-Security" value "max-age=31536000; includeSubDomains; preload"
match response header set "Permissions-Policy" value "accelerometer=()"
pass request quick header "Host" value "video.076.moe" forward to <video>
return error
pass
}
relay www_tls {
listen on $relayd_addr port 443 tls
protocol reverse
forward to <video> port 9000 check tcp
}
relay www_http {
listen on $relayd_addr port 80
protocol reverse
forward to <video> port 9000 check tcp
}
relay pt {
listen on $relayd_addr port 1935 tls
protocol reverse
forward to <video> port 19355 check tcp
}
</code></pre><p>ポート1935番号は生配信の為です。</p>
<pre><code lang="">rcctl <span style="color:#a2f">enable</span> httpd
rcctl <span style="color:#a2f">enable</span> relayd
rcctl start httpd
acme-client -v 076.moe
rcctl start relayd
</code></pre><h2 id="nasfreebsd">サーバ2:NAS(FreeBSD)</h2>
<p>FreeBSDを使う理由はZFSです。<br />
インストールしながら、是非「ZFS」→「stripe」を選択して下さい。</p>
<p>まずはSSDを確認して下さい:</p>
<pre><code lang="">$ dmesg | grep ada
ada0 at ahcich0 bus <span style="color:#666">0</span> scbus0 target <span style="color:#666">0</span> lun <span style="color:#666">0</span>
ada0: <CT1000MX500SSD1 M3CR046> ACS-3 ATA SATA 3.x device
ada0: Serial Number 〇〇
ada0: 600.000MB/s transfers <span style="color:#666">(</span>SATA 3.x, UDMA6, PIO 512bytes<span style="color:#666">)</span>
ada0: Command Queueing enabled
ada0: 953869MB <span style="color:#666">(</span><span style="color:#666">1953525168</span> <span style="color:#666">512</span> byte sectors<span style="color:#666">)</span>
ada1 at ahcich1 bus <span style="color:#666">0</span> scbus1 target <span style="color:#666">0</span> lun <span style="color:#666">0</span>
ada1: <Lexar SSD NQ100 960GB SN11873> ACS-4 ATA SATA 3.x device
ada1: Serial Number 〇〇
ada1: 600.000MB/s transfers <span style="color:#666">(</span>SATA 3.x, UDMA6, PIO 512bytes<span style="color:#666">)</span>
ada1: Command Queueing enabled
ada1: 915715MB <span style="color:#666">(</span><span style="color:#666">1875385008</span> <span style="color:#666">512</span> byte sectors<span style="color:#666">)</span>
ses0: ada0,pass0 in <span style="color:#b44">'Slot 00'</span>, SATA Slot: scbus0 target <span style="color:#666">0</span>
ses0: ada1,pass1 in <span style="color:#b44">'Slot 01'</span>, SATA Slot: scbus1 target <span style="color:#666">0</span>
</code></pre><p>ada1を作成しましょう。</p>
<pre><code lang="">$ gpart create -s gpt ada1
$ gpart add -t freebsd-zfs -l disk1 ada1
</code></pre><p>zpoolを作って下さい。</p>
<pre><code lang="">$ zpool create backup ada1p1
$ zpool list
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
backup 888G 396K 888G - - 0% 0% 1.00x ONLINE -
zroot 920G 180G 740G - - 0% 19% 1.00x ONLINE -
$ zfs create zroot/peertube
</code></pre><p>ada1はバックアップ用に使用しますが、具体的な方法についてはこの記事では説明しません。</p>
<p>peertubeユーザーとグループを創作して下さい。</p>
<pre><code lang="">$ pw groupadd peertube
$ pw useradd peertube -g peertube -s /usr/sbin/nologin -d /nonexistent -c <span style="color:#b44">"PeerTube User"</span>
</code></pre><h3 id="etcexports">/etc/exports</h3>
<pre><code lang="">/zroot/peertube -maproot=peertube:peertube 192.168.0.104
</code></pre><h3 id="etcrcconf">/etc/rc.conf</h3>
<pre><code lang="">clear_tmp_enable="YES"
syslogd_flags="-ss"
hostname="freebsdnas"
keymap="jp.kbd"
ifconfig_re0="inet 192.168.0.143 netmask 255.255.255.0"
defaultrouter="192.168.0.1"
local_unbound_enable="YES"
sshd_enable="YES"
ntpd_enable="YES"
rsyncd_enable="YES"
moused_nondefault_enable="NO"
# Set dumpdev to "AUTO" to enable crash dumps, "NO" to disable
dumpdev="AUTO"
zfs_enable="YES"
zfs_enable="YES"
rpcbind_enable="YES"
nfs_server_enable="YES"
nfsd_flags="-u -t -n 2"
mountd_enable="YES"
</code></pre><p>サービスを起動して下さい。</p>
<pre><code lang="">$ service rpcbind start
$ service mountd start
$ service nfsd start
</code></pre><h2 id="peertubefreebsd">サーバ3:PeerTubeサーバ(FreeBSD)</h2>
<p>FreeBSDを使う理由は、PeerTubeの開発者がOpenBSDをサポートしていない為です。<br />
いつでも通りにPeerTubeをインストールして下さい(nginxの設定が不要です)。</p>
<h3 id="etcrcconf-1">/etc/rc.conf</h3>
<pre><code lang=""><span style="color:#b8860b">clear_tmp_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
<span style="color:#b8860b">syslogd_flags</span><span style="color:#666">=</span><span style="color:#b44">"-ss"</span>
<span style="color:#b8860b">hostname</span><span style="color:#666">=</span><span style="color:#b44">"peertubesrv"</span>
<span style="color:#b8860b">keymap</span><span style="color:#666">=</span><span style="color:#b44">"jp.kbd"</span>
<span style="color:#b8860b">ifconfig_re0</span><span style="color:#666">=</span><span style="color:#b44">"inet 192.168.0.104 netmask 255.255.255.0"</span>
<span style="color:#b8860b">defaultrouter</span><span style="color:#666">=</span><span style="color:#b44">"192.168.0.1"</span>
<span style="color:#b8860b">local_unbound_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
<span style="color:#b8860b">ifconfig_re0</span><span style="color:#666">=</span><span style="color:#b44">"DHCP"</span>
<span style="color:#b8860b">sshd_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
<span style="color:#080;font-style:italic"># Set dumpdev to "AUTO" to enable crash dumps, "NO" to disable</span>
<span style="color:#b8860b">dumpdev</span><span style="color:#666">=</span><span style="color:#b44">"AUTO"</span>
<span style="color:#b8860b">zfs_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
<span style="color:#b8860b">shavit_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
<span style="color:#b8860b">postgresql_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
<span style="color:#b8860b">redis_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
<span style="color:#b8860b">postgresql_class</span><span style="color:#666">=</span><span style="color:#b44">"postgres"</span>
<span style="color:#b8860b">peertube_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
<span style="color:#b8860b">nginx_enable</span><span style="color:#666">=</span><span style="color:#b44">"NO"</span>
<span style="color:#b8860b">nfs_server_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
<span style="color:#b8860b">nfs_client_enable</span><span style="color:#666">=</span><span style="color:#b44">"YES"</span>
</code></pre><pre><code lang="">$ <span style="color:#a2f">cd</span> /var/www/peertube
$ rm -rf storage
$ mkdir storage
$ mount -t nfs 192.168.0.143:/zroot/peertube /var/www/peertube/storage
$ df -h
Filesystem Size Used Avail Capacity Mounted on
zroot/ROOT/default 92G 63G 29G 68% /
devfs 1.0K 0B 1.0K 0% /dev
zroot/tmp 29G 3.1M 29G 0% /tmp
zroot/usr/home 36G 7.2G 29G 20% /usr/home
zroot 29G 96K 29G 0% /zroot
zroot/var/audit 29G 96K 29G 0% /var/audit
zroot/var/log 29G 90M 29G 0% /var/log
zroot/usr/ports 31G 2.0G 29G 6% /usr/ports
zroot/var/crash 29G 96K 29G 0% /var/crash
zroot/var/tmp 29G 136K 29G 0% /var/tmp
zroot/var/mail 29G 444K 29G 0% /var/mail
zroot/usr/src 29G 96K 29G 0% /usr/src
192.168.0.143:/zroot/peertube 815G 103G 712G 13% /var/www/peertube/storage
</code></pre><p>以上</p>
blog/programming-pyramid-of-doom.gmi
凛
【プログラミング】アドバイス1:運命のピラミッドを回避
2023-12-05T00:00:00Z
<p>プログラミングしたら、気づかずに運命のピラミッドを作る可能性が非常に高いです。<br />
これを回避した方が良いと思います。</p>
<h2 id="heading">運命のピラミッドとは?</h2>
<p>ブログラミングで、運命のピラミッドとは、ネストされたインデントの多くのレベルを使用し、コードが読みにくくなる事です。<br />
例えば、下記のC言語のコードは運命のピラミッドの問題は発生します。</p>
<pre><code lang=""><span style="color:#0b0;font-weight:bold">int</span> <span style="color:#00a000">main</span>(<span style="color:#0b0;font-weight:bold">int</span> argc, <span style="color:#0b0;font-weight:bold">char</span> <span style="color:#666">**</span>argv) {
<span style="color:#a2f;font-weight:bold">if</span> (argc <span style="color:#666">>=</span> <span style="color:#666">2</span>) {
Display <span style="color:#666">*</span>d <span style="color:#666">=</span> XOpenDisplay(<span style="color:#a2f">NULL</span>);
<span style="color:#a2f;font-weight:bold">if</span> (d <span style="color:#666">!=</span> <span style="color:#a2f">NULL</span>) {
<span style="color:#0b0;font-weight:bold">int</span> src <span style="color:#666">=</span> DefaultScreen(d);
Window w <span style="color:#666">=</span> XCreateSimpleWindow(d, RootWindow(d, scr), <span style="color:#666">0</span>, <span style="color:#666">0</span>, <span style="color:#666">500</span>, <span style="color:#666">500</span>, <span style="color:#666">1</span>, BlackPixel(d, scr), WhitePixel(d, scr));
XSelectInput(d, w, ExposureMask <span style="color:#666">|</span> KeyPressMask);
XMapWindow(d, w);
XFlush(d);
GC gc <span style="color:#666">=</span> XCreateGC(d, w, <span style="color:#666">0</span>, <span style="color:#a2f">NULL</span>);
<span style="color:#a2f;font-weight:bold">if</span> (gc <span style="color:#666">!=</span> <span style="color:#a2f">NULL</span>) {
XImage <span style="color:#666">*</span>ximg <span style="color:#666">=</span> openimg(d, argv[<span style="color:#666">1</span>])<span style="color:#666">:</span>
<span style="color:#a2f;font-weight:bold">if</span> (ximg <span style="color:#666">!=</span> <span style="color:#a2f">NULL</span>) {
<span style="color:#080;font-style:italic">// などなど
</span><span style="color:#080;font-style:italic"></span> }
} <span style="color:#a2f;font-weight:bold">else</span> {
fprintf(stderr, <span style="color:#b44">"グラフィックス内容を創作に失敗しました。</span><span style="color:#b62;font-weight:bold">\n</span><span style="color:#b44">"</span>);
}
} <span style="color:#a2f;font-weight:bold">else</span> {
fprintf(stderr, <span style="color:#b44">"画像を開けられません。</span><span style="color:#b62;font-weight:bold">\n</span><span style="color:#b44">"</span>);
}
} <span style="color:#a2f;font-weight:bold">else</span> {
printf(<span style="color:#b44">"使用方法: %s <画像ファイル></span><span style="color:#b62;font-weight:bold">\n</span><span style="color:#b44">"</span>, argv[<span style="color:#666">0</span>]);
}
<span style="color:#a2f;font-weight:bold">return</span> <span style="color:#666">0</span>;
}
</code></pre><p>変わりに、下記のコードを書いた方が良いです。</p>
<pre><code lang=""><span style="color:#0b0;font-weight:bold">int</span> <span style="color:#00a000">main</span>(<span style="color:#0b0;font-weight:bold">int</span> argc, <span style="color:#0b0;font-weight:bold">char</span> <span style="color:#666">**</span>argv) {
<span style="color:#a2f;font-weight:bold">if</span> (argc <span style="color:#666"><</span> <span style="color:#666">2</span>) { <span style="color:#080;font-style:italic">// argcは2つ以下の場合、続行する意味はありません。
</span><span style="color:#080;font-style:italic"></span> printf(<span style="color:#b44">"使用方法: %s <画像ファイル></span><span style="color:#b62;font-weight:bold">\n</span><span style="color:#b44">"</span>, argv[<span style="color:#666">0</span>]);
<span style="color:#a2f;font-weight:bold">return</span> <span style="color:#666">1</span>;
}
Display <span style="color:#666">*</span>d <span style="color:#666">=</span> XOpenDisplay(<span style="color:#a2f">NULL</span>);
<span style="color:#a2f;font-weight:bold">if</span> (d <span style="color:#666">==</span> <span style="color:#a2f">NULL</span>) { <span style="color:#080;font-style:italic">// 同様に、DisplayがNULLの場合、続行する意味はありません。
</span><span style="color:#080;font-style:italic"></span> fprintf(stderr, <span style="color:#b44">"画像を開けられません。</span><span style="color:#b62;font-weight:bold">\n</span><span style="color:#b44">"</span>);
<span style="color:#a2f;font-weight:bold">return</span> <span style="color:#666">-</span><span style="color:#666">1</span>;
}
<span style="color:#0b0;font-weight:bold">int</span> src <span style="color:#666">=</span> DefaultScreen(d);
Window w <span style="color:#666">=</span> XCreateSimpleWindow(d, RootWindow(d, scr), <span style="color:#666">0</span>, <span style="color:#666">0</span>, <span style="color:#666">500</span>, <span style="color:#666">500</span>, <span style="color:#666">1</span>, BlackPixel(d, scr), WhitePixel(d, scr));
XSelectInput(d, w, ExposureMask <span style="color:#666">|</span> KeyPressMask);
XMapWindow(d, w);
XFlush(d);
GC gc <span style="color:#666">=</span> XCreateGC(d, w, <span style="color:#666">0</span>, <span style="color:#a2f">NULL</span>);
<span style="color:#a2f;font-weight:bold">if</span> (gc <span style="color:#666">==</span> <span style="color:#a2f">NULL</span>) { <span style="color:#080;font-style:italic">// 同じパターン
</span><span style="color:#080;font-style:italic"></span> fprintf(stderr, <span style="color:#b44">"グラフィックス内容を創作に失敗しました。</span><span style="color:#b62;font-weight:bold">\n</span><span style="color:#b44">"</span>);
<span style="color:#a2f;font-weight:bold">return</span> <span style="color:#666">-</span><span style="color:#666">1</span>;
}
XImage <span style="color:#666">*</span>ximg <span style="color:#666">=</span> openimg(d, argv[<span style="color:#666">1</span>])<span style="color:#666">:</span>
<span style="color:#a2f;font-weight:bold">if</span> (ximg <span style="color:#666">==</span> <span style="color:#a2f">NULL</span>) { <span style="color:#080;font-style:italic">// また、同じパターン
</span><span style="color:#080;font-style:italic"></span> fprintf(stderr, <span style="color:#b44">"画像を開けられません: %s</span><span style="color:#b62;font-weight:bold">\n</span><span style="color:#b44">"</span>, argv[<span style="color:#666">1</span>]);
XFreeGC(d, gc);
XCloseDisplay(d);
<span style="color:#a2f;font-weight:bold">return</span> <span style="color:#666">-</span><span style="color:#666">1</span>;
}
<span style="color:#080;font-style:italic">// などなど
</span><span style="color:#080;font-style:italic"></span>
<span style="color:#a2f;font-weight:bold">return</span> <span style="color:#666">0</span>;
}
</code></pre><p><a href="https://gitler.moe/suwako/mivfx/src/branch/master/main.c">実際、C言語の開発者は運命のピラミッドを作る事は珍しいです。</a><br />
この問題は主にWeb開発で多く起こります。<br />
<a href="https://gitler.moe/tak4/bibis/src/branch/master/data-post.php">ですから、PHPの例えはご覧下さい。</a></p>
<pre><code lang=""> <span style="color:#b8860b">$thread_title</span> <span style="color:#666">=</span> <span style="color:#b44">''</span>;
<span style="color:#a2f;font-weight:bold">if</span> (<span style="color:#b8860b">$thread_id</span> <span style="color:#666">></span> <span style="color:#b44">''</span>) {
<span style="color:#b8860b">$thread_title</span> <span style="color:#666">=</span> load_post_title_by_id(<span style="color:#b8860b">$thread_id</span>);
<span style="color:#a2f;font-weight:bold">if</span> (<span style="color:#666">!</span><span style="color:#b8860b">$thread_title</span> <span style="color:#666">></span> <span style="color:#b44">''</span>) {
<span style="color:#b8860b">$thread_title</span> <span style="color:#666">=</span> <span style="color:#b44">'無題#'</span> <span style="color:#666">.</span> mb_substr(<span style="color:#b8860b">$thread_id</span>, <span style="color:#666">0</span>, <span style="color:#666">7</span>);
}
}
</code></pre><p>少しだけ変わります。</p>
<pre><code lang=""> <span style="color:#b8860b">$thread_title</span> <span style="color:#666">=</span> <span style="color:#b44">''</span>;
<span style="color:#a2f;font-weight:bold">if</span> (<span style="color:#b8860b">$threadid</span> <span style="color:#666">!=</span> <span style="color:#b44">''</span>) { <span style="color:#080;font-style:italic">// 文字列で「>」を使用すると、バグが発生する可能性があります。
</span><span style="color:#080;font-style:italic"></span> <span style="color:#b8860b">$thread_title</span> <span style="color:#666">=</span> load_post_title_by_id(<span style="color:#b8860b">$threadid</span>);
}
<span style="color:#a2f;font-weight:bold">if</span> (<span style="color:#b8860b">$thread_title</span> <span style="color:#666">!=</span> <span style="color:#b44">''</span>) { <span style="color:#080;font-style:italic">// !$thread_title > '' はかなりおかしいので、これも修正しました。
</span><span style="color:#080;font-style:italic"></span> <span style="color:#b8860b">$thread_title</span> <span style="color:#666">=</span> <span style="color:#b44">'無題#'</span> <span style="color:#666">.</span> mb_substr(<span style="color:#b8860b">$threadid</span>, <span style="color:#666">0</span>, <span style="color:#666">7</span>);
}
</code></pre><p>以上</p>
blog/sp-112.gmi
凛
【076】sp 1.1.2登場
2023-12-01T13:30:00Z
<h2 id="sp">spって何?</h2>
<p>spはシンプルなパスワードマネージャです。</p>
<h2 id="heading">変更</h2>
<ul>
<li>OpenBSDでのコンパイラーが発生された問題を修正した</li>
</ul>
<h2 id="heading-1">ソースコード</h2>
<p><a href="https://gitler.moe/suwako/sp">Gitler</a></p>
<p>以上</p>
blog/sp-111.gmi
凛
【076】sp 1.1.1登場
2023-12-01T13:00:00Z
<h2 id="sp">spって何?</h2>
<p>spはシンプルなパスワードマネージャです。</p>
<h2 id="heading">変更</h2>
<ul>
<li>make install-zsh部分を修正</li>
</ul>
<h2 id="heading-1">ソースコード</h2>
<p><a href="https://gitler.moe/suwako/sp">Gitler</a></p>
<p>以上</p>
blog/sp-110.gmi
凛
【076】sp 1.1.0登場
2023-12-01T00:00:00Z
<h2 id="sp">spって何?</h2>
<p>spはシンプルなパスワードマネージャです。</p>
<h2 id="heading">変更</h2>
<ul>
<li>TOTP対応</li>
<li>READMEファイルで使い方を詳しく説明する</li>
<li>zshキャプチャー</li>
<li>パスワード追加関数を安定化</li>
<li>パスワード表示とパスワードのコピー関数で、GNU Passで保存したパスワードの場合は改行を追加しない様に</li>
</ul>
<h2 id="heading-1">ソースコード</h2>
<p><a href="https://gitler.moe/suwako/sp">Gitler</a></p>
<p>以上</p>