# UCS RHEL7/RHEL8 Build & Release Workflow

Version: 2026-07-26  
Scope: Rust, Python, and Perl customer executables for Linux x86_64

## 1. Release contract

Every customer tool is built twice:

| Target | Build ABI | Maximum allowed GLIBC |
|---|---:|---:|
| RHEL7 / CentOS 7 | CentOS 7.9 | 2.17 |
| RHEL8 | RHEL/compatible 8 | 2.28 |

The build must run inside the matching container. Do not build a RHEL8 customer
binary directly on the Alibaba Cloud host: the host currently uses GLIBC 2.32.

The release directory contains only:

```text
/root/ucs_code/compiled_version/<tool>/
├── RHEL7/<tool>
├── RHEL8/<tool>
├── usage.html
├── SHA256SUMS
└── BUILD_MANIFEST.txt
```

Source stays separately under:

```text
/root/ucs_code/source_code/<domain>/<tool>/<language>/
```

Never put source, `.ucs_license.conf`, private keys, SSH keys, Cargo caches,
PyInstaller work files, Perl build caches, or test data in `compiled_version`.

## 2. Approved build images

| Language | RHEL7 | RHEL8 |
|---|---|---|
| Rust CLI/GUI | `ucs/rhel7-rust-gui-build:20260714` | `ucs/rhel8-rust-gui-build:20260714` |
| Python/PyInstaller | `ucs/centos7-build:latest` | `ucs/rhel8-python-build:20260726` |
| Perl/PAR Packager | `ucs/centos7-build:latest` | `ucs/rhel8-build:latest` |
| Runtime validation | `ucs/rhel7-runtime:20260713` | `ucs/rhel8-runtime:20260713` |

Verified server image facts:

- RHEL7: CentOS 7.9, GLIBC 2.17, Python 3.6.8, PyInstaller 4.10,
  Perl 5.16.3, PAR Packager 1.041, Rust 1.97.0.
- RHEL8 Rust: Rocky Linux 8.9, GLIBC 2.28, Rust 1.97.0.
- RHEL8 Perl: Red Hat UBI 8.10, GLIBC 2.28, Perl 5.26.3,
  PAR Packager 1.064.

### Rebuild the RHEL8 Python image when the toolchain changes

The image `ucs/rhel8-python-build:20260726` is installed on the server and was
validated with a PyInstaller one-file smoke binary. The binary required at most
GLIBC 2.14 and ran successfully in `ucs/rhel8-runtime:20260713`.

Create `/opt/ucs-build/rhel8-python/Dockerfile`:

```dockerfile
FROM registry.access.redhat.com/ubi8/ubi:8.10

RUN dnf -y install \
      python39 python39-devel gcc gcc-c++ make binutils \
      zlib-devel openssl openssl-devel findutils file which \
    && dnf clean all

RUN python3.9 -m pip install --no-cache-dir \
      --index-url https://mirrors.aliyun.com/pypi/simple \
      "pip<24" setuptools wheel \
      "pyinstaller==4.10" \
      "pyinstaller-hooks-contrib==2022.0"

WORKDIR /work
CMD ["/bin/bash"]
```

Build and verify:

```bash
docker build \
  -t ucs/rhel8-python-build:20260726 \
  -t ucs/rhel8-python-build:latest \
  /opt/ucs-build/rhel8-python

docker run --rm ucs/rhel8-python-build:20260726 \
  bash -lc 'cat /etc/redhat-release; ldd --version | head -1; python3.9 --version; pyinstaller --version'
```

Expected GLIBC: `2.28`.

## 3. Create an isolated build job

Run builds outside both `source_code` and `compiled_version`. Logs therefore
stay in the execution directory instead of beside the maintained source.

