tailscaleで管理者とユーザーを分離する

ラズパイ5のluantiサーバーの管理者とユーザーは少なくとも別のメールアカウントにしないと、サーバー含めた管理者エリアへのアクセスができてしまうので現実運用に即した多少のアップデート

つまり管理者エリアとユーザエリアはメールアカウントを別にして、ユーザアカウントの管理画面には最悪アクセスできても管理者エリアの管理画面へのアクセスは禁止する設定

ユーザデバイスは以下のuser@gmail.com(仮想のアドレスです)の配下に登録、管理者デバイスはadmin@gmail.comの配下に登録します

	"groups": {
		"group:admin":   ["admin@gmail.com"],
		"group:players": ["admin@gmail.com", "user@gmail.com"],
	},
	// Define the tags which can be applied to devices and by which users.
	// "tagOwners": {
	//  	"tag:example": ["autogroup:admin"],
	// },
	"tagOwners": {
		"tag:luanti-server": ["group:admin"],
	},
	// Define grants that govern access for users, groups, autogroups, tags,
	// Tailscale IP addresses, and subnet ranges.

	"acls": [
		{
			"action": "accept",
			"src":    ["group:players"],
			"dst": [
				"tag:luanti-server:30000-30010",
				"tag:luanti-server:22",
				"tag:luanti-server:5901",
			],
		},
	],

user@gmail.com側のドメインではACL設定はできないので、user@gmail.com側の相互ユーザ分離はできないですが、それは通常のWi-Fi環境下と大差はないだろうから現実的運用では、管理者エリアとユーザエリアを分離だけでも相当にセキュアだろうと思う

 

admin

 

tailscaleのセキュリティ設定

tailscale自体はtailnet以外からのアクセスに対しては強固ですが、前の記事で述べた通りいくつか考慮すべきポイントがあります

その中でもクライアント同士(特にユーザクライアントから管理クライアント)でアクセスできてしまうというのは避けなければいけない項目でしょう

そのための設定がACLです、tailscaleの管理画面で、”Access controls”のjsonファイルに以下のように書き込んで、クライアントからサーバーへのアクセスだけ許可します、ここで許可した以外は全部deny扱いなのでクライアント間は完全分離です

	"groups": {
		"group:admin":   ["user@gmail.com"],
		"group:players": ["user@gmail.com"],
	},
	// Define the tags which can be applied to devices and by which users.
	// "tagOwners": {
	//  	"tag:example": ["autogroup:admin"],
	// },
	"tagOwners": {
		"tag:luanti-server": ["group:admin"],
	},
	// Define grants that govern access for users, groups, autogroups, tags,
	// Tailscale IP addresses, and subnet ranges.
	"acls": [
		{
			"action": "accept",
			"src":    ["group:players"],
			"dst": [
				"tag:luanti-server:30000-30010",
				"tag:luanti-server:22",
				"tag:luanti-server:5901",
			],
		},
	],

jsonファイルを編集してセーブすると、Machinesの選択メニューでEdit ACL tagsが現れて、これを選択するとデバイスにタグ付できます

タグ付すると、

のようにmachinesのデバイス名の下にtag表示されるようになりますから、このタグがACL設定の"tag:luanti-server": ["group:admin"]と合致してアクセス(フロー)制御が実行できます

ラズパイ5のufw設定(使うポートだけ許可する)と、unattended-upgradesを使った自動セキュリティアップデートも追加しておきます

tailscaleのfree版だとアクセスの権限制限設定できないけれども、身内の運用ならばあえて管理画面のことは伝えずに運用だね

 

admin

Luantiの遠隔サーバー化

ラズパイ5なら軽量なLuantiサーバーなら動かせるのですが、リモートでどうやるかだったのですが、VPNを使えば良いと

ラズパイ5(Luanti service)- VPN ~~~~~~~ VPN – client

<VPN : tailscale>

VPNは今は第二世代とでもいうべき時代で、WireGuardと言われる最新のVPNプロトコルを使った無償で使えるVPN製品(カーネル空間動作で高速、軽量、最新の暗号化技術でセキュア)が注目で中でもtailscaleは広く使われているようです

特徴は中央集権型のサービスで、アカウントログオンしてクラウド側にデバイスを登録すると、登録したデバイス間でセキュアな通信が確保され、さらにMagicDNSという仕組みでそれぞれの登録デバイスにDNS情報が転送されるので、その情報を使って登録デバイス間の接続にはIPアドレスの意識さえ不要という優れもの

# tailscale起動、以下のコマンドでログイン画面のURLが出てくるからブラウザログイン
$ sudo tailscale up

# 以降の起動時には自動起動するように、以下を実行する
$ sudo systemctl enable minetest-server

# IPアドレスラズパイ側の確認(Magic DNS使うからIPアドレスを触る必要はない)
$ tailscale ip -4
100.83.206.1

% tailscale status

100.88.161.30  macbookpro  login_account@  macOS  -
100.83.206.1   rasp5       login_account@  linux  -

クライアントにはtailscaleのサイトからプラットフォーム対応のpkgを引っ張ってきてインストールして、起動時に同時にラズパイログオンアカウントと同じアカウントでログインすればマシンが追加されます、その後に例えばsshならば、

$ ssh usr@rasp5

でログオンできます、rasp5はMagic DNSの登録名です

Luantiならば、

こんな感じでアクセス

公開にあたってのセキュリティ運用対応、VPNそのものは信頼度高いのでtailscaleのログイン情報の保護に尽きる

1. 管理者アカウントを1個に固定

→ 「新しい端末を追加できるのは管理者だけ」

2. デバイス承認をON(超重要)

管理画面 → Settings → Device approval

これONにすると:

新しい端末がログインしても
管理者が承認しないと通信不可

3. キーは使わない(人力ログインのみ)

Tailscale自体は:

パスワードを持たない設計

全部:
Google / GitHub / Microsoft
の認証に丸投げ。

4. 不要端末の自動削除

Settings → Key Expiry

180日(default)に設定。

5. Subnet / Exit Node は使わない

デフォルトはOFFになってる

6. デフォルトユーザーpiのパスワードは安全性の高いものに安全性の高いものに変える

 

<Debain版のLunatic serverのインストール>

snap版では不都合あったので、ヘッドレス版(Debian公式の minetest-server デーモン)を入れる

% sudo apt install minetest-server

# サービスの自動起動enableと一時的な起動
% sudo systemctl enable minetest-server
% sudo systemctl start minetest-server

 

<ユーザーの追加と別ワールドの作成>

・指針:ユーザーは当然限定アクセスだけでroot権限は与えない

・ワールド:プレイ用の30000(dafult)ポート以外に編集や個人用のワールドは例えば30001ポートで起動するようにして環境は分離する

・手順:ユーザーluanti1を作ってサーバー起動

$ sudo adduser luanti1
$ su - luanti1

$ mkdir -p ~/.minetest/worlds/test		// 編集用のdirectory(test)を作る
$ /usr/lib/minetest/minetestserver   --world ~/.minetest/worlds/luanti1   --port 30001   --quiet   --logfile ""

複数のlunatiサーバー立ち上げても、元が軽量だからラズパイ5では今のところ余裕です

 

admin

Open WebUIでRAG化してみた

VS codeのCursorからRAG機能の呼び出しうまくいかなかったので、Open WebUIから呼び出しているollamaで動作するLocal LLMにRAG機能を追加してみた

Open WebUIを使えば完全にノーコードでRAG機能が追加できます、でも無論ハルシネーションもでるだろうけど、数年前の知識しかないLocal LLMから見れば大きな進歩だろうと思う

実はOpen WebUIには、ベクターDBもWebクローラー機能(実は内部ではfirecrawlとパイプラインでつながってるとのこと)も実装されてるから実現できるわけですが、チームでの使用とかには荷が重いと思いますが個人で使うならこれで当面は間に合うんじゃないかと思う

以下のイメージはチャットモードで作成済みのLocal LLM qwen2.5-coder:14B + RAGのモデル(Qwen2.5)がモデル選択メニューから見えてるとこ

 

で、このモデルの作り方は、ワークスペースを選択するとこの画面(モデル名はQwenにリネーム前)になります、今現在入力しているのは「モデル」と「ナレッジベース」だけ、プロンプトとツールは必要になった時点で追加します、モデルそのものは画面右上のNew Modelボタンを押して作成します

これはすでに作成したナレッジベースですが、

ナレッジの追加は、コレクションの追加メニューで右上の+ボタンを押すと種々の方法で追加できます、ここではwebpageから内部的にはおそらくmarkdown形式で取り込まれてます、これらはgithubから取り込んだmarkdownファイルですね

チャット画面で、投げたプロンプトに対して、回答の下部に(1 Source)というのが参考にされたナレッジになります

