getreadystack's picture
Upload index.html with huggingface_hub
a3b3c70 verified
Raw History Blame Contribute Delete
45.2 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sentry Migration 2026 — check your code for free</title>
<meta name="description" content="This free browser tool finds Sentry settings that GlitchTip ingest drops, DSNs still on sentry.io, and FSL self-hosted Sentry images.">
<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 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/glitchtip-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>Sentry Migration 2026 — check your code for free</h1>
<p class="lede">Finds Sentry SDK settings GlitchTip ingest drops (Session Replay, profiling, cron check-ins, sessions, attachments, metrics), DSNs still on sentry.io, and FSL self-hosted Sentry images. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">// sentry.client.config.ts - sample Next.js app moving from Sentry SaaS to GlitchTip
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://3f2a9c1e7b@o451234.ingest.us.sentry.io/4507712345",
tracesSampleRate: 1.0,
autoSessionTracking: true,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
profilesSampleRate: 1.0,
integrations: [
Sentry.replayIntegration(),
Sentry.browserProfilingIntegration(),
Sentry.feedbackIntegration({ colorScheme: "system" }),
],
});
export async function nightlyInvoiceJob(run: () =&gt; Promise&lt;void&gt;) {
await Sentry.withMonitor("nightly-invoices", run, {
schedule: { type: "crontab", value: "0 2 * * *" },
});
}
export function reportUpload(file: Uint8Array) {
Sentry.getCurrentScope().addAttachment({ filename: "upload.bin", data: file });
Sentry.metrics.count("upload.failed", 1);
}</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> Paste or open one Sentry init file: every setting GlitchTip drops or still routes to sentry.io is listed with its line, the envelope item it produces, and the GlitchTip-side fix. <strong>In the editor:</strong> Workspace migration report: every Sentry setting across all apps, CI files and compose files in the repo, exported as a Markdown or CSV checklist for the cutover pull request. — that part asks for a licence key. $29 once · one licence key per person or team seat. Sentry Team is $26/mo billed annually. <a href="https://getreadystack.com/api/buy/cl/polar_cl_298CgJtnZYoccavOy9Fai6GwNUqJK4Mdl6DuM1ZTicE" data-polar-checkout data-polar-checkout-theme="light">Get the workspace migration report — $29</a></div>
<script defer data-auto-init src="https://cdn.jsdelivr.net/npm/@polar-sh/checkout@latest/dist/embed.global.js"></script>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var GT_RULES = [
{
"id": "GT_DSN_SENTRY_IO",
"sev": "error",
"title": "DSN still points at sentry.io",
"item": "",
"pattern": "ingest\\.(?:[a-z]{2}\\.)?sentry\\.io|@sentry\\.io/\\d",
"msg": "This DSN sends every event to Sentry SaaS, not GlitchTip. After cutover the Sentry bill keeps running and GlitchTip shows nothing.",
"fix": "Replace it with the DSN from your GlitchTip project settings page (https://\u003ckey\u003e@\u003cyour-glitchtip-host\u003e/\u003cproject-id\u003e)."
},
{
"id": "GT_CLI_URL_SENTRY",
"sev": "error",
"title": "Upload URL still points at sentry.io",
"item": "",
"pattern": "^\\s*(?:url\\s*=|SENTRY_URL\\s*[=:]|defaults\\.url\\s*=)\\s*['\\\"]?https://(?:[a-z0-9-]+\\.)?sentry\\.io",
"msg": "Source maps and debug files from this config still go to sentry.io, so GlitchTip stack traces stay minified.",
"fix": "Set url to your GlitchTip host. glitchtip-cli reads .sentryclirc and SENTRY_URL, so the same file keeps working: glitchtip-cli sourcemaps upload ./dist --org \u003corg\u003e --project \u003cproject\u003e."
},
{
"id": "GT_REPLAY_INTEGRATION",
"sev": "error",
"title": "Session Replay integration",
"item": "replay_event / replay_recording",
"pattern": "replayIntegration\\s*\\(|replayCanvasIntegration\\s*\\(|new\\s+(?:Sentry\\.)?Replay\\s*\\(|['\\\"]@sentry/replay['\\\"]",
"msg": "Session Replay sends replay_event and replay_recording items. GlitchTip ingest lists both as ignored item types: the replays are dropped and the SDK still records them in the browser.",
"fix": "Remove the replay integration before switching the DSN. GlitchTip has no replay viewer."
},
{
"id": "GT_REPLAY_RATES",
"sev": "error",
"title": "Replay sample rate",
"item": "replay_event / replay_recording",
"pattern": "replaysSessionSampleRate|replaysOnErrorSampleRate|sessionReplay\\.(?:sessionSampleRate|onErrorSampleRate)|sessionReplay\\s*:\\s*\\{",
"msg": "Replay sample rates only feed Session Replay. GlitchTip ingest ignores replay_event and replay_recording, so this rate buys CPU and upload bytes for nothing.",
"fix": "Delete the replay sample rates (or set them to 0) together with the replay integration."
},
{
"id": "GT_PROFILING_RATE",
"sev": "error",
"title": "Profiling sample rate",
"item": "profile / profile_chunk",
"pattern": "profilesSampleRate|profiles_sample_rate|profileSessionSampleRate|profile_session_sample_rate|profilesSampler|profiles_sampler|profileLifecycle|profile_lifecycle",
"msg": "Profiling sends profile or profile_chunk items. GlitchTip ingest ignores both, so no profile ever appears.",
"fix": "Remove the profiling rate. For slow endpoints use GlitchTip Performance, which reads transactions and their spans."
},
{
"id": "GT_PROFILING_INTEGRATION",
"sev": "error",
"title": "Profiling integration or package",
"item": "profile / profile_chunk",
"pattern": "browserProfilingIntegration\\s*\\(|nodeProfilingIntegration\\s*\\(|@sentry/profiling-node|ProfilingIntegration\\s*\\(",
"msg": "The profiling integration samples the call stack and uploads profile items that GlitchTip ingest ignores.",
"fix": "Remove the integration and the @sentry/profiling-node dependency; keep tracesSampleRate for GlitchTip Performance."
},
{
"id": "GT_CRON_CHECKIN",
"sev": "error",
"title": "Cron monitor check-in",
"item": "check_in",
"pattern": "captureCheckIn\\s*\\(|withMonitor\\s*\\(|capture_checkin\\s*\\(|@monitor\\s*\\(|sentry_sdk\\.crons|monitor_config\\s*=|CaptureCheckIn\\s*\\(|instrumentCron\\s*\\(|instrumentNodeCron\\s*\\(|instrumentNodeSchedule\\s*\\(",
"msg": "Cron monitors send check_in items. GlitchTip ingest ignores check_in, so a scheduled job that stops running raises no alert.",
"fix": "Create a GlitchTip uptime monitor of type Heartbeat and ping its URL when the job succeeds, or wrap the job: glitchtip-cli monitors run \u003cENDPOINT_UUID\u003e -- \u003ccommand\u003e."
},
{
"id": "GT_SESSIONS",
"sev": "warn",
"title": "Release-health sessions on",
"item": "session / sessions",
"pattern": "autoSessionTracking\\s*:\\s*true|auto_session_tracking\\s*=\\s*True|enableAutoSessionTracking\\s*[:=]\\s*(?:true|YES)|browserSessionIntegration\\s*\\(",
"msg": "Session tracking sends session items. GlitchTip's own SDK pages set this option to false with the note 'GlitchTip does not support sessions'.",
"fix": "Set autoSessionTracking: false (JS) or auto_session_tracking=False (Python), as in GlitchTip's SDK docs."
},
{
"id": "GT_SESSION_API",
"sev": "warn",
"title": "Manual session call",
"item": "session",
"pattern": "\\.startSession\\s*\\(|\\.captureSession\\s*\\(|\\.endSession\\s*\\(|start_session\\s*\\(|end_session\\s*\\(",
"msg": "Manual session calls produce session items that GlitchTip ingest ignores; crash-free rates will not exist after the move.",
"fix": "Remove the call, or count sessions in your own analytics."
},
{
"id": "GT_ATTACHMENT",
"sev": "warn",
"title": "Event attachment",
"item": "attachment",
"pattern": "addAttachment\\s*\\(|add_attachment\\s*\\(|AddAttachment\\s*\\(|attachScreenshot\\s*[:=]\\s*(?:true|YES)|attachViewHierarchy\\s*[:=]\\s*(?:true|YES)|attach_screenshot\\s*=\\s*True",
"msg": "Attachments travel as attachment items. GlitchTip ingest ignores them (it only parses a native minidump when no event came with it), so the file never reaches the issue.",
"fix": "Put the small facts you need into setContext / set_context or tags; keep large files in your own storage and add their URL as context."
},
{
"id": "GT_METRICS",
"sev": "warn",
"title": "Sentry metrics API",
"item": "trace_metric",
"pattern": "Sentry\\.metrics\\.\\w+\\s*\\(|sentry_sdk\\.metrics\\.|SentrySdk\\.Metrics\\.",
"msg": "The metrics API sends trace_metric items. GlitchTip ingest ignores trace_metric, so the counter stays at zero.",
"fix": "Send the value as a log line (enableLogs: true and Sentry.logger), which GlitchTip ingests, or move the metric to your metrics stack."
},
{
"id": "GT_STANDALONE_SPANS",
"sev": "warn",
"title": "Standalone span streaming",
"item": "span",
"pattern": "enableStandalone\\w*Spans|traceLifecycle\\s*:\\s*['\\\"]stream|trace_lifecycle['\\\"]?\\s*[:=]\\s*['\\\"]stream",
"msg": "Standalone or streamed spans are sent as span items. GlitchTip ingest ignores span items; it reads spans only inside a transaction.",
"fix": "Turn the experiment off so spans ship inside their transaction."
},
{
"id": "GT_TRACES_FULL",
"sev": "warn",
"title": "Every transaction sampled",
"item": "transaction",
"pattern": "tracesSampleRate\\s*:\\s*1(?:\\.0+)?\\b|traces_sample_rate\\s*=\\s*1(?:\\.0+)?\\b|enableTracing\\s*:\\s*true|enable_tracing\\s*=\\s*True",
"msg": "A 1.0 rate sends every transaction, and hosted GlitchTip counts each one as an event: Free is 1,000 events/mo, Small $15/mo is 100k. Past the quota GlitchTip throttles, and at 2x the quota it blocks fully, errors included.",
"fix": "Use a low rate as GlitchTip's FAQ advises: tracesSampleRate: 0.01 (1 percent)."
},
{
"id": "GT_SENTRY_CLI_DEFAULT",
"sev": "info",
"title": "sentry-cli upload step",
"item": "",
"pattern": "getsentry/action-release|sentry-cli\\s+(?:sourcemaps|releases|debug-files)|npx\\s+@sentry/cli",
"msg": "sentry-cli uploads to https://sentry.io/ unless a url is set, so this CI step may still feed the old project.",
"fix": "Set SENTRY_URL to your GlitchTip host for this step, or call glitchtip-cli sourcemaps inject / upload instead."
},
{
"id": "GT_SELFHOSTED_SENTRY",
"sev": "info",
"title": "Self-hosted Sentry image (FSL)",
"item": "",
"pattern": "image:\\s*['\\\"]?(?:ghcr\\.io/)?getsentry/(?:sentry|snuba|relay|symbolicator|vroom)\\b",
"msg": "Self-hosted Sentry's web app is licensed FSL-1.1-Apache-2.0 (open.sentry.io/licensing): free to run for your business, but not as a commercial Sentry-like offering until each release turns Apache-2.0 after two years. GlitchTip is MIT.",
"fix": "If you resell error tracking or host it for clients, run glitchtip/glitchtip (PostgreSQL 14+, one service, Valkey optional) instead."
},
{
"id": "GT_CLIENT_REPORTS",
"sev": "info",
"title": "Client reports on",
"item": "client_report",
"pattern": "sendClientReports\\s*:\\s*true|send_client_reports\\s*=\\s*True",
"msg": "Client reports (counts of events the SDK discarded) are sent as client_report items, which GlitchTip ingest ignores.",
"fix": "Set sendClientReports: false to save the request."
}
];</script>
<script>/* Sentry -> GlitchTip migration lint. Same file runs in VS Code (node) and in the free web page. */
(function () {
var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.GT_RULES;
// Item types GlitchTip ingest accepts vs. ignores: glitchtip-backend apps/event_ingest/schema.py (SupportedItemType / IgnoredItemType)
var INGESTED = ['event', 'transaction', 'user_report', 'feedback', 'log', 'otel_log'];
var COMPILED = RULES.map(function (r) { return { r: r, re: new RegExp(r.pattern) }; });
function isComment(line) { return /^\s*(\/\/|#(?!\s*!)|\*|\/\*|<!--|;)/.test(line); }
function check(text, opts) {
opts = opts || {};
var lines = String(text || '').split(/\r?\n/);
var findings = [];
var dropped = {};
lines.forEach(function (line, i) {
if (isComment(line)) return;
COMPILED.forEach(function (c) {
if (!c.re.test(line)) return;
if (c.r.item && c.r.item !== 'transaction') dropped[c.r.item] = 1;
var what = c.r.item ? ' [envelope item: ' + c.r.item + ']' : '';
findings.push({ check: c.r.id, sev: c.r.sev, msg: c.r.msg + what + ' Fix: ' + c.r.fix, line: i + 1, title: c.r.title, fix: c.r.fix, item: c.r.item });
});
});
var errors = findings.filter(function (f) { return f.sev === 'error'; }).length;
return { findings: findings, summary: { total: findings.length, errors: errors, dropped_item_types: Object.keys(dropped), ingested: INGESTED } };
}
var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
if (typeof module !== 'undefined') module.exports = api;
if (typeof window !== 'undefined') window.GTENGINE = 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 GTREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = GTREPORT;
else window.GTREPORT = GTREPORT;
</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.GTENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.GTREPORT.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://open-vsx.org/extension/readystack/glitchtip-migration-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'glitchtip-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><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 GlitchTip Migration Lint for Sentry 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:"glitchtip-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">Workspace migration report ($29 once, one licence key per person or team seat): every Sentry setting across all apps, CI files and compose files in the repo, exported as a Markdown or CSV checklist for the cutover pull request.</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">Sentry Team is $26/mo billed annually; GlitchTip hosted Small is $15/mo for 100k events</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_298CgJtnZYoccavOy9Fai6GwNUqJK4Mdl6DuM1ZTicE" 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/sku395439_result_card.jpg" alt="GlitchTip Migration Lint for Sentry" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Six Sentry features stopped arriving when our sample Next.js app pointed its DSN at GlitchTip: one lint pass over sentry.client.config.ts returned 11 findings, 7 errors. If you are an engineering team or agency moving from Sentry to GlitchTip, this is the part the migration guides skip.</p>
<p>Why teams move. Sentry&#x27;s web app is licensed FSL-1.1-Apache-2.0 (open.sentry.io/licensing): you may run it for your own business, but not as a commercial Sentry-like offering until each release converts to Apache-2.0 after two years. Sentry Team costs $26/mo billed annually and Business $80/mo. GlitchTip is MIT-licensed, speaks the Sentry SDK protocol, and hosted plans start free at 1,000 events/mo, with Small at $15/mo for 100k events.</p>
<p>What breaks without a sound. Because GlitchTip accepts the same SDKs, the swap looks like a one-line DSN change. It is not. GlitchTip&#x27;s ingest code (apps/event_ingest/schema.py in glitchtip-backend) accepts six envelope item types: event, transaction, user_report, feedback, log and otel_log. It lists session, attachment, check_in, profile, replay_event, replay_recording, span and trace_metric as ignored. The SDK keeps sending them, and the data is simply not stored.</p>
<p>In our sample file that meant:</p>
<ul><li>replayIntegration() plus two replay sample rates: Session Replay still records in every browser, then gets dropped.</li><li>profilesSampleRate: 1.0 and browserProfilingIntegration(): no profile ever appears.</li><li>Sentry.withMonitor(&quot;nightly-invoices&quot;): the cron check-in is dropped, so a nightly job that stops running raises no alert. This is the expensive one.</li><li>autoSessionTracking: true: GlitchTip&#x27;s own SDK pages set it to false with the note &quot;GlitchTip does not support sessions&quot;.</li><li>addAttachment(upload.bin): the file never reaches the issue.</li><li>Sentry.metrics.count(&quot;upload.failed&quot;): the counter stays at zero.</li></ul>
<p>Two more findings are not about dropped data. The DSN still pointed at o451234.ingest.us.sentry.io, so events would keep flowing to Sentry. And tracesSampleRate: 1.0 sends every transaction; hosted GlitchTip counts each one as an event, throttles past the quota and blocks fully at 2x the quota, errors included. GlitchTip&#x27;s FAQ suggests 0.01.</p>
<p>The fixes. Remove the replay and profiling integrations and rates. Replace cron monitors with a GlitchTip uptime monitor of type Heartbeat, or wrap the job with glitchtip-cli monitors run. Set autoSessionTracking to false. Move attachment facts into setContext. Send metric values as logs with enableLogs: true, which GlitchTip ingests. The fixed file returns 0 findings.</p>
<p>How the lint works. GlitchTip Migration Lint is a VS Code extension with 16 rules. Open a file and every finding appears in the Problems panel with its line, the envelope item it produces and the GlitchTip-side fix. The same engine runs on a free web page where you paste a file. It reads JS/TS, Python, Dart, Swift, Kotlin, Java, C#, YAML, .env, .properties and .sentryclirc, and it also flags sentry-cli steps with no url (they upload to https://sentry.io/ by default) and self-hosted getsentry images.</p>
<p>Why not a chatbot. A chatbot will happily rewrite your Sentry.init, but it does not know GlitchTip&#x27;s ignore list, and it only sees the snippet you paste, not the CI step or compose file next to it.</p>
<p>Free and paid. Linting the open file is free, with no key, and finishes the job for a single-app repo. The workspace migration report covers every app, CI file and compose file and exports a Markdown or CSV checklist for the cutover pull request.</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/sku395439_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku395439_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 GlitchTip Migration Lint do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It scans Sentry SDK setup code, .sentryclirc, CI and docker-compose files for settings GlitchTip will not ingest: Session Replay, profiling, cron check-ins, sessions, attachments and metrics. It also flags DSNs and upload URLs still pointing at sentry.io and self-hosted Sentry images under the FSL licence. It has 16 rules, and each finding names the line, the envelope item and the GlitchTip-side 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 this Sentry to GlitchTip lint for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Engineering teams, agencies and self-hosters moving from Sentry SaaS or self-hosted Sentry to GlitchTip, in the US, the EU or anywhere else. It suits the person who owns the cutover pull request: a Next.js, Node, Django or mobile app whose Sentry.init was written for Sentry features and is about to point its DSN at a GlitchTip host.</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 change the DSN or ask a chatbot?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Changing the DSN works for errors, and GlitchTip returns success for everything else too. Its ingest code ignores session, attachment, check_in, profile, replay and trace_metric items, so Session Replay, profiles, cron check-ins and metrics vanish without an error. A chatbot does not know that list and only sees the snippet you paste.</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">Linting one open or pasted file is free, with no key and no limit: every finding shows its line, the dropped envelope item and the fix, from all 16 rules. The paid workspace report ($29 once, one licence key per person or team seat) scans every app, CI file and compose file in the repo and exports a Markdown or CSV checklist.</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">How does the cost compare with staying on Sentry?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Sentry Team is $26/mo billed annually and Business is $80/mo. Hosted GlitchTip is free up to 1,000 events/mo, Small is $15/mo for 100k events and Medium $50/mo for 500k. The lint itself is free per file; the $29 workspace report is a one-time payment, not a subscription, for the whole repo checklist.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does GlitchTip Migration Lint do?", "acceptedAnswer": {"@type": "Answer", "text": "It scans Sentry SDK setup code, .sentryclirc, CI and docker-compose files for settings GlitchTip will not ingest: Session Replay, profiling, cron check-ins, sessions, attachments and metrics. It also flags DSNs and upload URLs still pointing at sentry.io and self-hosted Sentry images under the FSL licence. It has 16 rules, and each finding names the line, the envelope item and the GlitchTip-side fix."}}, {"@type": "Question", "name": "Who is this Sentry to GlitchTip lint for?", "acceptedAnswer": {"@type": "Answer", "text": "Engineering teams, agencies and self-hosters moving from Sentry SaaS or self-hosted Sentry to GlitchTip, in the US, the EU or anywhere else. It suits the person who owns the cutover pull request: a Next.js, Node, Django or mobile app whose Sentry.init was written for Sentry features and is about to point its DSN at a GlitchTip host."}}, {"@type": "Question", "name": "Why not just change the DSN or ask a chatbot?", "acceptedAnswer": {"@type": "Answer", "text": "Changing the DSN works for errors, and GlitchTip returns success for everything else too. Its ingest code ignores session, attachment, check_in, profile, replay and trace_metric items, so Session Replay, profiles, cron check-ins and metrics vanish without an error. A chatbot does not know that list and only sees the snippet you paste."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Linting one open or pasted file is free, with no key and no limit: every finding shows its line, the dropped envelope item and the fix, from all 16 rules. The paid workspace report ($29 once, one licence key per person or team seat) scans every app, CI file and compose file in the repo and exports a Markdown or CSV checklist."}}, {"@type": "Question", "name": "How does the cost compare with staying on Sentry?", "acceptedAnswer": {"@type": "Answer", "text": "Sentry Team is $26/mo billed annually and Business is $80/mo. Hosted GlitchTip is free up to 1,000 events/mo, Small is $15/mo for 100k events and Medium $50/mo for 500k. The lint itself is free per file; the $29 workspace report is a one-time payment, not a subscription, for the whole repo checklist."}}]}</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:395439,slug:"glitchtip-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/personal-data-map-dsar-audit"><span class="yj-rel-t">Personal Data Map: find every column a GDPR subject access request has to reac</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/portfolio-website-template"><span class="yj-rel-t">Portfolio Website Template — Freelance Designer Portfolio &amp; Case Study Site (8</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/real-estate-webflow-template"><span class="yj-rel-t">Real Estate Webflow Template Alternative — 7-Page Self-Hosted HTML Broker Site</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/webflow-restaurant-template"><span class="yj-rel-t">Webflow Restaurant Template — delivered as self-contained HTML you own: 26 pag</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/webflow-saas-template"><span class="yj-rel-t">Webflow SaaS Template — a 16-Page SaaS Marketing Site with the Copy Already Wr</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/almalinux-migration-lint"><span class="yj-rel-t">AlmaLinux Migration Lint — CentOS 7/8</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>