```bash
export TOOL=ucs_example
export LANGUAGE=rust                       # rust | python | perl
export SOURCE=/root/ucs_code/source_code/<domain>/<tool>/<language>
export STAMP="$(date +%Y%m%d_%H%M%S)"
export JOB="/root/ucs_build_jobs/${TOOL}/${STAMP}"

mkdir -p "$JOB/src" "$JOB/out/RHEL7" "$JOB/out/RHEL8" "$JOB/logs"
rsync -a --delete \
  --exclude target --exclude build --exclude dist --exclude __pycache__ \
  --exclude '*.log' --exclude '.ucs_license.conf' \
  "$SOURCE/" "$JOB/src/"

find "$JOB/src" -type f -print0 | sort -z | xargs -0 sha256sum \
  > "$JOB/SOURCE_SHA256SUMS"
```

Record source revision before building:

```bash
{
  echo "tool=$TOOL"
  echo "language=$LANGUAGE"
  echo "built_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  echo "source=$SOURCE"
  git -C "$SOURCE" rev-parse HEAD 2>/dev/null | sed 's/^/git_commit=/'
} > "$JOB/BUILD_MANIFEST.txt"
```

## 4. Rust workflow

### 4.1 Preflight

```bash
cd "$JOB/src"
cargo fmt --all -- --check
cargo test --locked
cargo clippy --all-targets --all-features -- -D warnings
```

If the project deliberately has accepted Clippy warnings, document the exact
exception in `BUILD_MANIFEST.txt`; do not silently remove the gate.

### 4.2 RHEL7 build

```bash
chown -R 1000:1000 "$JOB"

docker run --rm \
  -v "$JOB:/work" -w /work/src \
  ucs/rhel7-rust-gui-build:20260714 \
  bash -lc '
    set -euo pipefail
    export CARGO_TARGET_DIR=/work/target/RHEL7
    cargo build --release --locked --bin '"$TOOL"' \
      2>&1 | tee /work/logs/rust_RHEL7.log
    install -m 0755 "$CARGO_TARGET_DIR"/release/'"$TOOL"' /work/out/RHEL7/'"$TOOL"'
  '
```

### 4.3 RHEL8 build

```bash
docker run --rm \
  -v "$JOB:/work" -w /work/src \
  ucs/rhel8-rust-gui-build:20260714 \
  bash -lc '
    set -euo pipefail
    export CARGO_TARGET_DIR=/work/target/RHEL8
    cargo build --release --locked --bin '"$TOOL"' \
      2>&1 | tee /work/logs/rust_RHEL8.log
    install -m 0755 "$CARGO_TARGET_DIR"/release/'"$TOOL"' /work/out/RHEL8/'"$TOOL"'
  '
```

The separate target directories are mandatory; they prevent RHEL7 objects from
being reused by the RHEL8 build or vice versa.

The shared UCS Rust license crate must be compiled into the executable.
`main()` must call `ucs_check_license()` before protected work. The release
must not include `.ucs_license.conf`.

## 5. Python/PyInstaller workflow

Set the entrypoint:

```bash
export ENTRY=ucs_example.py
```

### 5.1 Python preflight

```bash
cd "$JOB/src"
python3 -m compileall -q .
python3 -m unittest discover -s tests -p 'test_*.py'
```

If the project uses `pytest`, install the pinned test dependency in a disposable
test environment and run `python3 -m pytest`; do not add pytest to the customer
bundle.

### 5.2 RHEL7 one-file build

```bash
chown -R 1000:1000 "$JOB"

docker run --rm \
  -v "$JOB:/work" -w /work/src \
  ucs/centos7-build:latest \
  bash -lc '
    set -euo pipefail
    python3 -m PyInstaller \
      --noconfirm --clean --onefile \
      --name '"$TOOL"' \
      --distpath /work/out/RHEL7 \
      --workpath /work/pyinstaller/RHEL7/work \
      --specpath /work/pyinstaller/RHEL7/spec \
      '"$ENTRY"' \
      2>&1 | tee /work/logs/python_RHEL7.log
  '
```

### 5.3 RHEL8 one-file build

```bash
docker run --rm \
  -v "$JOB:/work" -w /work/src \
  ucs/rhel8-python-build:20260726 \
  bash -lc '
    set -euo pipefail
    python3.9 -m PyInstaller \
      --noconfirm --clean --onefile \
      --name '"$TOOL"' \
      --distpath /work/out/RHEL8 \
      --workpath /work/pyinstaller/RHEL8/work \
      --specpath /work/pyinstaller/RHEL8/spec \
      '"$ENTRY"' \
      2>&1 | tee /work/logs/python_RHEL8.log
  '
```

