import axios from 'axios';
import Docker from 'dockerode';
export interface GHCRAuthConfig {
username: string;
password: string; // GitHub Personal Access Token or GITHUB_TOKEN
}
export class GHCRClient {
private registryURL = 'https://ghcr.io';
private authConfig?: GHCRAuthConfig;
constructor() {}
async authenticate(token: string, username?: string): Promise<GHCRAuthConfig> {
// For GHCR, username is typically the GitHub username or organization
// Password is the Personal Access Token
this.authConfig = {
username: username || 'USER', // Docker requires a username, but GHCR uses token
password: token,
};
return this.authConfig;
}
getAuthConfig(): Docker.AuthConfig | undefined {
if (!this.authConfig) {
return undefined;
}
return {
username: this.authConfig.username,
password: this.authConfig.password,
serveraddress: this.registryURL,
};
}
async getImageManifest(imageName: string): Promise<any> {
// Parse image name: ghcr.io/owner/repo:tag
const parts = imageName.replace('ghcr.io/', '').split(':');
const repo = parts[0];
const tag = parts[1] || 'latest';
const url = `https://ghcr.io/v2/${repo}/manifests/${tag}`;
try {
const headers: Record<string, string> = {
Accept: 'application/vnd.docker.distribution.manifest.v2+json',
};
if (this.authConfig) {
// Get bearer token from GHCR
const token = await this.getBearerToken(repo, 'pull');
headers.Authorization = `Bearer ${token}`;
}
const response = await axios.get(url, { headers });
return response.data;
} catch (error: any) {
throw new Error(`Failed to get GHCR manifest: ${error.response?.data?.message || error.message}`);
}
}
private async getBearerToken(repo: string, scope: string): Promise<string> {
if (!this.authConfig) {
throw new Error('Not authenticated with GHCR');
}
try {
const auth = Buffer.from(`${this.authConfig.username}:${this.authConfig.password}`).toString('base64');
const response = await axios.get(`https://ghcr.io/token?service=ghcr.io&scope=repository:${repo}:${scope}`, {
headers: {
Authorization: `Basic ${auth}`,
},
});
return response.data.token;
} catch (error: any) {
throw new Error(`Failed to get GHCR bearer token: ${error.response?.data?.message || error.message}`);
}
}
}