Skip to content

Commit 2858516

Browse files
dgibbs64Copilot
andcommitted
feat(labeler): implement Linux support verification for server requests
* Added checks for Linux support based on issue content and Steam API data. * Integrated AI analysis for documentation to assess Linux compatibility. * Automatically create or remove labels based on support verification results. * Enhanced feedback for users regarding Linux server support status. Co-authored-by: Copilot <copilot@github.com>
1 parent c76326c commit 2858516

1 file changed

Lines changed: 239 additions & 0 deletions

File tree

.github/workflows/labeler.yml

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,245 @@ jobs:
754754
}
755755
}
756756
757+
// === Linux support verification for server request issues ===
758+
// Runs only on opened/edited events to avoid reprocessing every label change.
759+
const isServerRequest =
760+
desiredType === 'type: game server request' ||
761+
existingLabels.has('type: game server request') ||
762+
/\[server request\]/i.test(title);
763+
764+
if (isServerRequest && shouldRunAi) {
765+
const officialDocsSection = extractSection('Official dedicated server documentation');
766+
const linuxBinaryProofSection = extractSection('Linux binary proof');
767+
const guidesSection = extractSection('Guides');
768+
const steamSection = extractSection('Steam').trim();
769+
const isSteamNo = /^no$/i.test(steamSection);
770+
const isSteamYes = /^yes$/i.test(steamSection);
771+
const steamAppIdRaw = extractSection('Steam appid').trim();
772+
const steamAppId = /^\d+$/.test(steamAppIdRaw) ? steamAppIdRaw : null;
773+
774+
const supportEvidenceText = [officialDocsSection, linuxBinaryProofSection, guidesSection]
775+
.join('\n')
776+
.trim();
777+
778+
// Deterministic textual checks to avoid trusting checkbox-only reports.
779+
const windowsOnlyPatterns = [
780+
/\bwindows\s+only\b/i,
781+
/\bonly\s+windows\b/i,
782+
/\bno\s+linux\s+support\b/i,
783+
/\blinux\s+not\s+supported\b/i,
784+
/\bdoes\s+not\s+support\s+linux\b/i,
785+
];
786+
const wineRequiredPatterns = [
787+
/\brequires?\s+wine\b/i,
788+
/\buse\s+wine\b/i,
789+
/\brun\s+with\s+wine\b/i,
790+
/\bvia\s+wine\b/i,
791+
/\bproton\b/i,
792+
];
793+
const linuxEvidencePatterns = [
794+
/\blinux\b/i,
795+
/\bubuntu\b/i,
796+
/\bdebian\b/i,
797+
/\blinuxgsm\b/i,
798+
/\bsteamcmd\s*\+app_update\b/i,
799+
];
800+
const windowsBinaryHint = /\b\.exe\b/i.test(supportEvidenceText);
801+
const deterministicWindowsOnly = windowsOnlyPatterns.some((re) => re.test(supportEvidenceText));
802+
const deterministicWineRequired = wineRequiredPatterns.some((re) => re.test(supportEvidenceText));
803+
const hasLinuxEvidence = linuxEvidencePatterns.some((re) => re.test(supportEvidenceText));
804+
805+
// Steam API check: platforms.linux for the given AppID.
806+
let steamLinuxSupport = null;
807+
if (steamAppId) {
808+
try {
809+
const steamRes = await fetch(
810+
`https://store.steampowered.com/api/appdetails?appids=${steamAppId}&filters=platforms`,
811+
{ signal: AbortSignal.timeout(8000) }
812+
);
813+
if (steamRes.ok) {
814+
const steamData = await steamRes.json();
815+
const appData = steamData[steamAppId];
816+
if (appData?.success && appData?.data?.platforms) {
817+
steamLinuxSupport = appData.data.platforms.linux === true;
818+
console.log(`Steam AppID ${steamAppId} linux=${steamLinuxSupport}`);
819+
}
820+
}
821+
} catch (err) {
822+
console.log(`Steam API check failed: ${err.message}`);
823+
}
824+
}
825+
826+
// AI analysis of official docs/guides for Linux evidence.
827+
let aiLinuxAssessment = null;
828+
if (supportEvidenceText.length > 10) {
829+
try {
830+
const linuxAiRes = await fetch(
831+
`https://models.github.ai/orgs/${owner}/inference/chat/completions`,
832+
{
833+
method: 'POST',
834+
headers: {
835+
Accept: 'application/vnd.github+json',
836+
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
837+
'X-GitHub-Api-Version': '2026-03-10',
838+
'Content-Type': 'application/json',
839+
},
840+
body: JSON.stringify({
841+
model: 'openai/gpt-4.1-mini',
842+
temperature: 0.1,
843+
max_tokens: 200,
844+
messages: [
845+
{
846+
role: 'system',
847+
content:
848+
'You analyze game server documentation to determine Linux support. ' +
849+
'Return only JSON. Be conservative: only say "no" if evidence clearly shows Windows-only.',
850+
},
851+
{
852+
role: 'user',
853+
content:
854+
`Analyze for native Linux dedicated server support:\n\nOfficial docs: ${officialDocsSection.slice(0, 400)}\nLinux binary proof: ${linuxBinaryProofSection.slice(0, 500)}\nGuides: ${guidesSection.slice(0, 800)}\n\n` +
855+
'Return JSON: {"linux_support": "yes"|"no"|"unknown", "confidence": "high"|"medium"|"low", "reason": "one sentence"}',
856+
},
857+
],
858+
}),
859+
}
860+
);
861+
if (linuxAiRes.ok) {
862+
const linuxAiData = await linuxAiRes.json();
863+
const raw = linuxAiData.choices?.[0]?.message?.content || '{}';
864+
aiLinuxAssessment = parseTriageResponse(raw);
865+
console.log(`AI linux assessment: ${JSON.stringify(aiLinuxAssessment)}`);
866+
}
867+
} catch (err) {
868+
console.log(`Linux AI check failed: ${err.message}`);
869+
}
870+
}
871+
872+
// Determine verdict: confirmed = deterministic evidence; suggested = AI advisory.
873+
const noLinuxFromDeterministicText =
874+
deterministicWindowsOnly ||
875+
(deterministicWineRequired && !hasLinuxEvidence) ||
876+
(windowsBinaryHint && !hasLinuxEvidence);
877+
const noLinuxFromSteam = steamLinuxSupport === false;
878+
const noLinuxFromAi =
879+
aiLinuxAssessment?.linux_support === 'no' &&
880+
(aiLinuxAssessment?.confidence === 'high' || aiLinuxAssessment?.confidence === 'medium');
881+
882+
const confirmedNoLinux = noLinuxFromDeterministicText || noLinuxFromSteam;
883+
const suggestsNoLinux = noLinuxFromAi && !confirmedNoLinux;
884+
const confirmedLinuxFromSteam = steamLinuxSupport === true;
885+
const linuxYesFromAi =
886+
aiLinuxAssessment?.linux_support === 'yes' &&
887+
(aiLinuxAssessment?.confidence === 'high' || aiLinuxAssessment?.confidence === 'medium');
888+
889+
const NO_LINUX_LABEL = 'status: no linux support';
890+
const LINUX_MARKER = '<!-- linux-support-check -->';
891+
const steamDbLink = steamAppId ? `https://steamdb.info/app/${steamAppId}/` : null;
892+
const shouldApplyNoLinuxLabel = confirmedNoLinux || suggestsNoLinux;
893+
894+
if (shouldApplyNoLinuxLabel) {
895+
// Auto-create the label if it does not exist yet.
896+
try {
897+
await github.rest.issues.getLabel({ owner, repo, name: NO_LINUX_LABEL });
898+
} catch (err) {
899+
if (err.status === 404) {
900+
try {
901+
await github.rest.issues.createLabel({
902+
owner,
903+
repo,
904+
name: NO_LINUX_LABEL,
905+
color: 'd73a4a',
906+
description: 'Game server does not have confirmed native Linux support',
907+
});
908+
} catch (createErr) {
909+
console.log(`Could not create label "${NO_LINUX_LABEL}": ${createErr.message}`);
910+
}
911+
}
912+
}
913+
labelsToAdd.add(NO_LINUX_LABEL);
914+
} else if (existingLabels.has(NO_LINUX_LABEL)) {
915+
labelsToRemove.add(NO_LINUX_LABEL);
916+
}
917+
918+
const reasons = [];
919+
if (deterministicWindowsOnly) reasons.push('the provided docs/guides explicitly indicate Windows-only or no Linux support');
920+
if (deterministicWineRequired && !hasLinuxEvidence) reasons.push('the provided docs/guides indicate a Wine/Proton requirement rather than native Linux binaries');
921+
if (windowsBinaryHint && !hasLinuxEvidence) reasons.push('the provided evidence appears to reference Windows binaries (.exe) without clear Linux server evidence');
922+
if (isSteamNo) reasons.push('request is marked as non-Steam, so Steam platform checks were intentionally skipped');
923+
if (noLinuxFromSteam) reasons.push(`Steam API reports no Linux platform support for AppID ${steamAppId}`);
924+
if (confirmedLinuxFromSteam) reasons.push(`Steam API reports Linux platform support for AppID ${steamAppId}`);
925+
if (noLinuxFromAi && aiLinuxAssessment?.reason) reasons.push(`AI analysis of provided documentation: ${aiLinuxAssessment.reason}`);
926+
if (linuxYesFromAi && aiLinuxAssessment?.reason) reasons.push(`AI analysis indicates Linux support: ${aiLinuxAssessment.reason}`);
927+
928+
let verdictLine = 'Linux support could not be confirmed automatically from the submitted details.';
929+
if (confirmedNoLinux) {
930+
verdictLine = 'This server request does **not** appear to have native Linux support, which is required for LinuxGSM.';
931+
} else if (suggestsNoLinux) {
932+
verdictLine = 'This server request **may not** have native Linux support based on submitted evidence.';
933+
} else if (confirmedLinuxFromSteam) {
934+
verdictLine = 'Steam metadata indicates this server supports Linux.';
935+
} else if (linuxYesFromAi) {
936+
verdictLine = 'Submitted documentation appears to indicate Linux server support.';
937+
}
938+
939+
const steamBlock = isSteamNo
940+
? '**Steam:** No (non-Steam request)\n**Steam API:** Not applicable\n\n'
941+
: steamAppId
942+
? `**Steam:** ${isSteamYes ? 'Yes' : 'Unspecified'}\n` +
943+
`**Steam AppID:** ${steamAppId}\n` +
944+
`**Steam API:** ${steamLinuxSupport === null ? 'No definitive platform response' : steamLinuxSupport ? 'Linux supported' : 'Linux not supported'}\n` +
945+
`**SteamDB:** ${steamDbLink}\n\n`
946+
: `**Steam:** ${isSteamYes ? 'Yes' : 'Unspecified'}\n` +
947+
'**Steam AppID:** Not provided in the issue form.\n' +
948+
(isSteamYes ? '**Steam API:** Skipped until valid AppID is provided.\n\n' : '\n');
949+
950+
const linuxCommentBody =
951+
`${LINUX_MARKER}\n` +
952+
`**Linux Support Check**\n\n` +
953+
`${verdictLine}\n\n` +
954+
`${steamBlock}` +
955+
(reasons.length > 0
956+
? `**Evidence:**\n${reasons.map((r) => `- ${r}`).join('\n')}\n\n`
957+
: '') +
958+
`LinuxGSM only supports **native Linux dedicated servers**. Wine and Windows-only servers are not supported.\n\n` +
959+
`If support is unclear, please provide:\n` +
960+
`- Official Linux dedicated server documentation\n` +
961+
`- Linux server binaries or release notes\n` +
962+
`- Linux startup instructions or commands\n\n` +
963+
`_This check was performed automatically using Steam API and submitted issue details (with AI assistance for document interpretation)._`;
964+
965+
try {
966+
const allComments = await github.paginate(github.rest.issues.listComments, {
967+
owner,
968+
repo,
969+
issue_number: issueNumber,
970+
per_page: 100,
971+
});
972+
const existingLinuxComment = [...allComments].reverse().find(
973+
(c) => c.user?.type === 'Bot' && c.body?.includes(LINUX_MARKER)
974+
);
975+
976+
if (existingLinuxComment) {
977+
await github.rest.issues.updateComment({
978+
owner,
979+
repo,
980+
comment_id: existingLinuxComment.id,
981+
body: linuxCommentBody,
982+
});
983+
} else {
984+
await github.rest.issues.createComment({
985+
owner,
986+
repo,
987+
issue_number: issueNumber,
988+
body: linuxCommentBody,
989+
});
990+
}
991+
} catch (err) {
992+
console.log(`Could not post Linux support comment: ${err.message}`);
993+
}
994+
}
995+
757996
issue-potential-duplicates:
758997
if: github.repository_owner == 'GameServerManagers' && github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'edited' || github.event.action == 'reopened')
759998
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)