Git Product home page Git Product logo

openvpn-formula's Introduction

openvpn-formula

Travis CI Build Status Semantic Release

Formula to install and configure openvpn server and client.

See the full SaltStack Formulas installation and usage instructions.

If you are interested in writing or contributing to formulas, please pay attention to the Writing Formula Section.

If you want to use this formula, please pay attention to the FORMULA file and/or git tag, which contains the currently released version. This formula is versioned according to Semantic Versioning.

See Formula Versioning Section for more details.

If you need (non-default) configuration, please refer to:

Commit message formatting is significant!!

Please see How to contribute for more details.

Installs OpenVPN.

Configures OpenVPN client and server. Multiple clients and servers are possible.

Configures OpenVPN GUI (Windows only). Sets global registry settings as described here.

Manages TAP-Windows device adapters (Windows only). Ensures that any devices specified with dev_node in pillar exist.

Installs and configures an ifconfig_pool_persist file. Used to assign host IPs.

Don't setup a OpenVPN client service, but add ready-to-use NetworkManager configurations.

See openvpn/pillar.example.

This formula does can optionally deploy certificates and keys, but does not generate them. This must be done manually or with another formula.

Linux testing is done with kitchen-salt.

  • Ruby
  • Docker
$ gem install bundler
$ bundle install
$ bin/kitchen test [platform]

Where [platform] is the platform name defined in kitchen.yml, e.g. debian-9-2019-2-py3.

Creates the docker instance and runs the openvpn main state, ready for testing.

Runs the inspec tests on the actual instance.

Removes the docker instance.

Runs all of the stages above in one go: i.e. destroy + converge + verify + destroy.

Gives you SSH access to the instance for manual testing.

Windows/FreeBSD/OpenBSD testing is done with kitchen-salt.

  • Ruby
  • Virtualbox
  • Vagrant
$ gem install bundler
$ bundle install --with=vagrant
$ bin/kitchen test [platform]

Where [platform] is the platform name defined in kitchen.vagrant.yml, e.g. windows-81-latest-py3.

When testing using Vagrant you must set the environment variable KITCHEN_LOCAL_YAML to kitchen.vagrant.yml. For example:

$ KITCHEN_LOCAL_YAML=kitchen.vagrant.yml bin/kitchen test      # Alternatively,
$ export KITCHEN_LOCAL_YAML=kitchen.vagrant.yml
$ bin/kitchen test

Then run the following commands as needed.

Creates the Vagrant instance and runs the openvpn main state, ready for testing.

Runs the inspec tests on the actual instance.

Removes the Vagrant instance.

Runs all of the stages above in one go: i.e. destroy + converge + verify + destroy.

Gives you RDP/SSH access to the instance for manual testing.

openvpn-formula's People

Contributors

aboe76 avatar alxwr avatar anderbubble avatar aviau avatar baby-gnu avatar cmclaughlin avatar dafyddj avatar daks avatar daschatten avatar diegofd avatar dimitry-unified-streaming avatar genuss avatar iggy avatar javierbertoli avatar kevinschmidt avatar landergate avatar marconett avatar myii avatar nmadhok avatar noelmcloughlin avatar puneetk avatar sc250024 avatar semantic-release-bot avatar stasjok avatar t0fik avatar timjones avatar timwhite avatar viper233 avatar wenzel avatar wwentland avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

openvpn-formula's Issues

[BUG] Unable to manage file: Jinja error: openvpn/macros.jinja using salt-ssh

Your setup

Formula commit hash / release tag

I'm using HEAD on master

Versions reports (master & minion)

Sunning salt-ssh

Salt Version:
          Salt: 3003rc1+348.gd637879696
 
Dependency Versions:
          cffi: Not Installed
      cherrypy: Not Installed
      dateutil: Not Installed
     docker-py: Not Installed
         gitdb: Not Installed
     gitpython: Not Installed
        Jinja2: 2.11.3
       libgit2: Not Installed
      M2Crypto: Not Installed
          Mako: Not Installed
       msgpack: 1.0.2
  msgpack-pure: Not Installed
  mysql-python: Not Installed
     pycparser: Not Installed
      pycrypto: Not Installed
  pycryptodome: 3.10.1
        pygit2: Not Installed
        Python: 3.8.10 (default, Jun  2 2021, 10:49:15)
  python-gnupg: Not Installed
        PyYAML: 5.4.1
         PyZMQ: 21.0.2
         smmap: Not Installed
       timelib: Not Installed
       Tornado: 4.5.3
           ZMQ: 4.3.3
 
System Versions:
          dist: ubuntu 20.04 focal
        locale: utf-8
       machine: x86_64
       release: 5.11.0-37-generic
        system: Linux
       version: Ubuntu 20.04 focal

Bug details

Describe the bug

The following error happens when trying to apply openvpn formula useing salt-ssh.
Unable to manage file: Jinja error: openvpn/macros.jinja
I have the following in my Saltfile which normally fixes the issue.
- salt://openvpn/macros.jinja

Here's the output

     Comment: Unable to manage file: Jinja error: openvpn/macros.jinja
              /var/tmp/.salt_6cb4b9_salt/running_data/var/cache/salt/minion/files/base/openvpn/files/common_opts.jinja(1):
              ---
              {% from "openvpn/macros.jinja" import multipart_param with context %}    <======================
              
              {%- if config.daemon is defined and config.daemon == True %}
              daemon
              {%- endif -%}
              
              [...]
              ---
              Traceback (most recent call last):
                File "/var/tmp/.salt_6cb4b9_salt/pyall/salt/utils/templates.py", line 501, in render_jinja_tmpl
                  output = template.render(**decoded_context)
                File "/var/tmp/.salt_6cb4b9_salt/pyall/jinja2/environment.py", line 1090, in render
                  self.environment.handle_exception()
                File "/var/tmp/.salt_6cb4b9_salt/pyall/jinja2/environment.py", line 832, in handle_exception
                  reraise(*rewrite_traceback_stack(source=source))
                File "/var/tmp/.salt_6cb4b9_salt/pyall/jinja2/_compat.py", line 28, in reraise
                  raise value.with_traceback(tb)
                File "<template>", line 5, in top-level template code
                File "/var/tmp/.salt_6cb4b9_salt/running_data/var/cache/salt/minion/files/base/openvpn/files/common_opts.jinja", line 1, in top-level template code
                  {% from "openvpn/macros.jinja" import multipart_param with context %}
                File "/var/tmp/.salt_6cb4b9_salt/pyall/salt/utils/jinja.py", line 198, in get_source
                  raise TemplateNotFound(template)
              jinja2.exceptions.TemplateNotFound: openvpn/macros.jinja

Feature request: Ability to define additional capabilites in systemd file

Using the pam-auth plugin that ships with OpenVPN, requires editing of the systemd file to add CAP_AUDIT_WRITE to be able to authenticate against PAM.

At present the formula does not manage the systemd file, and instead uses the one that ships with the package. Can we add the ability to amend the systemd file with the additional capabilty if required?

Thanks.

Formula does not create config file.

I set this up and it creates the systemd service and installs openvpn, but it doesn't create the config file, so the service fails to start.

This is on CentOS 7.

          ID: openvpn_myclient_service
    Function: service.running
        Name: openvpn@myclient
      Result: False
     Comment: Service openvpn@myclient is already enabled, and is dead
     Started: 23:48:11.589550
    Duration: 102.755 ms
     Changes:   

[root@gateway etc]# systemctl status [email protected]
โ— [email protected] - OpenVPN Robust And Highly Flexible Tunneling Application On myclient
   Loaded: loaded (/usr/lib/systemd/system/[email protected]; enabled; vendor preset: disabled)
   Active: failed (Result: exit-code) since Tue 2017-03-07 23:45:57 UTC; 6s ago
  Process: 18570 ExecStart=/usr/sbin/openvpn --daemon --writepid /var/run/openvpn/%i.pid --cd /etc/openvpn/ --config %i.conf (code=exited, status=1/FAILURE)

Mar 07 23:45:57 gateway.example.com systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On myclient...
Mar 07 23:45:57 gateway.example.com systemd[1]: [email protected]: control process exited, code=exited status=1
Mar 07 23:45:57 gateway.example.com systemd[1]: Failed to start OpenVPN Robust And Highly Flexible Tunneling Application On myclient.
Mar 07 23:45:57 gateway.example.com systemd[1]: Unit [email protected] entered failed state.
Mar 07 23:45:57 gateway.example.com systemd[1]: [email protected] failed.
[root@gateway ~]# cd /etc/openvpn
[root@gateway openvpn]# ls -lha
total 16K
drwxr-xr-x   2 root root 4.0K Mar  7 23:28 .
drwxr-xr-x. 79 root root 4.0K Mar  7 23:47 ..
-rw-r--r--   1 root root  245 Mar  7 23:26 dh1024.pem
-rw-r--r--   1 root root  424 Mar  7 23:28 dh2048.pem

Key file readable for anyone

The state deploys world-readable keys.
config.sls should be updated as follows:

{% if config.key is defined and config.key_content is defined %}
# Deploy {{ type }} {{ name }} private key file
openvpn_config_{{ type }}_{{ name }}_key_file:
  file.managed:
    - name: {{ config.key }}
    - contents_pillar: openvpn:{{ type }}:{{ name }}:key_content
    - mode: '660'
    - makedirs: True
    - watch_in:
      - service: openvpn_service
{% endif %}

Pillar: implement *_source as an alternative to *_content

There's no need to funnel data through ca_content, ta_content and others. This just slows down Pillar.

In my Pillar definitions I have do to stunts like this which are just not needed if a source file is provided:

       ta_content: |
         {{ __salt__['cp.get_file_str']('/path/to/predefined/openvpn_tls_auth/'+name+'.ta.key')|indent(8) }}

ID declaration in service.sls failing to work upon first run.

I'm having a problem getting this formula to work properly the first time out. I have a restart of openvpn at the very end of it (in the config.sls file) all to make sure the changes made to the certificate are reloaded. Here is the error I get when ID openvpn_{{ name }}_service tries to run the first time out.

openvpn_service_restart:
  cmd.run:
    - name: service {{ map.service }} restart
    - order: last

I even tried to put this ID in the services.sls file before the ID openvpn_{{ name }}_service, but no dice. I look in the log of what salt did, and the openvpn_pkgs are installed first. Regardless, it was my thought that this wouldn't run unless the items in the Require: block was met.

However, the first time I run it, I get the following error:

          ID: openvpn_myserver_service
    Function: service.running
        Name: openvpn@myserver
      Result: False
     Comment: Running scope as unit run-r70bab1a6cfcd4241a068d9ca2a5cb485.scope.
              Job for [email protected] failed because the control process exited with error code. See "systemctl status [email protected]" and "journalctl -xe" for details.
     Started: 20:12:07.482321
    Duration: 83.163 ms
     Changes:  

If I run it a second time, it works. The errors I get in syslog are useless as what appeared to be produced in the systemctl output. Do you have any ideas as to what needs to be satisfied before this works properly? I am using salt-ssh to apply the state rather than plain salt.

from /var/log/syslog:

Jan 30 20:12:06 openvpn systemd[1]: Starting OpenVPN service...
Jan 30 20:12:06 openvpn systemd[1]: Started OpenVPN service.
Jan 30 20:12:07 openvpn systemd[1]: Started /bin/systemctl start [email protected].
Jan 30 20:12:07 openvpn systemd[1]: Created slice system-openvpn.slice.
Jan 30 20:12:07 openvpn systemd[1]: Starting OpenVPN connection to myserver..
Jan 30 20:12:07 openvpn systemd[1]: [email protected]: Control process exited, code=exited status=1
Jan 30 20:12:07 openvpn systemd[1]: Failed to start OpenVPN connection to myserver.
Jan 30 20:12:07 openvpn systemd[1]: [email protected]: Unit entered failed state.
Jan 30 20:12:07 openvpn systemd[1]: [email protected]: Failed with result 'exit-code'.
Jan 30 20:12:07 openvpn systemd[1]: Started /bin/systemctl stop [email protected].
Jan 30 20:12:07 openvpn systemd[1]: Stopped OpenVPN connection to myserver
Jan 30 20:12:07 openvpn systemd[1]: Started /bin/systemctl start [email protected].
Jan 30 20:12:07 openvpn systemd[1]: Starting OpenVPN connection to myserver...
Jan 30 20:12:07 openvpn systemd[1]: [email protected]: Control process exited, code=exited status=1
Jan 30 20:12:07 openvpn systemd[1]: Failed to start OpenVPN connection to myserver.
Jan 30 20:12:07 openvpn systemd[1]: [email protected]: Unit entered failed state.
Jan 30 20:12:07 openvpn systemd[1]: [email protected]: Failed with result 'exit-code'.

EDIT: well, it looks like it needs to deploy the config files first. I replaced the ID with:

openvpn_{{ name }}_service:
  cmd.run:
    - name: systemctl start {{ service_name }}.service
    - require:
      - pkg: openvpn_pkgs
      - sls: openvpn

Which then results in the following errors in /var/log/syslog:

Jan 30 21:47:59 openvpn systemd[1]: Started ACPI event daemon.
Jan 30 21:48:03 openvpn systemd[1]: Created slice system-openvpn.slice.
Jan 30 21:48:03 openvpn systemd[1]: Starting OpenVPN connection to redteam1...
Jan 30 21:48:03 openvpn ovpn-myserver[3735]: Options error: In [CMD-LINE]:1: Error opening configuration file: /etc/openvpn/myserver.conf
Jan 30 21:48:03 openvpn ovpn-myserver[3735]: Use --help for more information.
Jan 30 21:48:03 openvpn systemd[1]: [email protected]: Control process exited, code=exited status=1
Jan 30 21:48:03 openvpn systemd[1]: Failed to start OpenVPN connection to myserver.
Jan 30 21:48:03 openvpn systemd[1]: [email protected]: Unit entered failed state.
Jan 30 21:48:03 openvpn systemd[1]: [email protected]: Failed with result 'exit-code'.

Thanks

Service & config-dir split start with fedora 27

After upgrading some box to Fedora 27 it appears that the default package splits openvpn configuration with two services and config directories:

For server:
openvpn-server@{name}.service
/etc/openvpn/server/{name}.conf

For client:
openvpn-client@{name}.service
/etc/openvpn/client/{name}.conf

I'm not quite familiar with Jinja maps to patch it directly that's why i'm opening this issue.

server_bridge bugged in openvpn.config state

server_bridge and server are exclusive while server has a default value set in jinja.

Update server.jinja as follows

{%- if config.server is defined %}
server {{ config.server }}
{%- else %}
{%- if config.server_bridge is defined %}
# server_bridge defined, skipping default server directive
{%- else %}
server 10.8.0.0 255.255.255.0
{%- endif %}
{%- endif %}

Problems with this formula being a github fork

There are a few issues regarding this formula being a linked github fork:

  1. It cannot be found via search ( https://github.com/search?utf8=%E2%9C%93&q=openvpn+formula )
  2. Any type of contributions on this formula do not show in contributor's activity graphs

The first one being really bad.

It is possible to solve this by one of these steps (depending which one is easiest to accompish for the maintainers of the project):

Solution is someone from the organization owners emailing github support and asking them to unlink the "fork" link. Everything will be the same, we will only loose the "fork from daschatten/locale-fromula" message below the repositories name at the top. This will fix the mentioned problems.

[FEATURE] use system user and group

Is your feature request related to a problem?

Not related to a problem, could be related to best practices.

Describe the solution you'd like

Currently the user and group are created as "normal" non-system. I would like to have them created as system-user and system-group with no home-dir or have the option to do so in the pillar ... only if there is a good reason not to do so.

general_config.sls

openvpn_group:
  group.present:
    - name: {{ map.group }}
    - system: true
    - require_in:
      - file: openvpn_config_dir
      - sls: openvpn.config
{%- endif %}
{%- if not (map.manage_user is sameas false or map.user == 'nobody') %}
openvpn_user:
  user.present:
    - name: {{ map.user }}
    - home: "/nonexistent"
    - createhome: false
    - system: true
    - gid: {{ map.group }}
{%-   if manage_group %}
    - require:
      - group: openvpn_group
{%-   endif %}
    - require_in:
      - file: openvpn_config_dir
      - sls: openvpn.config
{%- endif %}

Describe alternatives you've considered

I can make the changes in my own fork, but in general thats not realy handy.

Additional context

I currently do not have much time to create a pull-request myself unfortunately. But if no one picks this up, I'll see what I can do later.

tunnels are restarted every time highstate runs

I recently upgraded to the latest version of this formula for systemd suppor (on AWS Linux 2).

And I noticed every time I run highstate my tunnels are restarted.

          ID: obsolete_openvpn_employees-udp-1194_service
    Function: service.dead
        Name: openvpn@employees-udp-1194
      Result: True
     Comment: Service openvpn@employees-udp-1194 has been disabled, and is dead
     Started: 04:51:09.669200
    Duration: 250.489 ms
     Changes:
              ----------
              openvpn@employees-udp-1194:
                  True
----------
          ID: openvpn_employees-udp-1194_service
    Function: service.running
        Name: openvpn@employees-udp-1194
      Result: True
     Comment: Service openvpn@employees-udp-1194 has been enabled, and is running
     Started: 04:51:10.094798
    Duration: 254.426 ms
     Changes:
              ----------
              openvpn@employees-udp-1194:
                  True

In service.sls there's a comment "For an successful upgrade we need to make sure the old services are deactivated". I guess there's a bug on the name of the old service though? It's not clear what case we're trying to cover here. Perhaps we can just delete the entire service.dead block?

[BUG] Windows (support) testing has been broken since around 2021-W42

Your setup

Formula commit hash / release tag

As provided in the CI.

Versions reports (master & minion)

As provided in the CI.

Pillar / config used

As provided in the CI.


Bug details

Describe the bug

Windows testing (and probably support) has been broken from around W42 of 2021 (failing in every weekly testing until now):

Sample of the failed states:

        ----------
          ID: openvpn_create_dh_512
           Function: cmd.run
        Name: "C:\Program Files\OpenVPN\bin\openssl" dhparam -out "C:\Program Files\OpenVPN\config/dh512.pem" 512
             Result: False
            Comment: Command ""C:\Program Files\OpenVPN\bin\openssl" dhparam -out "C:\Program Files\OpenVPN\config/dh512.pem" 512" run
            Started: 10:46:38.345913
           Duration: 13427.013 ms
            Changes:   
              ----------
              pid:
                  7140
              retcode:
                  1
              stderr:
                  '"C:\Program Files\OpenVPN\bin\openssl"' is not recognized as an internal or external command,
                  operable program or batch file.
        ----------
...
        ----------
          ID: openvpn_service
           Function: service.running
        Name: OpenVPNService
             Result: False
            Comment: An exception occurred in this state: Traceback (most recent call last):
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\utils\win_service.py", line 122, in info
                  handle_svc = win32service.OpenService(
              pywintypes.error: (1060, 'OpenService', 'The specified service does not exist as an installed service.')
              
              During handling of the above exception, another exception occurred:
              
              Traceback (most recent call last):
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\state.py", line 2179, in call
                  ret = self.states[cdata["full"]](
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\loader\lazy.py", line 149, in __call__
                  return self.loader.run(run_func, *args, **kwargs)
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\loader\lazy.py", line 1201, in run
                  return self._last_context.run(self._run_as, _func_or_method, *args, **kwargs)
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\loader\lazy.py", line 1216, in _run_as
                  return _func_or_method(*args, **kwargs)
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\loader\lazy.py", line 1249, in wrapper
                  return f(*args, **kwargs)
                File "C:\Program Files\Salt Project\Salt\bin\Lib\site-packages\salt-3004-py3.8.egg\salt\states\service.py", line 1018, in mod_watch
                  if __salt__["service.status"](name, sig, **status_kwargs):
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\loader\lazy.py", line 149, in __call__
                  return self.loader.run(run_func, *args, **kwargs)
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\loader\lazy.py", line 1201, in run
                  return self._last_context.run(self._run_as, _func_or_method, *args, **kwargs)
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\loader\lazy.py", line 1216, in _run_as
                  return _func_or_method(*args, **kwargs)
                File "C:\Program Files\Salt Project\Salt\bin\Lib\site-packages\salt-3004-py3.8.egg\salt\modules\win_service.py", line 511, in status
                  results[service] = info(service)["Status"] in ["Running", "Stop Pending"]
                File "C:\Program Files\Salt Project\Salt\bin\Lib\site-packages\salt-3004-py3.8.egg\salt\modules\win_service.py", line 304, in info
                  return salt.utils.win_service.info(name=name)
                File "C:\Program Files\Salt Project\Salt\bin\lib\site-packages\salt-3004-py3.8.egg\salt\utils\win_service.py", line 131, in info
                  raise CommandExecutionError("Failed To Open {}: {}".format(name, exc.strerror))
              salt.exceptions.CommandExecutionError: Failed To Open OpenVPNService: The specified service does not exist as an installed service.

Steps to reproduce the bug

Run Kitchen testing.

Expected behaviour

CI to pass again, as before.

Attempts to fix the bug

Additional context

Empty config

Hello

on debian strech and saltstack 2018.3.2+ds-1 generate empty config

# OpenVPN server configuration 'xxxx'
# Managed by Salt
# Template: salt://openvpn/files/server.jinja


; Networking
proto udp


; Operating system
dev tun
user nobody
group nogroup
persist-key
persist-tun


; Data transfer


; Logging / Debugging
verb 3


; Cryptographic parameters
# ca is not set !
# cert is not set !
# key is not set !

; check with https://bettercrypto.org/static/applied-crypto-hardening.pdf

; Certificate verification
; (Anti MITM)


; Up/Down Scripts


tls-server
port 1194

i must remove in file openvpn/config.sls on line 37 (this) json_encode_dict and now config for server is generate OK.

config:
`openvpn:
  lookup:
    dh_files: ['4096']
  server:
    name:
      port: 1194
      proto: udp
      topology: subnet
      ca: /etc/openvpn/ca.crt
      ca_content: |
        -----BEGIN CERTIFICATE-----
        -----END CERTIFICATE-----
      cert: /etc/openvpn/cert.pem
      cert_content: |
        -----BEGIN CERTIFICATE-----
        -----END CERTIFICATE-----
      key: /etc/openvpn/key.pem
      key_content: |
        -----BEGIN PRIVATE KEY-----
        -----END PRIVATE KEY-----
      dh: dh4096.pem
      server: 'x.0.0.0 255.255.255.0'
      ifconfig_pool_persist: ipp.txt
      push:
        - "route x.0.0.0 255.255.255.0"
      client_config:
        xxxxx: |
          ifconfig-push x.0.0.x 255.255.255.0
      client_to_client: False
      tls_auth: /etc/openvpn/tls.key 0
      tls_crypt: /etc/openvpn/tls.key
      tls_version_min: 1.2
      ta_content: |
        -----BEGIN OpenVPN Static key V1-----
        -----END OpenVPN Static key V1-----
      comp_lzo:
      user: nobody
      group: nogroup
      persist_key:
      persist_tun:
      status: /var/log/openvpn/openvpn-status.log
      log: /var/log/openvpn/openvpn.log
      client_config_dir: clients
`

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.