getreadystack's picture
Upload index.html with huggingface_hub
1e16dca verified
Raw History Blame Contribute Delete
45.9 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>AlmaLinux CentOS Migration — check your files for free</title>
<meta name="description" content="This free tool flags CentOS images, repositories and packages that need changes for AlmaLinux.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script defer data-auto-init src="https://cdn.jsdelivr.net/npm/@polar-sh/checkout@latest/dist/embed.global.js"></script>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/almalinux-migration-lint"><style data-yj-mfit="1">#out{max-width:100%}#out td,#out th{overflow-wrap:anywhere}#out pre,#out code{white-space:pre-wrap;overflow-wrap:anywhere}@media (max-width:640px){table{display:block;max-width:100%;overflow-x:auto}pre{max-width:100%;overflow-x:auto}code,a{overflow-wrap:anywhere}img,svg,video,canvas,iframe{max-width:100%}textarea,input,select{max-width:100%;box-sizing:border-box}.yj-lgbar{position:static!important;justify-content:flex-end;margin:0 0 10px}#out table:not(:has(tr>:nth-child(4))){display:table;table-layout:fixed;width:100%}#out table:has(tr>:nth-child(4)),#out table:has(tr>:nth-child(4)) tbody,#out table:has(tr>:nth-child(4)) tr,#out table:has(tr>:nth-child(4)) td{display:block;width:auto;overflow:visible}#out table:has(tr>:nth-child(4)) thead{display:none}#out table:has(tr>:nth-child(4)) tr{padding:10px 0;border-bottom:1px solid rgba(128,128,128,.28)}#out table:has(tr>:nth-child(4)) td{border:0;padding:1px 0}#out table:has(tr>:nth-child(4)) td:nth-child(-n+3){display:inline-block;margin-right:10px;font-size:12px;opacity:.78}}</style>
</head>
<body>
<div class="wrap">
<h1>AlmaLinux CentOS Migration — check your files for free</h1>
<p class="lede">AlmaLinux migration check for CentOS Linux 7/8 files: flags EOL centos images, dead mirrorlist.centos.org repos, EPEL 7, SCL and EL7 packages removed in EL8/9, with the AlmaLinux fix. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">#!/usr/bin/env bash
# bootstrap.sh — provisions the build agent (written for CentOS 7)
set -euo pipefail
docker pull centos:7
docker run --rm quay.io/centos/centos:stream8 cat /etc/redhat-release
cat &gt; /etc/yum.repos.d/base.repo &lt;&lt;'REPO'
[base]
name=CentOS-7 - Base
mirrorlist=http://mirrorlist.centos.org/?release=7&amp;arch=x86_64&amp;repo=os
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
REPO
yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
yum install -y centos-release-scl
yum install -y devtoolset-11-gcc yum-cron ntp \
python-pip python-devel
systemctl enable --now yum-cron
scl enable devtoolset-11 -- make -C /opt/build
authconfig --enablesssd --update
systemctl restart network
if grep -q CentOS /etc/redhat-release; then
echo "centos host"
fi</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Every CentOS 7/8 leftover in the open file, with the line that breaks and the AlmaLinux fix, free and offline. <strong>In the editor:</strong> Scan the whole workspace in one pass and export one migration report for the change ticket. — that part asks for a licence key. <a href="https://getreadystack.com/api/buy/cl/polar_cl_E7s1yXN8TTsXTg4qiaoSemryY96mVKUKEndde3SnB0z" data-polar-checkout data-polar-checkout-theme="light">Full version: workspace scan + migration report — $29 once</a></div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var AL_RULES = [
{
"id": "centos7-image",
"sev": "error",
"pats": [
{
"re": "(?:^|[\\s\\\"'=/])centos:(?:centos)?7(?:[.\\d]*)?(?![\\w.-])"
},
{
"re": "quay\\.io/centos/centos:(?:centos)?7\\b"
}
],
"msg": "CentOS Linux 7 image: CentOS Linux 7 reached end of life on June 30, 2024. No security updates, and its yum repos are gone from the mirrors.",
"fix": "FROM almalinux:9 (or almalinux:8 for an EL8 target). AlmaLinux 9 is supported until 2032."
},
{
"id": "centos8-image",
"sev": "error",
"pats": [
{
"re": "(?:^|[\\s\\\"'=/])centos:(?:centos)?8(?:[.\\d]*)?(?![\\w.-])"
},
{
"re": "(?:^|[\\s\\\"'=])centos:latest\\b"
},
{
"re": "^\\s*FROM\\s+centos\\s*$"
}
],
"msg": "CentOS Linux 8 image (centos:latest and a bare FROM centos also resolve to 8): CentOS Linux 8 reached end of life on December 31, 2021.",
"fix": "FROM almalinux:8 keeps the EL8 package set; AlmaLinux 8 is supported until 2029."
},
{
"id": "centos-stream8-image",
"sev": "error",
"pats": [
{
"re": "centos(?:/centos)?:stream8\\b"
}
],
"msg": "CentOS Stream 8 image: CentOS Stream 8 reached end of life on May 31, 2024.",
"fix": "Use almalinux:8 (same EL8 ABI, supported until 2029) or almalinux:9."
},
{
"id": "dead-mirrorlist",
"sev": "error",
"pats": [
{
"re": "mirrorlist\\.centos\\.org"
}
],
"msg": "mirrorlist.centos.org no longer answers, so yum fails with 'Cannot find a valid baseurl' before any package installs.",
"fix": "Point the repo at AlmaLinux: mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/baseos (install almalinux-release instead of editing by hand)."
},
{
"id": "mirror-centos-baseurl",
"sev": "error",
"pats": [
{
"re": "mirror\\.centos\\.org/(?:centos/)?(?:7|8|\\$releasever)"
}
],
"msg": "mirror.centos.org path for CentOS 7/8: after end of life the content was removed from the mirrors and moved to vault.centos.org.",
"fix": "Replace the .repo with AlmaLinux's (repo.almalinux.org/almalinux/$releasever/BaseOS/$basearch/os/)."
},
{
"id": "vault-stopgap",
"sev": "warn",
"pats": [
{
"re": "vault\\.centos\\.org"
}
],
"msg": "vault.centos.org is a frozen archive: the build works again, but nothing in it gets a security fix after the end-of-life date.",
"fix": "Treat the vault repo as a bridge only; move the image or host to AlmaLinux 8 or 9."
},
{
"id": "epel7",
"sev": "error",
"pats": [
{
"re": "epel-release-latest-7"
},
{
"re": "/epel/7/"
},
{
"re": "RPM-GPG-KEY-EPEL-7\\b"
}
],
"msg": "EPEL 7 ended with CentOS 7 and was moved to the Fedora archive; the epel-release-latest-7 URL no longer gives updates.",
"fix": "dnf install epel-release on AlmaLinux (EPEL 8/9), after enabling powertools (EL8) or crb (EL9)."
},
{
"id": "scl-devtoolset",
"sev": "error",
"pats": [
{
"re": "centos-release-scl"
},
{
"re": "\\bdevtoolset-\\d+"
},
{
"re": "\\bscl\\s+enable\\b"
},
{
"re": "\\brh-(?:python|php|ruby|nodejs|postgresql|mysql|git)\\d+"
}
],
"msg": "Software Collections (SCL, devtoolset, rh-*) exist only for EL7; there is no centos-release-scl on EL8 or EL9.",
"fix": "Use gcc-toolset-N for compilers and dnf module streams (dnf module enable php:8.2) for language runtimes."
},
{
"id": "yum-cron",
"sev": "error",
"pats": [
{
"re": "(?\u003c![\\w-])yum-cron(?![\\w-])"
}
],
"msg": "yum-cron was removed in EL8, so the automatic update job silently stops existing.",
"fix": "dnf install dnf-automatic \u0026\u0026 systemctl enable --now dnf-automatic.timer"
},
{
"id": "ntp-removed",
"sev": "error",
"pats": [
{
"re": "(?\u003c![\\w.-])ntp(?:d|date)?(?![\\w.-])",
"near": "(?:yum|dnf|install|systemctl|service|chkconfig|package|name:)",
"within": 0
}
],
"msg": "The ntp package (ntpd, ntpdate) was removed in EL8.",
"fix": "Use chrony: dnf install chrony \u0026\u0026 systemctl enable --now chronyd (chronyc makestep replaces ntpdate)."
},
{
"id": "network-scripts",
"sev": "warn",
"pats": [
{
"re": "(?\u003c![\\w-])network-scripts(?![\\w-])"
},
{
"re": "(?:systemctl\\s+restart\\s+network|service\\s+network\\s+restart)(?![\\w-])"
}
],
"msg": "The legacy network service (network-scripts) is deprecated in EL8 and removed in EL9.",
"fix": "Use NetworkManager: nmcli connection reload \u0026\u0026 nmcli connection up \u003cname\u003e."
},
{
"id": "authconfig",
"sev": "error",
"pats": [
{
"re": "(?\u003c![\\w-])authconfig(?![\\w-])"
}
],
"msg": "authconfig was removed in EL8.",
"fix": "Use authselect (authselect select sssd with-mkhomedir)."
},
{
"id": "docker-el7-pkg",
"sev": "error",
"pats": [
{
"re": "(?:yum|dnf|microdnf)\\b[^#]*\\binstall\\b[^#]*(?\u003c![\\w-])docker(?![\\w-])"
}
],
"msg": "The 'docker' package came from CentOS 7 extras; EL8/9 repos do not ship it.",
"fix": "Use podman (dnf install podman podman-docker) or add the docker-ce repo."
},
{
"id": "python-unversioned-pkg",
"sev": "error",
"pats": [
{
"re": "(?\u003c![\\w-])python-(?:pip|devel|setuptools|virtualenv|requests|six|yaml)(?![\\w-])"
},
{
"re": "(?:yum|dnf|microdnf)\\b[^#]*\\binstall\\b[^#]*(?\u003c![\\w./-])python(?![\\w.-])"
}
],
"msg": "EL7 package name: unversioned python / python-* packages do not exist on EL8/9, and EL9 ships no Python 2.",
"fix": "Install python3, python3-pip, python3-devel (python39/python3.11 streams on EL8)."
},
{
"id": "python-shebang",
"sev": "warn",
"any": true,
"pats": [
{
"re": "^#!\\s*/usr/bin/(?:env\\s+)?python\\s*$"
}
],
"msg": "Unversioned python shebang: EL8 has no /usr/bin/python by default, so the script fails with 'bad interpreter'.",
"fix": "#!/usr/bin/python3"
},
{
"id": "ansible-distribution-centos",
"sev": "error",
"pats": [
{
"re": "ansible_distribution\\s*(?:==|!=)\\s*['\\\"]CentOS['\\\"]"
},
{
"re": "ansible_(?:facts\\[['\\\"]distribution['\\\"]\\]|distribution)\\s+in\\s+\\[[^\\]]*['\\\"]CentOS['\\\"]"
}
],
"msg": "On AlmaLinux ansible_distribution is 'AlmaLinux', so a task gated on 'CentOS' is skipped without an error.",
"fix": "when: ansible_os_family == 'RedHat' (and ansible_distribution_major_version for 8/9)."
},
{
"id": "centos-release-file",
"sev": "warn",
"pats": [
{
"re": "/etc/centos-release"
},
{
"re": "ID=\\\"?centos\\\"?"
},
{
"re": "grep\\s+(?:-\\w+\\s+)*['\\\"]?CentOS['\\\"]?\\s+/etc/(?:redhat|system)-release"
}
],
"msg": "OS detection that looks for CentOS: AlmaLinux has no /etc/centos-release and /etc/os-release says ID=\"almalinux\", so this branch never runs.",
"fix": "Read /etc/os-release and test ID_LIKE for \"rhel\" plus VERSION_ID."
},
{
"id": "centos-gpg-key",
"sev": "error",
"pats": [
{
"re": "RPM-GPG-KEY-CentOS-(?:7|8|Official)\\b"
}
],
"msg": "CentOS signing key: AlmaLinux packages are signed with a different key, so gpgcheck fails on every AlmaLinux package.",
"fix": "gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9 (or -8)."
},
{
"id": "el7-rpm",
"sev": "warn",
"pats": [
{
"re": "\\.el7(?:_\\d+)?(?:\\.centos)?\\.(?:x86_64|noarch|aarch64|src)\\.rpm"
},
{
"re": "/el7/|/rhel7/|/centos/7/"
}
],
"msg": "Hard-coded EL7 RPM or repo path: built against EL7 libraries (glibc 2.17, OpenSSL 1.0.2), it will not install cleanly on EL8/9.",
"fix": "Point at the el8/el9 build of the same package, or use $releasever in the URL."
},
{
"id": "vagrant-centos-box",
"sev": "error",
"pats": [
{
"re": "vm\\.box\\s*=\\s*['\\\"](?:centos/[78]|generic/centos[78]|bento/centos-[78][\\d.]*)['\\\"]"
}
],
"msg": "Vagrant box for CentOS 7/8: the base OS is past end of life.",
"fix": "config.vm.box = \"almalinux/9\" (or \"almalinux/8\")."
},
{
"id": "rhel-licence-gate",
"sev": "info",
"pats": [
{
"re": "(?\u003c![\\w-])convert2rhel(?![\\w-])"
},
{
"re": "subscription-manager\\s+(?:register|attach)"
}
],
"msg": "This path converts to Red Hat Enterprise Linux, which needs a paid subscription per server (RHEL Server Standard is US$878.90 per year on the Red Hat Store). AlmaLinux is free to run.",
"fix": "If the subscription is not wanted, use almalinux-deploy (EL8 in place) or ELevate (CentOS 7 to AlmaLinux 8)."
}
];</script>
<script>/* AlmaLinux migration lint: one engine for VS Code and the free web page. */
(function () {
var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.AL_RULES;
var COMMENT = /^\s*(#|\/\/|;)/;
var COMPILED = RULES.map(function (r) {
return {
rule: r,
pats: (r.pats || []).map(function (p) {
return { re: new RegExp(p.re), near: p.near ? new RegExp(p.near) : null, within: p.within || 0 };
})
};
});
// A Dockerfile RUN or shell line may continue with "\": join it so an install list reads as one line.
function logical(lines) {
var out = [], buf = '', start = 0;
for (var i = 0; i < lines.length; i++) {
if (buf === '') start = i;
var l = lines[i];
if (/\\\s*$/.test(l) && !COMMENT.test(l)) { buf += l.replace(/\\\s*$/, ' '); continue; }
out.push({ text: buf + l, line: start + 1, raw: lines.slice(start, i + 1) });
buf = '';
}
if (buf) out.push({ text: buf, line: start + 1, raw: lines.slice(start) });
return out;
}
function lineOf(entry, re) {
for (var j = 0; j < entry.raw.length; j++) if (re.test(entry.raw[j])) return entry.line + j;
return entry.line;
}
function check(text, opts) {
var entries = logical(String(text || '').split(/\r?\n/));
var findings = [];
for (var i = 0; i < entries.length; i++) {
var e = entries[i], isComment = COMMENT.test(e.text);
for (var k = 0; k < COMPILED.length; k++) {
var r = COMPILED[k].rule;
if (isComment && !r.any) continue;
if (r.any && i > 0) continue;
for (var p = 0; p < COMPILED[k].pats.length; p++) {
var pat = COMPILED[k].pats[p];
if (!pat.re.test(e.text)) continue;
if (pat.near && !pat.near.test(e.text)) {
var ok = false;
for (var b = i - 1; b >= 0 && b >= i - pat.within; b--) if (pat.near.test(entries[b].text)) { ok = true; break; }
if (!ok) continue;
}
findings.push({ check: r.id, sev: r.sev, msg: r.msg + ' Fix: ' + r.fix, line: lineOf(e, pat.re) });
break;
}
}
}
return { findings: findings };
}
var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
if (typeof module !== 'undefined') module.exports = api;
if (typeof window !== 'undefined') window.ALENGINE = api;
})();
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
const f = (res && res.findings) || [];
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
return lines.join('\n');
}
function toHtml(res) {
const f = (res && res.findings) || [];
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var ALREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = ALREPORT;
else window.ALREPORT = ALREPORT;
</script>
<script>
(function(){
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
today.value=new Date().toISOString().slice(0,10);
function run(){ try { var res=window.ALENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.ALREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
go.onclick=run; if (src.value.trim()) run();
})();
</script>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.almalinux-migration-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'almalinux-migration-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/almalinux-migration-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'almalinux-migration-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.almalinux-migration-lint</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind AlmaLinux Migration Lint — CentOS 7/8 every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"almalinux-migration-lint",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);document.addEventListener("click",function(ev){var b=ev.target&&ev.target.closest?ev.target.closest("button,input[type=submit]"):null;if(b&&!b.closest("[data-yj],[data-yj-cta],[data-yj-install],[data-yj-alert],.yj-find,.yj-related,#yj-ask,.yjp"))mv(ev);},true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the full version <span class="yj-price">$29</span></h2><p class="yj-cta-paid" style="margin:.25rem 0 .6rem;color:#1f2a37">Scan the whole workspace in one pass and export one migration report for the change ticket (every file, line, rule and fix in one Markdown report), $29 once, one licence key per person or team seat.</p><p class="yj-cta-pay" style="margin:.25rem 0 1rem;color:#5b6b7c;font-size:.94rem">One payment, one licence key for this tool. The key is shown right after payment.</p><p class="yj-cta-anchor" style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A RHEL Server Standard subscription is US$878.90 per server per year on the Red Hat Store (SKU RH00004); AlmaLinux is free to run.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_E7s1yXN8TTsXTg4qiaoSemryY96mVKUKEndde3SnB0z" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div><section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-29">Updated 2026-09-29</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-29","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku389576_result_card.jpg" alt="AlmaLinux Migration Lint — CentOS 7/8" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Six CentOS 7 leftovers on one card and 15 findings in all: that is what an AlmaLinux migration check reports on a 26-line CentOS 7 bootstrap script, for the sysadmins, DevOps and platform engineers who still build images, Vagrant boxes and Ansible roles on CentOS.</p>
<h3>Migrate from CentOS Linux 7 to AlmaLinux: what ended and when</h3>
<p>CentOS Linux 8 reached end of life on December 31, 2021. CentOS Stream 8 followed on May 31, 2024, and CentOS Linux 7 on June 30, 2024. The CentOS project said that at end of life the content would be removed from the mirrors and moved to vault.centos.org, where it is archived with no further updates. Today mirrorlist.centos.org no longer answers, so any script that still points yum at it stops at the first install with &quot;Cannot find a valid baseurl&quot;.</p>
<p>AlmaLinux 8 is supported until 2029 and AlmaLinux 9 until 2032, according to the AlmaLinux FAQ. The target is clear. The hard part is finding every line that assumes CentOS 7.</p>
<h3>The sample file</h3>
<p>The sample is a bootstrap.sh that provisions a build agent. It pulls centos:7 and a centos:stream8 image, writes a .repo file with mirrorlist.centos.org and the CentOS 7 GPG key, installs EPEL 7 from epel-release-latest-7, adds Software Collections, installs devtoolset-11, yum-cron, ntp, python-pip and python-devel, enables yum-cron, calls scl enable, runs authconfig, restarts the legacy network service, and finally greps /etc/redhat-release for the word CentOS.</p>
<p>The linter reports 15 findings from 12 of its 21 rules: 13 errors and 2 warnings. Six of them are on the result card:</p>
<ul><li>docker pull centos:7 becomes docker pull almalinux:9.</li><li>The mirrorlist.centos.org repo becomes the AlmaLinux mirrorlist, mirrors.almalinux.org.</li><li>epel-release-latest-7 becomes dnf install epel-release (EPEL 8/9).</li><li>yum-cron, removed in EL8, becomes dnf-automatic.timer.</li><li>scl enable devtoolset-11 becomes gcc-toolset-N; there is no centos-release-scl on EL8 or EL9.</li><li>authconfig --enablesssd becomes authselect select sssd.</li></ul>
<p>The other findings: the Stream 8 image, the CentOS GPG key (gpgcheck fails on AlmaLinux packages), ntp (use chrony), python-pip and python-devel (use python3-*), the network restart (use nmcli), and the grep for CentOS, which never matches on AlmaLinux.</p>
<p>The clean version of the same script, rewritten for AlmaLinux 9, gives 0 findings.</p>
<h3>Why grep and chatbots miss it</h3>
<p>A grep for centos finds the image and the repo, then stops. yum-cron, ntp, authconfig, devtoolset and python-pip never contain the word. Ask a chatbot to port a Dockerfile and it usually swaps the FROM line and keeps the EL7 package list, which then fails on the first dnf install. An Ansible task gated on ansible_distribution == &quot;CentOS&quot; is worse: on AlmaLinux the fact reads &quot;AlmaLinux&quot;, so the task is skipped without an error.</p>
<h3>The paid route you may not need</h3>
<p>The alternative is to convert to Red Hat Enterprise Linux. A RHEL Server Standard subscription is US$878.90 per server per year on the Red Hat Store (SKU RH00004). The linter flags convert2rhel and subscription-manager register lines as an info finding, so the team can decide on purpose rather than by default.</p>
<h3>How to run it</h3>
<p>Open a file in VS Code and run the check command, or paste the file into the free web page. Every finding in the open file is free and offline. For hosts upgraded in place, AlmaLinux&#x27;s ELevate project moves one major version per step.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku389576_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku389576_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does AlmaLinux Migration Lint check?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads Dockerfiles, Vagrantfiles, yum .repo files, kickstarts, shell scripts and Ansible YAML and flags CentOS Linux 7 and 8 leftovers that break on AlmaLinux 8 or 9: EOL centos images, mirrorlist.centos.org repos, CentOS GPG keys, EPEL 7, SCL and devtoolset, yum-cron, ntp, network-scripts, authconfig and CentOS-only OS checks. It has 21 rules, and each finding names the AlmaLinux fix.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Sysadmins, DevOps and platform engineers who still have CentOS 7 or CentOS 8 in build images, Vagrant boxes, bootstrap scripts or Ansible roles and must move them to AlmaLinux. CentOS Linux 7 reached end of life on June 30, 2024, and mirrorlist.centos.org no longer answers, so these files fail at the first yum call.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask a chatbot or run grep for centos?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A grep for centos hits every comment and misses the lines that break without the word: yum-cron, ntp, authconfig, scl enable devtoolset-11, python-pip and ansible_distribution checks. A chatbot usually rewrites the FROM line and leaves the removed EL7 packages. This linter checks every line against 21 rules and gives the AlmaLinux replacement for each one.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Every finding in the open file is free, in the browser page and in VS Code, offline and without a key. The full version scans the whole workspace in one pass and exports one migration report for the change ticket. It costs $29 once, with one licence key per person or team seat. There is no subscription.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does the paid alternative cost?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Converting CentOS to Red Hat Enterprise Linux instead of AlmaLinux needs a subscription per server: RHEL Server Standard is US$878.90 per server per year on the Red Hat Store (SKU RH00004). AlmaLinux 8 is supported until 2029 and AlmaLinux 9 until 2032 at no charge, so most teams only need the file changes this linter lists.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does AlmaLinux Migration Lint check?", "acceptedAnswer": {"@type": "Answer", "text": "It reads Dockerfiles, Vagrantfiles, yum .repo files, kickstarts, shell scripts and Ansible YAML and flags CentOS Linux 7 and 8 leftovers that break on AlmaLinux 8 or 9: EOL centos images, mirrorlist.centos.org repos, CentOS GPG keys, EPEL 7, SCL and devtoolset, yum-cron, ntp, network-scripts, authconfig and CentOS-only OS checks. It has 21 rules, and each finding names the AlmaLinux fix."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "Sysadmins, DevOps and platform engineers who still have CentOS 7 or CentOS 8 in build images, Vagrant boxes, bootstrap scripts or Ansible roles and must move them to AlmaLinux. CentOS Linux 7 reached end of life on June 30, 2024, and mirrorlist.centos.org no longer answers, so these files fail at the first yum call."}}, {"@type": "Question", "name": "Why not just ask a chatbot or run grep for centos?", "acceptedAnswer": {"@type": "Answer", "text": "A grep for centos hits every comment and misses the lines that break without the word: yum-cron, ntp, authconfig, scl enable devtoolset-11, python-pip and ansible_distribution checks. A chatbot usually rewrites the FROM line and leaves the removed EL7 packages. This linter checks every line against 21 rules and gives the AlmaLinux replacement for each one."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Every finding in the open file is free, in the browser page and in VS Code, offline and without a key. The full version scans the whole workspace in one pass and exports one migration report for the change ticket. It costs $29 once, with one licence key per person or team seat. There is no subscription."}}, {"@type": "Question", "name": "What does the paid alternative cost?", "acceptedAnswer": {"@type": "Answer", "text": "Converting CentOS to Red Hat Enterprise Linux instead of AlmaLinux needs a subscription per server: RHEL Server Standard is US$878.90 per server per year on the Red Hat Store (SKU RH00004). AlmaLinux 8 is supported until 2029 and AlmaLinux 9 until 2032 at no charge, so most teams only need the file changes this linter lists."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:389576,slug:"almalinux-migration-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/robots-txt-ai-crawler-audit"><span class="yj-rel-t">AI Crawler Rules - robots.txt Audit for AI Search</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ai-model-license-lint"><span class="yj-rel-t">AI Model License Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/brave-search-api-migration-check"><span class="yj-rel-t">Brave Search API Migration Check — Google Custom Search &amp; Bing</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/bulk-sender-lint"><span class="yj-rel-t">Bulk Sender Lint - the Gmail, Yahoo and Outlook.com rules, checked in your mai</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cinc-migration-check"><span class="yj-rel-t">Cinc Migration Check: Chef Infra licence gate lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/crashlytics-migration-lint"><span class="yj-rel-t">Crashlytics Migration Lint for App Center</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body>
</html>