diff --git a/build/fish/build.sh b/build/fish/build.sh
new file mode 100755
index 000000000..2345caa51
--- /dev/null
+++ b/build/fish/build.sh
@@ -0,0 +1,100 @@
+#!/usr/bin/bash
+#
+# {{{ CDDL HEADER
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+# }}}
+
+# Copyright 2026 OmniOS Community Edition (OmniOSce) Association.
+
+. ../../lib/build.sh
+
+PROG=fish
+VER=4.5.0
+PKG=ooce/shell/fish
+SUMMARY="Fish is a smart and user-friendly command line shell"
+DESC="friendly interactive shell"
+
+SKIP_SSP_CHECK=1
+SKIP_LICENCES=COPYING
+
+BUILD_DEPENDS_IPS="
+ ooce/developer/rust
+ ooce/developer/cmake
+ library/pcre2
+ runtime/python-$PYTHONPKGVER
+"
+
+RUN_DEPENDS_IPS="
+ library/pcre2
+ runtime/python-$PYTHONPKGVER
+"
+
+XFORM_ARGS+=" -DPREFIX=${PREFIX#/}"
+
+set_arch 64
+set_clangver
+
+CONFIGURE_OPTS+="
+ -DCMAKE_BUILD_TYPE=Release
+ -DCMAKE_INSTALL_PREFIX=$PREFIX
+ -DCMAKE_VERBOSE_MAKEFILE=1
+ -DCMAKE_INSTALL_SYSCONFDIR="/etc"
+ -DSYS_PCRE2_INCLUDE_DIR="/usr/include/pcre"
+ -DFISH_USE_SYSTEM_PCRE2=ON
+ -DWITH_MESSAGE_LOCALIZATION=ON
+ -DWITH_DOCS=OFF
+"
+
+# use GNU msgfmt; otherwise the build fails
+PATH="$GNUBIN:$PATH:$OOCEBIN"
+
+crate_patch() {
+ logmsg "Fetching crates"
+ logcmd $CARGO fetch --manifest-path $TMPDIR/$BUILDDIR/Cargo.toml \
+ || logerr "Fetching crates failed"
+ logmsg "Patching crates"
+ pushd $CARGO_HOME >/dev/null
+ for patchfile in $SRCDIR/patches-crate/*.patch; do \
+ gpatch --backup --version-control=numbered -p0 < $patchfile ; \
+ done ;
+ popd >/dev/null
+}
+
+pre_configure() {
+ typeset arch=$1
+
+ CONFIGURE_OPTS[$arch]="
+ -DCMAKE_BUILD_TYPE=Release
+ -DCMAKE_INSTALL_PREFIX=$PREFIX
+ -DCMAKE_VERBOSE_MAKEFILE=1
+ -DCMAKE_INSTALL_SYSCONFDIR="/etc"
+ -DSYS_PCRE2_INCLUDE_DIR="/usr/include/pcre"
+ -DFISH_USE_SYSTEM_PCRE2=ON
+ -DWITH_GETTEXT=ON
+ -DBUILD_DOCS=OFF
+ "
+}
+
+init
+download_source $PROG fish-$VER ""
+patch_source
+export CARGO_HOME=$TMPDIR/cargo_crates
+mkdir -p $CARGO_HOME
+crate_patch
+prep_build cmake
+build -noctf
+#run_testsuite check
+strip_install
+make_package
+clean_up
+
+# Vim hints
+# vim:ts=4:sw=4:et:fdm=marker
diff --git a/build/fish/local.mog b/build/fish/local.mog
new file mode 100644
index 000000000..fb1cfe141
--- /dev/null
+++ b/build/fish/local.mog
@@ -0,0 +1,16 @@
+# {{{ CDDL HEADER
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+# }}}
+
+# Copyright 2026 OmniOS Community Edition (OmniOSce) Association.
+
+license COPYING license=GPLv2
+
diff --git a/build/fish/patches-crate/01-libc.patch b/build/fish/patches-crate/01-libc.patch
new file mode 100644
index 000000000..1b276b134
--- /dev/null
+++ b/build/fish/patches-crate/01-libc.patch
@@ -0,0 +1,20 @@
+--- registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.180/src/unix/solarish/mod.rs.~1~ 2006-07-23 21:21:28.000000000 -0400
++++ registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.180/src/unix/solarish/mod.rs 2025-11-11 09:06:45.275025555 -050
+@@ -2390,6 +2390,9 @@
+ // sys/sendfile.h
+ pub const SFV_FD_SELF: c_int = -2;
+
++// command names for confstr sys/unistd.h
++pub const _CS_PATH: c_int = 65;
++
+ const fn _CMSG_HDR_ALIGN(p: usize) -> usize {
+ (p + _CMSG_HDR_ALIGNMENT - 1) & !(_CMSG_HDR_ALIGNMENT - 1)
+ }
+@@ -2756,6 +2759,7 @@
+ addrlen: *mut crate::socklen_t,
+ ) -> ssize_t;
+ pub fn mkstemps(template: *mut c_char, suffixlen: c_int) -> c_int;
++ pub fn mkostemp(template: *mut c_char, flags: c_int) -> c_int;
+ pub fn futimesat(fd: c_int, path: *const c_char, times: *const crate::timeval) -> c_int;
+ pub fn futimens(dirfd: c_int, times: *const crate::timespec) -> c_int;
+ pub fn utimensat(
diff --git a/build/fish/patches/01-revert-termios-use-nix-wrapper-bb7dce941acf51b11ee49994f799ba90911872ac.patch b/build/fish/patches/01-revert-termios-use-nix-wrapper-bb7dce941acf51b11ee49994f799ba90911872ac.patch
new file mode 100644
index 000000000..714014155
--- /dev/null
+++ b/build/fish/patches/01-revert-termios-use-nix-wrapper-bb7dce941acf51b11ee49994f799ba90911872ac.patch
@@ -0,0 +1,525 @@
+diff -Naur fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/builtins/fg.rs fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/builtins/fg.rs
+--- fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/builtins/fg.rs 2026-02-01 04:13:13.000000000 -0500
++++ fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/builtins/fg.rs 2026-02-01 03:58:51.000000000 -0500
+@@ -6,9 +6,7 @@
+ use crate::tokenizer::tok_command;
+ use crate::wutil::perror;
+ use crate::{env::EnvMode, tty_handoff::TtyHandoff};
+-use libc::STDIN_FILENO;
+-use nix::sys::termios::{self, tcsetattr};
+-use std::os::fd::BorrowedFd;
++use libc::{STDIN_FILENO, TCSADRAIN};
+
+ use super::prelude::*;
+
+@@ -144,14 +142,9 @@
+ }
+ let tmodes = job_group.tmodes.borrow();
+ if job_group.wants_terminal() && tmodes.is_some() {
+- let tmodes = tmodes.as_ref().unwrap();
+- if tcsetattr(
+- unsafe { BorrowedFd::borrow_raw(STDIN_FILENO) },
+- termios::SetArg::TCSADRAIN,
+- tmodes,
+- )
+- .is_err()
+- {
++ let termios = tmodes.as_ref().unwrap();
++ let res = unsafe { libc::tcsetattr(STDIN_FILENO, TCSADRAIN, termios) };
++ if res < 0 {
+ perror("tcsetattr");
+ }
+ }
+diff -Naur fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/builtins/fish_key_reader.rs fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/builtins/fish_key_reader.rs
+--- fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/builtins/fish_key_reader.rs 2026-02-01 04:13:13.000000000 -0500
++++ fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/builtins/fish_key_reader.rs 2026-02-01 03:58:51.000000000 -0500
+@@ -47,7 +47,7 @@
+
+ for evt in [VINTR, VEOF] {
+ let modes = shell_modes();
+- let cc = Key::from_single_byte(modes.control_chars[evt]);
++ let cc = Key::from_single_byte(modes.c_cc[evt]);
+
+ if match_key_event_to_key(&key_evt, &cc).is_some() {
+ if recent_keys
+@@ -61,7 +61,7 @@
+ }
+ streams.err.appendln(&wgettext_fmt!(
+ "Press ctrl-%c again to exit",
+- char::from(modes.control_chars[evt] + 0x60)
++ char::from(modes.c_cc[evt] + 0x60)
+ ));
+ return false;
+ }
+@@ -162,8 +162,8 @@
+ let modes = shell_modes();
+ streams.err.appendln(&wgettext_fmt!(
+ "or press ctrl-%c or ctrl-%c twice in a row.",
+- char::from(modes.control_chars[VINTR] + 0x60),
+- char::from(modes.control_chars[VEOF] + 0x60)
++ char::from(modes.c_cc[VINTR] + 0x60),
++ char::from(modes.c_cc[VEOF] + 0x60)
+ ));
+ streams.err.appendln(L!("\n"));
+ }
+diff -Naur fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/common.rs fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/common.rs
+--- fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/common.rs 2026-02-01 04:13:13.000000000 -0500
++++ fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/common.rs 2026-02-01 03:58:51.000000000 -0500
+@@ -19,7 +19,6 @@
+ use fish_widestring::{
+ ENCODE_DIRECT_END, decode_byte_from_char, encode_byte_to_char, subslice_position,
+ };
+-use nix::sys::termios::Termios;
+ use std::env;
+ use std::ffi::{CStr, CString, OsStr, OsString};
+ use std::os::unix::prelude::*;
+@@ -885,7 +884,7 @@
+ Some(in_pos)
+ }
+
+-pub fn shell_modes() -> MutexGuard<'static, Termios> {
++pub fn shell_modes() -> MutexGuard<'static, libc::termios> {
+ crate::reader::SHELL_MODES.lock().unwrap()
+ }
+
+diff -Naur fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/input_common.rs fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/input_common.rs
+--- fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/input_common.rs 2026-02-01 04:13:13.000000000 -0500
++++ fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/input_common.rs 2026-02-01 03:58:51.000000000 -0500
+@@ -878,7 +878,7 @@
+ self.push_back(evt);
+ }
+ });
+- let vintr = shell_modes().control_chars[libc::VINTR];
++ let vintr = shell_modes().c_cc[libc::VINTR];
+ if vintr != 0
+ && key.is_some_and(|key| {
+ match_key_event_to_key(&key, &Key::from_single_byte(vintr))
+@@ -1619,7 +1619,7 @@
+ fn select_interrupted(&mut self) {}
+
+ fn enqueue_interrupt_key(&mut self) {
+- let vintr = shell_modes().control_chars[libc::VINTR];
++ let vintr = shell_modes().c_cc[libc::VINTR];
+ if vintr != 0 {
+ let interrupt_evt = CharEvent::from_key(KeyEvent::from_single_byte(vintr));
+ if stop_query(self.blocking_query()) {
+diff -Naur fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/job_group.rs fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/job_group.rs
+--- fish-shell-bb7dce941acf51b11ee49994f799ba90911872ac/src/job_group.rs 2026-02-01 04:13:13.000000000 -0500
++++ fish-shell-55e925526480e9e22d659724660ae9e484bc77fd/src/job_group.rs 2026-02-01 03:58:51.000000000 -0500
+@@ -2,7 +2,6 @@
+ use crate::prelude::*;
+ use crate::proc::{JobGroupRef, Pid};
+ use crate::signal::Signal;
+-use nix::sys::termios::Termios;
+ use std::cell::RefCell;
+ use std::num::NonZeroU32;
+ use std::sync::atomic::{AtomicI32, Ordering};
+@@ -61,7 +60,7 @@
+ pub struct JobGroup {
+ /// If set, the saved terminal modes of this job. This needs to be saved so that we can restore
+ /// the terminal to the same state when resuming a stopped job.
+- pub tmodes: RefCell