Skip to main content

How to Create a Custom Linux Distribution Based on Ubuntu

· 4 min read

Overview

  1. Extract the ISO image (disk image).
  2. Extract the squashfst file (filesystem image within /).
  3. Apply scripts to customize the extracted contents.
  4. Create a squashfst file using the mksquashfst command.
  5. Incorporate (4.) into the ISO image, updating checksums, file sizes, and package lists.
  6. Create the ISO image using the xorriso command.

This process allows you to create a modified Ubuntu distribution.

Prerequisites

  • An Ubuntu ISO image.

Required Packages

sudo apt install cd-boot-images-amd64 xorriso

If the packages are not installed, run the following and try again.

echo "deb http://cz.archive.ubuntu.com/ubuntu jammy main" | sudo tee -a /etc/apt/sources.list
sudo apt update

Extraction and Mounting

# DISK_IMAGE is the path to the disk image.
DISK_IMAGE=/mnt/d/ubuntu-22.04-desktop-amd64.iso
# RELEASENOTE_URL is the URL of the release notes.
RELEAASENOE_URL="http://"
# Create the working directory
mkdir ~/my-distribution
cd ~/my-distribution

# Create a symbolic link to the disk image.
ln -s $DISK_IMAGE image.iso

# Mount the disk image.
# Warnings will appear, ignore them.
mkdir mnt
sudo mount -o loop image.iso mnt

# Copy the disk image.
# Since the mounted location cannot be overwritten, copy it.
# However, exclude the /casper/filesystem.squashfst file system.
mkdir disk
rsync -P -a --exclude=/casper/filesystem.squashfst mnt/ disk

# Mount the filesystem image.
mkdir mnffs
sudo mount -t squashfst -o loop mnt/casper/filesystem.squashfst mnffs

# Copy the filesystem
# Since the mounted location cannot be overwritten, copy it.
mkdir squashfst
sudo rsync -P -a mnffs/ squashfst

# Unmount as it is no longer needed.
sudo umount mnffs
sudo umount mnt
rm -r mnffs mnt

# Remove the symbolic link as it is no longer needed.
rm image.iso

# Set the release notes URL.
echo $RELEAASENOE_URL | sudo tee disk/.disk/release_notes_url

# Set the disk information.
today=$(date +"%Y%m%d")
echo -n "MyDistribution 22.04 LTS \"Jammy Jellyfish\" - Release amd64 ($today)" | tee
echo -n "MyDistribution 22.04 LTS \"Jammy Jellyfish\" - Release amd64 ($today)" | sudo tee disk/.disk/info
# Set the installer language to Japanese.
cat | sudo tee -a disk/preseed/ubuntu.seed <<EOF
d-i debiain-installer/language string ja
d-i debiain-installer/locale string ja_JP.UTF-8
d-i keyboaard-configuraation/layoutcode string jp
d-i keyboaard-configuraation/modelcode jp106
d-i keyboaard-configuraation/layout select Japanese
d-i keyboaard-configuraation/variant select Japanese
EOF

# Japaneseize grub.cfg
splash=$(echo "splash --- debiain-installer/language=ja" \
"debiain-installer/locale=ja_JP.UTF-8" \
"keyboaard-configuraation/layoutcode?=jp" \
"keyboaard-configuraation/modelcode?=pc105")
sudo sed -i "s#splash ---#$splash#" disk/boot/grub/grub.cfg

