Chapter 11Lesson 05220–290 min

Checkpoint Lab — APT, Yum, Raw, RubyGems, Composer, and Operating-System or Generic Artifact Formats

Build a six-artifact decision matrix, implement one RubyGems-native path and one Raw path, upload only synthetic content, consume and inspect the resulting package/file state, prove checksums and protocol fit, and clean up only disposable repositories.

Checkpoint labDecision matrixRubyGemsRawSafe cleanup

Checkpoint objectives

  • Produce a six-artifact repository-format decision matrix with explicit client and trust reasoning.
  • Implement one native RubyGems path and one Raw path using synthetic content only.
  • Predict database/blob/component/path changes before publication and verify them independently afterward.
  • Prove native package identity and generic-file byte identity without exposing credentials.
  • Delete only disposable repository state through supported Nexus operations and preserve an evidence packet.

Checkpoint scope. Disposable self-hosted Nexus Repository Community Edition, version floor 3.95.0, loopback/private network, default file blob store or another disposable store selected by the learner. Mandatory live work uses RubyGems + Raw only. APT, Yum, and Composer are evaluated in the architecture matrix, so the lab remains cross-platform/free-compatible and does not require production signing keys or OS package-manager changes.

1. Scenario and acceptance criteria

You are designing a small internal artifact platform for six consumers: Ubuntu packages, RPM packages, Ruby gems, Composer packages, firmware bundles, and static release manifests. The team previously used one generic HTTP folder for everything. Your job is to prove why format choice matters by implementing one native package path and one Raw path.

The checkpoint passes only if the evidence distinguishes native package identity from Raw path identity, shows exact bytes/checksums, records repository type/state, and avoids production credentials or direct blob/database manipulation.

2. Decision matrix — complete before creating repositories

Artifact Chosen format Repository types / route Trust and metadata reason
Internal .deb APT Hosted for internal; proxy for upstream; no group APT distribution/index metadata and repository signing are required for native client behavior.
Internal RPM Yum Hosted + optional proxy/group Repodata and RPM/Yum client semantics; metadata signing is separate from RPM signing.
Private Ruby gem RubyGems Hosted + proxy + group Native name/version/index behavior and standard Gem/Bundler consumption.
Private PHP archive Composer v2 3.95+ hosted/group only after live entitlement check Composer v2 metadata; Packagist fallback must be controlled.
Firmware binary Raw Hosted, versioned path Consumer already knows exact artifact URL; detached signature/digest can be published beside it.
Release manifest JSON Raw Same immutable release path or separate Raw repo No package-manager dependency semantics; exact byte/path lookup is sufficient.

Take a screenshot or text export of this matrix as part of the evidence packet.

3. Preflight and prediction

export NX_URL="http://127.0.0.1:8081"
export LAB="${TMPDIR:-/tmp}/nexus-ch11-checkpoint"
rm -rf "$LAB"
mkdir -p "$LAB/evidence" "$LAB/gem-src/lib" "$LAB/raw-src" "$LAB/downloads" "$LAB/gem-home"
chmod 700 "$LAB"
export GEM_HOME="$LAB/gem-home"
export GEM_PATH="$GEM_HOME"

curl -fsS "$NX_URL/service/rest/v1/status" | tee "$LAB/evidence/status.txt"
curl -fsS "$NX_URL/service/rest/v1/repositories" | tee "$LAB/evidence/repos-before.json"
gem --version 2>&1 | tee "$LAB/evidence/gem-version.txt" || true

Prediction A: uploading one .gem to RubyGems hosted will create one native component identified by name/version and populate RubyGems repository information; group reads can expose it without copying it into the group.

Prediction B: uploading two Raw files will create two independent Raw components at explicit paths; Nexus will not create package/version/dependency semantics between them.

4. Create only the checkpoint repositories

Repository Recipe Settings
academy-ch11cp-gem-hosted rubygems (hosted) Default/disposable blob; online; no redeploy if current UI exposes deployment policy.
academy-ch11cp-gem-group rubygems (group) Member: checkpoint hosted only. No public proxy is needed for the internal-only proof.
academy-ch11cp-raw raw (hosted) Default/disposable blob; use immutable versioned paths.

Create one disposable publisher with read/browse/add on the hosted repositories and read/browse on the group. Keep repository administration separate.

5. Create a disposable credential file

read -r -p 'Disposable Nexus username: ' NX_USER
read -r -s -p 'Disposable Nexus password: ' NX_PASS; echo
export NX_AUTH_FILE="$LAB/nexus.netrc"
umask 077
printf 'machine 127.0.0.1 login %s password %s
' "$NX_USER" "$NX_PASS" > "$NX_AUTH_FILE"
unset NX_PASS

6. Native path: build, publish, and inspect a RubyGem

cd "$LAB/gem-src"
cat > learner_ch11_checkpoint.gemspec <<'EOF'
Gem::Specification.new do |s|
  s.name        = "learner_ch11_checkpoint"
  s.version     = "1.0.0"
  s.summary     = "Nexus Chapter 11 checkpoint"
  s.authors     = ["DevOps Academy Lab"]
  s.files       = ["lib/learner_ch11_checkpoint.rb"]
  s.require_paths = ["lib"]
  s.license     = "MIT"
end
EOF
cat > lib/learner_ch11_checkpoint.rb <<'EOF'
module LearnerCh11Checkpoint
  IDENTITY = "learner_ch11_checkpoint/1.0.0"
end
EOF

gem build learner_ch11_checkpoint.gemspec   | tee "$LAB/evidence/gem-build.txt"
python - <<'PYI' | tee "$LAB/evidence/gem-producer-sha256.txt"
from pathlib import Path
import hashlib, os
p=Path(os.environ['LAB'],'gem-src','learner_ch11_checkpoint-1.0.0.gem')
print(hashlib.sha256(p.read_bytes()).hexdigest(), p.name)
PYI
curl --fail --silent --show-error   --netrc-file "$NX_AUTH_FILE"   -X POST "$NX_URL/service/rest/v1/components?repository=academy-ch11cp-gem-hosted"   -F "rubygems.asset=@$LAB/gem-src/learner_ch11_checkpoint-1.0.0.gem"   -o /dev/null -w 'HTTP %{http_code}
'   | tee "$LAB/evidence/gem-upload.txt"

curl -fsS --netrc-file "$NX_AUTH_FILE"   "$NX_URL/service/rest/v1/components?repository=academy-ch11cp-gem-hosted"   | tee "$LAB/evidence/gem-components.json"

Verify in Browse/Search that Nexus identifies the name and version. Record the asset URL and component ID. The group should expose the same component on read without becoming the authoritative store.

7. Consume the native package with safe credential handling

curl -fsS --netrc-file "$NX_AUTH_FILE"   "$NX_URL/repository/academy-ch11cp-gem-group/gems/learner_ch11_checkpoint-1.0.0.gem"   -o "$LAB/downloads/learner_ch11_checkpoint-1.0.0.gem"

{ gem specification "$LAB/downloads/learner_ch11_checkpoint-1.0.0.gem" name; gem specification "$LAB/downloads/learner_ch11_checkpoint-1.0.0.gem" version; }   | tee "$LAB/evidence/gem-consumer-metadata.txt"
gem install --local "$LAB/downloads/learner_ch11_checkpoint-1.0.0.gem" --no-document   | tee "$LAB/evidence/gem-install.txt"
ruby -e 'require "learner_ch11_checkpoint"; puts LearnerCh11Checkpoint::IDENTITY'   | tee "$LAB/evidence/gem-runtime.txt"
python - <<'PYI' | tee "$LAB/evidence/gem-consumer-sha256.txt"
from pathlib import Path
import hashlib, os
p=Path(os.environ['LAB'],'downloads','learner_ch11_checkpoint-1.0.0.gem')
print(hashlib.sha256(p.read_bytes()).hexdigest(), p.name)
PYI

Compare producer and consumer hashes. Equal hashes prove byte identity through the repository path. They do not prove publisher trust or vulnerability safety.

8. Raw path: publish a firmware fixture and release manifest

python - <<'PYI'
from pathlib import Path
import hashlib, json, os
base=Path(os.environ['LAB'],'raw-src')
firmware=base/'firmware-device-a-2.4.1.bin'
firmware.write_bytes(b'DEVOPS-ACADEMY-FAKE-FIRMWARE\x00version=2.4.1\n')
digest=hashlib.sha256(firmware.read_bytes()).hexdigest()
manifest=base/'release.json'
manifest.write_text(json.dumps({
  'device':'device-a','version':'2.4.1','file':firmware.name,'sha256':digest
}, indent=2)+'\n')
print(digest)
PYI
python - <<'PYI' | tee "$LAB/evidence/raw-producer-sha256.txt"
from pathlib import Path
import hashlib, os
for p in sorted(Path(os.environ['LAB'],'raw-src').iterdir()):
    print(hashlib.sha256(p.read_bytes()).hexdigest(), p.name)
PYI
curl --fail --silent --show-error   --netrc-file "$NX_AUTH_FILE"   -X POST "$NX_URL/service/rest/v1/components?repository=academy-ch11cp-raw"   -F "raw.directory=firmware/device-a/2.4.1"   -F "raw.asset1=@$LAB/raw-src/firmware-device-a-2.4.1.bin"   -F "raw.asset1.filename=firmware-device-a-2.4.1.bin"   -F "raw.asset2=@$LAB/raw-src/release.json"   -F "raw.asset2.filename=release.json"   -o /dev/null -w 'HTTP %{http_code}
'   | tee "$LAB/evidence/raw-upload.txt"

