From 8f78bd5092712aa584662aee7dc27d4bd0876d65 Mon Sep 17 00:00:00 2001
From: Ben Mitchinson
Date: Tue, 11 Feb 2020 16:24:41 -0600
Subject: [PATCH 01/12] Dockerfile for PWA - Needs Compose + Volumes
---
pwa/.dockerignore | 2 ++
pwa/Dockerfile | 16 ++++++++++++++++
2 files changed, 18 insertions(+)
create mode 100644 pwa/.dockerignore
create mode 100644 pwa/Dockerfile
diff --git a/pwa/.dockerignore b/pwa/.dockerignore
new file mode 100644
index 0000000..929dccc
--- /dev/null
+++ b/pwa/.dockerignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log*
\ No newline at end of file
diff --git a/pwa/Dockerfile b/pwa/Dockerfile
new file mode 100644
index 0000000..a8041eb
--- /dev/null
+++ b/pwa/Dockerfile
@@ -0,0 +1,16 @@
+FROM node:12.10.0
+
+WORKDIR /app
+# cd app folder within container.
+
+COPY package*.json ./
+
+RUN npm install
+
+COPY . .
+# copy {everything in PWA} {WORKDIR (/app)}
+# this is okay because node_modules is ignored
+
+EXPOSE 3000
+
+CMD ["npm", "start"]
\ No newline at end of file
From 457bbd9ad9a97a6b42a7945c6673268b81ee876d Mon Sep 17 00:00:00 2001
From: Ben Mitchinson
Date: Tue, 11 Feb 2020 16:39:14 -0600
Subject: [PATCH 02/12] compose with mounted volumes
---
docker-compose.yml | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 docker-compose.yml
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..6165d62
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,12 @@
+version: '3'
+
+services:
+ pwa:
+ build:
+ context: ./pwa
+ command: npm start
+ volumes:
+ - ./pwa/:/app
+ ports:
+ - "8000:3000"
+
\ No newline at end of file
From 14f7ae67297741bfa082322e7166b8f6bdf14518 Mon Sep 17 00:00:00 2001
From: abpwrs
Date: Wed, 12 Feb 2020 11:43:42 -0600
Subject: [PATCH 03/12] ENH: changing from port 8000 to 3000 for consistency
---
docker-compose.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index 6165d62..652a30b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,5 +8,5 @@ services:
volumes:
- ./pwa/:/app
ports:
- - "8000:3000"
+ - "3000:3000"
\ No newline at end of file
From 64e766e90d0391eaf74e7edfe675c31b6efc8be9 Mon Sep 17 00:00:00 2001
From: Ben Mitchinson
Date: Fri, 14 Feb 2020 09:50:27 -0600
Subject: [PATCH 04/12] name the pwa container
---
docker-compose.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index 6165d62..50999bc 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,4 +9,4 @@ services:
- ./pwa/:/app
ports:
- "8000:3000"
-
\ No newline at end of file
+ container_name: haiti_pwa
\ No newline at end of file
From 384e90da662f75cf97ac68c69e825ba42a4408c0 Mon Sep 17 00:00:00 2001
From: Ben Mitchinson <115bwm@gmail.com>
Date: Sat, 15 Feb 2020 22:18:35 -0600
Subject: [PATCH 05/12] Create eduroam_config_cat_gen.py
---
pine/eduroam_config_cat_gen.py | 1011 ++++++++++++++++++++++++++++++++
1 file changed, 1011 insertions(+)
create mode 100644 pine/eduroam_config_cat_gen.py
diff --git a/pine/eduroam_config_cat_gen.py b/pine/eduroam_config_cat_gen.py
new file mode 100644
index 0000000..79e63f2
--- /dev/null
+++ b/pine/eduroam_config_cat_gen.py
@@ -0,0 +1,1011 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ * edited so that NM_AVAILABLE is false, originally from https://cat.eduroam.org/
+
+ * **************************************************************************
+ * Contributions to this work were made on behalf of the GÉANT project,
+ * a project that has received funding from the European Union’s Framework
+ * Programme 7 under Grant Agreements No. 238875 (GN3)
+ * and No. 605243 (GN3plus), Horizon 2020 research and innovation programme
+ * under Grant Agreements No. 691567 (GN4-1) and No. 731122 (GN4-2).
+ * On behalf of the aforementioned projects, GEANT Association is
+ * the sole owner of the copyright in all material which was developed
+ * by a member of the GÉANT project.
+ * GÉANT Vereniging (Association) is registered with the Chamber of
+ * Commerce in Amsterdam with registration number 40535155 and operates
+ * in the UK as a branch of GÉANT Vereniging.
+ *
+ * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands.
+ * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
+ *
+ * License: see the web/copyright.inc.php file in the file structure or
+ * /copyright.php after deploying the software
+
+Authors:
+ Tomasz Wolniewicz
+ Michał Gasewicz (Network Manager support)
+
+Contributors:
+ Steffen Klemer https://github.com/sklemer1
+ ikerb7 https://github.com/ikreb7
+Many thanks for multiple code fixes, feature ideas, styling remarks
+much of the code provided by them in the form of pull requests
+has been incorporated into the final form of this script.
+
+This script is the main body of the CAT Linux installer.
+In the generation process configuration settings are added
+as well as messages which are getting translated into the language
+selected by the user.
+
+The script is meant to run both under python 2.7 and python3. It tests
+for the crucial dbus module and if it does not find it and if it is not
+running python3 it will try rerunning iself again with python3.
+"""
+import argparse
+import base64
+import getpass
+import os
+import re
+import subprocess
+import sys
+import uuid
+from shutil import copyfile
+
+NM_AVAILABLE = True
+CRYPTO_AVAILABLE = True
+DEBUG_ON = False
+DEV_NULL = open("/dev/null", "w")
+STDERR_REDIR = DEV_NULL
+
+
+def debug(msg):
+ """Print debugging messages to stdout"""
+ if not DEBUG_ON:
+ return
+ print("DEBUG:" + str(msg))
+
+
+def missing_dbus():
+ """Handle missing dbus module"""
+ global NM_AVAILABLE
+ debug("Cannot import the dbus module")
+ NM_AVAILABLE = False
+
+
+def byte_to_string(barray):
+ """conversion utility"""
+ return "".join([chr(x) for x in barray])
+
+
+def get_input(prompt):
+ if sys.version_info.major < 3:
+ return raw_input(prompt) # pylint: disable=undefined-variable
+ return input(prompt)
+
+
+debug(sys.version_info.major)
+
+
+try:
+ import dbus
+except ImportError:
+ if sys.version_info.major == 3:
+ missing_dbus()
+ if sys.version_info.major < 3:
+ try:
+ subprocess.call(['python3'] + sys.argv)
+ except:
+ missing_dbus()
+ sys.exit(0)
+
+try:
+ from OpenSSL import crypto
+except ImportError:
+ CRYPTO_AVAILABLE = False
+
+
+if sys.version_info.major == 3 and sys.version_info.minor >= 7:
+ import distro
+else:
+ import platform
+
+
+# the function below was partially copied
+# from https://ubuntuforums.org/showthread.php?t=1139057
+def detect_desktop_environment():
+ """
+ Detect what desktop type is used. This method is prepared for
+ possible future use with password encryption on supported distros
+ """
+ desktop_environment = 'generic'
+ if os.environ.get('KDE_FULL_SESSION') == 'true':
+ desktop_environment = 'kde'
+ elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
+ desktop_environment = 'gnome'
+ else:
+ try:
+ shell_command = subprocess.Popen(['xprop', '-root',
+ '_DT_SAVE_MODE'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = shell_command.communicate()
+ info = out.decode('utf-8').strip()
+ except (OSError, RuntimeError):
+ pass
+ else:
+ if ' = "xfce4"' in info:
+ desktop_environment = 'xfce'
+ return desktop_environment
+
+
+def get_system():
+ """
+ Detect Linux platform. Not used at this stage.
+ It is meant to enable password encryption in distros
+ that can handle this well.
+ """
+ if sys.version_info.major == 3 and sys.version_info.minor >= 7:
+ system = distro.linux_distribution()
+ else:
+ system = platform.linux_distribution()
+ desktop = detect_desktop_environment()
+ return [system[0], system[1], desktop]
+
+
+def run_installer():
+ """
+ This is the main installer part. It tests for MN availability
+ gets user credentials and starts a proper installer.
+ """
+ global DEBUG_ON
+ global NM_AVAILABLE
+ username = ''
+ password = ''
+ silent = False
+ pfx_file = ''
+ parser = argparse.ArgumentParser(description='eduroam linux installer.')
+ parser.add_argument('--debug', '-d', action='store_true', dest='debug',
+ default=False, help='set debug flag')
+ parser.add_argument('--username', '-u', action='store', dest='username',
+ help='set username')
+ parser.add_argument('--password', '-p', action='store', dest='password',
+ help='set text_mode flag')
+ parser.add_argument('--silent', '-s', action='store_true', dest='silent',
+ help='set silent flag')
+ parser.add_argument('--pfxfile', action='store', dest='pfx_file',
+ help='set path to user certificate file')
+ args = parser.parse_args()
+ if args.debug:
+ DEBUG_ON = True
+ print("Running debug mode")
+
+ if args.username:
+ username = args.username
+ if args.password:
+ password = args.password
+ if args.silent:
+ silent = args.silent
+ if args.pfx_file:
+ pfx_file = args.pfx_file
+ debug(get_system())
+ debug("Calling InstallerData")
+ installer_data = InstallerData(silent=silent, username=username,
+ password=password, pfx_file=pfx_file)
+
+ # test dbus connection
+ if not NM_AVAILABLE:
+ print('NM avail')
+ config_tool = CatNMConfigTool()
+ if config_tool.connect_to_nm() is None:
+ NM_AVAILABLE = False
+ if NM_AVAILABLE:
+ print('NM not avail')
+ # no dbus so ask if the user will want wpa_supplicant config
+ if installer_data.ask(Messages.save_wpa_conf, Messages.cont, 1):
+ sys.exit(1)
+ installer_data.get_user_cred()
+ installer_data.save_ca()
+ if not NM_AVAILABLE:
+ config_tool.add_connections(installer_data)
+ else:
+ wpa_config = WpaConf()
+ wpa_config.create_wpa_conf(Config.ssids, installer_data)
+ installer_data.show_info(Messages.installation_finished)
+
+
+class Messages(object):
+ """
+ These are initial definitions of messages, but they will be
+ overridden with translated strings.
+ """
+ quit = "Really quit?"
+ username_prompt = "enter your userid"
+ enter_password = "enter password"
+ enter_import_password = "enter your import password"
+ incorrect_password = "incorrect password"
+ repeat_password = "repeat your password"
+ passwords_differ = "passwords do not match"
+ installation_finished = "Installation successful"
+ cat_dir_exists = "Directory {} exists; some of its files may be " \
+ "overwritten."
+ cont = "Continue?"
+ nm_not_supported = "This NetworkManager version is not supported"
+ cert_error = "Certificate file not found, looks like a CAT error"
+ unknown_version = "Unknown version"
+ dbus_error = "DBus connection problem, a sudo might help"
+ yes = "Y"
+ nay = "N"
+ p12_filter = "personal certificate file (p12 or pfx)"
+ all_filter = "All files"
+ p12_title = "personal certificate file (p12 or pfx)"
+ save_wpa_conf = "NetworkManager configuration failed, " \
+ "but we may generate a wpa_supplicant configuration file " \
+ "if you wish. Be warned that your connection password will be saved " \
+ "in this file as clear text."
+ save_wpa_confirm = "Write the file"
+ wrongUsernameFormat = "Error: Your username must be of the form " \
+ "'xxx@institutionID' e.g. 'john@example.net'!"
+ wrong_realm = "Error: your username must be in the form of 'xxx@{}'. " \
+ "Please enter the username in the correct format."
+ wrong_realm_suffix = "Error: your username must be in the form of " \
+ "'xxx@institutionID' and end with '{}'. Please enter the username " \
+ "in the correct format."
+ user_cert_missing = "personal certificate file not found"
+ # "File %s exists; it will be overwritten."
+ # "Output written to %s"
+
+
+class Config(object):
+ """
+ This is used to prepare settings during installer generation.
+ """
+ instname = ""
+ profilename = ""
+ url = ""
+ email = ""
+ title = "eduroam CAT"
+ servers = []
+ ssids = []
+ del_ssids = []
+ eap_outer = ''
+ eap_inner = ''
+ use_other_tls_id = False
+ server_match = ''
+ anonymous_identity = ''
+ CA = ""
+ init_info = ""
+ init_confirmation = ""
+ tou = ""
+ sb_user_file = ""
+ verify_user_realm_input = False
+ user_realm = ""
+ hint_user_input = False
+
+
+class InstallerData(object):
+ """
+ General user interaction handling, supports zenity, kdialog and
+ standard command-line interface
+ """
+
+ def __init__(self, silent=False, username='', password='', pfx_file=''):
+ self.graphics = ''
+ self.username = username
+ self.password = password
+ self.silent = silent
+ self.pfx_file = pfx_file
+ debug("starting constructor")
+ if silent:
+ self.graphics = 'tty'
+ else:
+ self.__get_graphics_support()
+ self.show_info(Config.init_info.format(Config.instname,
+ Config.email, Config.url))
+ if self.ask(Config.init_confirmation.format(Config.instname,
+ Config.profilename),
+ Messages.cont, 1):
+ sys.exit(1)
+ if Config.tou != '':
+ if self.ask(Config.tou, Messages.cont, 1):
+ sys.exit(1)
+ if os.path.exists(os.environ.get('HOME') + '/.cat_installer'):
+ if self.ask(Messages.cat_dir_exists.format(
+ os.environ.get('HOME') + '/.cat_installer'),
+ Messages.cont, 1):
+ sys.exit(1)
+ else:
+ os.mkdir(os.environ.get('HOME') + '/.cat_installer', 0o700)
+
+ def save_ca(self):
+ """
+ Save CA certificate to .cat_installer directory
+ (create directory if needed)
+ """
+ certfile = os.environ.get('HOME') + '/.cat_installer/ca.pem'
+ debug("saving cert")
+ with open(certfile, 'w') as cert:
+ cert.write(Config.CA + "\n")
+
+ def ask(self, question, prompt='', default=None):
+ """
+ Propmpt user for a Y/N reply, possibly supplying a default answer
+ """
+ if self.silent:
+ return 0
+ if self.graphics == 'tty':
+ yes = Messages.yes[:1].upper()
+ nay = Messages.nay[:1].upper()
+ print("\n-------\n" + question + "\n")
+ while True:
+ tmp = prompt + " (" + Messages.yes + "/" + Messages.nay + ") "
+ if default == 1:
+ tmp += "[" + yes + "]"
+ elif default == 0:
+ tmp += "[" + nay + "]"
+ inp = get_input(tmp)
+ if inp == '':
+ if default == 1:
+ return 0
+ if default == 0:
+ return 1
+ i = inp[:1].upper()
+ if i == yes:
+ return 0
+ if i == nay:
+ return 1
+ if self.graphics == "zenity":
+ command = ['zenity', '--title=' + Config.title, '--width=500',
+ '--question', '--text=' + question + "\n\n" + prompt]
+ elif self.graphics == 'kdialog':
+ command = ['kdialog', '--yesno', question + "\n\n" + prompt,
+ '--title=', Config.title]
+ returncode = subprocess.call(command, stderr=STDERR_REDIR)
+ return returncode
+
+ def show_info(self, data):
+ """
+ Show a piece of information
+ """
+ if self.silent:
+ return
+ if self.graphics == 'tty':
+ print(data)
+ return
+ if self.graphics == "zenity":
+ command = ['zenity', '--info', '--width=500', '--text=' + data]
+ elif self.graphics == "kdialog":
+ command = ['kdialog', '--msgbox', data]
+ else:
+ sys.exit(1)
+ subprocess.call(command, stderr=STDERR_REDIR)
+
+ def confirm_exit(self):
+ """
+ Confirm exit from installer
+ """
+ ret = self.ask(Messages.quit)
+ if ret == 0:
+ sys.exit(1)
+
+ def alert(self, text):
+ """Generate alert message"""
+ if self.silent:
+ return
+ if self.graphics == 'tty':
+ print(text)
+ return
+ if self.graphics == 'zenity':
+ command = ['zenity', '--warning', '--text=' + text]
+ elif self.graphics == "kdialog":
+ command = ['kdialog', '--sorry', text]
+ else:
+ sys.exit(1)
+ subprocess.call(command, stderr=STDERR_REDIR)
+
+ def prompt_nonempty_string(self, show, prompt, val=''):
+ """
+ Prompt user for input
+ """
+ if self.graphics == 'tty':
+ if show == 0:
+ while True:
+ inp = str(getpass.getpass(prompt + ": "))
+ output = inp.strip()
+ if output != '':
+ return output
+ while True:
+ inp = str(get_input(prompt + ": "))
+ output = inp.strip()
+ if output != '':
+ return output
+
+ if self.graphics == 'zenity':
+ if val == '':
+ default_val = ''
+ else:
+ default_val = '--entry-text=' + val
+ if show == 0:
+ hide_text = '--hide-text'
+ else:
+ hide_text = ''
+ command = ['zenity', '--entry', hide_text, default_val,
+ '--width=500', '--text=' + prompt]
+ elif self.graphics == 'kdialog':
+ if show == 0:
+ hide_text = '--password'
+ else:
+ hide_text = '--inputbox'
+ command = ['kdialog', hide_text, prompt]
+
+ output = ''
+ while not output:
+ shell_command = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = shell_command.communicate()
+ output = out.decode('utf-8').strip()
+ if shell_command.returncode == 1:
+ self.confirm_exit()
+ return output
+
+ def get_user_cred(self):
+ """
+ Get user credentials both username/password and personal certificate
+ based
+ """
+ if Config.eap_outer == 'PEAP' or Config.eap_outer == 'TTLS':
+ self.__get_username_password()
+ if Config.eap_outer == 'TLS':
+ self.__get_p12_cred()
+
+ def __get_username_password(self):
+ """
+ read user password and set the password property
+ do nothing if silent mode is set
+ """
+ password = "a"
+ password1 = "b"
+ if self.silent:
+ return
+ if self.username:
+ user_prompt = self.username
+ elif Config.hint_user_input:
+ user_prompt = '@' + Config.user_realm
+ else:
+ user_prompt = ''
+ while True:
+ self.username = self.prompt_nonempty_string(
+ 1, Messages.username_prompt, user_prompt)
+ if self.__validate_user_name():
+ break
+ while password != password1:
+ password = self.prompt_nonempty_string(
+ 0, Messages.enter_password)
+ password1 = self.prompt_nonempty_string(
+ 0, Messages.repeat_password)
+ if password != password1:
+ self.alert(Messages.passwords_differ)
+ self.password = password
+
+ def __get_graphics_support(self):
+ if os.environ.get('DISPLAY') is not None:
+ shell_command = subprocess.Popen(['which', 'zenity'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ shell_command.wait()
+ if shell_command.returncode == 0:
+ self.graphics = 'zenity'
+ else:
+ shell_command = subprocess.Popen(['which', 'kdialog'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ shell_command.wait()
+ # out, err = shell_command.communicate()
+ if shell_command.returncode == 0:
+ self.graphics = 'kdialog'
+ else:
+ self.graphics = 'tty'
+ else:
+ self.graphics = 'tty'
+
+ def __process_p12(self):
+ debug('process_p12')
+ pfx_file = os.environ['HOME'] + '/.cat_installer/user.p12'
+ if CRYPTO_AVAILABLE:
+ debug("using crypto")
+ try:
+ p12 = crypto.load_pkcs12(open(pfx_file, 'rb').read(),
+ self.password)
+ except:
+ debug("incorrect password")
+ return False
+ else:
+ if Config.use_other_tls_id:
+ return True
+ try:
+ self.username = p12.get_certificate().\
+ get_subject().commonName
+ except:
+ self.username = p12.get_certificate().\
+ get_subject().emailAddress
+ return True
+ else:
+ debug("using openssl")
+ command = ['openssl', 'pkcs12', '-in', pfx_file, '-passin',
+ 'pass:' + self.password, '-nokeys', '-clcerts']
+ shell_command = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = shell_command.communicate()
+ if shell_command.returncode != 0:
+ return False
+ if Config.use_other_tls_id:
+ return True
+ out_str = out.decode('utf-8').strip()
+ subject = re.split(r'\s*[/,]\s*',
+ re.findall(r'subject=/?(.*)$',
+ out_str, re.MULTILINE)[0])
+ cert_prop = {}
+ for field in subject:
+ if field:
+ cert_field = re.split(r'\s*=\s*', field)
+ cert_prop[cert_field[0].lower()] = cert_field[1]
+ if cert_prop['cn'] and re.search(r'@', cert_prop['cn']):
+ debug('Using cn: ' + cert_prop['cn'])
+ self.username = cert_prop['cn']
+ elif cert_prop['emailaddress'] and \
+ re.search(r'@', cert_prop['emailaddress']):
+ debug('Using email: ' + cert_prop['emailaddress'])
+ self.username = cert_prop['emailaddress']
+ else:
+ self.username = ''
+ self.alert("Unable to extract username "
+ "from the certificate")
+ return True
+
+ def __select_p12_file(self):
+ """
+ prompt user for the PFX file selection
+ this method is not being called in the silent mode
+ therefore there is no code for this case
+ """
+ if self.graphics == 'tty':
+ my_dir = os.listdir(".")
+ p_count = 0
+ pfx_file = ''
+ for my_file in my_dir:
+ if my_file.endswith('.p12') or my_file.endswith('*.pfx') or \
+ my_file.endswith('.P12') or my_file.endswith('*.PFX'):
+ p_count += 1
+ pfx_file = my_file
+ prompt = "personal certificate file (p12 or pfx)"
+ default = ''
+ if p_count == 1:
+ default = '[' + pfx_file + ']'
+
+ while True:
+ inp = get_input(prompt + default + ": ")
+ output = inp.strip()
+
+ if default != '' and output == '':
+ return pfx_file
+ default = ''
+ if os.path.isfile(output):
+ return output
+ print("file not found")
+
+ if self.graphics == 'zenity':
+ command = ['zenity', '--file-selection',
+ '--file-filter=' + Messages.p12_filter +
+ ' | *.p12 *.P12 *.pfx *.PFX', '--file-filter=' +
+ Messages.all_filter + ' | *',
+ '--title=' + Messages.p12_title]
+ shell_command = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ cert, err = shell_command.communicate()
+ if self.graphics == 'kdialog':
+ command = ['kdialog', '--getopenfilename',
+ '.', '*.p12 *.P12 *.pfx *.PFX | ' +
+ Messages.p12_filter, '--title', Messages.p12_title]
+ shell_command = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=STDERR_REDIR)
+ cert, err = shell_command.communicate()
+ return cert.decode('utf-8').strip()
+
+ def __save_sb_pfx(self):
+ """write the user PFX file"""
+ certfile = os.environ.get('HOME') + '/.cat_installer/user.p12'
+ with open(certfile, 'wb') as cert:
+ cert.write(base64.b64decode(Config.sb_user_file))
+
+ def __get_p12_cred(self):
+ """get the password for the PFX file"""
+ if Config.eap_inner == 'SILVERBULLET':
+ self.__save_sb_pfx()
+ else:
+ if self.silent:
+ pfx_file = self.pfx_file
+ else:
+ pfx_file = self.__select_p12_file()
+ try:
+ copyfile(pfx_file, os.environ['HOME'] +
+ '/.cat_installer/user.p12')
+ except (OSError, RuntimeError):
+ print(Messages.user_cert_missing)
+ sys.exit(1)
+ if self.silent:
+ username = self.username
+ if not self.__process_p12():
+ sys.exit(1)
+ if username:
+ self.username = username
+ else:
+ while not self.password:
+ self.password = self.prompt_nonempty_string(
+ 0, Messages.enter_import_password)
+ if not self.__process_p12():
+ self.alert(Messages.incorrect_password)
+ self.password = ''
+ if not self.username:
+ self.username = self.prompt_nonempty_string(
+ 1, Messages.username_prompt)
+
+ def __validate_user_name(self):
+ # locate the @ character in username
+ pos = self.username.find('@')
+ debug("@ position: " + str(pos))
+ # trailing @
+ if pos == len(self.username) - 1:
+ debug("username ending with @")
+ self.alert(Messages.wrongUsernameFormat)
+ return False
+ # no @ at all
+ if pos == -1:
+ if Config.verify_user_realm_input:
+ debug("missing realm")
+ self.alert(Messages.wrongUsernameFormat)
+ return False
+ debug("No realm, but possibly correct")
+ return True
+ # @ at the beginning
+ if pos == 0:
+ debug("missing user part")
+ self.alert(Messages.wrongUsernameFormat)
+ return False
+ pos += 1
+ if Config.verify_user_realm_input:
+ if Config.hint_user_input:
+ if self.username.endswith('@' + Config.user_realm, pos-1):
+ debug("realm equal to the expected value")
+ return True
+ debug("incorrect realm; expected:" + Config.user_realm)
+ self.alert(Messages.wrong_realm.format(Config.user_realm))
+ return False
+ if self.username.endswith(Config.user_realm, pos):
+ debug("realm ends with expected suffix")
+ return True
+ debug("realm suffix error; expected: " + Config.user_realm)
+ self.alert(Messages.wrong_realm_suffix.format(
+ Config.user_realm))
+ return False
+ pos1 = self.username.find('@', pos)
+ if pos1 > -1:
+ debug("second @ character found")
+ self.alert(Messages.wrongUsernameFormat)
+ return False
+ pos1 = self.username.find('.', pos)
+ if pos1 == pos:
+ debug("a dot immediately after the @ character")
+ self.alert(Messages.wrongUsernameFormat)
+ return False
+ debug("all passed")
+ return True
+
+
+class WpaConf(object):
+ """
+ Prepare and save wpa_supplicant config file
+ """
+ def __prepare_network_block(self, ssid, user_data):
+ out = """network={
+ ssid=\"""" + ssid + """\"
+ key_mgmt=WPA-EAP
+ pairwise=CCMP
+ group=CCMP TKIP
+ eap=""" + Config.eap_outer + """
+ ca_cert=\"""" + os.environ.get('HOME') + """/.cat_installer/ca.pem\"
+ identity=\"""" + user_data.username + """\"
+ altsubject_match=\"""" + ";".join(Config.servers) + """\"
+ phase2=\"auth=""" + Config.eap_inner + """\"
+ password=\"""" + user_data.password + """\"
+ anonymous_identity=\"""" + Config.anonymous_identity + """\"
+}
+ """
+ return out
+
+ def create_wpa_conf(self, ssids, user_data):
+ """Create and save the wpa_supplicant config file"""
+ wpa_conf = os.environ.get('HOME') + \
+ '/.cat_installer/cat_installer.conf'
+ with open(wpa_conf, 'w') as conf:
+ for ssid in ssids:
+ net = self.__prepare_network_block(ssid, user_data)
+ conf.write(net)
+
+
+class CatNMConfigTool(object):
+ """
+ Prepare and save NetworkManager configuration
+ """
+ def __init__(self):
+ self.cacert_file = None
+ self.settings_service_name = None
+ self.connection_interface_name = None
+ self.system_service_name = None
+ self.nm_version = None
+ self.pfx_file = None
+ self.settings = None
+ self.user_data = None
+ self.bus = None
+
+ def connect_to_nm(self):
+ """
+ connect to DBus
+ """
+ try:
+ self.bus = dbus.SystemBus()
+ except dbus.exceptions.DBusException:
+ print("Can't connect to DBus")
+ return None
+ # main service name
+ self.system_service_name = "org.freedesktop.NetworkManager"
+ # check NM version
+ self.__check_nm_version()
+ debug("NM version: " + self.nm_version)
+ if self.nm_version == "0.9" or self.nm_version == "1.0":
+ self.settings_service_name = self.system_service_name
+ self.connection_interface_name = \
+ "org.freedesktop.NetworkManager.Settings.Connection"
+ # settings proxy
+ sysproxy = self.bus.get_object(
+ self.settings_service_name,
+ "/org/freedesktop/NetworkManager/Settings")
+ # settings interface
+ self.settings = dbus.Interface(sysproxy, "org.freedesktop."
+ "NetworkManager.Settings")
+ elif self.nm_version == "0.8":
+ self.settings_service_name = "org.freedesktop.NetworkManager"
+ self.connection_interface_name = "org.freedesktop.NetworkMana" \
+ "gerSettings.Connection"
+ # settings proxy
+ sysproxy = self.bus.get_object(
+ self.settings_service_name,
+ "/org/freedesktop/NetworkManagerSettings")
+ # settings intrface
+ self.settings = dbus.Interface(
+ sysproxy, "org.freedesktop.NetworkManagerSettings")
+ else:
+ print(Messages.nm_not_supported)
+ return None
+ debug("NM connection worked")
+ return True
+
+ def __check_opts(self):
+ """
+ set certificate files paths and test for existence of the CA cert
+ """
+ self.cacert_file = os.environ['HOME'] + '/.cat_installer/ca.pem'
+ self.pfx_file = os.environ['HOME'] + '/.cat_installer/user.p12'
+ if not os.path.isfile(self.cacert_file):
+ print(Messages.cert_error)
+ sys.exit(2)
+
+ def __check_nm_version(self):
+ """
+ Get the NetworkManager version
+ """
+ try:
+ proxy = self.bus.get_object(
+ self.system_service_name, "/org/freedesktop/NetworkManager")
+ props = dbus.Interface(proxy, "org.freedesktop.DBus.Properties")
+ version = props.Get("org.freedesktop.NetworkManager", "Version")
+ except dbus.exceptions.DBusException:
+ version = ""
+ if re.match(r'^1\.', version):
+ self.nm_version = "1.0"
+ return
+ if re.match(r'^0\.9', version):
+ self.nm_version = "0.9"
+ return
+ if re.match(r'^0\.8', version):
+ self.nm_version = "0.8"
+ return
+ self.nm_version = Messages.unknown_version
+
+ def __delete_existing_connection(self, ssid):
+ """
+ checks and deletes earlier connection
+ """
+ try:
+ conns = self.settings.ListConnections()
+ except dbus.exceptions.DBusException:
+ print(Messages.dbus_error)
+ exit(3)
+ for each in conns:
+ con_proxy = self.bus.get_object(self.system_service_name, each)
+ connection = dbus.Interface(
+ con_proxy,
+ "org.freedesktop.NetworkManager.Settings.Connection")
+ try:
+ connection_settings = connection.GetSettings()
+ if connection_settings['connection']['type'] == '802-11-' \
+ 'wireless':
+ conn_ssid = byte_to_string(
+ connection_settings['802-11-wireless']['ssid'])
+ if conn_ssid == ssid:
+ debug("deleting connection: " + conn_ssid)
+ connection.Delete()
+ except dbus.exceptions.DBusException:
+ pass
+
+ def __add_connection(self, ssid):
+ debug("Adding connection: " + ssid)
+ server_alt_subject_name_list = dbus.Array(Config.servers)
+ server_name = Config.server_match
+ if self.nm_version == "0.9" or self.nm_version == "1.0":
+ match_key = 'altsubject-matches'
+ match_value = server_alt_subject_name_list
+ else:
+ match_key = 'subject-match'
+ match_value = server_name
+ s_8021x_data = {
+ 'eap': [Config.eap_outer.lower()],
+ 'identity': self.user_data.username,
+ 'ca-cert': dbus.ByteArray(
+ "file://{0}\0".format(self.cacert_file).encode('utf8')),
+ match_key: match_value}
+ if Config.eap_outer == 'PEAP' or Config.eap_outer == 'TTLS':
+ s_8021x_data['password'] = self.user_data.password
+ s_8021x_data['phase2-auth'] = Config.eap_inner.lower()
+ if Config.anonymous_identity != '':
+ s_8021x_data['anonymous-identity'] = Config.anonymous_identity
+ s_8021x_data['password-flags'] = 0
+ if Config.eap_outer == 'TLS':
+ s_8021x_data['client-cert'] = dbus.ByteArray(
+ "file://{0}\0".format(self.pfx_file).encode('utf8'))
+ s_8021x_data['private-key'] = dbus.ByteArray(
+ "file://{0}\0".format(self.pfx_file).encode('utf8'))
+ s_8021x_data['private-key-password'] = self.user_data.password
+ s_8021x_data['private-key-password-flags'] = 0
+ s_con = dbus.Dictionary({
+ 'type': '802-11-wireless',
+ 'uuid': str(uuid.uuid4()),
+ 'permissions': ['user:' +
+ os.environ.get('USER')],
+ 'id': ssid
+ })
+ s_wifi = dbus.Dictionary({
+ 'ssid': dbus.ByteArray(ssid.encode('utf8')),
+ 'security': '802-11-wireless-security'
+ })
+ s_wsec = dbus.Dictionary({
+ 'key-mgmt': 'wpa-eap',
+ 'proto': ['rsn'],
+ 'pairwise': ['ccmp'],
+ 'group': ['ccmp', 'tkip']
+ })
+ s_8021x = dbus.Dictionary(s_8021x_data)
+ s_ip4 = dbus.Dictionary({'method': 'auto'})
+ s_ip6 = dbus.Dictionary({'method': 'auto'})
+ con = dbus.Dictionary({
+ 'connection': s_con,
+ '802-11-wireless': s_wifi,
+ '802-11-wireless-security': s_wsec,
+ '802-1x': s_8021x,
+ 'ipv4': s_ip4,
+ 'ipv6': s_ip6
+ })
+ self.settings.AddConnection(con)
+
+ def add_connections(self, user_data):
+ """Delete and then add connections to the system"""
+ self.__check_opts()
+ self.user_data = user_data
+ for ssid in Config.ssids:
+ self.__delete_existing_connection(ssid)
+ self.__add_connection(ssid)
+ for ssid in Config.del_ssids:
+ self.__delete_existing_connection(ssid)
+
+
+Messages.quit = "Really quit?"
+Messages.username_prompt = "enter your userid"
+Messages.enter_password = "enter password"
+Messages.enter_import_password = "enter your import password"
+Messages.incorrect_password = "incorrect password"
+Messages.repeat_password = "repeat your password"
+Messages.passwords_differ = "passwords do not match"
+Messages.installation_finished = "Installation successful"
+Messages.cat_dir_exisits = "Directory {} exists; some of its files may " \
+ "be overwritten."
+Messages.cont = "Continue?"
+Messages.nm_not_supported = "This NetworkManager version is not " \
+ "supported"
+Messages.cert_error = "Certificate file not found, looks like a CAT " \
+ "error"
+Messages.unknown_version = "Unknown version"
+Messages.dbus_error = "DBus connection problem, a sudo might help"
+Messages.yes = "Y"
+Messages.no = "N"
+Messages.p12_filter = "personal certificate file (p12 or pfx)"
+Messages.all_filter = "All files"
+Messages.p12_title = "personal certificate file (p12 or pfx)"
+Messages.save_wpa_conf = "NetworkManager configuration failed, but we " \
+ "may generate a wpa_supplicant configuration file if you wish. Be " \
+ "warned that your connection password will be saved in this file as " \
+ "clear text."
+Messages.save_wpa_confirm = "Write the file"
+Messages.wrongUsernameFormat = "Error: Your username must be of the " \
+ "form 'xxx@institutionID' e.g. 'john@example.net'!"
+Messages.wrong_realm = "Error: your username must be in the form of " \
+ "'xxx@{}'. Please enter the username in the correct format."
+Messages.wrong_realm_suffix = "Error: your username must be in the " \
+ "form of 'xxx@institutionID' and end with '{}'. Please enter the " \
+ "username in the correct format."
+Messages.user_cert_missing = "personal certificate file not found"
+Config.instname = "University of Iowa"
+Config.profilename = "Everyone"
+Config.url = "http://its.uiowa.edu/helpdesk"
+Config.email = "its-helpdesk@uiowa.edu"
+Config.title = "eduroam CAT"
+Config.server_match = "net-auth-1.its.uiowa.edu"
+Config.eap_outer = "PEAP"
+Config.eap_inner = "MSCHAPV2"
+Config.init_info = "This installer has been prepared for {0}\n\nMore " \
+ "information and comments:\n\nEMAIL: {1}\nWWW: {2}\n\nInstaller created " \
+ "with software from the GEANT project."
+Config.init_confirmation = "This installer will only work properly if " \
+ "you are a member of {0}."
+Config.user_realm = "uiowa.edu"
+Config.ssids = ['eduroam']
+Config.del_ssids = ['UI-DeviceNet', 'UI-Guest', 'eduroam-test']
+Config.servers = ['DNS:net-auth-1.its.uiowa.edu']
+Config.use_other_tls_id = False
+Config.hint_user_input = True
+Config.verify_user_realm_input = True
+Config.tou = ""
+Config.CA = """-----BEGIN CERTIFICATE-----
+MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
+iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
+cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
+BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
+MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
+BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
+aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
+3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
+tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
+Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
+VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
+79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
+c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
+Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
+c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
+UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
+Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
+BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
+Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
+VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
+ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
+8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
+iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
+Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
+XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
+qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
+VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
+L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
+jjxDah2nGN59PRbxYvnKkKj9
+-----END CERTIFICATE-----
+"""
+run_installer()
From 28a410b315321f730bad593d89420fd15cf90acc Mon Sep 17 00:00:00 2001
From: Ben Mitchinson <115bwm@gmail.com>
Date: Sat, 15 Feb 2020 22:31:37 -0600
Subject: [PATCH 06/12] Create ReadMe.md
---
pine/ReadMe.md | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 pine/ReadMe.md
diff --git a/pine/ReadMe.md b/pine/ReadMe.md
new file mode 100644
index 0000000..430b9c3
--- /dev/null
+++ b/pine/ReadMe.md
@@ -0,0 +1,8 @@
+These should enable the pine to use Iowa Eduroam on boot with the detailed user creds
+in wpa_wupplicant.conf
+
+### Random Tips
+- The py script was used to generate for the first time the wpa config, but
+that's now included in this repo, so I don't think it will ever need to be run again.
+- `chsh -s /bin/bash john` to set a new users terminal to bash over just sh
+- set/replace `/etc/wpa_supplicant/wpa_supplicant.conf` + and `/etc/network/interfaces`
From 12e8a67319a3972005fc48fd95dd8d24cea577dd Mon Sep 17 00:00:00 2001
From: Ben Mitchinson <115bwm@gmail.com>
Date: Sat, 15 Feb 2020 22:33:14 -0600
Subject: [PATCH 07/12] Create wpa_supplicant.conf
---
pine/wpa_supplicant.conf | 15 +++++++++++++++
1 file changed, 15 insertions(+)
create mode 100644 pine/wpa_supplicant.conf
diff --git a/pine/wpa_supplicant.conf b/pine/wpa_supplicant.conf
new file mode 100644
index 0000000..f7a81cf
--- /dev/null
+++ b/pine/wpa_supplicant.conf
@@ -0,0 +1,15 @@
+network={
+ ssid="eduroam"
+ key_mgmt=WPA-EAP
+ pairwise=CCMP
+ group=CCMP TKIP
+ eap=PEAP
+ ca_cert="/home/rock64/.cat_installer/ca.pem"
+ identity="{enter your hawk id}@uiowa.edu"
+ altsubject_match="DNS:net-auth-1.its.uiowa.edu"
+ phase2="auth=MSCHAPV2"
+ password="{enter your password here}"
+ anonymous_identity=""
+ priority=9
+ id_str="default"
+}
From e5e4cba7f38cbbf15194e12ceaa6d804d583ff61 Mon Sep 17 00:00:00 2001
From: Ben Mitchinson <115bwm@gmail.com>
Date: Sat, 15 Feb 2020 22:34:09 -0600
Subject: [PATCH 08/12] Create interfaces
---
pine/interfaces | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 pine/interfaces
diff --git a/pine/interfaces b/pine/interfaces
new file mode 100644
index 0000000..d794c7e
--- /dev/null
+++ b/pine/interfaces
@@ -0,0 +1,7 @@
+# ifupdown has been replaced by netplan(5) on this system. See
+# /etc/netplan for current configuration.
+# To re-enable ifupdown on this system, you can run:
+# sudo apt install ifupdown
+iface wlan0 inet dhcp
+wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
+iface default inet dhcp
From cc3984c844a353b3a1c352c202b680c1972488af Mon Sep 17 00:00:00 2001
From: Ben Mitchinson <115bwm@gmail.com>
Date: Sat, 15 Feb 2020 23:13:05 -0600
Subject: [PATCH 09/12] Update ReadMe.md
---
pine/ReadMe.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/pine/ReadMe.md b/pine/ReadMe.md
index 430b9c3..10cd1cf 100644
--- a/pine/ReadMe.md
+++ b/pine/ReadMe.md
@@ -6,3 +6,7 @@ in wpa_wupplicant.conf
that's now included in this repo, so I don't think it will ever need to be run again.
- `chsh -s /bin/bash john` to set a new users terminal to bash over just sh
- set/replace `/etc/wpa_supplicant/wpa_supplicant.conf` + and `/etc/network/interfaces`
+
+### Controlling Led from bash:
+- `sh -c "echo '0' > /sys/devices/platform/leds/leds/work-led/brightness"` - white led
+- `sh -c "echo '0' > /sys/devices/platform/leds/leds/standby-led/brightness"` - red led
From 90b44ebdba5280b874451c1e61c24b8f4359c164 Mon Sep 17 00:00:00 2001
From: Ben Mitchinson
Date: Sun, 16 Feb 2020 02:12:37 -0600
Subject: [PATCH 10/12] corrected node_modules logic
---
docker-compose.yml | 1 -
pwa/Dockerfile | 14 +++++++-------
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index 36c19ec..8c8a8a3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -4,7 +4,6 @@ services:
pwa:
build:
context: ./pwa
- command: npm start
volumes:
- ./pwa/:/app
ports:
diff --git a/pwa/Dockerfile b/pwa/Dockerfile
index a8041eb..397c7e0 100644
--- a/pwa/Dockerfile
+++ b/pwa/Dockerfile
@@ -1,16 +1,16 @@
FROM node:12.10.0
-WORKDIR /app
-# cd app folder within container.
+COPY package*.json ./data/
-COPY package*.json ./
+WORKDIR /data
RUN npm install
-COPY . .
-# copy {everything in PWA} {WORKDIR (/app)}
-# this is okay because node_modules is ignored
+ENV PATH /data/node_modules/.bin:$PATH
+
+COPY . /data/app
+WORKDIR /data/app
EXPOSE 3000
-CMD ["npm", "start"]
\ No newline at end of file
+CMD ["npm", "start"]
From e9ef1e0dcc8c16fde8e939bd41a0b6d239d3ff42 Mon Sep 17 00:00:00 2001
From: Ben Mitchinson <115bwm@gmail.com>
Date: Sun, 16 Feb 2020 11:18:49 -0600
Subject: [PATCH 11/12] Update ReadMe.md
---
pine/ReadMe.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/pine/ReadMe.md b/pine/ReadMe.md
index 10cd1cf..ca25b40 100644
--- a/pine/ReadMe.md
+++ b/pine/ReadMe.md
@@ -1,7 +1,7 @@
+### Networking tips
These should enable the pine to use Iowa Eduroam on boot with the detailed user creds
in wpa_wupplicant.conf
-### Random Tips
- The py script was used to generate for the first time the wpa config, but
that's now included in this repo, so I don't think it will ever need to be run again.
- `chsh -s /bin/bash john` to set a new users terminal to bash over just sh
@@ -10,3 +10,5 @@ that's now included in this repo, so I don't think it will ever need to be run a
### Controlling Led from bash:
- `sh -c "echo '0' > /sys/devices/platform/leds/leds/work-led/brightness"` - white led
- `sh -c "echo '0' > /sys/devices/platform/leds/leds/standby-led/brightness"` - red led
+
+Further todos for configuration are listed [in this issue](https://github.com/Haiti-Distributed-Healthcare-System/hdhs/issues/20#issuecomment-586730519)
From 0d9d87ecf153093df5cda9be80e7f0b02aa504df Mon Sep 17 00:00:00 2001
From: Ben Mitchinson
Date: Mon, 17 Feb 2020 08:33:05 -0600
Subject: [PATCH 12/12] Update README.md
---
README.md | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index dc72f36..22d218b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,19 @@
-# hdhs
-Haiti Distributed Healthcare System
+
+
+
Haiti Community Health Initiative Application
+
+
+ A containerized progressive web application paired with patient data management tooling built to serve the Haiti Community Health Initiative in their mission to help form healthy and self-directed communities in Haiti.
+
+
+---
+
+## Getting Started
+
+The application runs on docker, which can be installed [here](https://docs.docker.com/install/).
+If you're a linux user, you'll also need to install [docker-compose](https://docs.docker.com/compose/install/) separately. Otherwise, it's included in the Mac and PC versions of "Docker Desktop"
+
+- The PWA and accompanying database can be started by running the command `docker-compose up` from the root directory.
+ - `docker-compose down` will then stop the docker processes from running in the background.
+- Once the images have been built and the processes started, the PWA can be seen at your localhost, at the port specified in [docker-compose.yml](https://github.com/Haiti-Distributed-Healthcare-System/hdhs/blob/dev/docker-compose.yml).
+- More tips to troubleshoot docker are available [here](https://github.com/Haiti-Distributed-Healthcare-System/hdhs/wiki/Using-Docker).