その部分の拡大は、以下のとおりでmarkdownからの抽出になってますね

しばらくはこの環境でパーソナルエージェント的な使い方をやってみます、違うエージェントが必要になればその時はモデル追加すれば良い、自分が持っているいろんなファイルをぶち込んでしまいたいと思う

P.S. 2026/2/10

Local LLMも分野特化してきていて、例えばqwen2.5-coder:14Bは英語は全くダメ、qwen2.5:14Bはちゃんと使えるから、用途ごとに専門家用意しとく感覚だね

 

admin

ollamaのモデルを変えてみた

昨日はとりあえずのcodeサポート用のモデルを入れてみたけど、結構重量級だったので同等性能でも負荷が軽いモデルに、同時に編集時のtab補完してくれる軽量モデルを設定してみた

% ollama list
NAME                 ID              SIZE      MODIFIED
qwen2.5-coder:14B    9ec8897f747e    9.0 GB    28 minutes ago
qwen2.5-coder:7b     dae161e27b0e    4.7 GB    44 minutes ago

Continueのconfig.jsonファイルの設定

tabAutocompleteModel:このブロックがタブ補完時のLLM指定

{
  "models":[
   {
      "title": "Main Coder (14B)",
      "provider": "ollama",
      "model": "qwen2.5-coder:14b"
     }
    ],
  "tabAutocompleteModel": {
    "title": "Tab Autocomplete",
    "provider": "ollama",
    "model": "qwen2.5-coder:7b"
  }
}

qwen2.5-coder:14b:メモリ使用量がmaxでも24GB程度で収まるようになったので、こちらにしよう

しかしLocal LLMは特にプログラム言語系の変化の早い領域ではRAG使うようにしないと実質的には使えないね、次はRAG使えるようにしよう

-> 2026/2/3:やってみたけどできなかった(@rag search “hello”が通らない)、まだ流動的なところが多そうだからしばしサスペンド

 

admin

ContinueでVScodeのA.I補完

VScodeでcopilotというは安定してるけど、無償だと限度があるから補完的にLocal LLMを使ってみる

<環境>

M4 MacBook Pro/32GB

<手順>

deepseek-coder-v2:latestがそこそこらしいからollamaに追加する

% ollama list
NAME                         ID              SIZE      MODIFIED
deepseek-coder-v2:latest     63fb193b3a9b    8.9 GB    5 hours ago

動かすとメモリは30GB程度消費するからyellow markレベルだけど動作はできる

・ブラウザからの使用

Docker経由が環境を汚染しないからそのやり方で、8080はDcoker内部のportで外部にはport 3000で公開される

% docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main

Dockerでの見え方

・VScodeからの使用

① 拡張機能 Continueをインストールする

② Continueのための設定ファイルを作る

% mkdir -p ~/.continue
code ~/.continue/config.json

VScodeのエディタが起動するので、以下を入力

{
  "models": [
    {
      "title": "DeepSeek Local",
      "provider": "ollama",
      "model": "deepseek-coder-v2:latest"
    }
  ],
  "defaultModel": "DeepSeek Local"
}

設定を保存してVScodeを再起動すると、codeの選択 -> 右クリックでContinueのchat/edit起動ができる、Continue自体のwindowsは左側に開く

感覚、まあまあ使えるかなと思う、Rustの組み込みとかはまだ学習レベルが低い感じはあるけどね

 

admin

 

ラズピコrustでmicro:bitと連携させてみた

ラズパイ財団プロダクト同士でなんか作ってみようと思って、micro:bitをフロントエンドとしてmic入力とLED出力を使った、音声のレベル表示(一応LED25個で25レベルの表示ができます)をやってみた

macro:bit (p1) –> ラズピコADC1(平均化処理)-> PWM出力およそ22KHzをrcでローパス -> micro:bit(p2)という流れ

<micro:bit側のコード>

from microbit import *

def show_bar(level):  # level: 0-25
    display.clear()
    height = level // 5  # 5段に分割
    width = level % 5    # 各段の幅
    
    # 下からバー積み上げ
    for y in range(5):
        if 4-y < height:
            for x in range(5):
                display.set_pixel(x, 4-y, 9)
        elif 4-y == height:
            for x in range(width):
                display.set_pixel(x, 4-y, 9)

while True:
    mic_level = microphone.sound_level()
    pin1.write_analog(mic_level * 4)
    
    control_level = pin2.read_analog()
    # 0-25に変換
    bar_level = min(control_level // 40, 25) 
    show_bar(bar_level)
    sleep(10)

<ラズピコ側>

Cargo.toml

embedded-halの1.0系は0.2と互換なし、embeded-halはobject指向言語で言うところのinterfaceでrp-235x-halは実装、つまりプロセッサが変わっても共通だろうinterfaceをembedded-halはtraitの集合として持っている


[package]
edition = "2024"
name = "rust_starter"
version = "0.1.0"
license = "MIT or Apache-2.0"


[dependencies]
cortex-m = "0.7.7"
cortex-m-rt = "0.7.5"
rp235x-hal = "0.3"
embedded-hal = "0.2"
#embedded-hal = "1.0.0" does not support rp235x-hal yet
defmt = "1.0.1"
defmt-rtt = "1.0.1"
panic-probe = { version = "1.0", features = ["print-defmt"] }

[build-dependencies]
regex = "1.11.0"

[target.'cfg( target_arch = "arm" )'.dependencies]
panic-probe = { version = "1", features = ["print-defmt"] }

[target."thumbv8m.main-none-eabihf".dependencies]
rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] }

<Rustコード>

Lチカコードを修正

#![no_std]
#![no_main]

use defmt_rtt as _;
use hal::pac;

use embedded_hal::adc::OneShot;
use embedded_hal::blocking::delay::DelayUs;
use embedded_hal::PwmPin;

#[cfg(target_arch = "riscv32")]
use panic_halt as _;
#[cfg(target_arch = "arm")]
use panic_probe as _;

// Alias for our HAL crate
use hal::entry;

#[cfg(rp2350)]
use rp235x_hal as hal;

#[cfg(rp2040)]
use rp2040_hal as hal;

// use bsp::entry;
// use bsp::hal;
// use rp_pico as bsp;

/// The linker will place this boot block at the start of our program image. We
/// need this to help the ROM bootloader get our code up and running.
/// Note: This boot block is not necessary when using a rp-hal based BSP
/// as the BSPs already perform this step.
#[unsafe(link_section = ".boot2")]
#[used]
#[cfg(rp2040)]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;

/// Tell the Boot ROM about our application
#[unsafe(link_section = ".start_block")]
#[used]
#[cfg(rp2350)]
pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe();

/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz.
/// Adjust if your board has a different frequency
const XTAL_FREQ_HZ: u32 = 12_000_000u32;
const DELAY: u32 = 1000; // μs delay
const SAMPLES: u32 = 250;

/// Entry point to our bare-metal application.
///
/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function
/// as soon as all global variables and the spinlock are initialised.
///
/// The function configures the rp2040 and rp235x peripherals, then toggles a GPIO pin in
/// an infinite loop. If there is an LED connected to that pin, it will blink.
#[entry]
fn main() -> ! {
    // 1. Peripherals取得
    let mut pac = pac::Peripherals::take().unwrap();

    // 2. クロック・ウォッチドッグ初期化
    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
    let clocks = hal::clocks::init_clocks_and_plls(
        XTAL_FREQ_HZ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    // 3. GPIO初期化
    let sio = hal::Sio::new(pac.SIO);
    let pins = hal::gpio::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks);

    // 4. ADC設定
    let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS);
    let mut adc_pin = hal::adc::AdcPin::new(pins.gpio26).unwrap();

    // 5. PWM設定
    let pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS);
    let mut pwm = pwm_slices.pwm0;
    pwm.set_ph_correct();
    pwm.set_top(4095);
    pwm.enable();

    // GP0 = PWM Slice 0 Channel A(alomost 20KHz duty 99% @3.3v full scale)

    let mut pwm_channel = pwm.channel_a;
    let _pwm_pin = pins.gpio0.into_function::<hal::gpio::FunctionPwm>(); // GP0 as a PWM output

    loop {
        let mut sum: u32 = 0;

        // measure "samples" time and make average
        for _ in 0..SAMPLES {
            let v: u16 = adc.read(&mut adc_pin).unwrap();
            sum += v as u32;
        }

        let avg = (sum / SAMPLES) as u16;
        pwm_channel.set_duty(avg);
        delay.delay_us(DELAY);
    }
}

/// Program metadata for `picotool info`
#[unsafe(link_section = ".bi_entries")]
#[used]
pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [
    hal::binary_info::rp_cargo_bin_name!(),
    hal::binary_info::rp_cargo_version!(),
    hal::binary_info::rp_program_description!(c"Blinky Example"),
    hal::binary_info::rp_cargo_homepage_url!(),
    hal::binary_info::rp_program_build_attribute!(),
];

// End of file

動いてるところの動画は以下に、

まだ周辺回路サポートのcrateは途上ではあるけれども、組み込みでも普通に使えるようになってきたのは大きな進歩

 

admin

ラズピコでRust

ラズベリー財団がVScodeのpcio拡張機能でRust(実はZephyrも)サポートするようになったので動かしてみた

<構成と環境>

・ラズピコ2(無線なし)

・M4 MacBook Pro 32GB

・純正デバッグプローブ

 

<手順>

すでにインストール済みのpico拡張機ののメニューにはRustプロジェクト作成のメニューが出ています

ここでNew Rust Projectを選択するとLチカのサンプル、実は初代ラズピコやラズピコのRISCコア対応にもなってます、が開かれます

ラズピコ2にデバッガー接続、ピン立っていないので追加で半田付けして接続

接続確認は、

% probe-rs list
The following debug probes were found:
[0]: Debug Probe _CMSIS_DAP_ -- 2e8a:000c:E663AC91D3826F39 (CMSIS-DAP)

のように見えればつながっています

 

この状態でほぼすぐにデバッグができました、Runはファイルモードでしか機能しませんがデバッグは上位互換なので問題なし、デバッグだと.uf2のファイルも必要なくて、バイナリそのままがラズピコに書き込まれます

これ、デバッガーでブレークポイント設定して止めたとこ

 

めちゃくちゃ簡単に環境構築できるから、まだ無線関連のcrateは不備だけど、もうRustでできることはRust以外の選択は、ラズピコではないと思う

 

admin

STM32開発環境はVScodeにしない方が正解

STM32のVScode拡張機能でサポート出来るらしいのでやってみたけど、既存のraspberry picoのc開発環境を破壊してしまう

具体的にはVScodeの全体設定ファイルであるsetting.json、

~/Library/”Application Support”/code/User/settings.json

をSTM32の拡張機能がある状態ではstm32の環境に汚染(上書き)させてしまう、profileを分離というのもあるんだろうけど、そんな面倒なことやるなら、STM32の環境ではbuildはCubeIDE(クラシックだけど確実)で実行してソースコードのupdateはたとえばcursorとか使い分けるのが正解だよね

実はraspberry pico tool(VScode拡張機能)もc言語のbuild環境構築を楽にするためのツールなんだけどね

もはやラズピコもRustを公式でサポート(Wi-Fi, BLEはcrateが未完)始めてんだから、今後はc言語使わなければいけないケースは徐々に減っていくよ間違いなく

それと、これから参入の若手はRustとcどっち選ぶと言われたら、間違ってもcは選ばないはずだしね

ということでc言語は他に選択できない時だけ使うようにしましょう

 

admin

 

スペアナの見栄えを改善

ベースレベルがかなりバタつくので、平均化処理を入れてみた

やっていることは、過去の平均値の重み付け0.9、最新の計算値の重み付け0.1で加算していくだけ、ベースレベルが暴れなくなるので視覚上の効果は結構大きい改善、スペアナ的になるという意味でね

	/* 5. 平均化と実効値計算(振幅スペクトル : db) */
	for (uint32_t i = 0; i < FFT_LEN / 2; i++) {
		float32_t real = fft_out[2 * i];
		float32_t imag = fft_out[2 * i + 1];
		cur[i] = (real * real + imag * imag) * HANN_GAIN_CORRECTION + 1e-12f;
		// averaging
		if (first) {
			fft_ave[i] = cur[i];
		} else {
			fft_ave[i] = fft_ave[i] * 0.9f + cur[i] * 0.1f;
		}
		fft_rms[i] = (int16_t) (10.0f * log10f(fft_ave[i]));
	}
	first = false;	// to set first flag to false

<写真>

<条件>

以前の記事と変わらずですが、

・入力は5KHzのduty50%のPWM

・ADCサンプリング周波数 200KHzで一次IIRフィルタ(cut off 20KHz)処理後に1/5 decimation

・Hann windows処理

・FFT実行後に平均化処理後に対数スケール変換

前の記事の補足でも書いたけど、一次IIRフィルタにすることで15KHzの第三高調波はほぼ理論通りのおよそ10db(正確には9db)に収まってる、ただし20KHzに近づくとノイズレベルが落ちているのはフィルターの影響です

 

admin