CRE8LEARN INSTITUTE

C6-LIE-19 Linux Essentials
Section A Worked Reference RHEL

Environment build, configuration and evidence baseline (10 marks)

Instructor: Teboho Mpholo (Mr.), Founder and Lead Instructor
End Assessment: issued 12 October 2026, closes 9 November 2026 at 23:59
Edition: Red Hat Enterprise Linux 8, 9 or 10, and its rebuilds (AlmaLinux, Rocky Linux, CentOS Stream)

Read first. Checkpoint 1 is Monday 19 October: Section A complete and data files seeded. Examples below use a made-up student (12345678) and a made-up machine (lab-pc-07) running Red Hat Enterprise Linux 9.4. Replace them with your own details. Your own machine, your own outputs and your own sentences are what earn the marks. Anything copied word for word from this page, especially the environment paragraph, is generic and scores zero.

Order of work

  1. A1 steps 1 and 2: folders, profile, filesystem proof.
  2. A2: prepare the stick and run your first session_end.sh.
  3. A1 steps 3 to 5: fingerprint, tools, environment paragraph.
  4. A3: naledi.conf and the config loader.
  5. Seed the data files, then deposit CP1 on Blackboard.

A1. Identify your environment and establish your workspace 4 marks

Step 1. Project folders and shell identity

export STUDENT_ID="12345678"
mkdir -p ~/naledi_$STUDENT_ID/bin ~/naledi_$STUDENT_ID/lib ~/naledi_$STUDENT_ID/etc \
         ~/naledi_$STUDENT_ID/data ~/naledi_$STUDENT_ID/logs ~/naledi_$STUDENT_ID/backup \
         ~/naledi_$STUDENT_ID/docs ~/naledi_$STUDENT_ID/evidence/screenshots \
         ~/naledi_$STUDENT_ID/evidence/transcripts ~/naledi_$STUDENT_ID/evidence/sysconfig
cat > ~/naledi_$STUDENT_ID/docs/profile.snippet <<'EOF'
export STUDENT_ID="12345678"
export PS1='[$STUDENT_ID] \u@\h:\w\$ '
naledi_id() { printf '%s | %s@%s | %s\n' "$STUDENT_ID" "$(whoami)" "$(uname -n)" "$(date '+%F %T')"; }
export HISTTIMEFORMAT='%F %T '
shopt -s histappend
EOF
cat ~/naledi_$STUDENT_ID/docs/profile.snippet >> ~/.bashrc
source ~/.bashrc
naledi_id

Step 2. Prove the filesystem carries ownership, modes and ACLs

naledi_id
findmnt -T ~/naledi_$STUDENT_ID
getenforce
cd ~/naledi_$STUDENT_ID && touch .acltest && setfacl -m u:$USER:rw .acltest && getfacl .acltest; rm -f .acltest

How to read it. On Red Hat Enterprise Linux the project directory is usually on XFS, mounted under /dev/mapper/rhel-root (an LVM logical volume), and FSTYPE should read xfs. ext4 and btrfs are also acceptable. OPTIONS must not show noacl. A successful setfacl followed by a user:yourname:rw- line in getfacl proves ACL support. FAT, exFAT, NTFS, NFS and CIFS shares fail this test, and evidence from them scores zero. getenforce should print Enforcing; that is SELinux doing its job and it does not block your project files, so record the value and move on. If your home is on a share, use the local path your lab gives you and write which path you used.

Step 3. System fingerprint

cd ~/naledi_$STUDENT_ID
{ naledi_id; for c in "hostnamectl" "uname -a" "cat /proc/version" "systemctl is-system-running" "ps -p 1 -o comm=" "lsblk -f" "findmnt" "free -h" "swapon --show" "id" "getenforce"; do echo; echo "## $c"; $c 2>&1; done; } | tee evidence/sysconfig/fingerprint.txt
getent passwd $USER | cut -d: -f7

