auto-pod
Version:
Automatically choose tunneling or direct connection for cocoapods. As easy as `pod` itself. Help you get rid of GFW of China.
261 lines (212 loc) • 7.04 kB
text/typescript
import * as cli from 'commander';
import { spawn } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import * as yaml from 'js-yaml';
import { AddressInfo } from 'net';
import { resolve } from 'path';
import * as shell from 'shelljs';
import * as url from 'url';
import { getLogger, init } from './Logger';
import { IConfig, IProxy, ProxyServer } from './ProxyServer';
let displayError = true;
const optionNames = [
'logLevel',
'config',
'debug',
'tunneling',
];
export interface ICmdOptions {
logLevel?: string;
config?: string;
debug?: boolean;
tunneling?: boolean;
}
export function getFileConfig(filePath: string): IConfig {
const absFile = resolve(process.cwd(), filePath);
if (!existsSync(absFile)) {
getLogger()
.error('Cannot find auto-pod.yaml. You may also use another config file by "-c" param.');
throw new Error(`Cannot find ${absFile}`);
}
const content = readFileSync(absFile).toString('utf8');
let config = null;
try {
config = yaml.safeLoad(content);
} catch (err) {
getLogger().error(`invalid yaml content: ${err.message}`);
const error = new Error(`invalid yaml content: ${err.message}`);
// error.code = err.code;
throw error;
}
const fileConfig = {
excludes: config.excludes || [],
forceTunneling: false,
includes: config.includes || [],
proxies: [],
};
// Parse each proxies as providers.
if (!config.proxies || !(config.proxies instanceof Array)) {
getLogger()
.error('No proxies is set. Please provide us at least one element in `proxies` field.');
throw new Error("No proxies is set.");
}
if (config.proxies.length === 0) {
throw new Error("No proxies is provided.");
}
for (const proxy of config.proxies) {
let opts: IProxy = {};
if ('string' === typeof proxy) opts = url.parse(proxy);
// prefer `hostname` over `host`, because of `url.parse()`
opts.host = opts.hostname || opts.host;
// SOCKS doesn't *technically* have a default port, but this is
// the same default that `curl(1)` uses
opts.nport = +opts.port || 1080;
if (opts.host && opts.path) {
// if both a `host` and `path` are specified then it's most likely the
// result of a `url.parse()` call... we need to remove the `path` portion so
// that `net.connect()` doesn't attempt to open that as a unix socket file.
delete opts.path;
delete opts.pathname;
}
const originalLookup = opts.lookup;
// figure out if we want socks v4 or v5, based on the "protocol" used.
// Defaults to 5.
opts.lookup = false;
switch (opts.protocol) {
case 'socks4:':
opts.lookup = true;
// pass through
case 'socks4a:':
opts.version = 4;
break;
case 'socks5:':
opts.lookup = true;
// pass through
case 'socks:': // no version specified, default to 5h
case 'socks5h:':
opts.version = 5;
break;
default:
throw new TypeError(
`A "socks" protocol must be specified! Got: ${proxy.protocol}`,
);
}
if (opts.auth) {
const auth = opts.auth.split(':');
opts.authentication = { username: auth[0], password: auth[1] };
// opts.userid = auth[0];
}
// Set to manually set lookup value.
if (typeof originalLookup !== 'undefined') {
opts.lookup = !!originalLookup;
}
fileConfig.proxies.push(opts);
}
if ('force-tunneling' in config) {
fileConfig.forceTunneling = !!config['force-tunneling'];
}
return fileConfig;
}
export function getOptionsArgs(args): ICmdOptions {
const options = {};
optionNames.forEach((name) => {
if (Object.hasOwnProperty.apply(args, [name])) {
if (typeof args[name] !== 'string') {
throw new Error(`string "${name}" expected`);
}
options[name] = args[name];
}
});
return options;
}
export function setUpGitProxy(port: number) {
shell.exec(`git config --global http.proxy http://127.0.0.1:${port}/`);
shell.exec(`git config --global https.proxy http://127.0.0.1:${port}/`);
}
export function unsetGitProxy() {
shell.exec(`git config --unset --global http.proxy`);
shell.exec(`git config --unset --global https.proxy`);
}
function unsafeMain() {
const pkgJson = readFileSync(`${__dirname}/../package.json`, 'utf-8');
const version = JSON.parse(pkgJson).version;
cli.version(version)
// .option('-s, --socks [socks]', 'specify your socks proxy host, default: 127.0.0.1:1080')
// .option('-p, --port [port]',
// 'specify the listening port of http proxy server, default: 8080')
// .option('-l, --host [host]',
// 'specify the listening host of http proxy server, default: 127.0.0.1')
.option('-c, --config [config]', 'read configs from file in json format')
.option('-t, --tunneling', 'Force tunneling all traffic through proxies.')
.option('--debug ', 'Enable debug mode')
.option('--level [level]', 'log level, vals: info, error')
.parse(process.argv);
const options = getOptionsArgs(cli);
if (!options.debug) {
displayError = false;
}
if (options.logLevel && typeof options.logLevel === 'string') {
init(options.logLevel);
}
// Find auto-pod.yaml.
const configFile = options.config || 'auto-pod.yaml';
const fileConfig = getFileConfig(configFile);
if (options.tunneling) {
// Overwrites force tunneling
fileConfig.forceTunneling = true;
}
// Startup the server.
const server = new ProxyServer(fileConfig);
server.on('listening', () => {
// Get running port
const address = server.address() as AddressInfo;
getLogger().info(`Server listening on: ${address.address}:${address.port}`);
//
// Shell run the pod.
setUpGitProxy(address.port);
getLogger().info("Setup git proxy.");
// Run shelljs.
// shell.exec("pod")
// Copy the process env.
const env = JSON.parse(JSON.stringify(process.env));
env.http_proxy = `http://127.0.0.1:${address.port}`;
env.https_proxy = `http://127.0.0.1:${address.port}`;
getLogger().info("Starting pod...");
const pod = spawn('pod', cli.args, {
env,
shell: true,
stdio: 'pipe',
});
pod.stdout.on('data', (data) => {
process.stdout.write(data);
});
pod.stderr.on('data', (data) => {
process.stdout.write(data);
});
pod.on('close', (code: number) => {
if (code !== 0) {
getLogger().error('Failed to execute the pod.');
} else {
getLogger().info("Pod finished.");
}
unsetGitProxy();
getLogger().info("Git proxy removed.");
// Stop the server
server.unref();
// Shutdown the http proxy server.
server.close();
});
});
// Listening at any random port.
server.listen({ host: '127.0.0.1', port: 0 });
}
export function main() {
try {
unsafeMain();
} catch (e) {
if (displayError) {
getLogger().error(e);
process.exit(1);
}
}
}