Skip to content

Commit 7ef0d29

Browse files
committed
add auto install trufflehog script
1 parent b487db1 commit 7ef0d29

File tree

2 files changed

+281
-2
lines changed

2 files changed

+281
-2
lines changed

.husky/pre-commit

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,28 @@ git update-index --again
66
pnpm eslint $(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') --fix
77
git update-index --again
88

9-
# check for secrets
9+
10+
# Only adjust PATH on macOS/Linux/WSL
11+
UNAME=$(uname -s 2>/dev/null || echo "")
12+
case "$UNAME" in
13+
Linux|Darwin|*WSL*)
14+
export PATH="$HOME/.local/bin:/usr/local/bin:/opt/homebrew/bin:$PATH"
15+
;;
16+
esac
17+
18+
# run trufflehog check script
19+
tsx scripts/check-trufflehog.mts
20+
1021
if [ -z "$CI" ] && [ -z "$GITHUB_ACTIONS" ]; then
11-
trufflehog git file://. --since-commit HEAD --fail
22+
if command -v trufflehog >/dev/null 2>&1; then
23+
TRUFFLEHOG_BIN=$(command -v trufflehog)
24+
elif where trufflehog >/dev/null 2>&1; then
25+
TRUFFLEHOG_BIN=$(where trufflehog | head -n 1)
26+
else
27+
echo "Error: trufflehog is not installed or not in PATH"
28+
exit 1
29+
fi
30+
31+
"$TRUFFLEHOG_BIN" git file://. --since-commit HEAD --fail
1232
fi
33+

scripts/check-trufflehog.mts

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import { execSync } from 'child_process';
2+
import { platform, arch } from 'os';
3+
import path from 'path';
4+
import fs from 'fs';
5+
import https from 'https';
6+
7+
const getLatestTrufflehogVersion = (): Promise<string> => {
8+
return new Promise((resolve, reject) => {
9+
https
10+
.get(
11+
'https://api.github.com/repos/trufflesecurity/trufflehog/releases/latest',
12+
{
13+
headers: { 'User-Agent': 'node.js' }, // GitHub API requires a User-Agent
14+
},
15+
(res) => {
16+
let data = '';
17+
res.on('data', (chunk) => (data += chunk));
18+
res.on('end', () => {
19+
try {
20+
const json = JSON.parse(data);
21+
resolve(json.tag_name); // Example: "v3.63.2"
22+
} catch (err) {
23+
reject(err);
24+
}
25+
});
26+
},
27+
)
28+
.on('error', reject);
29+
});
30+
};
31+
32+
interface PlatformConfig {
33+
os: string;
34+
ext: string;
35+
}
36+
37+
interface PlatformInfo {
38+
os: string;
39+
arch: string;
40+
ext: string;
41+
}
42+
43+
const getPlatformInfo = (): PlatformInfo => {
44+
const currentOs = platform();
45+
const architecture = arch();
46+
47+
const platforms: Record<string, PlatformConfig> = {
48+
win32: { os: 'windows', ext: '.exe' },
49+
darwin: { os: 'darwin', ext: '' },
50+
linux: { os: 'linux', ext: '' },
51+
};
52+
53+
const archs: Record<string, string> = {
54+
x64: 'amd64',
55+
arm64: 'arm64',
56+
};
57+
58+
if (!platforms[currentOs] || !archs[architecture]) {
59+
throw new Error(`Unsupported platform: ${currentOs} ${architecture}`);
60+
}
61+
62+
return {
63+
os: platforms[currentOs].os,
64+
arch: archs[architecture],
65+
ext: platforms[currentOs].ext,
66+
};
67+
};
68+
69+
const getBinaryPath = (): string => {
70+
const home = process.env.HOME || process.env.USERPROFILE;
71+
if (!home) {
72+
throw new Error('HOME or USERPROFILE environment variable not set');
73+
}
74+
const binDir = path.join(home, '.local', 'bin');
75+
if (!fs.existsSync(binDir)) {
76+
fs.mkdirSync(binDir, { recursive: true });
77+
}
78+
return binDir;
79+
};
80+
81+
const downloadFile = (url: string, dest: string): Promise<void> => {
82+
return new Promise((resolve, reject) => {
83+
const file = fs.createWriteStream(dest);
84+
const request = https.get(url, (response) => {
85+
if (response.statusCode === 302 || response.statusCode === 301) {
86+
const redirectUrl = response.headers.location;
87+
if (!redirectUrl) {
88+
reject(new Error('Redirect location not found'));
89+
return;
90+
}
91+
file.close();
92+
downloadFile(redirectUrl, dest).then(resolve).catch(reject);
93+
return;
94+
}
95+
96+
if (response.statusCode !== 200) {
97+
file.close();
98+
fs.unlink(dest, () => {
99+
reject(
100+
new Error(
101+
`Failed to download file. HTTP status code: ${response.statusCode} - ${response.statusMessage}`,
102+
),
103+
);
104+
});
105+
return;
106+
}
107+
108+
const contentLength = parseInt(
109+
response.headers['content-length'] || '0',
110+
10,
111+
);
112+
let downloadedBytes = 0;
113+
114+
response.on('data', (chunk) => {
115+
downloadedBytes += chunk.length;
116+
if (contentLength > 0) {
117+
const progress = (downloadedBytes / contentLength) * 100;
118+
process.stdout.write(`\rDownloading... ${progress.toFixed(1)}%`);
119+
}
120+
});
121+
122+
response.pipe(file);
123+
124+
file.on('finish', () => {
125+
process.stdout.write('\n');
126+
file.close();
127+
128+
const stats = fs.statSync(dest);
129+
if (stats.size === 0) {
130+
fs.unlinkSync(dest);
131+
reject(new Error('Downloaded file is empty'));
132+
return;
133+
}
134+
135+
fs.chmodSync(dest, '755');
136+
resolve();
137+
});
138+
});
139+
140+
request.on('error', (err: Error) => {
141+
fs.unlink(dest, () => reject(err));
142+
});
143+
144+
request.setTimeout(30000, () => {
145+
request.destroy();
146+
fs.unlink(dest, () => reject(new Error('Download timeout')));
147+
});
148+
});
149+
};
150+
151+
const isTrufflehogInstalled = (): boolean => {
152+
try {
153+
execSync('trufflehog --help', { stdio: 'ignore' });
154+
return true;
155+
} catch {
156+
return false;
157+
}
158+
};
159+
160+
const updatePathInShellConfig = async (binPath: string): Promise<void> => {
161+
const shell = process.env.SHELL;
162+
const home = process.env.HOME;
163+
164+
if (!home) {
165+
throw new Error('HOME or USERPROFILE environment variable not set');
166+
}
167+
168+
let configFile = '';
169+
if (shell?.includes('zsh')) {
170+
configFile = path.join(home, '.zshrc');
171+
} else {
172+
configFile = path.join(home, '.bashrc');
173+
}
174+
175+
const pathLine = `\n# Added by trufflehog installer\nexport PATH="${binPath}:$PATH"\n`;
176+
177+
try {
178+
const content = fs.existsSync(configFile)
179+
? fs.readFileSync(configFile, 'utf-8')
180+
: '';
181+
182+
if (!content.includes(binPath)) {
183+
fs.appendFileSync(configFile, pathLine);
184+
console.log(`Updated ${configFile} with PATH configuration`);
185+
}
186+
} catch (error) {
187+
console.error(
188+
'Failed to update shell configuration:',
189+
error instanceof Error ? error.message : String(error),
190+
);
191+
}
192+
};
193+
194+
const installTrufflehog = async (): Promise<void> => {
195+
try {
196+
const { os, arch, ext } = getPlatformInfo();
197+
const TRUFFLEHOG_VERSION = await getLatestTrufflehogVersion();
198+
const versionNumber = TRUFFLEHOG_VERSION.startsWith('v')
199+
? TRUFFLEHOG_VERSION.slice(1)
200+
: TRUFFLEHOG_VERSION;
201+
const archiveName = `trufflehog_${versionNumber}_${os}_${arch}.tar.gz`;
202+
const downloadUrl = `https://github.com/trufflesecurity/trufflehog/releases/download/${TRUFFLEHOG_VERSION}/${archiveName}`;
203+
204+
// TODO: Add support for windows
205+
if (os === 'windows') {
206+
throw new Error(
207+
`Windows is not supported for trufflehog auto installation kindly install it manually from ${downloadUrl}`,
208+
);
209+
}
210+
211+
const binPath = getBinaryPath();
212+
const archivePath = path.join(binPath, archiveName);
213+
214+
console.log(`Downloading trufflehog archive from ${downloadUrl}`);
215+
await downloadFile(downloadUrl, archivePath);
216+
217+
execSync(`tar -xzf ${archivePath} -C ${binPath}`);
218+
fs.unlinkSync(archivePath);
219+
220+
const binaryName = `trufflehog${ext}`;
221+
const binaryPath = path.join(binPath, binaryName);
222+
223+
// Set executable permission on the binary
224+
fs.chmodSync(binaryPath, 0o755);
225+
226+
await updatePathInShellConfig(binPath);
227+
228+
console.log('trufflehog installed successfully!');
229+
} catch (error) {
230+
console.error(
231+
'Failed to install trufflehog:',
232+
error instanceof Error ? error.message : String(error),
233+
);
234+
process.exit(1);
235+
}
236+
};
237+
238+
const main = async (): Promise<void> => {
239+
try {
240+
if (!isTrufflehogInstalled()) {
241+
console.log('trufflehog is not installed.');
242+
await installTrufflehog();
243+
244+
if (!isTrufflehogInstalled()) {
245+
throw new Error('Installation verification failed');
246+
}
247+
}
248+
} catch (error) {
249+
console.error(error instanceof Error ? error.message : String(error));
250+
process.exit(1);
251+
}
252+
};
253+
254+
// Wait for the promise to resolve before exiting
255+
await main().catch((error: unknown) => {
256+
console.error(error instanceof Error ? error.message : String(error));
257+
process.exit(1);
258+
});

0 commit comments

Comments
 (0)