Also screenshot each command on its own. Then write one sentence per item about your machine, using this frame: On (hostname), (command) shows (value), which means (consequence for my project). Example sentences for the made-up lab-pc-07, running Red Hat Enterprise Linux 9.4, to show the level expected:

ItemExample sentence (made-up values, do not copy)
Distributionhostnamectl reports Red Hat Enterprise Linux 9.4 on bare metal, so there is no container or VM layer limiting what my user can do.
Kernel, architectureKernel 5.14.0 on x86_64 means every tool in this assessment is available as a standard 64-bit build.
Init systemPID 1 is systemd and is-system-running says running, so systemctl --user timers are available and I do not need the cron fallback.
Storage and project filesystemMy home sits on /dev/mapper/rhel-root formatted XFS, so ownership, modes and ACLs all work for Section C.
Other mountsfindmnt shows my USB stick at /run/media/yourname as exfat with noexec, which is why I run session_end.sh through bash and never work on the stick.
Memory and swapfree -h shows 15 GiB total but 11 GiB available, and swapon shows /dev/mapper/rhel-swap sized 2 GiB, so a runaway script would hit the ulimit before the machine suffers.
Account and groupsid shows uid 1043 in groups student and users but not wheel, so I cannot use sudo and must use ACLs on my own files.
SELinuxgetenforce prints Enforcing, so SELinux is on, but my project files are user_home_t and I am not asked to change policy.
Default shellMy passwd entry lists /bin/bash, the same shell I type in, so the profile lines in ~/.bashrc apply to my sessions.

Step 4. Tool check

cd ~/naledi_$STUDENT_ID
{ naledi_id; for t in setfacl getfacl flock awk script systemctl tar sha256sum; do p=$(command -v $t) && echo "$t | $p | $($t --version 2>&1 | head -1)" || echo "$t | MISSING"; done; echo "getenforce | $(command -v getenforce) | $(getenforce)"; } | tee evidence/sysconfig/tools.txt
rpm -q acl util-linux gawk tar coreutils
awk --version | head -1

On Red Hat Enterprise Linux, awk is GNU Awk (gawk), so awk --version prints the version. rpm -q gives a definitive installed or not installed answer for the package that provides each tool, and that is what a RHEL administrator would rely on. For anything MISSING, write the substitute and why it is acceptable. The judgement earns the mark:

MissingAcceptable substituteState this limit
flockAtomic mkdir lock directory removed by a trapA killed process leaves the lock behind, so add stale-lock detection
scriptbash -x piped through tee into logs/Does not capture a full interactive session
setfacl, getfaclNone that satisfies C1. Record that you asked the lab for the acl packageACL evidence in C1 becomes impossible

You will not have dnf entitlement on a shared RHEL lab machine, and you do not need it: every tool this paper requires is preinstalled. If one is missing, the substitute column above tells you what to use.

Step 5. Environment description (120 words maximum)

Answer four things using facts from your own fingerprint: what the machine is (name it), what persists between sessions, what you may not change, and which habit in your routine answers each limit. Check the length with wc -w. Example for the made-up lab-pc-07, to show the shape only:

I work on lab-pc-07, a shared Red Hat Enterprise Linux 9.4 machine in the computer lab, with no root access. Student accounts reset between sessions, so only my USB stick persists, mounted at /run/media/yourname with exfat and noexec, which is why I run session_end.sh through bash and never work on the stick. My project lives in my home directory on XFS under /dev/mapper/rhel-root, which carries ownership, modes and ACLs. Each session I restore the latest archive, record the terminal with script, then run session_end.sh to extend my hash chain. I checked every tool with command -v and rpm -q before starting. The profile lines in docs/profile.snippet bring my banner back after every reset.

Self-test: replace your hostname with a classmate's. If the paragraph is still true, it is generic.

Screenshots for A1 (numbered, such as A1-01.png): banner and folders; findmnt, getenforce and the ACL test; each fingerprint command; the tool check and the rpm -q output. Each is full window with the banner, the command and its output visible.

A2. Working storage, session log and checkpoint deposits 3 marks

Appendix D fix. On the first run, chain.txt does not exist. Because of set -e and pipefail, the line that reads the previous link ends the script after the archive is made and before any chain line is written. The fix is one extra line, touch "$CHAIN", added below. If your copy of the paper shows a box-drawing bar instead of a plain pipe, type the ordinary keyboard pipe |.

Step 1. Put session_end.sh on your stick

Mount the stick, then save this as session_end.sh on it. On Red Hat Enterprise Linux 8 and later the desktop mounts removable media at /run/media/$USER/MYSTICK. Older RHEL 7 builds used /media, and the paper uses that path, so use whatever findmnt shows on your machine.

#!/usr/bin/env bash
# session_end.sh - run at the end of every working session
#   usage: bash session_end.sh /run/media/$USER/MYSTICK
set -euo pipefail
: "${STUDENT_ID:?set STUDENT_ID in your shell profile}"
STICK="${1:?usage: session_end.sh /path/to/your/device}"
ARCHIVE="$STICK/C6LIE19_${STUDENT_ID}_$(date +%F_%H%M).tar.gz"
CHAIN="$STICK/chain.txt"
tar czf "$ARCHIVE" -C "$HOME" "naledi_$STUDENT_ID"
HASH=$(sha256sum "$ARCHIVE" | cut -d" " -f1)
touch "$CHAIN"
PREV=$(tail -n 1 "$CHAIN" 2>/dev/null | awk -F"|" "{print \$NF}")
PREV=${PREV:-GENESIS}
LINK=$(printf "%s%s" "$PREV" "$HASH" | sha256sum | cut -d" " -f1)
printf "%s|%s|%s|%s|%s|%s\n" "$(date -Is)" "$STUDENT_ID" "$(uname -n)" \
       "$(basename "$ARCHIVE")" "$HASH" "$LINK" >> "$CHAIN"
printf "Archived %s\nChain link %s\n" "$(basename "$ARCHIVE")" "$LINK"

Know what each part does, because you may be asked: the archive is named with date and time; HASH is the archive's SHA-256; PREV is the last link, or GENESIS on the first run; LINK is the SHA-256 of PREV joined to HASH; one line is appended to chain.txt. Changing an old archive changes its hash and breaks every later link.

Step 2. The session routine

# START of every session (skip the restore on your very first one)
tar xzf "$(ls -t /run/media/$USER/MYSTICK/C6LIE19_${STUDENT_ID}_20*.tar.gz | head -1)" -C ~
cat ~/naledi_$STUDENT_ID/docs/profile.snippet >> ~/.bashrc; source ~/.bashrc
script -a ~/naledi_$STUDENT_ID/logs/session-$(date +%F).typescript

# ... do your work ...

# END of every session
history > ~/naledi_$STUDENT_ID/evidence/history.txt
exit                                   # leaves script, closes the typescript
bash /run/media/$USER/MYSTICK/session_end.sh /run/media/$USER/MYSTICK

Step 3. Checkpoint deposits

CPDateState of the work
CP1Mon 19 OctoberSection A complete, data files seeded
CP2Mon 26 OctoberB1 to B4 working
CP3Mon 2 NovemberB5 to B8 working, Section C started
CP4Mon 9 NovemberFinal submission
N=1                                    # 1, 2, 3 or 4
cd ~
tar czf C6LIE19_${STUDENT_ID}_cp$N.tar.gz -C ~/naledi_$STUDENT_ID bin lib etc data docs
cp /run/media/$USER/MYSTICK/chain.txt .
sha256sum C6LIE19_${STUDENT_ID}_cp$N.tar.gz | tee -a ~/naledi_$STUDENT_ID/evidence/checkpoints.sha256

Upload the archive and chain.txt to the Blackboard Checkpoint folder, and screenshot the confirmation showing the upload time. Run session_end.sh just before, so the chain on your stick matches what you deposit. Do not build Section B ahead of time: a near-complete system at CP1 scores zero for E2.

