Skip to content

Instantly share code, notes, and snippets.

@takuan-osho
Last active January 7, 2024 05:09
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 takuan-osho/068f4417d3652461f3f1b3d97fc4caf7 to your computer and use it in GitHub Desktop.
Save takuan-osho/068f4417d3652461f3f1b3d97fc4caf7 to your computer and use it in GitHub Desktop.
sample codes for generating OpenVPN config file
{
"domains": [
"sample.domain.example.com" # sample domain
]
}
package main
import (
"context"
"encoding/json"
"flag"
"html/template"
"log"
"net"
"os"
"github.com/tailscale/hujson"
"golang.org/x/sync/errgroup"
)
type Data struct {
IPs []string
}
type Config struct {
Domains []string `json:"domains"`
}
func readDomains(filename string) ([]string, error) {
fileBytes, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var cfg Config
b, err := hujson.Standardize(fileBytes)
if err != nil {
return nil, err
}
err = json.Unmarshal(b, &cfg)
if err != nil {
return nil, err
}
return cfg.Domains, nil
}
func readTemplate(filename string) (string, error) {
fileBytes, err := os.ReadFile(filename)
if err != nil {
return "", err
}
return string(fileBytes), nil
}
func main() {
var domainListFile string
var templateFile string
var outputFile string
flag.StringVar(&domainListFile, "file", "domains.jsonc", "file containing a list of domains")
flag.StringVar(&templateFile, "template", "profile.ovpn.tmpl", "OVPN template file written in go template format")
flag.StringVar(&outputFile, "output", "downloaded-client-config-programmed.ovpn", "output file")
flag.Parse()
domains, err := readDomains(domainListFile)
if err != nil {
log.Fatalf("Failed to read domains: %v", err)
}
ipChan := make(chan string, len(domains))
eg, ctx := errgroup.WithContext(context.Background())
for _, domain := range domains {
domain := domain // https://golang.org/doc/faq#closures_and_goroutines
eg.Go(func() error {
ips, err := net.LookupIP(domain)
if err != nil {
return err
}
for _, ip := range ips {
if ip.To4() != nil {
select {
case ipChan <- ip.String():
case <-ctx.Done():
return ctx.Err()
}
}
}
return nil
})
}
go func() {
eg.Wait()
close(ipChan)
}()
ipList := make([]string, 0, len(domains))
for ip := range ipChan {
ipList = append(ipList, ip)
}
if err := eg.Wait(); err != nil {
// If IP lookup fails, it is not a fatal problem and we can still generate the config file
log.Printf("Failed to lookup IPs: %v", err)
}
data := Data{IPs: ipList}
templateText, err := readTemplate(templateFile)
if err != nil {
log.Fatalf("Failed to read template: %v", err)
}
tmpl, err := template.New("config").Parse(templateText)
if err != nil {
log.Fatalf("Failed to parse template: %s", err)
}
file, err := os.Create(outputFile)
if err != nil {
log.Fatalf("Failed to create file: %s", err)
}
defer file.Close()
err = tmpl.Execute(file, data)
if err != nil {
log.Fatalf("Failed to execute template: %s", err)
}
}
client
dev tun
proto udp
remote clientvpn.example.com 443
remote-random-hostname
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-GCM
verb 3
route-nopull
{{range .IPs}}
route {{.}} 255.255.255.255
{{- end }}
<ca>
-----BEGIN CERTIFICATE-----
<sensitive>
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
<sensitive>
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
<sensitive>
-----END PRIVATE KEY-----
</key>
auth-user-pass
reneg-sec 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment