npm package: @meat-nuggets/wireguard-tools
Description: The best way to interact with WireGuard from Node
Author: Guardline
Latest version: 0.1.0

### Disclaimer
> This is a fork of the original project located here: https://github.com/guardline-vpn/wireguard-tools.
> 
> Whilst the original project intended to be used on the host where Wireguard VPN server runs,
>   this fork is supposed to be used from the controlling server that runs on NodeJS and talks to Wireguard servers via SSH. 

# Wireguard tools for Nodejs

This lib includes a class and set of helper functions for working with WireGuard config files in javascript/typescript.

100% Typescript!

[Check out the docs](https://guardline-vpn.github.io/wireguard-tools/) with 💖 from [typedoc](https://typedoc.org/):

[https://guardline-vpn.github.io/wireguard-tools/](https://guardline-vpn.github.io/wireguard-tools/)

## To use

`npm i wireguard-tools`

or

`yarn add wireguard-tools`

### Basic config

```ts
import path from 'path'
import { WgConfig } from 'wireguard-tools'

const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')

const config1 = new WgConfig({
  wgInterface: { address: ['10.10.1.1'] },
  filePath
})

await config1.generateKeys()

```

### Generate Keys

```ts
import path from 'path'
import { WgConfig } from 'wireguard-tools'

const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')

const config1 = new WgConfig({
  wgInterface: {
    address: ['10.10.1.1']
  },
  filePath
})

const { publicKey, preSharedKey, privateKey } = await config1.generateKeys({ preSharedKey: true })

```

### Write to disk

```ts
import path from 'path'
import { WgConfig } from 'wireguard-tools'

const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')

const config1 = new WgConfig({
  wgInterface: {
    address: ['10.10.1.1']
  },
  filePath
})

await config1.generateKeys()
await config1.writeToFile()

```

### Parse config from disk

```ts
import path from 'path'
import { WgConfig, getConfigObjectFromFile } from 'wireguard-tools'

const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')

const thatConfigFromFile = await getConfigObjectFromFile({ filePath })

const config1 = new WgConfig({
  ...thatConfigFromFile,
  filePath
})

// OR:
const config2 = new WgConfig({ filePath })
await config2.parseFile()

// OR:
const config3 = new WgConfig()
await config3.parseFile(filePath)

// Public key will not be available because it's not saved in the WireGuard config,
// so you need to generate keys again (it will use the existing private key)
await config1.generateKeys()
```

### Bring up /bring down a WgConfig as a WireGuard interface

```ts
import path from 'path'
import { WgConfig } from 'wireguard-tools'

const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')

const config1 = new WgConfig({
  wgInterface: {
    address: ['10.10.1.1']
  },
  filePath
})

await config1.generateKeys()
await config1.writeToFile()

// bring up
await config1.up()

// bring down
await config1.down()
```

### To change a WgConfig while up, you need to restart

```ts
import path from 'path'
import { WgConfig } from 'wireguard-tools'

const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')

const config1 = new WgConfig()
// Assuming the WireGuard config file is already on disk...
await config1.parseFile(filePath)

await config1.generateKeys()

// bring up
await config1.up()

// create new key pair
await config1.generateKeys({ overwrite: true })
// change the name
config1.wgInterface.name = 'new-name'

// write the file to save
await config1.writeToFile()

// restart for the changes to take effect
await config1.restart()
```

### Or use the save() shorcut method to write and restart

Note, using this method will start the WireGuard interface if it's down unless `{ noUp: true }` is passed in.

```ts
import path from 'path'
import { WgConfig } from 'wireguard-tools'

const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')

const config1 = new WgConfig()
// Assuming the WireGuard config file is already on disk...
await config1.parseFile(filePath)

await config1.generateKeys()

// bring up
await config1.up()

// create new key pair
await config1.generateKeys({ overwrite: true })
// change the name
config1.wgInterface.name = 'new-name'

// write the file and restart
await config1.save()
```

### Generate and add peers from and to configs

```ts
import path from 'path'
import { WgConfig } from 'wireguard-tools'

const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const filePath2 = path.join(__dirname, '/configs', '/guardline-client.conf')

const server = new WgConfig({
  wgInterface: { address: ['10.10.1.1'] },
  filePath
})

const client = new WgConfig({
  wgInterface: { address: ['10.10.1.2'] },
  filePath: filePath2
})

// gen keys
await Promise.all([
  server.generateKeys({ preSharedKey: true }),
  client.generateKeys({ preSharedKey: true })
])

// make a peer from server
const serverAsPeer = server.createPeer({
  allowedIps: ['10.1.1.1/32'],
  preSharedKey: server.preSharedKey
})

// add that as a peer to client
client.addPeer(serverAsPeer)

// make a peer from client and add it to server
server.addPeer(client.createPeer({
  allowedIps: ['10.10.1.1/32'],
  preSharedKey: client.preSharedKey
}))
```

### Or use the createPeerPairs tool to do this with ease

```ts
import path from 'path'
import { WgConfig, createPeerPairs } from 'wireguard-tools'

// make a load of configs
let configs: WgConfig[] = []

for (let i = 1; i <= 10; i++) {
  configs.push(new WgConfig({
    wgInterface: {
      address: [`10.10.1.${i}`],
      name: `Client-${i}`
    },
    filePath: path.join(__dirname, '/configs', `/guardline-${i}.conf`)
  }))
}

// get their key pairs
await Promise.all(configs.map(x => x.generateKeys()))

// add them all as peers of each other
createPeerPairs(configs.map(x => {
  return {
    config: x,
    peerSettings: {
      allowedIps: ['10.10.1.1/32']
    }
  }
}))

// write them all to disk
await Promise.all(configs.map(x => x.writeToFile()))

```

### Or more advanced

```ts
import path from 'path'
import { WgConfig, createPeerPairs } from 'wireguard-tools'

// make a load of configs
let configs: WgConfig[] = []

for (let i = 1; i <= 10; i++) {
  configs.push(new WgConfig({
    wgInterface: {
      address: [`10.10.1.${i}`],
      privateKey: '',
      name: `Client-${i}`
    },
    filePath: path.join(__dirname, '/configs', `/guardline-${i}.conf`)
  }))
}

// get their key pairs
await Promise.all(configs.map(x => x.generateKeys()))

// add them all as peers of each other
createPeerPairs(configs.map(x => {
  return {
    config: x,
    peerSettings: ({ thisConfig, peerConfig }) => {
      const peerAddress = peerConfig.wgInterface.address
      const peerPresharedKey = peerConfig.preSharedKey
      return {
        allowedIps: peerAddress,
        preSharedKey: peerPresharedKey,
        name: peerConfig.wgInterface.name,
        persistentKeepalive: thisConfig.wgInterface.address.includes('10.10.1.1') ? 25 : undefined
      }
    }
  }
}))

// write them all to disk
await Promise.all(configs.map(x => x.writeToFile()))

```

### Random helpers

```ts
import path from 'path'
import {
  checkWgIsInstalled,
  generateKeyPair,
  generateConfigString,
  parseConfigString,
  getConfigStringFromFile,
  getConfigObjectFromFile,
} from ''


// check WireGuard is installed on the system and print version
const version = await checkWgIsInstalled()
console.log(version) // wireguard-tools v1.0.20200827 - https://git.zx2c4.com/wireguard-tools/


// generate a WG key pair (needs wg installed on system)
const { publicKey, privateKey, preSharedKey } = await generateKeyPair({ preSharedKey: true })
console.log({ publicKey, privateKey, preSharedKey })
/**
 * {
 *   publicKey: '257CQncfArO8QLIcc23Hhyq2IvnBszCl8XUU9TA42Q4=',
 *   privateKey: '6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q=',
 *   preSharedKey: 'NlqKE2Ja7AAQhDZpevUwi7pjlnU7HZgcPLI0F/gVPfs='
 * }
 */


// Generate a string version of the WgConfig suitable for saving to a Wireguard Config file
const configString = generateConfigString({
  wgInterface: {
    name: 'Client 1',
    address: ['10.10.1.1'],
    privateKey: '6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q'
  },
  peers: [
    {
      allowedIps: ['10.10.1.1/32'],
      publicKey: 'FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s='
    }
  ]
})
console.log(configString)
/**
 * [Interface]
 * # Name = Client 1
 * Address = 10.10.1.1
 * PrivateKey = 6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q
 * 
 * [Peer]
 * PublicKey = FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s=
 * AllowedIPs = 10.10.1.1/32
 */


// Parse a config object from a WireGuard config file string
const configObj = parseConfigString(configString)
console.log(configObj)
/**
 * {
 *   wgInterface: {
 *     address: [ '10.10.1.1' ],
 *     privateKey: '6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q',
 *     name: 'Client 1'
 *   },
 *   peers: [
 *     {
 *       allowedIps: [Array],
 *       publicKey: 'FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s='
 *     }
 *   ]
 * }
 */


// Get a raw wireguard config string from a file
const confStringFromFile = getConfigStringFromFile({
  filePath: path.join(__dirname, '/configs', '/wg0.conf')
})
console.log(confStringFromFile)
/**
 * [Interface]
 * # Name = Client 1
 * Address = 10.10.1.1
 * PrivateKey = 6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q
 *
 * [Peer]
 * PublicKey = FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s=
 * AllowedIPs = 10.10.1.1/32
 */


// Get a parsed WgConfigObject from a wireguard config file
const confObjFromFile = getConfigObjectFromFile({
  filePath: path.join(__dirname, '/configs', '/wg0.conf')
})
console.log(confObjFromFile)
/**
 * {
 *   wgInterface: {
 *     address: [ '10.10.1.1' ],
 *     privateKey: '6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q',
 *     name: 'Client 1'
 *   },
 *   peers: [
 *     {
 *       allowedIps: [Array],
 *       publicKey: 'FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s='
 *     }
 *   ]
 * }
 */
```

### Extensive example

Here is one extensive example of usage that should give you an idea of what to do:

```ts
import path from 'path'
import { WgConfig, getConfigObjectFromFile, createPeerPairs, checkWgIsInstalled } from 'wireguard-tools'

const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')

const test = async () => {
  try {
    // make a new config
    const config1 = new WgConfig({
      wgInterface: {
        address: ['10.10.1.1']
      },
      filePath
    })

    // give the config a name
    config1.wgInterface.name = 'Guardline Server'

    // update some other properties
    config1.wgInterface.postUp = ['echo "Guardline Server Up!"']
    config1.wgInterface.listenPort = 5280

    // make a keypair for the config and a pre-shared key
    const keypair = await config1.generateKeys({ preSharedKey: true })

    // these keys will be saved to the config object
    console.log(keypair.publicKey === config1.publicKey) // true
    console.log(keypair.preSharedKey === config1.preSharedKey) // true
    console.log(keypair.privateKey === config1.wgInterface.privateKey) // true

    // write the config to disk
    await config1.writeToFile()

    // read that file into another config object
    const thatConfigFromFile = await getConfigObjectFromFile({ filePath })
    const config2FilePath = path.join(__dirname, '/configs', '/guardline-server-2.conf')
    const config2 = new WgConfig({
      ...thatConfigFromFile,
      filePath: config2FilePath
    })

    // both configs private key will be the same because config2 has been parsed
    // from the file written by config
    console.log(config1.wgInterface.privateKey === config2.wgInterface.privateKey)

    // however, config2 doesn't have a public key becuase WireGuard doesn't save the
    // the public key in the config file.
    // To get the public key, you'll need to run generateKeys on config2
    // it'll keep it's private key and derive a public key from it
    await config2.generateKeys()

    // so now the two public keys will be the same
    console.log(config1.publicKey === config2.publicKey) // true

    // you can generate a new keypair by passing an arg:
    config2.generateKeys({ overwrite: true })

    // so now their public/private keys are different
    console.log(config1.publicKey === config2.publicKey) // false

    // you can create a peer object from a WgConfig like this
    const config2AsPeer = config2.createPeer({
      allowedIps: ['10.10.1.1/32']
    })

    // you can add a peer to a config like this:
    config1.addPeer(config2AsPeer)

    // and remove a peer like this
    config1.removePeer(config2.publicKey)

    // or you make two WgConfigs peers of each other like this:
    createPeerPairs([
      {
        config: config1,
        // The peer settings to apply when adding this config as a peer
        peerSettings: {
          allowedIps: ['10.10.1.1'],
          preSharedKey: config1.preSharedKey
        }
      },
      {
        config: config2,
        peerSettings: {
          allowedIps: ['10.10.1.2']
        }
      }
    ])

    // That will end up with config1 having config2 as a peer
    // and config2 having config1 as a peer
    console.log(config1.getPeer(config2.publicKey)) // logs the peer
    console.log(config2.getPeer(config1.publicKey)) // logs the peer

    // Check that the system has wireguard installed and log the version like this
    // (will throw an error if not installed)
    const wgVersion = await checkWgIsInstalled()
    console.log(wgVersion)

    // if wireguard is installed, you can bring up your config like this:
    // (make sure it's been written to file first!)
    await config1.writeToFile()
    await config1.up() // Wireguard interface is up

    // you can change something about the interface while it's up
    config1.wgInterface.dns = ['1.1.1.1']
    config1.writeToFile()

    // but make sure you restart the interface for your changes to take effect
    await config1.restart()

    // and finally, when you're done, take down the interface like this
    await config1.down()

    // Thanks for reading!
  } catch (e) {
    console.error(e)
  }
}

test()
```

## To develop

- Clone this repo
- Run `yarn`
- Run `yarn watch`

## TODO

Write more docs as always