Skip to content

Instantly share code, notes, and snippets.

@rluvaton
Last active October 15, 2023 19:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rluvaton/0eb2d2279f6942b089ba019df777e877 to your computer and use it in GitHub Desktop.
Save rluvaton/0eb2d2279f6942b089ba019df777e877 to your computer and use it in GitHub Desktop.
Bundle install for mac that temporarly disable IPv6 for network interface while downloading

Fixed bundle install for MacOS

This is a zx script that fix bundle install

How to run:

The bundle-install file should be without extension (but not required) added the extension to make GitHub syntax highlighting work

So from now we just gonna use bundle-install instead of bundle-install.mjs

  1. npm i -g zx
  2. Copy the bundle-install to where you want (preferably somewhere that is globally accessible - in PATH)
  3. Run chmod u+x ./bundle-install adding execution permission to the script
  4. Run bundle-install where you want to run bundle install (if you did not add the file to somewhere that is globally accessible, you will need to run using ./bundle-install)

Why it needs a fix?

Because apparently running bundle install will fail due to server timeout caused by api.rubygems.org having issues with IPv6

Reference: gem cannot access rubygems.org

How this script fix it?

  1. It will get all network interfaces that have IPv6 set as Automatic
  2. Disable them
  3. Run bundle install
  4. Revert the changed network interfaces IPv6 to Automatic
#!/usr/bin/env zx --experimental
/* vim: syntax=javascript
*/
// Setting verbose to false so it won't print the executing command which make it hard for scripts
// TODO - should allow setting verbose from command line
const isDebug = process.env.DEBUG && process.env.DEBUG !== "false";
const ogVerbose = $.verbose;
$.verbose = ogVerbose;
async function getAllNetworkServices() {
// Get all network setup
const { stdout } = await $`networksetup -listallnetworkservices`;
const networkServicesName = stdout.split("\n").filter(Boolean);
const networkServices = {};
for (let i = 0; i < networkServicesName.length; i++) {
const networkService = networkServicesName[i];
try {
const info = await getNetworkServiceInfo(networkService);
if (info.IPv6 === "automatic") {
networkServices[networkService] = info;
}
} catch (e) {
if (i === 0) {
// First line is: `An asterisk (*) denotes that a network service is disabled.`
// But I'm not sure if this is always the case
// so checking just in case
continue;
}
printError(
"Error getting info for network service: " + networkService,
e
);
}
}
return networkServices;
}
async function getNetworkServiceInfo(name) {
// Get all network setup
const { stdout } = await $`networksetup -getinfo ${name}`;
const info = Object.fromEntries(
stdout
.split("\n")
.filter(Boolean)
.map((item) => item.split(":").map((item) => item.trim()))
);
if (!info.IPv6) {
console.error(
`${chalk.red(
`No IPv6 info for ${chalk.bold(name)}`
)} stdout:\n${stdout}\n`
);
throw new Error("No IPv6 info");
}
info.IPv6 = info.IPv6.toLowerCase();
return info;
}
async function disableIPv6ForInterfaces(allInterfaces) {
for (const interfaceName in allInterfaces) {
await $`networksetup -setv6off ${interfaceName}`;
}
}
async function revertAutomaticIPv6ForInterfaces(allInterfaces) {
const failedInterfaces = [];
for (const interfaceName in allInterfaces) {
try {
await $`networksetup -setv6automatic ${interfaceName}`;
} catch (e) {
failedInterfaces.push(interfaceName);
console.error(e);
}
}
if (failedInterfaces.length) {
console.error(
`${chalk.red(
`Failed to revert IPv6 to automatic for the following interfaces:`
)} ${failedInterfaces.map((item) => `"${item}"`).join(", ")}`
);
}
}
async function bundleInstall(dryRun = false) {
const cwd = process.cwd();
const currentScript = __filename;
const indexOfCurrentScript = process.argv.findIndex(
(item) => path.join(cwd, item) === currentScript || (path.isAbsolute(item) && item === currentScript)
);
if (indexOfCurrentScript === -1) {
throw new Error("Couldn't find current script in argv");
}
// Only get argument after the current script (like node <zx-path> --experimental <this-script>)
const bundleInstallArgs = process.argv.slice(indexOfCurrentScript + 1);
if (!dryRun) {
// Run bundle install with this script arguments
await $`bundle install ${bundleInstallArgs}`;
}
}
// Dry run bundle install
await bundleInstall(true);
// Avoiding having a lot of output when not debugging
$.verbose = isDebug;
const allInterfacesWithIPv6AsAutomatic = await getAllNetworkServices();
$.verbose = ogVerbose;
try {
$.verbose = isDebug;
await disableIPv6ForInterfaces(allInterfacesWithIPv6AsAutomatic);
$.verbose = true;
await bundleInstall();
} finally {
$.verbose = true;
const msg = chalk.bold(
"Gonna revert the following interfaces back to automatic:"
);
const interfaces = Object.keys(allInterfacesWithIPv6AsAutomatic)
.map((item) => `- ${item}`)
.join("\n");
console.log(`${msg}\n${interfaces}`);
await revertAutomaticIPv6ForInterfaces(allInterfacesWithIPv6AsAutomatic);
}
function printError(message, error) {
if (!isDebug && !ogVerbose) {
return;
}
console.error(message, error);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment