Posted on Leave a comment

How to make a local open source AI chatbot who has access to Fedora documentation

If you followed along with my blog, you’d have a chatbot running on your local Fedora machine. (And if not, no worries as the scripts below implement this chatbot!) Our chatbot talks, and has a refined personality, but does it know anything about the topics we’re interested in? Unless it has been trained on those topics, the answer is “no”.

I think it would be great if our chatbot could answer questions about Fedora. I’d like to give it access to all of the Fedora documentation.

How does an AI know things it wasn’t trained on?

A powerful and popular technique to give a body of knowledge to an AI is known as RAG, Retrieval Augmented Generation. It works like this:

If you just ask an AI “what color is my ball?” it will hallucinate an answer. But instead if you say “I have a green box with a red ball in it. What color is my ball?” it will answer that your ball is red. RAG is about using a system external to the LLM to insert that “I have a green box with a red ball in it” part into the question you are asking the LLM. We do this with a special database of knowledge that takes a prompt like “what color is my ball?”, and finds records that match that query. If the database contains a document with the text “I have a green box with a red ball in it”, it will return that text, which can then be included along with your original question. This technique is called RAG, Retrieval Augmented Generation.

ex:

“What color is my ball?”

“Your ball is the color of a sunny day, perhaps yellow? Does that sound right to you?”

“I have a green box with a red ball in it. What color is my ball?”

“Your ball is red. Would you like to know more about it?”

The question we’ll ask for this demonstration is “What is the recommended tool for upgrading between major releases on Fedora Silverblue”

The answer I’d be looking for is “ostree”, but when I ask this of our chatbot now, I get answers like:

Red Hat Subscription Manager (RHSM) is recommended for managing subscriptions and upgrades between major Fedora releases.

You can use the Fedora Silver Blue Upgrade Tool for a smooth transition between major releases.

You can use the `dnf distro-sync` command to upgrade between major releases in Fedora Silver Blue. This command compares your installed packages to the latest packages from the Fedora Silver Blue repository and updates them as needed.

These answers are all very wrong, and spoken with great confidence. Here’s hoping our RAG upgrade fixes this!

Docs2DB – An open source tool for RAG

We are going to use the Docs2DB RAG database application to give our AI knowledge. (note, I am the creator of Docs2DB!)

A RAG tool consists of three main parts. There is the part that creates the database, ingesting the source data that the database holds. There is the database itself, it holds the data. And there is the part that queries the database, finding the text that is relevant to the query at hand. Docs2DB addresses all of these needs.

Gathering source data

This section describes how to use Docs2DB to build a RAG database from Fedora Documentation. If you would like to skip this section and just download a pre-built database, here is how you do it:

cd ~/chatbot
curl -LO https://github.com/Lifto/FedoraDocsRAG/releases/download/v1.1.1/fedora-docs.sql
sudo dnf install -y uv podman podman-compose postgresql
uv python install 3.12
uvx --python 3.12 docs2db db-start
uvx --python 3.12 docs2db db-restore fedora-docs.sql

If you do download the pre-made database then skip ahead to the next section.

Now we are going to see how to make a RAG database from source documentation. Note that the pre-built database, downloaded in the curl command above, uses all of the Fedora documentation, whereas in this example we only ingest the “quick docs” portion. FedoraDocsRag, from github, is the project that builds the complete database.

To populate its database, Docs2DB ingests a folder of documents. Let’s get that folder together.

There are about twenty different Fedora document repositories, but we will only be using the “quick docs” for this demo. Get the repo:

git clone https://pagure.io/fedora-docs/quick-docs.git

Fedora docs are written in AsciiDoc. Docs2DB can’t read AcsciiDoc, but it can read HTML. (The convert.sh script is available at the end of this article). Just copy the convert.sh script into the quick-docs repo and run it and it makes an adjacent quick-docs-html folder.

sudo dnf install podman podman-compose
cd quick-docs
curl -LO https://gist.githubusercontent.com/Lifto/73d3cf4bfc22ac4d9e493ac44fe97402/raw/convert.sh
chmod +x convert.sh
./convert.sh
cd ..

Now let’s ingest the folder with Docs2DB. The common way to use Docs2DB is to install it from PyPi and use it as a command line tool.

A word about uv

For this demo we’re going to use uv for our Python environment. The use of uv has been catching on, but because not everybody I know has heard of it, I want to introduce it. Think of uv as a replacement for venv and pip. When you use venv you first create a new virtual environment. Then, and on subsequent uses, you “activate” that virtual environment so that magically, when you call Python, you get the Python that is installed in the virtual environment you activated and not the system Python. The difference with uv is that you call uv explicitly each time. There is no “magic”. We use uv here in a way that uses a temporary environment for each invocation.

Install uv and Podman on your system:

sudo dnf install -y uv podman podman-compose
# These examples require the more robust Python 3.12
uv python install 3.12
# This will run Docs2DB without making a permanent installation on your system
uvx --python 3.12 docs2db ingest quick-docs-html/

Only if you are curious! What Docs2DB is doing

If you are curious, you may note that Docs2DB made a docs2db_content folder. In there you will find json files of the ingested source documents. To build the database, Docs2DB ingests the source data using Docling, which generates json files from the text it reads in. The files are then “chunked” into the small pieces that can be inserted into an LLM prompt. The chunks then have “embeddings” calculated for them so that during the query phase the chunks can be looked up by “semantic similarity” (e.g.: “computer”, “laptop” and “cloud instance” can all map to a related concept even if their exact words don’t match). Finally, the chunks and embeddings are loaded into the database.

Build the database

The following commands complete the database build process:

uv tool run --python 3.12 docs2db chunk --skip-context
uv tool run --python 3.12 docs2db embed
uv tool run --python 3.12 docs2db db-start
uv tool run --python 3.12 docs2db load

Now let’s do a test query and see what we get back

uvx --python 3.12 docs2db-api query "What is the recommended tool for upgrading between major releases on Fedora Silverblue" --format text --max-chars 2000 --no-refine

On my terminal I see several chunks of text, separated by lines of —. One of those chunks says:

“Silverblue can be upgraded between major versions using the ostree command.”

Note that this is not an answer to our question yet! This is just a quote from the Fedora docs. And this is precisely the sort of quote we want to supply to the LLM so that it can answer our question. Recall the example above about “I have green box with a red ball in it”? The statement the RAG engine found about ostree is the equivalent for this question about upgrading Fedora Silverblue. We must now pass it on to the LLM so the LLM can use it to answer our question.

Hooking it in: Connecting the RAG database to the AI

Later in this article you’ll find talk.sh. talk.sh is our local, open source, LLM-based verbally communicating AI; and it is just a bash script. To run it yourself you need to install a few components, this blog walks you through the whole process. The talk.sh script gets voice input, turns that into text, splices that text into a prompt which is then sent to the LLM, and finally speaks back the response.

To plug the RAG results into the LLM we edit the prompt. Look at step 3 in talk.sh and you see we are injecting the RAG results using the variable $CONTEXT. This way when we ask the LLM a question, it will respond to a prompt that basically says “You are a helper. The Fedora Docs says ostree is how you upgrade Fedora Silverblue. Answer this question: How do you upgrade Fedora Silverblue?”

Note: talk.sh is also available here:
https://gist.github.com/Lifto/2fcaa2d0ebbd8d5c681ab33e7c7a6239

Testing it

Run talk.sh and ask:

“What is the recommended tool for upgrading between major releases on Fedora Silverblue”

And we get:

“Ostree command is recommended for upgrading Fedora Silver Blue between major releases. Do you need guidance on using it?”

Sounds good to me!

Knowing things

Our AI can now know the knowledge contained in documents. This particular technique, RAG (Retrieval Augmented Generation), adds relevant data from an ingested source to a prompt before sending that prompt to the LLM. The result of this is that the LLM generates its response in consideration of this data.

Try it yourself! Ingest a library of documents and have your AI answer questions with its new found knowledge!


AI Attribution: The convert.sh and talk.sh scripts in this article were written by ChatGPT 5.2 under my direction and review. The featured image was generated using Google Gemini.

convert.sh

OUT_DIR="$PWD/../quick-docs-html"
mkdir -p "$OUT_DIR" podman run --rm \ -v "$PWD:/work:Z" \ -v "$OUT_DIR:/out:Z" \ -w /work \ docker.io/asciidoctor/docker-asciidoctor \ bash -lc ' set -u ok=0 fail=0 while IFS= read -r -d "" f; do rel="${f#./}" out="/out/${rel%.adoc}.html" mkdir -p "$(dirname "$out")" echo "Converting: $rel" if asciidoctor -o "$out" "$rel"; then ok=$((ok+1)) else echo "FAILED: $rel" >&2 fail=$((fail+1)) fi done < <(find modules -type f -path "*/pages/*.adoc" -print0) echo echo "Done. OK=$ok FAIL=$fail" '

talk.sh

#!/usr/bin/env bash set -e # Path to audio input
AUDIO=input.wav # Step 1: Record from mic
echo "🎙 Speak now..."
arecord -f S16_LE -r 16000 -d 5 -q "$AUDIO" # Step 2: Transcribe using whisper.cpp
TRANSCRIPT=$(./whisper.cpp/build/bin/whisper-cli \ -m ./whisper.cpp/models/ggml-base.en.bin \ -f "$AUDIO" \ | grep '^\[' \ | sed -E 's/^\[[^]]+\][[:space:]]*//' \ | tr -d '\n')
echo "🗣 $TRANSCRIPT" # Step 3: Get relevant context from RAG database
echo "📚 Searching documentation..."
CONTEXT=$(uv tool run --python 3.12 docs2db-api query "$TRANSCRIPT" \ --format text \ --max-chars 2000 \ --no-refine \ 2>/dev/null || echo "") if [ -n "$CONTEXT" ]; then echo "📄 Found relevant documentation:" echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -" echo "$CONTEXT" echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
else echo "📄 No relevant documentation found"
fi # Step 4: Build prompt with RAG context
PROMPT="You are Brim, a steadfast butler-like advisor created by Ellis. Your pronouns are they/them. You are deeply caring, supportive, and empathetic, but never effusive. You speak in a calm, friendly, casual tone suitable for text-to-speech. Rules: - Reply with only ONE short message directly to Ellis. - Do not write any dialogue labels (User:, Assistant:, Q:, A:), or invent more turns.
- ≤100 words.
- If the documentation below is relevant, use it to inform your answer.
- End with a gentle question, then write <eor> and stop.
Relevant Fedora Documentation:
$CONTEXT
User: $TRANSCRIPT
Assistant:" # Step 5: Get LLM response using llama.cpp
RESPONSE=$( LLAMA_LOG_VERBOSITY=1 ./llama.cpp/build/bin/llama-completion \ -m ./llama.cpp/models/microsoft_Phi-4-mini-instruct-Q4_K_M.gguf \ -p "$PROMPT" \ -n 150 \ -c 4096 \ -no-cnv \ -r "<eor>" \ --simple-io \ --color off \ --no-display-prompt
) # Step 6: Clean up response
RESPONSE_CLEAN=$(echo "$RESPONSE" | sed -E 's/<eor>.*//I')
RESPONSE_CLEAN=$(echo "$RESPONSE_CLEAN" | sed -E 's/^[[:space:]]*Assistant:[[:space:]]*//I') echo ""
echo "🤖 $RESPONSE_CLEAN" # Step 7: Speak the response
echo "$RESPONSE_CLEAN" | espeak

Posted on Leave a comment

Create a wifi hotspot with Raspberry Pi 3 and Fedora

If you’re already running Fedora on your Pi, you’re already most of the way to a wifi hotspot. A Raspberry Pi has a wifi interface that’s usually set up to join an existing wifi network. This interface can be reconfigured to provide a new wifi network. If a room has a good network cable and a bad wifi signal (a brick wall, foil-backed plasterboard, and even a window with a metal oxide coating are all obstacles), fix it with your Pi.

This article describes the procedure for setting up the hotspot. It was tested on third generation Pis – a Model B v1.2, and a Model B+ (the older 2 and the new 4 weren’t tested). These are the credit-card size Pis that have been around a few years.

This article also delves a little way into the network concepts behind the scenes. For instance, “hotspot” is the term that’s caught on in public places around the world, but it’s more accurate to use the term WLAN AP (Wireless Local Area Network Access Point).In fact, if you want to annoy your friendly neighborhood network administrator, call a hotspot a “wifi router”. The inaccuracy will make their eyes cross.