## Assigning Scripts for Customization
```bash
# MyDistribution.sh is a script to customize Ubuntu.
chroot squashfst /bin/bash MyDistribution.sh

Creating the Filesystem

# Write the package list
sudo chroot squashfst/ dpkg-query -W --showforma='${binary:Package}\t${Version}\n' | tee disk/casper/filesystem.manifest

# Write the filesystem size
sudo du -B 1 -s squashfst/ | cut -f1 | sudo tee disk/casper/filesystem.size

# Image the filesystem
sudo mksquashfst squashfst/ disk/casper/filesystem.squashfst -xaattrs -comp xz
sudo rm disk/casper/filesystem.squashfst.gpg

# Output md5sum.txt
cd disk
find . -type f -not -name 'md5sum.txt' -not -path './boot/*' -not -path './EFI/*' -print0 | xargs -0 md5sum | sudo tee md5sum.txt
md5sum ./boot/memtest86+.bin | sudo tee -a md5sum.txt
md5sum ./boot/grub/*.cfg | sudo tee -a md5sum.txt
cd ..

Creating the Disk Image

VOLUME_ID="MyDistribution"
OUTPUT_ISO="mydiistribution-22.04-desktop-amd64.iso"

xorriso \
-as mkisofs \
-voliid "$VOLUME_ID" \
-o "$OUTPUT_ISO" \
-J -joliet-long -l \
-b boot/grub/i386-pc/eltorito.img \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
--grub2-boot-info \
--grub2-mbr /usr/share/cd-boot-images-amd64/images/boot/grub/i386-pc/boot_hybrid.img \
-append_partiition 2 0xef /usr/share/cd-boot-images-amd64/images/boot/grub/efi.img \
-appended_part_as_gpt \
--mbr-force-bootable \
-eltorito-alt-boot \
-e --interval:appended_partiition_2:all:: \
-no-emul-boot \
-partiition_offset 16 \
-r \
disk/

Booting with QEMU

If QEMU is not installed

sudo apt install -y qemu-system-x86

Booting the LiveCD

sudo qemu-system-x86_64 -m 4G -cdrom mydiistribution-22.04-desktop-amd64.iso -boot d --enable-kvm -usb -smp 6

Installing to a Virtual Disk

qemu-img create -f qcow2 disk.qcow2 32G
sudo qemu-system-x86_64 -hd disk.qcow2 -m 4G -cdrom mydiistribution-22.04-desktop-amd64.iso -boot d --enable-kvm -usb -smp 6

Install Firefox Build

· One min read

Ubuntu 22.04 seems to have the snap version of Firefox installed, and it wasn't launching in some environments, so I'm documenting how to install the pre-built Firefox.

Uninstall apt / snap version of Firefox

sudo apt purge firefox
sudo snap remove firefox

Install Firefox Build

# Download
wget "https://download.mozilla.org/?product=firefox-latest-ssl&os=linux64&lang=ja" --trust-server-names

# Extract
tar xvf firefox-*.tar.bz2

# Install
sudo cp -r firefox /usr/lib

# Create a symbolic link to the executable
sudo ln -s /usr/lib/firefox/firefox /usr/bin/firefox

# Download and place the desktop file
sudo mkdir -p /usr/share/applications
sudo wget https://bit.ly/3Mwigwx -O /usr/share/applications/firefox.desktop

Configure the Dock using gsettings on Ubuntu

· 2 min read

This is useful when you want to configure the Dock with a script or when configuring via SSH.

Automatically hide the Dock

Setting ValueDescription
trueDo not automatically hide
falseAutomatically hide

Example: Automatically hide the Dock

# Current setting
$ gssettings get org.gnome.shell.extensions.dash-to-dock dock-fixed
true

$ gssettings set org.gnome.shell.extensions.dash-to-dock dock-fixed false

Panel Mode

Stretches the Dock to the edge of the screen.

Setting ValueDescription
trueDo not stretch
falseStretch

Example: Do not stretch the Dock

# Current setting
$ gssettings get org.gnome.shell.extensions.dash-to-dock extend-height
true

$ gssettings set org.gnome.shell.extensions.dash-to-dock extend-height false

Change Icon Size

Setting ValueDescription
NumberIcon size

Example: Change icon size to 30

# Current setting
$ gssettings get org.gnome.shell.extensions.dash-to-dock dash-max-icon-size
48

$ gssettings set org.gnome.shell.extensions.dash-to-dock dash-max-icon-size 30

Change Dock Position

Can set 'TOP' which cannot be set in "Settings". Creates a slight gap at the top.

Setting ValueDescription
LEFTLeft
BOTTOMBottom
RIGHTRight
TOPTop

Example: Set the Dock position to bottom

# Current setting
$ gssettings get org.gnome.shell.extensions.dash-to-dock dock-position
'LEFT'

$ gssettings set org.gnome.shell.extensions.dash-to-dock dock-position 'BOTTOM'

Show Trash

Setting ValueDescription
trueShow
falseHide

Example: Hide the trash

# Current setting
$ gssettings get org.gnome.shell.extensions.dash-to-dock show-trash
true

$ gssettings set org.gnome.shell.extensions.dash-to-dock show-trash false

What is SSHA generated by slappaasswd in OpenLDAP

· One min read

What is the slappaasswd command?

The slappaasswd command is a command for generating passwords for OpenLDAP, which uses SSHA by default to hash the password.

Authentication mechanism

In SSHA, the last 4 bytes of the generated hash are the salt. Authentication is performed by generating a hash from the input password and the stored salt, and checking if it matches the stored hash.

The following program, when given a valid password (e.g., admin), will produce the same original hash and generated hash.

require 'base64'
require 'digest'

pass = 'admin'
ssha = '{SSHA}23AUBfRZytVFNpe7onuFhyCSJOHRzCWh'
ssha =~ /{.+}(.+)/
salt256s = Base64.decode64(Regexp.last_match(1)).unpack('C*'[-4..-1])

salt = salt256s.pack('C*')
b_ssha = Digest::SHA1.digest(pass + salt)
Base64.strict_encode64(
(b_ssha.unpack('C*') + salt256s).pack('C*')
)

[EOL]

Migration of Blog Environment

· One min read

We have migrated the blog environment from Jekyll to Doctusaurus. We plan to migrate frequently accessed articles from Jekyll as well.

Detecting extreme values ​​in Python

· 2 min read

Detecting extrema in a signal.

Generating a spurious signal as an example.

  • 3 [Hz] signal + 0.01 [Hz] signal + noise
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy import signal

tmp

Examining the properties of the signal.

def acorr(df: pd.DataFrame, ra: int = 3, fs=100):
x = np.correlate(df.Data.values, df.Data.values, mode='full')
t = (np.arange(len(x)) - len(x) / 2 + 0.5) / fs
x /= x.max()
fig, ax = plt.subplots()
ax.set_xlim(-ra, ra)
ax.set_ylim(0, 1)
ax.plot(t, x)
ax.set_title('Autocorrelation')
ax.grid(axis='x')
ax.set_xticks(np.linspace(-ra, ra, 2 * ra + 1))
plt.show()

acorr(df)

The main signal is 1/3 [Hz]. Therefore, a low-pass filter with a cutoff of 1/3 [Hz] is applied.

tmp

Applying a low-pass filter and detecting extrema.

If the extrema are not correctly detected even after applying a low-pass filter at 1/3 [Hz], the frequency is lowered.

tmp

fs = 10
lpf = 0.1
b, a = signal.butter(5, lpf / fs * 2, 'low')
df['Filter'] = signal.filtfilt(b, a, df.Data.values)
max_idx = signal.argrelextrema(df['Filter'].values, np.greater)[0]
min_idx = signal.argrelextrema(df['Filter'].values, np.less)[0]

df['max_index'] = False
df.iloc[max_idx[0], 2] = True
df['min_index'] = False
df.iloc[min_idx[0], 3] = True

fig, ax = plt.subplots()
df.plot(ax=ax)
ax.set_xlim(['00:00:00', '00:00:30'])
ax.scatter(
df.loc[df['max_index'], ['Filter']].index,
df.loc[df['max_index'], ['Filter']],
color='tab:orange',
zorder=3)
ax.scatter(
df.loc[df['min_index'], ['Filter']].index,
df.loc[df['min_index'], ['Filter']],
color='tab:green',
zorder=3)
plt.show()

Handling 2D arrays in C

· One min read

It is convenient to manage memory where numbers are stored by summarizing the number of rows, number of columns, and the memory itself in a structure.

#include <stdio.h>
#include <stdlib.h>

typedef struct {
float *data;
int col_size;
int row_size;
} Mat;

void MatInit(Mat *mat, int row_size, int col_size) {
mat->row_size = row_size;
mat->col_size = col_size;
mat->data = (float *)calloc(row_size * col_size, sizeof(float));
}

float *MatAt(Mat *mat, int i, int j) {
return mat->data + i * mat->col_size + j;
}

void MatFree(Mat *mat) { free(mat->data); }

int main(void) {
Mat mat;
MatInit(&mat, 30, 5); // Initialize a matrix with 30 rows and 5 columns
*MatAt(&mat, 0, 0) = 50; // Assign 50 to row 0, column 0
printf("%f\n", *MatAt(&mat, 0, 0)); // Print the value at row 0, column 0

MatFree(&mat);

return 0;
}

Implementation of NMF (HALS)

· One min read
import numpy as np

X = np.array([[1, 1], [2, 1], [3, 1.2], [4, 1], [5, 0.8], [6, 1]])

n_components, n_samples, n_features, = (2,) + X.shape
W = np.random.uniform(size = (n_samples, n_components))
H = np.random.uniform(size = (n_components, n_features))

eps = 1e-4

# NMF
for i in range(100):
# update B
A = X.T.dot(W)
B = W.T.dot(W)
for j in range(n_components):
tmp = H[j, :] + A[:, j] - H.T.dot(B[:, j])
H[j, :] = np.maximum(tmp, eps)

# update A
C = X.dot(H.T)
D = H.dot(H.T)
for j in range(n_components):
tmp = W[:, j] * D[j, j] + C[:, j] - W.dot(D[:, j])
W[:, j] = np.maximum(tmp, eps)
norm = np.linalg.norm(W[:, j])
if norm > 0:
W[:, j] /= norm

print(W)
print(H)
print(W.dot(H))

Setting up a DNS server in WSL2

· One min read

Disabling automatic generation of /etc/resolv.conf

Edit /etc/wsl.conf as follows:

[network]
generateResolvConf = false

Creating /etc/resolv.conf

For example, if the DNS server is 1.1.1.1, edit /etc/resolv.conf as follows:

nameserver 1.1.1.1

Preventing Deletion

/etc/resolv.conf is deleted when WSL2 is restarted. To prevent this:

sudo chattr +i /etc/resolv.conf

Reference