Step 4. Logbook, typescripts and history

Twelve or more entries on twelve or more different days, about three sessions a week. Each quotes the chain link printed by that session.

## 2026-10-18, Session 2
Attempted: created naledi.conf and the config loader.
Failed: loader accepted PERMIT_DAYS=abc because the regex was missing the anchors.
Changed: anchored the pattern with ^ and $, retested with bad values.
Chain link: (paste the link printed by session_end.sh)

Keep docs/logbook.md in the project, the typescripts in logs/ and the history export in evidence/. Include your mistakes in all three, because the marker looks for the real search path.

Step 5. Verify your own chain before you submit

STICK=/run/media/$USER/MYSTICK; prev=GENESIS
while IFS='|' read -r ts sid host name hash link; do
  calc=$(sha256sum "$STICK/$name" | cut -d' ' -f1)
  exp=$(printf '%s%s' "$prev" "$calc" | sha256sum | cut -d' ' -f1)
  if [ "$calc" = "$hash" ] && [ "$exp" = "$link" ]; then echo "OK   $name"; else echo "BAD  $name"; fi
  prev=$link
done < "$STICK/chain.txt"

Every line should print OK, and the last link must equal the last line of chain.txt. Keep every archive and never edit chain.txt by hand.

A3. Configuration, not hard-coding 3 marks

Step 1. etc/naledi.conf

# NALEDI operating parameters. Only the administrator role edits this file.
PROJECT_ROOT="/home/youruser/naledi_12345678"
SEED_MIN_ANIMALS=100
DISTRICT_COUNT=6
TIMER_TIME="02:30"
PERMIT_DAYS=14
INSPECT_AGE_MONTHS=18
PENALTY_BWP=75
RETENTION_DAYS=30
TAG_PREFIX="BW-NAL-"

Write PROJECT_ROOT as a full absolute path. A tilde does not expand inside quotes, and the C2 test with env -i runs with no HOME. Do not edit this file with gedit or another GUI editor opened as root; you are an ordinary user and the file is yours.

Step 2. lib/config.sh

