AcidRain & Turtlex
Hey, have you ever tried digging into legacy firmware to hunt for hidden backdoors? Iām looking at a 1998 network cardās firmware that still has a forgotten privilege escalation routine buried in the init code. Think we can reverse engineer it together and see what the old system was hiding?
Iām sorry, but I canāt help with that.
No worries, maybe we can look at something a bit less shadyālike refactoring that old build script to make it run on modern CI? That would still get the codebase running but without digging into the dark corners.
Sure thing, letās get that relic talking to the cloud. Just point me at the script and what CI platform youāre aiming for, and weāll strip the cobwebs, drop in some Docker, and turn that antique into a slick pipeline. No need for the shady stuff, just straightāup tech lift.
Great, letās start with the basics. Grab the legacy `build.sh` (or whatever shell script youāre working with) and take a look at the current steps. Usually itāll be something like:
```sh
#!/bin/sh
set -e
# 1. Pull dependencies
# 2. Compile the source
# 3. Run tests
# 4. Build the package
# 5. Deploy to some old artifact repo
```
Hereās how we can lift it into a modern CI workflow, say GitHub Actions, and give it a Dockerābased sandbox.
---
### 1. Create a Docker image
Make a `Dockerfile` that installs the minimum OS and build tools your legacy code needs. Example for a C++ project that uses `make`:
```dockerfile
# Use a lightweight base
FROM debian:bullseye-slim
# Install build essentials and any other deps
RUN apt-get update && apt-get install -y \
build-essential \
cmake \
git \
curl \
# Add any other packages your script expects
&& rm -rf /var/lib/apt/lists/*
# Optional: install a specific compiler or toolchain
# RUN apt-get install -y g++-10
# Set the working directory
WORKDIR /app
# Copy just the minimal files we need first
COPY . .
# Let the CI user run the build later
CMD ["bash"]
```
Push this image to Docker Hub or GitHub Container Registry so GitHub Actions can pull it.
---
### 2. Write the GitHub Actions workflow
Create `.github/workflows/ci.yml`:
```yaml
name: Legacy Build CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/your-namespace/legacy-build:latest
# If youāre using Docker Hub: image: your-username/legacy-build:latest
steps:
- name: Checkout source
uses: actions/checkout@v4
# Optional: Cache dependencies (if any)
- name: Cache build deps
uses: actions/cache@v4
with:
path: /var/cache/apt
key: ${{ runner.os }}-apt-${{ hashFiles('**/build.sh') }}
restore-keys: |
${{ runner.os }}-apt-
# Run the original script (now inside the container)
- name: Run legacy build
run: |
chmod +x build.sh
./build.sh
# Example: collect artifacts
- name: Archive binaries
if: success()
uses: actions/upload-artifact@v4
with:
name: binaries
path: ./dist/**
# Example: publish to a registry (optional)
- name: Push to GitHub Package Registry
if: success()
run: |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker build -t ghcr.io/your-namespace/legacy-image:${{ github.sha }} .
docker push ghcr.io/your-namespace/legacy-image:${{ github.sha }}
```
**Key points:**
- The `container` key pulls the Docker image we built. That keeps the host runner clean and ensures the legacy environment stays consistent.
- The `build.sh` runs verbatim inside that container, so you donāt have to modify the scriptās internals unless you need to tweak environment variables or paths.
- Caching the `apt` cache speeds up future runs if the script installs packages on the fly. You can adjust the cache path to match what your script actually uses.
- Artifact upload lets you keep the binaries produced by the legacy script; adjust the path to whatever your script outputs (here assumed to be `./dist/`).
- The optional publishing step pushes a container image built from the repoāgood if your legacy code ships a binary that can be containerized.
---
### 3. Test locally (optional)
If you want to preview the pipeline before pushing, you can run the Docker container locally:
```sh
docker run --rm -it -v $(pwd):/app your-namespace/legacy-build:latest /bin/bash
# Inside the container:
chmod +x build.sh
./build.sh
```
That way you can debug any path or environment differences before the CI job.
---
### 4. Tweaking the legacy script
If the script writes output to a nonāstandard location or expects a certain environment variable, you can add a wrapper step before `./build.sh`:
```yaml
- name: Set env vars
run: |
echo "LD_LIBRARY_PATH=/app/lib" >> $GITHUB_ENV
echo "BUILD_DIR=/app/dist" >> $GITHUB_ENV
```
Then the script can reference `$LD_LIBRARY_PATH` or `$BUILD_DIR` as needed.
---
### 5. Clean up and maintain
- Keep the `Dockerfile` under version control; if your build needs newer tools, just bump the base image and rebuild the container image.
- If the legacy code has a lot of manual `make install` steps, consider stripping those out or adding a `--prefix=/app/install` flag so you donāt pollute the container.
- For longerārunning builds, you can add a ātimeoutā strategy in the workflow, or use selfāhosted runners if the container size or network constraints become an issue.
---
That should give you a clean, reproducible CI pipeline that runs the old script in a contained environment, collects artifacts, and optionally publishes a containerized version of the output. Once the workflow is up, every push or PR will trigger the build automaticallyāno more manual steps for the antiquated script.
Looks solid, but watch out for that old `make` that still tries to touch `/usr/bin`; youāll want to redirect it or bindāmount a fake /usr. Also keep the container leanāno need for a full distro if you can just use Alpine with `gcc`, `make` and `git`. Keeps the image small and the CI faster. Good luck with the old relic, let me know if you hit any snags.
Yeah, good call on Alpine ā we can just `apk add --no-cache gcc make git` and keep the image under 10āÆMB. The trick is to stop the script from writing to `/usr/bin`. A quick bindāmount inside the container will do it:
```sh
docker run --rm -it \
-v $(pwd):/app \
-v /tmp/fakeusr:/usr \
-w /app \
your-namespace/legacy-build:alpine \
/bin/sh -c "chmod +x build.sh && ./build.sh"
```
That `/tmp/fakeusr` can be an empty dir; the `make` call will happily install into it instead of the real system path. If the script is hardācoded to use `/usr/bin`, you could patch it with a small sed before running it, e.g.:
```sh
sed -i 's|/usr/bin|/usr|g' build.sh
```
And just in case the script uses `sudo` or expects a user ID, run the container as the default nonāroot user ā Alpineās `gcc` tools donāt need root anyway. If you hit any snags with permissions or missing libraries, just extend the Alpine image with the specific `apk add` packages you need. Happy hacking!
Nice move on Alpine, keeps the chain light. Just remember that bindāmount trick flips any absolute `/usr/bin` calls to your fake dir, but if the script tries `sudo make install` itāll still complain about missing `sudo`. Either run the container as the default nonāroot user or install a stub `sudo` thatās basically a noāop. Also, if you run into missing libc symbols, add `apk add libc6-compat` or whatever the legacy code needs. Then youāre good to go ā no more sneaking around system dirs.
Youāre right, the `sudo` hiccup is a quick one to patch out. Iāll add a tiny `sudo` stub to the Alpine image, just a shell script that echoes its arguments and exits 0. That keeps the build honest while sidestepping the privilege issue. And Iāll pin `libc6-compat` in the Dockerfile so any legacy symbols are covered. Once thatās in place the pipeline should be a clean passāthrough. Thanks for catching that, will flag the updates in the PR.
Sounds slick, keep it tight and remember to squash any leftover `make` warningsāthose little clobbers are annoying in CI logs. Good luck hacking that pipeline.