まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

ツイッター(X)が API 制約を含めて色々と不便になってしばらく経過しました。ツイッターでリツイートボットとか作ってた個人のものはほぼ全滅状態ですが、さすがに「しばらく待てばまた使えるようになる、、」などと楽観的には考えない方が良さそうな感じですね。

世の中には使いにくくなったツイッターの後釜を狙う SNS も多く出てきましたが、今のところはまだツイッターを使っている人が圧倒的多数の印象です。まあ開発者的にはツイッターはもう見限ってる人も少なくないんでしょうけど、SNS はあくまで「発信の場」であって、使っているのは開発者ばかりじゃないですからね。メッセージを届ける先の規模含めると、使い勝手が現状維持ならまだしばらくツイッターが使われていくのでしょう。


一方で、とはいえ多くの SNS が新たに生まれてきてはいます。実は自分も「お絵描き SNS」という位置づけの MyDoodles なんていうものを密かにリリースしていたりもするのですが、、、そんな中でツイッターの共同創業者の一人だったジャック・ドーシーさんが新たに立ち上げた分散型 SNS である Bluesky Social (以下 "Bluesky")はツイッターに似たインターフェースを持ち、リリースからしばらくは招待制で運用されていましたが、先ごろ招待制が撤廃されて誰でもアカウントを作れるようになりました。比較的早い段階から API も公開されており、開発者視点では「次に来るのはこれかも」と期待しています。


そんな Bluesky の API を使って、ツイッターでは(個人では事実上)作れなくなってしまったボットを作ってみることにしました。自分が作ろうと思ったのは拙作マンホールマップが持つ機能の1つである「今日のマンホール」を午前零時に自動でつぶやくボットです。実はこれまでは私自身が人間ボットになって(笑)今日のマンホールを手動でつぶやいていたのですが、これを Bluesky 内で自動化する、というものです。ボットとしての基本的な考え方はツイッターの頃と大きく変わるものではないのですが、分散 SNS である Bluesky の、まだ充分に熟しているとは言えない API を使って実装することはチャレンジでもあり、色々面倒な考慮も必要にはなるのですが、とりあえず作れそうだったので、その中身の解説をするのが本ブログエントリの目的です。なお以下で紹介する内容は 2024-02-12 時点の情報であることをご了承ください。


【開発環境】
まず Bluesky API を紹介している本家ページがこちらです:
https://www.docs.bsky.app/docs/get-started

2024021201


SDK としては Node.js と Python 向けに提供されているようですが、Curl からも使える REST API があるようなので REST API でプログラミングするつもりであれば事実上プログラミング言語に制約はないことになります。今回以下で紹介するプログラミングの環境としては自分が普段使っている Node.js を使うことにします。

Node.js の場合、@atproto/api というライブラリを使うことで比較的簡単に Bluesky API を使ったアプリケーションの開発ができるようになります。こんな感じでインストールして使います:
$ npm install @atproto/api


【認証】
Bluesky API を使って試しに自分自身の情報でも取得・・・ するのもいいですが、まずは認証を行う必要があります。ツイッターなどでは開発者向けページからアプリケーションを登録して API キーを発行して OAuth 2.0 で・・・ といった手順で認証を行っていました。Bluesky もいずれはそのような形態になるものと思いますが、現時点では OAuth2.0 はサポートされておらず、ユーザー ID とパスワードを使って直接ログインする形になるようです。ちょうど Bluesky の招待制も終わったことなので、自分はこのボット用(というかマンホールマップ用)のアカウントを1つ新たに作成しました。そして Node.js の場合はこのような形で認証(ログイン)を行います:
var { BskyAgent } = require( '@atproto/api' );

var agent = new BskyAgent({
  service: "https://bsky.social"
});
agent.login({
  identifier: "myname@email.com",
  password: "mypassword",
}).then( async function(){
  // ログイン成功後の処理へ
    :
    :
});