Add only required project data and hidden imports:

```text
--add-data "assets:assets"
--hidden-import package.module
--paths ./common
```

Do not add NumPy, pandas, Qt, or complete site-packages unless the source
actually imports them. Inspect the PyInstaller analysis and archive before
promotion:

```bash
pyi-archive_viewer -l "$JOB/out/RHEL8/$TOOL"
du -h "$JOB/out/RHEL7/$TOOL" "$JOB/out/RHEL8/$TOOL"
```

The shared Python UCS license checker must be bundled and called before
protected work. PyInstaller packages Python bytecode and runtime; it is a
deployment format, not equivalent to source encryption.

## 6. Perl/PAR Packager workflow

Set the entrypoint. Add every dynamically loaded module explicitly to the
individual `pp` commands:

```bash
export ENTRY=ucs_example.pl
```

### 6.1 Perl preflight

```bash
cd "$JOB/src"
perl -c "$ENTRY"
prove -lr t
```

### 6.2 RHEL7 build

```bash
chown -R 1000:1000 "$JOB"

docker run --rm \
  -v "$JOB:/work" -w /work/src \
  ucs/centos7-build:latest \
  bash -lc '
    set -euo pipefail
    pp -o /work/out/RHEL7/'"$TOOL"' \
      -I /work/src/lib \
      -M JSON::PP -M Excel::Writer::XLSX \
      '"$ENTRY"' \
      2>&1 | tee /work/logs/perl_RHEL7.log
    chmod 0755 /work/out/RHEL7/'"$TOOL"'
  '
```

### 6.3 RHEL8 build

```bash
docker run --rm \
  -v "$JOB:/work" -w /work/src \
  ucs/rhel8-build:latest \
  bash -lc '
    set -euo pipefail
    pp -o /work/out/RHEL8/'"$TOOL"' \
      -I /work/src/lib \
      -M JSON::PP -M Excel::Writer::XLSX \
      '"$ENTRY"' \
      2>&1 | tee /work/logs/perl_RHEL8.log
    chmod 0755 /work/out/RHEL8/'"$TOOL"'
  '
```

`pp` normally detects static `use` statements. List modules loaded through
runtime `require`, plugin discovery, or computed names explicitly with `-M`.
Include project modules with `-I` and confirm they appear in:

```bash
unzip -l "$JOB/out/RHEL8/$TOOL"
```

The shared Perl UCS license checker must be included in the PAR executable and
called before protected work.

## 7. Mandatory validation gates

### 7.1 File and ABI checks

```bash
for target in RHEL7 RHEL8; do
  bin="$JOB/out/$target/$TOOL"
  test -x "$bin"
  file "$bin"
  readelf -h "$bin" | grep 'Machine:.*Advanced Micro Devices X86-64'
  objdump -T "$bin" 2>/dev/null \
    | sed -n 's/.*GLIBC_\([0-9.]*\).*/\1/p' \
    | sort -Vu | tail -1 \
    | tee "$JOB/logs/${target}_max_glibc.txt"
done
```

Reject a RHEL7 binary requiring GLIBC newer than 2.17 or a RHEL8 binary
requiring GLIBC newer than 2.28.

### 7.2 Run in clean runtime containers

```bash
docker run --rm \
  -v "$JOB/out/RHEL7:/release:ro" \
  ucs/rhel7-runtime:20260713 \
  /release/"$TOOL" --help

docker run --rm \
  -v "$JOB/out/RHEL8:/release:ro" \
  ucs/rhel8-runtime:20260713 \
  /release/"$TOOL" --help
```

For GUI tools, run `--help` first, then a functional smoke test under Xvfb or a
real RHEL desktop. CLI success does not prove GUI library compatibility.

### 7.3 License tests

Run three cases for each target:

1. Valid license: mount `/root/ucs_code/.ucs_license.conf` read-only beside the
   executable and confirm the protected operation succeeds.
2. Missing license: confirm the protected operation returns nonzero.
3. Corrupt license: mount a malformed test file and confirm it is rejected.

