├── requirements.txt ├── .gitignore ├── Procfile ├── screenshot.JPG ├── github_get_url.JPG ├── .gitpod.yml ├── .github └── workflows │ └── main.yml ├── Readme.md ├── deploy.sh └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit 2 | paramiko -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | id_rsa* 2 | dokku_host.config -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: streamlit run main.py --server.port $PORT 2 | -------------------------------------------------------------------------------- /screenshot.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conradbez/dokku-dashboard/HEAD/screenshot.JPG -------------------------------------------------------------------------------- /github_get_url.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conradbez/dokku-dashboard/HEAD/github_get_url.JPG -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: pip3 install -r requirements.txt 3 | - command: rm "/home/gitpod/.ssh/id_rsa" ; echo "-----BEGIN RSA PRIVATE KEY-----" >> "/home/gitpod/.ssh/id_rsa" && echo $SSH_PRIVATE_KEY | fold -w 64 >> ~/.ssh/id_rsa && echo "-----END RSA PRIVATE KEY-----" >> "/home/gitpod/.ssh/id_rsa" && echo "$SSH_PUB_KEY" >> ~/.ssh/id_rsa.pub && chmod 600 "/home/gitpod/.ssh/id_rsa" -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | - id: deploy 15 | name: Deploy to dokku 16 | uses: idoberko2/dokku-deploy-github-action@v1 17 | with: 18 | ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} 19 | dokku-host: 'conradbez.com' 20 | app-name: 'dashboard' 21 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Dokku Dashboard 2 | A GUI for Dokku to allow more Heroku like UX 3 | ![Screenshot of Dokku Dashboard](/screenshot.JPG) 4 | 5 | ## Features: 6 | * Create a new app 7 | * Manage environment variables for apps 8 | * Add Postgress service to an app 9 | * Reboot Dokku server 10 | 11 | ## Quickstart 12 | 1. [Deploy a Dokku instance](http://dokku.viewdocs.io/dokku/getting-started/installation/) 13 | 2. Run `sh deploy.sh` inside this repo from a machine with the ssh keys to access Dokku 14 | 3. Go to dashboard.yourdokkudomain 15 | 16 | ## How Dashboard works 17 | We generate a private/public key and add the public key to your Dokku instance. The private is encrypted (with the password you supplied when running deploy.sh) and uploaded to the dashboard app. 18 | When you enter the password on dashboard.yourdokkudomain the dashboard app reads this private key and open a ssh tunnel to your root dokku instance which it uses to orchestrate your environment. 19 | 20 | ## Key files 21 | * deploy.sh: runs on your local machine to generate a new ssh keys, add this key to Dokku and set up the dashboard app on Dokku 22 | * main.py: runs the dashboard app using Streamlit 23 | * Procfile: start command for Dokku to run the dashboard app -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | # Get password from user 2 | echo Enter a secure password you will use to access your dokku instance \(will be used to encrypt your ssh key\) 3 | read user_password 4 | 5 | # Get host name from user 6 | echo Enter a your dokku domain or IP address 7 | read dokku_host 8 | 9 | # Write hostname to config file, to be read by app after deployment 10 | rm dokku_host.config 11 | echo $dokku_host >> dokku_host.config 12 | 13 | ssh-keygen -f ./id_rsa -N $user_password 14 | 15 | 16 | # add the created ssh key 17 | ssh root@$dokku_host dokku ssh-keys:remove dokkudashboard 18 | cat ./id_rsa.pub | ssh root@$dokku_host dokku ssh-keys:remove dokkudashboard 19 | cat ./id_rsa.pub | ssh root@$dokku_host dokku ssh-keys:add dokkudashboard 20 | 21 | ssh root@$dokku_host dokku ssh-keys:remove admin_dokkudashboard 22 | cat ~/.ssh/id_rsa.pub | ssh root@$dokku_host dokku ssh-keys:remove admin_dokkudashboard 23 | cat ~/.ssh/id_rsa.pub | ssh root@$dokku_host dokku ssh-keys:add admin_dokkudashboard 24 | 25 | ssh root@$dokku_host cp /root/.ssh/authorized_keys -n /root/.ssh/authorized_keys.original # copy file if doesn't exist to preserve original 26 | ssh root@$dokku_host cp /root/.ssh/authorized_keys.original /root/.ssh/authorized_keys # make sure we base off the original 27 | cat ./id_rsa.pub | ssh root@$dokku_host tee -a /root/.ssh/authorized_keys 28 | ssh root@$dokku_host chmod o-r /root/.ssh/authorized_keys 29 | 30 | # create the dashboard app 31 | ssh -o StrictHostKeyChecking=no root@$dokku_host dokku --force apps:destroy dashboard 32 | ssh -o StrictHostKeyChecking=no root@$dokku_host dokku apps:create dashboard 33 | 34 | # deploy our dashboard app 35 | git remote remove dokku 36 | git remote add dokku dokku@$dokku_host:dashboard 37 | git add * 38 | # need to add these so our dashboared app has the connection details to execute commands on dokku 39 | git add --force id_rsa dokku_host.config 40 | git commit -m "deploying" 41 | git push dokku master 42 | git rm id_rsa dokku_host.config 43 | 44 | 45 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import paramiko 3 | from paramiko import AutoAddPolicy 4 | import os 5 | import re 6 | 7 | with open("dokku_host.config") as f: 8 | dokku_host = f.readlines() 9 | dokku_host = dokku_host[0].replace('\n','') 10 | print(dokku_host) 11 | st.sidebar.write('Connecting to {}'.format(dokku_host)) 12 | 13 | 14 | def cleanseUserInput(oneWordedInput): 15 | return re.sub(r'\W+', '', oneWordedInput) 16 | 17 | 18 | 19 | # User actions 20 | def appReport(appname): 21 | stdin, stdout, stderr = client.exec_command('dokku --force apps:report {}'.format(appname)) 22 | st.write(stdout.readlines()) 23 | 24 | 25 | def destroyApp(appname): 26 | confirm_destruction = st.text_input("Type app name to destroy") 27 | if confirm_destruction == appname: 28 | st.write('Press button to destroy app') 29 | if st.button(f'Destroy {appname}'): 30 | stdin, stdout, stderr = client.exec_command('dokku --force apps:destroy {}'.format(appname)) 31 | st.write(stdout.readlines()) 32 | else: 33 | st.write("Text does not match") 34 | 35 | 36 | def desplayAppLogs(appname): 37 | stdin, stdout, stderr = client.exec_command(f'dokku logs {appname}') 38 | st.write(stdout.readlines()) 39 | 40 | def managePostgress(appname): 41 | provisionPostress(appname) 42 | 43 | 44 | def provisionPostress(appname): 45 | postgress_name = cleanseUserInput(st.text_input('Name the new Postgress service:')) 46 | if len(postgress_name) > 0: 47 | try: 48 | st.write(execD("dokku postgres:create {}".format(postgress_name))) 49 | except: 50 | st.write("Seems you don't have the postgress add-on, we'll install that you you :)") 51 | st.write(execD("sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres")) 52 | st.write(execD("dokku postgres:create {}".format(postgress_name))) 53 | st.write(execD("dokku postgres:link {} {}".format(postgress_name,appname))) 54 | 55 | def deployFromPublicRepo(dokku_host,appname): 56 | repo_url = st.text_input('Repo url:') 57 | if len(repo_url)<1: 58 | st.image('github_get_url.JPF') 59 | else: 60 | import subprocess 61 | stdout, stderr = subprocess.Popen(['git', 'pull', 'repo_url'],stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 62 | st.write(stdout) 63 | st.write(stderr) 64 | command = f'git remote add dokku dokku@{dokku_host}:{appname} && git push dokku master'.split(' ') 65 | st.write('command') 66 | stdout, stderr = subprocess.Popen(command,stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 67 | st.write(stdout) 68 | st.write(stderr) 69 | 70 | 71 | 72 | def getConnectionToDokku(dokku_host, password): 73 | client = paramiko.SSHClient() 74 | f = open('./id_rsa','r') 75 | s = f.read() 76 | from io import StringIO 77 | keyfile = StringIO(s) 78 | mykey = paramiko.RSAKey.from_private_key(keyfile, password=password) 79 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 80 | client.connect(dokku_host, username='root', pkey=mykey, look_for_keys=False) 81 | stdin, stdout, stderr = client.exec_command('uptime') 82 | return client 83 | 84 | def managesEnvVars(appname): 85 | stdin, stdout, stderr = client.exec_command(f'dokku --quiet config {appname}') 86 | keys = stdout.readlines() 87 | st.write(keys) 88 | st.write('Set variable') 89 | newVarName = cleanseUserInput(st.text_input('Name:')) 90 | newVarValue = cleanseUserInput(st.text_input('Value:')) 91 | if newVarName and newVarValue and st.button(f'Set {newVarName} = {newVarValue}'): 92 | stdin, stdout, stderr = client.exec_command(f'dokku --quiet config:set {appname} {newVarName}={newVarValue}') 93 | st.write(stdout.readlines()) 94 | 95 | 96 | 97 | 98 | # Get ssh connection 99 | ssh_key_password = st.sidebar.text_input('Password set during deployment', type="password") 100 | if ssh_key_password: 101 | client = getConnectionToDokku(dokku_host= dokku_host, password=ssh_key_password) 102 | st.sidebar.write('Connected to dokku') 103 | 104 | def execD(com): 105 | global client 106 | stdin, stdout, stderr = client.exec_command(com) 107 | return stdout.readlines() 108 | 109 | availible_apps = execD('dokku --quiet apps:list') 110 | availible_apps = list(map(str.strip, availible_apps)) 111 | selected_app = st.sidebar.selectbox('What app would you like to work with',['Dokku server']+availible_apps+['Create new']) 112 | 113 | if selected_app=='Dokku server': 114 | if st.button(f'Reboot server'): 115 | client.exec_command('reboot') 116 | 117 | 118 | elif selected_app == 'Create new': 119 | user_app_name = cleanseUserInput(st.text_input('App name:')) 120 | if len(user_app_name)>0: 121 | stdin, stdout, stderr = client.exec_command('dokku apps:create '+user_app_name) 122 | st.write(stdout.readlines()) 123 | st.markdown("""Run `git remote add dokku dokku@{}:{} ` to add dokku to your git and then `git push dokku master` to deploy.""".format(dokku_host,user_app_name)) 124 | 125 | elif selected_app != 'No app selected' and selected_app: 126 | selected_action = st.selectbox(f'What would you like to do with: {selected_app}', [ 127 | 'Info', 128 | 'Manage env vars', 129 | 'Manage Postgress', 130 | 'Logs', 131 | 'Deploy from public repo', 132 | 'Destroy app', 133 | ]) 134 | if selected_action == 'Logs': 135 | desplayAppLogs(selected_app) 136 | 137 | if selected_action == 'Info': 138 | appReport(selected_app) 139 | 140 | 141 | if selected_action == 'Destroy app': 142 | destroyApp(selected_app) 143 | 144 | if selected_action == 'Manage Postgress': 145 | managePostgress(selected_app) 146 | 147 | if selected_action == 'Manage env vars': 148 | managesEnvVars(selected_app) 149 | 150 | if selected_action == 'Deploy from public repo': 151 | deployFromPublicRepo(dokku_host, selected_app) --------------------------------------------------------------------------------