"@atoproto/api" ライブラリから BskyAgent をインポートして、メールアドレス(上では "myname@email.com" の部分)とパスワード(同 "mypassword" 部分)を指定してログインします。このあたりはソースコード内に直接記載するのは危険なので、コードの管理方法には注意が必要です。


【ツイートする】
ボットを作るには「ツイートする」(書き込む)機能が必要です。ログイン後に単にテキストをツイートするだけなら簡単です。特に @atproto/api ライブラリを使っていれば post メソッドを使って以下のようにログイン後の処理を一行追加するだけです:
var { BskyAgent } = require( '@atproto/api' );

var agent = new BskyAgent({
  service: "https://bsky.social"
});
agent.login({
  identifier: "myname@email.com",
  password: "mypassword",
}).then( async function(){
  var res = await agent.post( { text: 'Hello, world.'} );
  console.log( {res} );
});

これでログインしたユーザーの権限で "Hello, world." とつぶやくことができます。簡単ですよね、ここまでは。


【facets 処理(リッチテキスト処理)】
テキストをつぶやくのは簡単でした。ツイッターなどではこのテキスト内に他のユーザーへのメンション(@)が含まれていたり、ハッシュタグ(#)やリンクが含まれていると自動的に解釈してリッチテキスト化してくれて(リンクとかを付けてくれて)いましたが、Bluesky API はそうはいきません(そもそも Bluesky にはまだハッシュタグという概念がありません)。この辺りはプログラミング内でリッチテキスト化(Bluesky では「facets 検知」といいます)する必要があります。今回作ろうとしているボットも必ずあるページへのリンクを含む内容になっているので post 実行前の facets 検知が必須です。この辺から少し面倒になってきます。

具体的にはこのようなコードになります:
var { BskyAgent, RichText } = require( '@atproto/api' );

var agent = new BskyAgent({
  service: "https://bsky.social"
});
agent.login({
  identifier: "myname@email.com",
  password: "mypassword",
}).then( async function(){
  var text = "リンクを含むテキスト https://manholemap.juge.me/";

  var rt = new RichText({ text: text });
  await rt.detectFacets( agent );

  var res = await agent.post({
    $type: 'app.bsky.feed.post',
    text: rt.text,
    facets: rt.facets
  });

  console.log( {res} );
});

まずリッチテキスト化処理を行うために @atproto/api ライブラリから BskyAgent だけでなく RichText もインポートしておきます。そしてリンクなどのリッチテキスト化が必要なテキストを RichText で初期化した結果を変数 rt で受け取り、更に detectFacets() 処理を実行します。するとテキスト部分は rt.text に、facets 情報が rt.facets にそれぞれ格納されます。ツイッターではこの辺りの処理が自動化されていたので楽でしたが、Bluesky でもここまでライブラリが用意されているのでさほど大変ではないですね。

最後に facets 処理した結果を post します。先のプレーンテキストでは text 要素だけを post していましたが、リッチテキストの場合は少し情報を加えてあげる必要がありますが、これでテキスト内の URL 部分はリンクになってポストされます。


【オープングラフ処理(OGP対応)】
ツイッターなど多くの SNS ではリンクを含む情報をポストすると、そのリンク先の内容の一部(テキストや画像)が埋め込まれる形で表示されます。これは OGP と呼ばれる規格で、リンク先が OCP 規格に沿って作られたページであれば、その情報を使って実現できるものです。

Bluesky の場合もリンク先の情報を埋め込むことはできるのですが、API でつぶやく場合は(現状では)この部分を全て手作業で行う必要があります。具体的にはこんな感じです:
var { BskyAgent, RichText } = require( '@atproto/api' );
var { parse } = require( 'node-html-parser' );

var agent = new BskyAgent({
  service: "https://bsky.social"
});
agent.login({
  identifier: "myname@email.com",
  password: "mypassword",
}).then( async function(){
  var text = "リンクを含むテキスト https://manholemap.juge.me/";

  //. OGP
  var url = "https://manholemap.juge.me/";
  var resp = await fetch( url );
  var html = parse( await( resp.text() ) );
  var title = html.querySelector( "meta[property='og:description']" ).getAttribute( "content" );

  //. embed image
  var ogpImg = html.querySelector( "meta[property='og:image']" ).getAttribute( "content" );
  var blob = await fetch( ogpImg );
  var buffer = await blob.arrayBuffer();
  var response = await agent.uploadBlob( new Uint8Array( buffer ), { encoding: "image/jpeg" } );
  var embed_params = {
    $type: "app.bsky.embed.external",
    external: {
      uri: "https://manholemap.juge.me/",
      thumb: {
        $type: "blob",
        ref: {
          $link: response.data.blob.ref.toString()
        },
        mimeType: response.data.blob.mimeType,
        size: response.data.blob.size
      },
      title: title,
      description: title
    }
  };

  var rt = new RichText({ text: text });
  await rt.detectFacets( agent );

  var res = await agent.post({
    $type: 'app.bsky.feed.post',
    text: rt.text,
    facets: rt.facets,
    embed: embed_params
  });

  console.log( {res} );
});

まず URL のリンク先情報を解析するために node-html-parser ライブラリをインポートしておきます。そしてリンク先の URL(上では "https://manholemap.juge.me/")を fetch して HTML を取得します。OGP の規格に沿って作られたページであれば、その画像は HTML 内で <meta property="og:image" content="***" /> の *** 部分に指定されることになっているのでその値を取り出します。そして再度その画像 URL を fetch してバイナリデータを取り出し、agent 変数を使ってアップロードします。そのアップロード結果の情報等を使って OGP の埋め込みデータ(embed_params 変数)を作成します。埋め込みデータを作ることができたら、その値を embed パラメータに加えて post する、という流れになります。これで OGP に対応したポスト処理を Node.js で行うことができるようになりました。


【サンプルソースコード公開】
後はこの実行を自動化することになりますが、今回は自分が使っているサーバーの crontab を使って日本時間の午前零時ちょうどに実行するようにしました。具体的なコードは後述しますが、大まかには、
(1)日本時間午前零時になったら、マンホールマップの「今日のマンホール」取得 API を呼び出し、
(2)その結果からリンク先 URL やポストするテキストを生成し、
(3)リンク先 URL の OGP 情報を使って画像やタイトルなどを取り出して、埋め込み、
(4)facets 処理を加えた上でポストする

という流れを行っています。そしてそのサンプルソースコードを以下で公開しています:
https://github.com/dotnsf/bsky_manhotalk_bot/blob/main/motd.js


上では解説していない部分を1点だけ。上述のように現状の Bluesky API は認証情報を ID とパスワードを直接指定する形で実行する必要があります。上のリンク先を見るとわかるのですが、今回公開したサンプルソースコードには ID やパスワードは含まれておらず、このソースコードを実行する際の環境変数から取得するようにしています。したがってこのソースコードを実行する時に、
$ BSKY_ID=myname@email.com BSKY_PW=mypassword node motd.js

といった具合に BSKY_ID 環境変数と BSKY_PW 環境変数にそれぞれ Bluesky へのログイン ID とパスワードを指定して実行することで正しく動くようにしています。

そうして作ったマンホールマップ・ボットはこちらのアカウントで公開しています:
https://bsky.app/profile/manholemap.bsky.social

2024021200


↑ツイッターとちがって Bluesky のアカウントを持っていない人でも見ることはできますが、可能であればアカウントを作ってフォローしてください。「今日のマンホール」が登録されている日全てで午前零時になると教えてくれます(登録されていない日もあります)。

とりあえず現時点では「今日のマンホール」専用で動くボットですが、いずれハッシュタグが実装されたら #manhotalk ハッシュタグを検索して・・といった挙動や、ちょっとした会話にも挑戦するつもりでいます。

相当限られたケースになると思うのですが、とある目的で、、、
 ・あるサーバー(物理サーバー または 仮想サーバー)上に、
 ・RHEL (または互換 OS)をインストールすると仮定した場合の、
  ・NIC の MAC アドレス
  ・NIC が認識された時のデバイス名("eth0" など)
  ・ストレージディスクが認識された時のデバイス名("/dev/sda" など)
を事前に知っておきたい、ということがありました(知ってる人なら、どういう目的でこれらの情報が必要なのかわかると思う)。 

「実際にインストールすればわかるじゃん」ということでもあるんですが、対象サーバー数が多かったりして、特殊な方法でまとめてインストールするようなケースで、その特殊なインストール方法を利用する際に必要な情報だと思ってください。そういうわけでインストールそのものの手間は取りたくないけど、上の情報だけ事前に知っておきたいのだが、具体的にどうすればよいか? というのが本ブログエントリのテーマです。

で、以下がその答の1つだと思っています。もう少し簡単に調べる方法があれば教えてください。

まず普通に DVD/ISO を使い、初期インストール時と同じようにブートします:
rhel9_fdisk_001


RHEL の場合、最初に GUI のインストーラー画面が起動します。通常インストールだとまずインストール言語を指定して、、となるのですが、今回はインストールが目的ではないので、この画面に到達したことを確認するだけで OK です:
rhel9_fdisk_002


おもむろに Ctrl + Alt + F2(Ctrl キーと Alt キーを押して、押したまま更に F2 キーも押す)を実行します。すると GUI のインストール画面を実行したままいったん抜けて TTY2 という、root ユーザーで利用できる別画面に切り替わります:
rhel9_fdisk_003


この画面内で必要な情報を調べることができます(そのための最小限のコマンドも使えるようになっています)。まず NIC の MAC アドレスとデバイス名を調べるには "ip a" というコマンドを実行します:
rhel9_fdisk_004


"ip a" コマンドを実行すると、認識されているネットワークインターフェースと、その情報が一覧表示されます。上の例だと "lo" と "enp0s3" という2つのインターフェースが認識されていますが、"lo" はローカルホスト用なのでこちらではなく、もう一つの "enp0s3" が目的のデバイス名ということがわかります。またその中に MAC アドレスも "08:00:27:df:e7:b0" と記載されていますね。


次にストレージディスクのデバイス名も調べます。今度は "fdisk -l" コマンドを実行します:
rhel9_fdisk_005


するとストレージデバイスの一覧が表示されます。ここは結構多くて探すのが少し難しいのですが容量などを参考にしながら探してみてください。上の画面では一番上に "/dev/sda" というディスクを認識しているようです。容量もあっているので、おそらくこれ("/dev/sda")がストレージのデバイス名ということになりそうです。


これで目的は達成しました。OS をインストールしない場合はこのまま終了していいですし、インストールする場合は Ctrl + Alt + F6(Ctrl キーと Alt キーを押して、押したまま更に F6キーも押す)を実行すると元の GUI インストーラー画面に戻れるので、続きの作業を行ってください:
rhel9_fdisk_006


 

インターネットの使えない環境下で RHEL(RedHat Enterprise Linux) 9.0 をインストールして、サブスクリプションを登録するまでの方法を調べてみました。

通常 RHEL 9.x を DVD や ISO からインストールする場合、サブスクリプションの登録がインストーラーの一部に組み込まれていて、(インターネット経由で)サブスクリプションを登録しないとインストールできないようになっています。つまり通常のインストールだとインターネット接続が必須です:
rhel9install_046
(SOFTWARE 欄の "Connect to Red Hat" と書かれた部分に注目。ここで RedHat ポータルにログインし、サブスクリプションを登録しないとインストーラーは先に進めなくなっています)

しかし特殊なケースで、インターネット接続が全くない(Proxy の設定によって接続できる、とかではなく、そもそもインターネット接続回線そのものが存在していないような)環境下で RHEL をインストールしたいケースもあるはずです。その場合の手順を紹介します。なお以下で紹介するスクリーンショットでは RHEL 9.0 を使っていますが、9.x であれば手順は同様のはずです。


【RHEL 9.x のオフラインインストール手順】
おおまかなオフラインインストール手順を紹介します:
(手順1)インターネット接続環境を使った事前準備
(手順2)RHEL 9.x のオフラインインストール
(手順3)オフライン環境下でのサブスクリプション登録



通常のインストールだとインストール中に RedHat カスタマーポータルの RHSM(RedHat Subscription Management) にアクセスしてサブスクリプションを登録することで、その場でシステムプロファイルを作成&登録します。一方、インターネットにアクセスできない環境での登録を考慮したケースでは事前に(手動で)システムプロファイルを作成して、その証明書ファイルをダウンロードしておきます。そしてオフライン環境で RHEL をインストールし、ダウンロードした証明書ファイルをインポートすることでサブスクリプション登録を行う、という手順を実行します。ある意味で実行順序が異なるだけで、RedHat のアカウントが必要になる点などは変わりありません。

なお(手順1)のみ、RHEL インストール先マシンとは別のインターネットの使える環境で実施する必要があります。手元の PC などインターネットの使える PC を使って実施してください。


【(手順1)インターネット接続環境を使った事前準備】
インターネットの使える環境下で RHSM のシステムページにアクセスします(ログインしていない場合はログインします)。

以下のような画面が表示されます(ログインしたアカウントで既に RHEL のサブスクリプションを利用している場合は利用中のサブスクリプションの一覧が表示されます)。今回は新しいサブスクリプションのプロファイルを作成したいので「新規作成」ボタンをクリックします:
2024011401


このサブスクリプションを登録するシステムの種類(物理システム/仮想システム、名前、アーキテクチャ、CPU 数、RHEL バージョン)を入力して、「作成」ボタンをクリックします(ここでは "air-gapped-vm" という名前で作成しています):
2024011402


先ほどの画面に戻ると、直前に新規作成したシステム("air-gapped-vm")が一覧に含まれて表示されます。この名前部分をクリックします:
2024011403


作成したシステムの詳細画面に移動します。「サブスクリプション」タブを選択すると、このシステムをどのサブスクリプション(有償/無償/トライアル/・・)に紐づけて利用するかの画面が表示されます。このシステムはまだ作成したばかりでどのサブスクリプションにも紐づいて(アタッチされて)いないはずです。紐づけを作成するので「サブスクリプションをアタッチ」ボタンをクリックします:
2024011404


自分のアカウントで利用可能なサブスクリプションの一覧が表示されるので、ここから利用するサブスクリプションを選択します。私は個人開発者向けに 15 システムまで無料で利用できる "RedHat Developer Subscription for Individuals" に登録しているので、このサブスクリプションを使うことにしています(というわけで下図では "RedHat Developer Subscription for Individuals" にチェックを入れています)。選択後に「サブスクリプションのアタッチ」ボタンをクリックします:
2024011405


「サブスクリプション」タブが選択された状態の画面に戻ります。先ほどとは異なり、サブスクリプションがアタッチされているのでアタッチ済みのサブスクリプションが表示されています。 この内容を確認後に「証明書のダウンロード」ボタンをクリックします:
2024011406


"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_certificates.zip"("x" は英数文字)という名前の zip ファイルが手元の PC にダウンロードされるので、このファイルを展開します。すると "consumer_export.zip" という名前の zip ファイルが含まれているので、これを取り出します:
2024011407


取り出した "consumer_export.zip" ファイルを再度展開すると、"entitlement_certificates" というフォルダが見つかるはずなので、このフォルダの中を参照します:
2024011408


"entitlement_certificates" フォルダ内に拡張子が ".pem" のファイルが1つ見つかります。このファイルが後ほどオフラインで登録することになるシステムプロファイルの証明書ファイルです。このファイルを取り出しておきます:
2024011409


後でオフライン状態の RHEL (をインストール前のシステム)で使えるよう、取り出した .pem ファイルを USB メモリにコピーしておきます。

地味に重要だと思っているのですが、ここで利用する USB メモリは「SD カードや micro SD カードを USB のカードリーダーに接続したもの」ではなく、純粋な USB メモリスティックを使うことを推奨します。理由は後からこの USB メモリを指定してシステムにマウントする作業を行うのですが、その際 USB メモリスティックだと名前でなんとなく見つけやすいのに対して、カードリーダーに接続されている場合は(もしかしたら認識すらされていない可能性もあるのに)名前ではわからないので、適当に指定してファイルシステムを破壊してしまう可能性もあるため、これを避けるためです。初めから容量のわかっている USB メモリスティックだと、その容量が名前の一部に含まれていたりする(後述の例だと "Memory Key 4GB" みたいな感じ)ので見つけやすいのでした。

ここまではインターネットの使える環境で行う、いわば「準備」作業です。ここから先はインターネットのない環境で実施します。



【(手順2)RHEL 9.x のオフラインインストール】
インターネット接続の無いマシンに RHEL 9.x の DVD(ISO) メディアをセットして起動&インストールします。以下、画像は RHEL 9.0 のメディアを使った時のものです。

最初はこんな画面になります。(デフォルトは "Test this media & install Red Hat Enterprise Linux 9.0" ですが)"Install Red Hat Enterprise Linux 9.x" を選択して Enter を押します(メディアテストする場合は放っておいても構いませんが、飛ばした方が早いです):
rhel9install_000


メディアからのブートが始まります:
rhel9install_001


こんな感じの画面になると「あと少し」です。もう少し待ちます:
rhel9install_002


画面が GUI になり、インストールウィザードが起動します。最初はインストールで使用する言語を選択します(ここでは English - English(United States) を選択したものとして説明を続けます。以下のスクショも英語画面のものです):
rhel9install_003


次にこのようなインストール要件を設定する画面になります。赤い文字が表示されている箇所は必須設定が未定になっていることを意味しており、全て指定しないとインストールまで進めません。 大事な点として、SOFTWARE のすぐ下にある "Connect to RedHat" 部分があります。インターネット接続が可能な環境であると判断されると、ここも赤文字になって RedHat のサブスクリプション登録を行うことになりますが、インターネット接続のない環境の場合はここをスルーできるようになります(スルーする場合は後でサブスクリプション登録が必要です)。 私の環境の場合、とりあえずキーボードを日本語キーボード配列にしたい(デフォルトは英語キーボード配列)ので Keyboard をクリックします:
rhel9install_004


キーボード配列設定画面に移動します。初期状態は "English(US)" しか登録されていないので、下の "+" をクリックして追加します:
rhel9install_005


追加するキーボード配列を選択します。ここでは "Japanese" を指定して "Add" ボタンを押します:
rhel9install_006


"Japanese" が追加されましたが、このままだとデフォルト設定は "English(US)" のままです。そこで "Japanese" を選択した状態で、"^" のボタンを押して、"Japanese" を一番上まで移動させます:
rhel9install_007


"Japanese" が一番上になったことでデフォルト設定になりました。これでキーボード配列の作業を完了するので左上の "Done" ボタンをクリックします:
rhel9install_008


元の画面に戻りました。キーボード配列は "Japanese, English(US)" になっています。 次に必須設定項目である root パスワードを指定します。"Root Password" と書かれた部分をクリックします:
rhel9install_009


root ユーザーのパスワードを、確認のため2度入力します。このシステムの root ユーザーに SSH からのパスワードログインを許可する場合は "Allow root SSH login with password" にチェックを入れます(入れない場合、SSH で直接 root パスワードによるログインはできなくなります)。終わったら左上の "Done" をクリックして元の画面に戻ります:
rhel9install_010


インストール先を指定する "Installation Destination" も必須指定項目です。ここをクリックします:
rhel9install_011


インストール先となるディスクを指定します。また "Storage Configuration" 項目はデフォルトの "Automatic" のままでも構いませんが、(ディスクサイズにもよりますが、"Automatic" だと "/home" がまあまあ大きめに作られてしまうので)自分でパーティションを設定したい場合は "Custom" を選びます。"Automatic" で "Done" をクリックすると元の画面に戻ります。"Custom" の場合は具体的なパーティショニングの画面に移動します:
rhel9install_012


"Custom" を選択した場合の次画面がこちらです。カスタマイズ指定方法はいくつかありますが、"Click here to create them automatically" をクリックしていったん "Automatic" と同じデフォルト設定にします:
rhel9install_013


デフォルトのパーティショニング設定が表示されます。この内容で問題なければこのまま "Done" でもいいのですが、「"/home" に3分の1程度作られるのが嫌で全部 "/" で管理したい」ということもあると思います。そんな場合は "/home" を選択した状態で "ー" をクリックして、"/home" を開放します:
rhel9install_014


パーティショニングから "/home" が消えました。同時に使われていた 30GB ちょっと(画面から確認できるサイズは "31.8GiB")の領域が余りました。これを "/" に加えたいと思います。"/" を選択します:
rhel9install_015


"Desired Capacity" 欄を余っている 31.8 GB を足した数に変更します。下の例ではもともと 65.14GB だった容量に余った 31.8 GB を加えて 96.64 (GiB) に変更しました:
rhel9install_016


これで確定する場合は画面を下にスクロールすると現れる "Update Settings" ボタンをクリックします:
rhel9install_017


画面上でも "/" の領域が 96.94 GiB に切り替わりました。他に必要な変更があればこの作業を繰り返します。変更が完了したら左上の "Done" をクリックします:
rhel9install_018


変更があった場合、「本当に変更を書き込むよ」という確認画面が表示されます。"Accept Changes" ボタンをクリックします:
rhel9install_019


これで "Installation Destination" の設定も完了しました。この時点で未設定な必須設定項目がなくなったので右下の "Begin Installation" ボタンが有効になっています。このままクリックしてもいいのですが、最後にインストール内容を指定する "Software Selection" を見ておきましょう:
rhel9install_020


デフォルトで "Server with GUI" が選択されています。つまりこのままインストールすると GUI 画面付きの RHEL 9.x がインストールされることになります。GUI が余分であれば "Minimal Install" に変更してもいいと思います。また画面右側で関連するソフトを個別追加できるようにもなっているので、自分の求める内容に変更してください。最後に "Done" をクリックします:
rhel9install_021


今回はこの内容でインストールしてみます。画面右下の "Begin Installation" ボタンをクリックします:
rhel9install_022


インストールが開始されます。ここからはしばらく待つだけになります:
rhel9install_023


無事にインストールが完了すると "Complate!" と表示されます。画面右下の "Reboot System" ボタンで再起動します:
rhel9install_024


インストールされたディスクから再起動が実行されます:
rhel9install_025


この例では "Server with GUI" でインストールしたので、起動画面は途中から GUI モードに切り替わります(画像だとわかりにくいですが、白いサークルがクルクル回っています):
rhel9install_026


GUI 画面の場合、初回起動時のみ以下のようなセットアップ画面が表示されます。"Start Setup" をクリックしてセットアップを行います:
rhel9install_027


まずは位置情報を提供するかどうかのプライバシー設定です。オフラインインストールの場合は、要件的にチェックを付けることはないのではないかと思います。ともあれ設定後に "Next" ボタンをクリック:
rhel9install_028


一般ユーザーの登録を行います。まずは名前とユーザー名を指定して "Next":
rhel9install_029


次の画面で先ほど入力した一般ユーザーのパスワードを設定します。設定後に "Next":
rhel9install_030


これで初期セットアップは完了です。最後に "Start Using Redhat Enterprise Linux" ボタンをクリックしてセットアップ画面を閉じます:
rhel9install_031


RHEL の概要説明ツアーアプリが起動しますが、不要であれば "No Thanks" で飛ばせます:
rhel9install_032


無事にインストールできました。インターネット接続がないので "System Not Registered" と表示されていますが、インストール作業そのものはこれで完了しています:
rhel9install_033


最後に簡単な使い方を説明しておきます。画面左上の "Activities" をクリックすると以下のような画面になり、使いたいアプリを検索したり、下のドックからアプリを起動できるようになります。試しにドックの右から2番目、ターミナルっぽいアイコンをクリックしてみます:
rhel9install_036


ターミナルが起動しました。こんな感じで GUI からアプリを起動することができます:
rhel9install_037


この後の作業で使うことになるので "sudo -i" して root に切り替えておきます:
rhel9install_038



【(手順3)オフライン環境下でのサブスクリプション登録】
このターミナルを使って【手順1】で取り出した証明書ファイルをインポートします。証明書ファイルをコピーした USB スティックを対象のマシンに差し込みます。そして、
# fdisk -l

というコマンドを実行します。コマンドの実行結果にはこのシステムがこの時点で認識しているディスクデバイスの一覧が表示されます。ここで先ほど差し込んだ USB スティックが認識されているデバイス名を見付けます。

下図の例だと、
  :
  :

Disk /dev/sdb: 3.84 GiB, 4127194624 bytes, 8060927 sectors
Disk model: Memory Key 4GB
  :
  :

Device    Boot Start     End Sectors  Size  Id Type
/dev/sdb1         63 8060926 8060064  3.8G   b W95  FAT32

のように表示されています。今回使っている USB メモリは容量が 4GB のもので、"Memory Key 4GB" という名前と、認識されている容量(3.84 GiB)からなんとなくこれ(/dev/sdb)が合致してそうだ、と判断できました(実際にどう表示されるかは USB メモリによって異なります)。そして更にその下の表示から、実際のパーティション名が "/dev/sdb1" であることがわかります。このパーティションをマウントすれば証明書ファイルが取り出せそうです:
rhel9install_039



といったことが確認できたので、インストール途中のこのシステムに /usb というフォルダを作ってマウントして取り出します。以下のコマンドを実行します("/dev/sdb1" の部分は実際に認識されているパーティション名に置き換えて実行してください):
# mkdir /usb

# mount /dev/sdb1 /usb

# df -h  (/dev/sdb1 が /usb にマウントされたことを確認)
rhel9install_040


これで USB メモリの内容はこのシステムの /usb フォルダ以下からアクセスできるようになりました。以下のコマンドで実際に証明書ファイル(.pem ファイル)が存在していることを確認します:
# ls -la /usb/*.pem

rhel9install_041



直接アクセスしてインポートしてもいいと思いますが、念のため一時フォルダにコピーしてからインポートすることにします。証明書ファイルを /tmp 以下にコピーします:
# cp /usb/*.pem /tmp

# ls -la /tmp/*.pem  (/tmp 以下に証明書ファイルがコピーされたことを確認)

rhel9install_042


これでこの後の作業には USB メモリスティックは不要なので、ファイルシステムからアンマウントして取り外しておきます:
# umount /dev/sdb1

# df -h  (/dev/sdb1 が /usb からアンマウントされたことを確認)

rhel9install_043


ここまでできれば上述の RedHat カスタマーポータルで紹介されていた subscription-manager コマンドを使った登録ができるようになります。というわけで以下を実行して、証明書をシステムにインポートします(/tmp 以下にコピーした .pem ファイル名を正確に入力してください):
# subscription-manager import --certificate=/tmp/8966514065945099419.pem

rhel9install_044


実行後に "Successfully imported certification" と表示されていれば成功です。念のため次のコマンドを実行して実際にインポートされた内容を確認することもできます(実行結果はかなり長いです。下図はその下の方の一部だけです):
# subscription-manager list --consumed

rhel9install_045



これでオフラインインストールした RHEL システムへのサブスクリプションのインポートまでができました。


(おまけ?)
手順の中でサブスクリプションは登録したんだけど、でもまだこの "System Not Registered" エラーがでるんだよな。。
rhel9install_033


その回避策はこれらしい。でも RedHat は推奨しない、とのこと:
https://access.redhat.com/ja/solutions/7013937


このページのトップヘ