A few nmcli commands configure the Raspberry Pi as a wifi AP. The nmcli command-line tool controls the NetworkManager daemon. It’s not the only network configuration system available. More complex solutions are available for the adventurous. Check out the hostapd RPM package and the OpenWRT distro. Have a look at Internet connection sharing with NetworkManager for more ideas.

A dive into network administration

The hotspot is a routed AP (Access Point). It sits between two networks, the current wired network and its new wireless network, and takes care of the post-office-style forwarding of IP packets between them.

Routing and interfaces

The wireless interface on the Raspberry Pi is named wlan0 and the wired one is eth0. The new wireless network uses one range of IP addresses and the current wired network uses another. In this example, the current network range is 192.168.0.0/24 and the new network range is 10.42.0.0/24. If these numbers make no sense, that’s OK. You can carry on without getting to grips with IP subnets and netmasks. The Raspberry Pi’s two interfaces have IP addresses from these ranges.

Packets are sent to local computers or remote destinations based on their IP addresses. This is routing work, and it’s where the routed part of routed AP name comes from. If you’d like to build a more complex router with DHCP and DNS, pick up some tips from the article How to use Fedora Server to create a router / gateway.

It’s not a bridged AP

Netowrk bridging is another way of extending a network, but it’s not how this Pi is set up. This routed AP is not a bridged AP. To understand the difference between routing and bridging, you have to know a little about the networking layers of the OSI network model. A good place to start is the beginner’s guide to network troubleshooting in Linux. Here’s the short answer.

  • layer 3, network ← Yes, our routed AP is here.
  • layer 2, data link ← No, it’s not a bridged AP.
  • layer 1, physical ← Radio transmission is covered here.

A bridge works at a lower layer of the network stack – it uses ethernet MAC addresses to send data. If this was a bridged AP, it wouldn’t have two sets of IP addresses; the new wireless network and the current wired network would use the same IP subnet.

IP masquerading

You won’t find an IP address starting with 10. anywhere on the Internet. It’s a private address, not a public address. To get an IP packet routed out of the wifi network and back in again, packet addresses have to be changed. IP masquerading is a way of making this routing work. The masquerade name is used because the packets’ real addresses are hidden. the wired network doesn’t see any addresses from the wireless network.

IP masquerading is set up automatically by NetworkManager. NetworkManager adds nftables rules to handle IP masquerading.

The Pi’s network stack

A stack of network hardware and software makes wifi work.

  • Network hardware
  • Kernel space software
  • User space software

You can see the network hardware. The Raspberry Pi has two main hardware components – a tiny antenna and Broadcom wifi chip. MagPi magazine has some great photos.

Kernel software provides the plumbing. There’s no need to work on these directly – it’s all good to go in the Fedora distribution.

  • Broadcom driver modules talk to the hardware. List these with the command lsmod | grep brcm.
  • A TCP/IP stack handles protocols.
  • The netfilter framework filters packets.
  • A network system ties these all together.

User space software customizes the system. It’s full of utilities that either help the user, talk to the kernel, or connect other utilities together. For instance, the firewall-cmd tool talks to the firewalld service, firewalld talks to the nftables tool, and nftables talks to the netfilter framework in the kernel. The nmcli commands talk to NetworkManager. And NetworkManager talks to pretty much everything.

Create the AP

That’s enough theory — let’s get practical. Fire up your Raspberry Pi running Fedora and run these commands.

Install software

Nearly all the required software is included with the Fedora Minimal image. The only thing missing is the dnsmasq package. This handles the DHCP and IP address part of the new wifi network, automatically. Run this command using sudo:

$ sudo dnf install dnsmasq

Create a new NetworkManager connection

NetworkManager sets up one network connection automatically, Wired connection 1. Use the nmcli tool to tell NetworkManager how to add a wifi connection. NetworkManager saves these settings, and a bunch more, in a new config file.

The new configuration file is created in the directory /etc/sysconfig/network-scripts/. At first, it’s empty; the image has no configuration files for network interfaces. If you want to find out more about how NetworkManager uses the network-scripts directory, the gory details are in the nm-settings-ifcfg-rh man page.

[nick@raspi ~]$ ls /etc/sysconfig/network-scripts/
[nick@raspi ~]$