Never copy the valid license into the release package.

### 7.4 Functional and regression tests

- Run the tool's unit tests before packaging.
- Run at least one representative input/golden-output test per target.
- Compare RHEL7 and RHEL8 outputs after removing timestamps and platform-only
  metadata.
- Confirm generated logs appear in the command execution directory, not beside
  the executable.
- For GUI tools, exercise startup, file open, core interaction, and clean exit.

### 7.5 Release hygiene

```bash
find "$JOB/out" \( \
  -name '*.rs' -o -name '*.py' -o -name '*.pl' -o \
  -name '.ucs_license.conf' -o -name '*.pem' -o -name 'id_rsa*' \
\) -print

strings "$JOB/out/RHEL7/$TOOL" "$JOB/out/RHEL8/$TOOL" \
  | grep -E '/root/ucs_code|/Users/ucs|/work/' && exit 1 || true
```

The `find` command must return nothing. Review any absolute paths found by
`strings`.

## 8. Produce release metadata

```bash
cd "$JOB/out"
sha256sum RHEL7/"$TOOL" RHEL8/"$TOOL" > SHA256SUMS
cp "$JOB/BUILD_MANIFEST.txt" BUILD_MANIFEST.txt
cp /path/to/latest/usage.html usage.html

{
  echo
  echo "RHEL7:"
  docker image inspect -f '{{.Id}}' ucs/rhel7-rust-gui-build:20260714
  echo "RHEL8:"
  docker image inspect -f '{{.Id}}' ucs/rhel8-rust-gui-build:20260714
} >> BUILD_MANIFEST.txt
```

For Python or Perl, record the corresponding image IDs and language/tool
versions instead.

## 9. Promote atomically and keep only the latest

Validate in staging first. Then:

```bash
export RELEASE=/root/ucs_code/compiled_version/$TOOL
export NEW="${RELEASE}.new_${STAMP}"
export OLD="${RELEASE}.previous_${STAMP}"

mkdir -p "$NEW"
cp -a "$JOB/out/." "$NEW/"

if test -d "$RELEASE"; then
  mv "$RELEASE" "$OLD"
fi
mv "$NEW" "$RELEASE"

# Re-run --help, ABI, license, and golden tests from $RELEASE here.

if test -d "$OLD"; then
  find "$OLD" -depth -delete
fi
```

Do not remove the previous directory until the deployed version passes.

## 10. Publish Usage

Keep one current help page and publish it to both locations:

```bash
install -o webadmin -g webadmin -m 0644 "$RELEASE/usage.html" \
  "/home/webadmin/website/usage/${TOOL}_usage.html"

install -o webadmin -g webadmin -m 0644 "$RELEASE/usage.html" \
  "/home/webadmin/website/docs/internal/usage/${TOOL}_usage.html"
```

Verify the public page does not contain the internal auth guard:

```bash
curl -fsS "https://www.universalchipstudio.com/usage/${TOOL}_usage.html" \
  > "$JOB/logs/public_usage.html"

grep -E 'auth\.js|data-auth-guard' "$JOB/logs/public_usage.html" && exit 1 || true
```

Update `/home/webadmin/website/usage/index.html` when adding a new tool.

## 11. Final release checklist

- [ ] Exact source revision and source hashes recorded.
- [ ] RHEL7 and RHEL8 built in matching containers.
- [ ] Unit tests and representative golden test passed.
- [ ] RHEL7 maximum GLIBC is 2.17 or older.
- [ ] RHEL8 maximum GLIBC is 2.28 or older.
- [ ] Valid, missing, and corrupt license cases passed.
- [ ] No source, license, key, cache, or build directory in the release.
- [ ] No unexpected absolute developer/server paths.
- [ ] `--help` and functional smoke tests passed in clean runtime containers.
- [ ] RHEL7/RHEL8 functional output is equivalent.
- [ ] `SHA256SUMS`, `BUILD_MANIFEST.txt`, and `usage.html` are current.
- [ ] Server source and compiled directories contain only the latest version.
- [ ] Public Usage URL returns HTTP 200 without login.