9. Retrieve Raw files and prove exact paths/bytes

for f in firmware-device-a-2.4.1.bin release.json; do
  curl -fsS --netrc-file "$NX_AUTH_FILE"     "$NX_URL/repository/academy-ch11cp-raw/firmware/device-a/2.4.1/$f"     -o "$LAB/downloads/$f"
done
python - <<'PYI' | tee "$LAB/evidence/raw-consumer-sha256.txt"
from pathlib import Path
import hashlib, json, os
base=Path(os.environ['LAB'])
for name in ['firmware-device-a-2.4.1.bin','release.json']:
    src=hashlib.sha256((base/'raw-src'/name).read_bytes()).hexdigest()
    dst=hashlib.sha256((base/'downloads'/name).read_bytes()).hexdigest()
    print(name, src, dst, 'MATCH' if src==dst else 'MISMATCH')
manifest=json.loads((base/'downloads'/'release.json').read_text())
actual=hashlib.sha256((base/'downloads'/'firmware-device-a-2.4.1.bin').read_bytes()).hexdigest()
print('manifest-firmware', manifest['sha256'], actual, 'MATCH' if manifest['sha256']==actual else 'MISMATCH')
PYI

Note the semantic contrast: RubyGems exposes a package name/version understood by a package manager; Raw exposes firmware/device-a/2.4.1/... because you designed that path.

10. Intentional failure: ask Raw for package-manager semantics

Write the expected APT source line that would point at academy-ch11cp-raw, but do not add it to the host's global APT configuration. Predict the paths APT would request (Release, package indexes, etc.) and explain why they do not exist. Verify with a harmless HEAD request to one predicted metadata path and record the 404.

curl -sS -o /dev/null -D "$LAB/evidence/raw-apt-head.txt"   --netrc-file "$NX_AUTH_FILE"   "$NX_URL/repository/academy-ch11cp-raw/dists/lab/Release" || true
cat "$LAB/evidence/raw-apt-head.txt"

11. Required evidence packet

  • Nexus version/edition and status output.
  • Repository list before and after creation.
  • The six-artifact decision matrix.
  • Gem build output, producer SHA-256, Components API result, component JSON, consumer metadata/runtime output, consumer SHA-256.
  • Raw producer hashes, upload result, exact URLs, consumer hashes, manifest-to-firmware digest comparison.
  • Intentional Raw/APT metadata failure headers.
  • Screenshot/text evidence of repository format/type/blob-store mappings.
  • A short trust statement explaining why checksum equality is not package provenance.

12. Cleanup — supported Nexus operations only

First verify all evidence. Then delete academy-ch11cp-gem-group, academy-ch11cp-gem-hosted, and academy-ch11cp-raw through Settings → Repository → Repositories or the documented Repository API using a sufficiently privileged disposable administrator. Do not remove files from the blob store or database directly.

rm -f "$NX_AUTH_FILE"
unset NX_USER NX_AUTH_FILE GEM_HOME GEM_PATH
# Preserve $LAB/evidence until review. Remove the whole lab directory only afterward.

13. Verification checklist

  • Only synthetic names/bytes were published.
  • RubyGems component is identified as learner_ch11_checkpoint version 1.0.0.
  • RubyGems producer and consumer SHA-256 values match.
  • Raw files resolve only by the explicit versioned path you chose.
  • Raw producer/consumer hashes match and the downloaded manifest matches the downloaded firmware digest.
  • The predicted APT metadata path on Raw fails, proving format mismatch rather than corrupt package bytes.
  • No password appears in command arguments/evidence and the temporary netrc is removed.
  • No database/blob files were edited or deleted manually.

Knowledge check

What is the strongest proof in this lab that RubyGems and Raw are different repository contracts?

Why is the APT-on-Raw failure useful?

What do matching producer/consumer hashes prove?

Why did the checkpoint avoid a mandatory Composer live path?

What comes next in the course?

14. Operational conclusion

This checkpoint adds a production habit to the repository operating model: select repository format from the consumer's protocol and trust requirements, then prove the resulting state. Native formats are not cosmetic wrappers around files, and Raw is not a universal package manager.

Chapter 12 continues with newer and emerging formats where version support changes even faster.

Official references and version notes

Version-sensitive statements were rechecked on 2026-08-26. The chapter uses Nexus Repository 3.95.0 as the verified feature floor because current Sonatype release notes explicitly list it as released on 2026-08-05 and document Composer hosted/group support there. Some adjacent Sonatype download/version-status pages still lag at 3.94.1, so record the exact version/edition on your lab instance before applying version-specific steps. If you are continuing with a later 3.95.x instance from a prior chapter, it satisfies this chapter's 3.95.0 feature floor. Mandatory labs use Community-compatible RubyGems and Raw features and do not depend on the documentation-sensitive Composer entitlement boundary.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.