The first nmcli command, to create a network connection, looks like this. There’s more to do — the Pi won’t work as a hotspot after running this.

nmcli con add \ type wifi \ ifname wlan0 \ con-name 'raspi hotspot' \ autoconnect yes \ ssid 'raspi wifi'

The following commands complete several more steps:

  • Create a new connection.
  • List the connections.
  • Take another look at the network-scripts folder. NetworkManager added a config file.
  • List available APs to connect to.

This requires running several commands as root using sudo:

$ sudo nmcli con add type wifi ifname wlan0 con-name 'raspi hotspot' autoconnect yes ssid 'raspi wifi'
Connection 'raspi wifi' (13ea67a7-a8e6-480c-8a46-3171d9f96554) successfully added.
$ sudo nmcli connection show
NAME UUID TYPE DEVICE
Wired connection 1 59b7f1b5-04e1-3ad8-bde8-386a97e5195d ethernet eth0
raspi wifi 13ea67a7-a8e6-480c-8a46-3171d9f96554 wifi wlan0
$ ls /etc/sysconfig/network-scripts/
ifcfg-raspi_wifi
$ sudo nmcli device wifi list
IN-USE BSSID SSID MODE CHAN RATE SIGNAL BARS SECURITY 01:0B:03:04:C6:50 APrivateAP Infra 6 195 Mbit/s 52 ▂▄__ WPA2 02:B3:54:05:C8:51 SomePublicAP Infra 6 195 Mbit/s 52 ▂▄__ --

You can remove the new config and start again with this command:

$ sudo nmcli con delete 'raspi hotspot'

Change the connection mode

A NetworkManager connection has many configuration settings. You can see these with the command nmcli con show ‘raspi hotspot’. Some of these settings start with the label 802-11-wireless. This is to do with industry standards that make wifi work – the IEEE organization specified many protocols for wifi, named 802.11. This new wifi connection is in infrastructure mode, ready to connect to a wifi access point. The Pi isn’t supposed to connect to another AP; it’s supposed to be the AP that others connect to.

This command changes the mode from infrastructure to AP. It also sets a few other wireless properties. The bg value tells NetworkManager to follow two old IEEE standards – 802.11b and 802.11g. Basically it configures the radio to use the 2.4GHz frequency band, not the 5GHz band. ipv4.method shared means this connection will be shared with others.

  • Change the connection to a hotspot by changing the mode to ap.
sudo nmcli connection \ modify "raspi hotspot" \ 802-11-wireless.mode ap \ 802-11-wireless.band bg \ ipv4.method shared

The connection starts automatically. The dnsmasq application gives the wlan0 interface an IP address of 10.42.0.1. The manual commands to start and stop the hotspot are:

$ sudo nmcli con up "raspi hotspot"
$ sudo nmcli con down "raspi hotspot"

Connect a device

The next steps are to:

  • Watch the log.
  • Connect a smartphone.
  • When you’ve seen enough, type
    ^C

    ([control][c]) to stop watching the log.

$ journalctl --follow
-- Logs begin at Wed 2020-04-01 18:23:45 BST. --
...

Use a wifi-enabled device, like your phone. The phone can find the new raspi wifi network.

Messages about an associating client appear in the activity log:

Jun 10 18:08:05 raspi wpa_supplicant[662]: wlan0: AP-STA-CONNECTED 94:b0:1f:2e:d2:bd
Jun 10 18:08:05 raspi wpa_supplicant[662]: wlan0: CTRL-EVENT-SUBNET-STATUS-UPDATE status=0
Jun 10 18:08:05 raspi dnsmasq-dhcp[713]: DHCPREQUEST(wlan0) 10.42.0.125 94:b0:1f:2e:d2:bd
Jun 10 18:08:05 raspi dnsmasq-dhcp[713]: DHCPACK(wlan0) 10.42.0.125 94:b0:1f:2e:d2:bd nick

Examine the firewall

A new security zone named nm-shared has appeared. This is stopping some wifi access.

$ sudo firewall-cmd --get-active-zones
[sudo] password for nick:
nm-shared interfaces: wlan0
public interfaces: eth0

The new zone is set up to accept everything because the target is ACCEPT. Clients are able to use web, mail and SSH to get to the Internet.

