Skip to content

Commit

Permalink
add main file, helpers, and config; add exit-hook and axios
Browse files Browse the repository at this point in the history
  • Loading branch information
mallendeo committed Feb 17, 2019
1 parent c41702c commit 1e0f20d
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 1 deletion.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"build": "cd docker && ./build.sh"
},
"dependencies": {
"async-exit-hook": "^2.0.1",
"axios": "^0.18.0",
"dockerode": "^2.5.8",
"dotenv": "^6.2.0",
"fs-extra": "^7.0.1",
Expand Down
1 change: 1 addition & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
pass: VPN_PASS,
imageName: IMAGE_NAME,
dockerPrefix: DOCKER_PREFIX,
reqLimit: 1,
proxy: {
startsFrom: 5000,
port: PORT
Expand Down
14 changes: 14 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict'

const _ = require('lodash')
const axios = require('axios')

exports.sample = (arr, num) => {
if (!num) return _.sample(arr)
return _.shuffle(arr).slice(0, num)
}

exports.myIp = async () => {
const { data } = await axios('https://api.ipify.org/?format=json')
return data.ip
}
181 changes: 181 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
'use strict'

const Docker = require('dockerode')
const fs = require('fs-extra')
const http = require('http')
const httpProxy = require('http-proxy')
const exitHook = require('async-exit-hook')

const config = require('./config')
const { sample, myIp } = require('./helpers')

const docker = new Docker()
const proxy = httpProxy.createProxyServer()

const state = {
allFiles: [],
publicIp: null
}

const create = async (name, port, auth = {}) => {
if (!state.allFiles.find(n => n === name)) {
throw Error(`ovpn file ${name} not found.`)
}

const created = await docker.createContainer({
Image: config.imageName,
name: `${config.dockerPrefix}-${name}`,
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
StdinOnce: false,
OpenStdin: false,
Tty: true,
Cmd: ['supervisord', '-n'],
ExposedPorts: {
'8118/tcp': {}
},
Volumes: {
'/ovpn': {}
},
Env: [
`VPN_USER=${auth.user || config.user}`,
`VPN_PASS=${auth.pass || config.pass}`,
`OVPN_FILE=${name}`
],
HostConfig: {
PortBindings: {
'8118/tcp': [{ HostIp: '127.0.0.1', HostPort: String(port) }]
},
Dns: ['8.8.8.8', '8.8.4.4'],
Binds: [`${__dirname}/../ovpn:/ovpn`],
CapAdd: ['NET_ADMIN'],
Devices: [{
PathOnHost: '/dev/net/tun',
PathInContainer: '/dev/net/tun',
CgroupPermissions: 'rwm'
}]
}
})

return created
}

const getContainers = async () => {
const all = await docker.listContainers({ all: true })
return all.filter(c => c.Image === config.imageName)
}

const renewVpn = async (name, retry) => {
console.log('renew VPN', name)
const vpn = state.vpns[name]

const found = (await getContainers()).find(c =>
c.Names[0] === `/${config.dockerPrefix}-${name}`
)
const container = docker.getContainer(found.Id)
try {
await container.stop()
console.log('stopped', name, found.Id)
await container.remove({ force: true })
console.log('removed', name, found.Id)

const newVpn = sample(state.filtered)
const _newCont = await create(newVpn, vpn.port)
await _newCont.start()

state.vpns[newVpn] = {
...vpn,
name: newVpn,
ready: true,
count: 0
}

delete state.vpns[name]
console.log(newVpn, 'started', 'on port', vpn.port)
} catch (err) {
console.error(err.message)

if (!retry) {
console.log('retrying...')
renewVpn(name, true)
}
}
}

// Proxy server
const server = http.createServer((req, res) => {
const available = Object.keys(state.vpns)
.filter(vpn => state.vpns[vpn].ready)
const vpn = sample(available)
console.log({ vpn }, state.publicIp)
proxy.web(req, res, {
target: {
host: 'localhost',
port: state.vpns[vpn].port
}
}, e => console.error(e.message))

const last = ++state.vpns[vpn].count > config.reqLimit
if (last) state.vpns[vpn].ready = false

res.on('finish', () => last && renewVpn(vpn))
})

// Default regex for US and Chilean VPNs
const initialConfig = async (regex = '^(us|cl)') => {
state.allFiles = await fs.readdir('./ovpn')
state.filtered = state.allFiles
.filter(f => f.endsWith('ovpn'))
.filter(s => new RegExp(regex).test(s))

state.vpns = sample(state.filtered, 6).reduce((obj, vpn, i) => ({
...obj,
[vpn]: {
name: vpn,
port: config.proxy.startsFrom + i,
count: 0,
ready: false
}
}), {})

return state.vpns
}

const main = async () => {
await initialConfig()
console.log('state.vpns', state.vpns)

await Promise.all(Object.values(state.vpns).map(async vpn => {
const _newCont = await create(vpn.name, vpn.port)
await _newCont.start()

state.vpns[vpn.name].ready = true

console.log(vpn.name, 'started', 'on port', vpn.port)
}))

console.log(state.vpns)
console.log('listening on port 5050')
server.listen(config.proxy.port)
state.publicIp = await myIp()
console.log('public ip', state.publicIp)
}

exitHook(async callback => {
const containers = await getContainers()

await Promise.all(containers.map(async ({ Id, Names }) => {
const container = docker.getContainer(Id)
try {
await container.remove({ force: true })
console.log(Names[0], 'removed')
} catch (e) {
console.error(e.message)
}
}))

callback()
})

main()
17 changes: 16 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ array-includes@^3.0.3:
define-properties "^1.1.2"
es-abstract "^1.7.0"

async-exit-hook@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3"

axios@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
dependencies:
follow-redirects "^1.3.0"
is-buffer "^1.1.5"

babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
Expand Down Expand Up @@ -526,7 +537,7 @@ flat-cache@^1.2.1:
rimraf "~2.6.2"
write "^0.2.1"

follow-redirects@^1.0.0:
follow-redirects@^1.0.0, follow-redirects@^1.3.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
dependencies:
Expand Down Expand Up @@ -662,6 +673,10 @@ is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"

is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"

is-callable@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
Expand Down

0 comments on commit 1e0f20d

Please sign in to comment.