|
| 1 | +# 🐧 LPI Exam Practice Engine — Linux Essentials 010-160 |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +> **Fully offline** · No server · No build step · No dependencies · Runs from a USB stick |
| 11 | +> **Accessible by design** · Screen readers · Dyslexia · Colour vision · Reduced motion |
| 12 | +
|
| 13 | +A browser-based practice engine for LPI certification exams, shipping with a configuration |
| 14 | +and a sample question pool for **Linux Essentials (010-160)**. |
| 15 | + |
| 16 | +Open `index.html` and it works immediately. |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## What this is, and what it deliberately is not |
| 21 | + |
| 22 | +**It is an engine.** The exam is described by one file, `config/exam.js`: format, timing, |
| 23 | +topics, objectives and their official weights. Point it at a different exam and the same |
| 24 | +code serves that exam. Nothing exam-specific exists outside `config/` and `data/`. |
| 25 | + |
| 26 | +**It ships a sample, not a study bank.** `data/questions.js` contains **40 questions** — |
| 27 | +exactly one per official weight point, so it forms precisely one full weighted exam and |
| 28 | +demonstrates the sampler working. Forty questions will not prepare anyone for a |
| 29 | +certification, and it is not trying to. |
| 30 | + |
| 31 | +**You bring your own pool.** LPI publishes the complete Linux Essentials learning material |
| 32 | +free of charge. You generate your own question bank from your own copy, keep it on your |
| 33 | +machine, and the engine picks it up automatically. The procedure is in |
| 34 | +[`docs/QUESTION-SOURCING.md`](docs/QUESTION-SOURCING.md). |
| 35 | + |
| 36 | +### Why it works this way |
| 37 | + |
| 38 | +The LPI Learning Materials are licensed **CC BY-NC-ND 4.0** — no commercial use, no |
| 39 | +distribution of derived works. Generating a personal question pool from them for your own |
| 40 | +study is fine. Publishing that pool under MIT is not, because MIT would grant everyone |
| 41 | +else rights that cannot be granted. |
| 42 | + |
| 43 | +The shipped questions are therefore written from the **published exam objectives** and |
| 44 | +general technical knowledge, which is what every third-party certification book has always |
| 45 | +done. Full reasoning, and the boundary this project draws, in |
| 46 | +[`docs/QUESTION-SOURCING.md`](docs/QUESTION-SOURCING.md) and |
| 47 | +[ADR-0004](docs/decisions/0004-content-licensing-boundary.md). |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## Modes |
| 52 | + |
| 53 | +| Mode | What it does | |
| 54 | +|------|--------------| |
| 55 | +| 📖 **Study** | Browse questions with instant feedback and explanations. Filter by topic, type or source. No timer. | |
| 56 | +| ⏱ **Exam** | 40 questions · 60-minute timer · no feedback until submission. | |
| 57 | +| 🎯 **Drill** | One objective at a time, until it sticks. | |
| 58 | +| 🔍 **Hard** | The trickier questions: multi-select, fill-in, common traps. | |
| 59 | + |
| 60 | +Question types: single choice · multi-select · fill-in (type the command). |
| 61 | + |
| 62 | +A sixth mode, concept maps, is held back pending a provenance decision — see |
| 63 | +[`docs/MAPS-PENDING.md`](docs/MAPS-PENDING.md). |
| 64 | + |
| 65 | +--- |
| 66 | + |
| 67 | +## Exam fidelity |
| 68 | + |
| 69 | +The real exam distributes 40 questions across 19 objectives by published weight. Objective |
| 70 | +3.3 alone is worth 4 of those 40 points; 1.3, 4.1 and 5.4 are worth 1 each. |
| 71 | + |
| 72 | +`assets/js/sampler.js` reproduces that weighting using largest-remainder apportionment — |
| 73 | +not rounding, which does not sum back to 40 and silently yields 39- or 41-question exams. |
| 74 | +Measured over 2,000 simulated exams: **7 / 9 / 9 / 8 / 7** questions across Topics 1–5, |
| 75 | +40.00 questions per exam. |
| 76 | + |
| 77 | +The 75% pass threshold is a practical proxy. Official scoring is 500 on a 200–800 scale |
| 78 | +and is not publicly reducible to a percentage. The UI says so. |
| 79 | + |
| 80 | +--- |
| 81 | + |
| 82 | +## ♿ Accessibility |
| 83 | + |
| 84 | +Accessibility was a first-class requirement, not a retrofit. Every option is a toggle in |
| 85 | +the panel injected by `a11y.js`, persists in `localStorage`, and works offline — there are |
| 86 | +no downloaded fonts or remote assets anywhere in the app. |
| 87 | + |
| 88 | +| Need | What is provided | |
| 89 | +|---|---| |
| 90 | +| **Low vision** | High-contrast mode, dark theme, adjustable text size | |
| 91 | +| **Colour vision deficiency** | Three palettes: achromatopsia, deuteranopia, tritanopia | |
| 92 | +| **Dyslexia** | Wide-letterform font, loosened line spacing, text never justified | |
| 93 | +| **Dyscalculia / test anxiety** | The exam timer can be hidden entirely, or paused | |
| 94 | +| **Motor impairment** | Enlarged tap and click targets | |
| 95 | +| **Vestibular disorders** | Reduced-motion mode | |
| 96 | +| **Screen readers** | ARIA labels and roles throughout, `aria-live` regions, skip links | |
| 97 | +| **Keyboard-only use** | Full keyboard navigation, managed focus order | |
| 98 | + |
| 99 | +**On the dyslexia font:** it uses widely available system faces (Comic Sans MS, Verdana) |
| 100 | +rather than OpenDyslexic. OpenDyslexic would have to be downloaded, and this application |
| 101 | +has to work from a USB stick with no network. A font that is present everywhere beats a |
| 102 | +better font that sometimes fails to load. |
| 103 | + |
| 104 | +`localStorage` is used for these preferences only. No analytics, no cookies, nothing leaves |
| 105 | +the machine. |
| 106 | + |
| 107 | +--- |
| 108 | + |
| 109 | +## Quick start |
| 110 | + |
| 111 | +```bash |
| 112 | +git clone <your-fork-url> |
| 113 | +cd lpi-exam-engine |
| 114 | +``` |
| 115 | + |
| 116 | +Open `index.html` in a browser. No server, no install. |
| 117 | + |
| 118 | +To build a real study bank, follow [`docs/QUESTION-SOURCING.md`](docs/QUESTION-SOURCING.md): |
| 119 | +download the free material from LPI, generate `data/questions.personal.js`, and the engine |
| 120 | +uses it instead of the sample. That file is git-ignored and never leaves your machine. |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +## Structure |
| 125 | + |
| 126 | +``` |
| 127 | +├── index.html · study.html · exam.html · drill.html · weak.html |
| 128 | +├── style.css |
| 129 | +├── a11y.js accessibility controller |
| 130 | +│ |
| 131 | +├── config/ |
| 132 | +│ └── exam.js THE exam definition — format, topics, official weights |
| 133 | +├── data/ |
| 134 | +│ ├── questions.js sample pool, 40 original questions |
| 135 | +│ └── questions.personal.js your pool — git-ignored, never committed |
| 136 | +├── assets/js/ |
| 137 | +│ ├── core.js helpers and metadata derived from the config |
| 138 | +│ └── sampler.js weighted exam sampling |
| 139 | +├── tools/ |
| 140 | +│ ├── validate-questions.js CI gate: bank integrity and provenance |
| 141 | +│ └── check-offline.js CI gate: no remote dependencies |
| 142 | +│ |
| 143 | +├── docs/ |
| 144 | +│ ├── QUESTION-SOURCING.md where questions may come from, and the boundary |
| 145 | +│ ├── MAPS-PENDING.md why the concept maps are not shipped yet |
| 146 | +│ ├── ARCHITECTURE.md engine design and fork procedure |
| 147 | +│ ├── AUTHORS.md who did what |
| 148 | +│ └── decisions/ architecture decision records |
| 149 | +│ |
| 150 | +├── .github/workflows/validate.yml |
| 151 | +├── .githooks/prepare-commit-msg adds the Assisted-by trailer (opt in locally) |
| 152 | +├── .gitattributes · .gitignore |
| 153 | +├── LICENSE MIT — the code |
| 154 | +└── LICENSE-CONTENT.md the questions and what you may do with them |
| 155 | +``` |
| 156 | + |
| 157 | +`config/exam.js` is JavaScript rather than JSON on purpose: the app must run from `file://` |
| 158 | +off a USB stick, and browsers block `fetch()` on `file://` origins. A script tag is not |
| 159 | +blocked, and the Node tooling reads the same file. See |
| 160 | +[ADR-0003](docs/decisions/0003-config-as-javascript.md). |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Forking for another LPI exam |
| 165 | + |
| 166 | +Verified formats: Security Essentials (020-100) and Web Development Essentials (030-100) |
| 167 | +are 40 questions in 60 minutes like Linux Essentials; LPIC-1 (101-500 / 102-500) is 60 |
| 168 | +questions in 90 minutes. The engine reads all of that from the config, which is why those |
| 169 | +numbers are not hardcoded anywhere. |
| 170 | + |
| 171 | +1. Use this repository as a template |
| 172 | +2. Replace `config/exam.js` — identity, format, topics, objectives and weights, |
| 173 | + transcribed from that exam's objectives page |
| 174 | +3. Replace `data/questions.js` with your own sample; every `s` value must exist in the config |
| 175 | +4. `node tools/validate-questions.js` — green means the fork is coherent |
| 176 | + |
| 177 | +All four steps work today. The one gap is concept maps, which are not shipped — |
| 178 | +see [`docs/MAPS-PENDING.md`](docs/MAPS-PENDING.md). |
| 179 | + |
| 180 | +--- |
| 181 | + |
| 182 | +## Quality controls |
| 183 | + |
| 184 | +GitHub Actions runs the validator on every push and pull request. It fails on schema |
| 185 | +violations, answer indices out of range, duplicate question text, duplicate IDs, objective |
| 186 | +keys absent from the config, topic/objective mismatches, and any question tagged as derived |
| 187 | +from the LPI material appearing in a committed bank. It warns when objective coverage |
| 188 | +drifts more than 2 points from the official weight share. |
| 189 | + |
| 190 | +```bash |
| 191 | +node tools/validate-questions.js data/questions.js config/exam.js |
| 192 | +node tools/check-offline.js |
| 193 | +``` |
| 194 | + |
| 195 | +`check-offline.js` fails the build if a remote script, stylesheet, image, webfont, CSS |
| 196 | +import, `fetch()` or `XMLHttpRequest` is introduced anywhere in the app. The |
| 197 | +offline-from-USB guarantee is a promise, and a promise nothing enforces decays. Plain |
| 198 | +`<a href>` links are not flagged — they are navigation, not a loaded resource. |
| 199 | + |
| 200 | +**What it cannot check:** whether an answer key is actually correct. Structural validity is |
| 201 | +mechanical; factual correctness is not. Verify your own questions. |
| 202 | + |
| 203 | +--- |
| 204 | + |
| 205 | +## Attribution |
| 206 | + |
| 207 | +Three roles, kept separate because merging them would misstate all of them. |
| 208 | + |
| 209 | +| Role | Who | |
| 210 | +|---|---| |
| 211 | +| **Concept, direction and every decision** | **[KalevIT](https://github.com/KalevIT)** — originated the project, set the scope and requirements, made accessibility a first-class constraint, commissioned the audit, raised the licensing question that shaped the content boundary, and ruled on every architectural trade-off | |
| 212 | +| **Application layer** | Claude Sonnet 4.6 (Anthropic) — the mode pages, stylesheet and accessibility controller | |
| 213 | +| **Engine, audit and documentation** | Claude Opus 5 (Anthropic) — config-driven engine, weighted sampler, CI gates, sample question pool, docs | |
| 214 | + |
| 215 | +The models are tools. Every decision recorded in |
| 216 | +[`docs/decisions/`](docs/decisions/) is the author's, including the ones taken against the |
| 217 | +advice given — and responsibility for this repository, defects included, is his alone. |
| 218 | + |
| 219 | +[`docs/AUTHORS.md`](docs/AUTHORS.md) has the full breakdown and, more usefully, the list of |
| 220 | +what was independently verified and what was not. |
| 221 | + |
| 222 | +--- |
| 223 | + |
| 224 | +## Licensing |
| 225 | + |
| 226 | +- **Code** — `core.js`, `sampler.js`, the validator, the pages, the CI: **MIT**. See `LICENSE`. |
| 227 | +- **Questions in `data/questions.js`** — original work, MIT alongside the code. See |
| 228 | + [`LICENSE-CONTENT.md`](LICENSE-CONTENT.md). |
| 229 | +- **The LPI Learning Materials** — © Linux Professional Institute, CC BY-NC-ND 4.0. Not |
| 230 | + included in this repository, not redistributed by it. Download your own free copy from |
| 231 | + [learning.lpi.org](https://learning.lpi.org/en/learning-materials/010-160/). |
| 232 | + |
| 233 | +Not affiliated with, endorsed by or sponsored by the Linux Professional Institute. |
| 234 | +Not official exam questions and no reproduction of real exam content. |
| 235 | + |
| 236 | +--- |
| 237 | + |
| 238 | +## Source material |
| 239 | + |
| 240 | +- [LPI Learning Materials: Linux Essentials v1.6](https://learning.lpi.org/en/learning-materials/010-160/) — free, CC BY-NC-ND 4.0 |
| 241 | +- [Official exam 010 objectives](https://www.lpi.org/our-certifications/exam-010-objectives/) — the weights in `config/exam.js` come from here |
| 242 | + |
| 243 | +## License |
| 244 | + |
| 245 | +MIT for the code. See `LICENSE` and [`LICENSE-CONTENT.md`](LICENSE-CONTENT.md). |
0 commit comments