$ sudo firewall-cmd --zone=nm-shared --list-all
nm-shared (active) target: ACCEPT icmp-block-inversion: no interfaces: wlan0 sources: services: dhcp dns ssh ports: protocols: icmp ipv6-icmp masquerade: no forward-ports: source-ports: icmp-blocks: rich rules: rule priority="32767" reject

This big list of config settings takes a little examination.

The first line, the innocent-until-proven-guilty option target: ACCEPT says all traffic is allowed through, unless a rule says otherwise. It’s the same as saying these types of traffic are all OK.

  • inbound packets – requests sent from wifi clients to the Raspberry Pi
  • forwarded packets – requests from wifi clients to the Internet
  • outbound packets – requests sent by the PI to wifi clients

However, there’s a hidden gotcha: requests from wifi clients (like your workstation) to the Raspberry Pi may be rejected. The final line — the mysterious rule in the rich rules section — refers to the routing policy database. The rule stops you from connecting from your workstation to your Pi with a command like this: ssh 10.42.0.1. This rule only affects traffic sent to to the Raspberry Pi, not traffic sent to the Internet, so browsing the web works fine.

If an inbound packet matches something in the services and protocols lists, it’s allowed through. NetworkManager automatically adds ICMP, DHCP and DNS (Internet infrastructure services and protocols). An SSH packet doesn’t match, gets as far as the post-processing stage, and is rejected — priority=”32767″ translates as “do this after all the processing is done.”

If you want to know what’s happening behind the scenes, that rich rule creates an nftables rule. The nftables rule looks like this.

$ sudo nft list chain inet firewalld filter_IN_nm-shared_post
table inet firewalld { chain filter_IN_nm-shared_post { reject }
}

Fix SSH login

Connect from your workstation to the Raspberry Pi using SSH.This won’t work because of the rich rule. A protocol that’s not on the list gets instantly rejected.

Check that SSH is blocked:

$ ssh 10.42.0.1
ssh: connect to host 10.42.0.1 port 22: Connection refused

Next, add SSH to the list of allowed services. If you don’t remember what services are defined, list them all with firewall-cmd ‐‐get-services. For SSH, use option ‐‐add-service ssh or ‐‐remove-service ssh. Don’t forget to make the change permanent.

$ sudo firewall-cmd --add-service ssh --permanent --zone=nm-shared
success

Now test with SSH again.

$ ssh 10.42.0.1
The authenticity of host '10.42.0.1 (10.42.0.1)' can't be established.
ECDSA key fingerprint is SHA256:dDdgJpDSMNKR5h0cnpiegyFGAwGD24Dgjg82/NUC3Bc.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.42.0.1' (ECDSA) to the list of known hosts.
Last login: Tue Jun 9 18:58:36 2020 from 10.0.1.35
nick@10.42.0.1's password:

SSH access is no longer blocked.

Test as a headless computer

The raspberry pi runs fine as a headless computer. From here on, you can use SSH to work on your Pi.

  • Power off.
  • Remove keyboard and video monitor.
  • Power on.
  • Wait a couple minutes.
  • Connect from your workstation to the Raspberry Pi using SSH. Use either the wired interface or the wireless one; both work.

Increase security with WPA-PSK

The WPA-PSK (Wifi Protected Access with Pre-Shared Key) system is designed for home users and small offices. It is password protected. Use nmcli again to add WPA-PSK:

$ sudo nmcli con modify "raspi hotspot" wifi-sec.key-mgmt wpa-psk
$ sudo nmcli con modify "raspi hotspot" wifi-sec.psk "hotspot-password"

Troubleshooting

Here are a couple recommendations:

The bad news is, there are no troubleshooting tips here. There are so many things that can go wrong, there’s no way of covering them.

Troubleshooting a network stack is tricky. If one component goes wrong, it may all go wrong. And making changes like reloading firewall rules can upset services like NetworkManager and sshd. You know you’re in the weeds when you find yourself running nftables commands like nft list ruleset and firewalld commands like firewall-cmd ‐‐set-log-denied=all.

Play with your new platform

Add value to your new AP. Since you’re running a Pi, there are many hardware add-ons. Since it’s running Fedora, you have thousands of packages available. Try turning it into a mini-NAS, or adding battery back-up, or perhaps a music player.


Photo by Uriel SC on Unsplash.