naledi_load_config() {
  local root conf k v
  root="$(cd "${BASH_SOURCE[0]%/*}/.." && pwd)" || return 2
  conf="${NALEDI_CONF:-$root/etc/naledi.conf}"
  [ -e "$conf" ] || { echo "naledi: config file not found: $conf" >&2; return 2; }
  [ -r "$conf" ] || { echo "naledi: config file not readable: $conf" >&2; return 2; }
  . "$conf"
  for k in SEED_MIN_ANIMALS DISTRICT_COUNT PERMIT_DAYS INSPECT_AGE_MONTHS PENALTY_BWP RETENTION_DAYS; do
    v="${!k:-}"
    [[ $v =~ ^[1-9][0-9]*$ ]] || { echo "naledi: $k must be a positive whole number (got '$v')" >&2; return 2; }
  done
  [ "${SEED_MIN_ANIMALS:-0}" -ge 100 ] || { echo "naledi: SEED_MIN_ANIMALS must be at least 100 (got '$SEED_MIN_ANIMALS')" >&2; return 2; }
  [[ ${TIMER_TIME:-} =~ ^([01][0-9]|2[0-3]):[0-5][0-9]$ ]] || { echo "naledi: TIMER_TIME must be HH:MM (got '${TIMER_TIME:-}')" >&2; return 2; }
  [[ ${TAG_PREFIX:-} =~ ^[A-Z0-9-]+$ ]] || { echo "naledi: TAG_PREFIX must be capitals, digits and hyphens (got '${TAG_PREFIX:-}')" >&2; return 2; }
  [ -d "${PROJECT_ROOT:-}" ] || { echo "naledi: PROJECT_ROOT is not a directory (got '${PROJECT_ROOT:-}')" >&2; return 2; }
  NALEDI_ROOT="$root"
}

Two RHEL notes. First, ${BASH_SOURCE[0]%/*} is used instead of dirname because dirname is an external binary that a stripped env -i run may not find on PATH, and this form uses only a Bash built-in. Second, the SEED_MIN_ANIMALS guard enforces the paper's "At least 100 animals" rule, so a marker who edits the file to 50 sees a clean refusal instead of silent acceptance.

Step 3. How bin/naledi uses it

#!/usr/bin/env bash
set -euo pipefail
NALEDI_ROOT="$(cd "${BASH_SOURCE[0]%/*}/.." && pwd)"
. "$NALEDI_ROOT/lib/config.sh"
naledi_load_config || exit $?
# from here on use $PERMIT_DAYS, $TAG_PREFIX and the rest. Never write 14 or BW-NAL- in code.

The project root is found from the script's own location, so nothing depends on your profile. That is what lets the scheduled job survive env -i later in C2. Sourcing the file runs it as code, which is acceptable here because only the administrator role may edit it (C1).

SELinux note for RHEL. A config file created inside your home directory carries the context user_home_t, which your shell can read normally. If you ever move a file in from /tmp or a USB stick with cp, it may carry tmp_t or unconfined_u:object_r:removable_t, and reads can be denied by SELinux even though the mode bits allow them. ls -Z etc/naledi.conf shows the context, and restorecon -Rv ~/naledi_$STUDENT_ID/etc resets it to the home-directory default. This is the single most common SELinux trap for this project, and a sentence about it in your report shows you understand the RHEL layer that sits above DAC.

Step 4. Prove it, with a screenshot of each case

cd ~/naledi_$STUDENT_ID
env -i /bin/bash bin/naledi                                    # works with no environment
sed -i 's/^PERMIT_DAYS=.*/PERMIT_DAYS=21/' etc/naledi.conf
env -i /bin/bash bin/naledi                                    # behaviour follows the file
sed -i 's/^PERMIT_DAYS=.*/PERMIT_DAYS=abc/' etc/naledi.conf
env -i /bin/bash bin/naledi; echo "exit=$?"                    # invalid value, exit 2
sed -i 's/^PERMIT_DAYS=.*/PERMIT_DAYS=14/' etc/naledi.conf
mv etc/naledi.conf etc/naledi.conf.off
env -i /bin/bash bin/naledi; echo "exit=$?"                    # missing file, exit 2
mv etc/naledi.conf.off etc/naledi.conf
chmod 000 etc/naledi.conf; env -i /bin/bash bin/naledi; echo "exit=$?"   # unreadable
chmod 640 etc/naledi.conf

Expected messages: naledi: PERMIT_DAYS must be a positive whole number (got 'abc') and naledi: config file not found: .... The unreadable test only fires when you are an ordinary user; if you have sudo, do not sudo the test, because root bypasses the mode and [ -r ] then succeeds. Your own wording and file layout should differ from this page, because near-identical scripts between students are referred for collusion.

Leftover check. Find hard-coded parameters in your code:

grep -rnE '\b(14|18|75|30|100)\b|BW-NAL' ~/naledi_$STUDENT_ID/bin ~/naledi_$STUDENT_ID/lib

Line numbers and loop counts are harmless. Any hit that is a parameter must move into naledi.conf.

Evidence files for Section A

File or itemContentsItem
evidence/sysconfig/fingerprint.txtFingerprint commands and output including getenforce, banner firstA1
evidence/sysconfig/tools.txtEach tool, its path, version, rpm -q output and any substitutesA1
evidence/screenshots/Numbered full-window captures: findmnt, getenforce, ACL test, each fingerprint command, tool checkA1
chain.txt and four checkpoint pairsArchive plus chain.txt deposited on each dateA2
docs/logbook.md, logs/*.typescript, evidence/history.txt12 or more dated entries quoting chain linksA2
Config screenshots and ls -Z outputWorking, changed, invalid, missing, unreadable, plus SELinux context noteA3