├── .gitignore ├── AUTHORS ├── CONTRIBUTING ├── LICENSE ├── Makefile ├── README.rst ├── TODO.txt ├── VERSION ├── bin ├── immutableworkstation.py └── update_ubuntu_host.sh ├── docker_vnc_immutable ├── Dockerfile ├── bgimage.jpg ├── buildit.sh ├── entrypoint.sh ├── functions.sh ├── immutableworkstation3.py ├── lib_config.py ├── lib_logging.py ├── notes.rst ├── rcassets │ ├── .emacs │ ├── .gitconfig │ ├── .pylintrc │ ├── .ssh │ │ ├── config │ │ └── known_hosts │ ├── ENTRYPOINT.sh │ ├── own_dev_tools.sh │ ├── postinit.sh │ └── requirements.txt ├── templates │ ├── apt.template │ ├── baseconfig.template │ ├── dockerfile.skeleton │ ├── emacs.template │ ├── github.template │ ├── github_cli.template │ ├── latex.template │ ├── otherdevtools.template │ ├── py3.template │ ├── sshX.template │ ├── terminal_setup_notes.txt │ ├── user.template │ ├── vnc.template │ └── vscode.template ├── test1.py └── vncstart.sh ├── docs ├── Makefile ├── conf.py ├── flashdrive.txt ├── index.rst ├── roadmap.rst ├── ssh-github.rst └── testing_with_docker.rst ├── setup.py ├── test ├── docopt-0.6.2.tar.gz ├── testmaker.sh └── testpost.sh └── workstation ├── Dockerfile ├── __init__.py ├── build_workstation.sh ├── initial_setup ├── .initial_laptop_install.sh.swo ├── .initial_laptop_install.sh.swp ├── 0_system_build.sh ├── 1_base_user_build.sh ├── 2_docker_workstation.sh ├── config_scripts │ ├── .setup_mikadotools.sh.swp │ ├── cleanupsnap.sh │ ├── enable_dropbox.sh │ ├── enable_vmware_horizon.sh │ ├── initial_laptop_install.sh │ ├── install_config_vim.sh │ ├── install_firefox.sh │ ├── install_git.sh │ ├── setup_mikadotools.sh │ └── setup_nix.sh └── mission_statement.txt ├── nix_notes.txt └── quickstart.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | #emacs crud 7 | *.*~ 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # IPython Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # dotenv 82 | .env 83 | 84 | # virtualenv 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Paul Brian -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | TBD 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Paul Brian 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #CONSTANTS 2 | PYTHON=python 3 | 4 | #TARGETS 5 | .PHONY : help clear wheel installlocal 6 | 7 | help : Makefile 8 | @sed -n 's/^##//p' $< 9 | 10 | ##clear: clean setup build stuff 11 | clear : 12 | rm -rf *.egg-info/ 13 | rm -rf dist/ build/ 14 | 15 | ##wheel: Build the wheel for distribution 16 | wheel : 17 | clear 18 | $(PYTHON) setup.py bdist_wheel 19 | 20 | ##installlocal: install locally for testing 21 | installlocal : 22 | clear 23 | $(PYTHON) setup.py install 24 | 25 | 26 | # useful reference for make: https://swcarpentry.github.io/make-novice/reference 27 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Docker Immutable Workstation 3 | ============================ 4 | 5 | The concept of an *immutable server* for production deployment is now 6 | fully mainstream, but the same concepts underpinning servers is less 7 | applied to the workstations on which the developers work. 8 | 9 | We have a tendency to start with a nice clean laptop, a Mac if we are 10 | lucky, and slowly but surely *stuff* creeps on, dependencies we did 11 | not know about appear and we stop trusting the platform we stand on. 12 | 13 | So I have used Docker to make my own *immutable workstation*. It 14 | means that I get *exactly* the same stack running on my 15 | banged-about-on-commute laptop, my wife's nice big screen iMac and 16 | even on my client's Windows box, that I had to use for client's policy 17 | reasons. So wherever I was, I was using the same config of emacs - 18 | using it on a windows machine or a mac or a Linux host, it was the 19 | same emacs, and the same nice set of tools like grep. And it was 20 | running XWindows in those places too. 21 | 22 | Secondly, I get the ratchet effect of continuously improving security 23 | - I can always improve something on the install, and just rerun 24 | `docker build` and I have permanently remembered to fix that security 25 | hole wherever I build my workstation. 26 | 27 | I have 'improved' the approach (this is many years old now), so that 28 | I run a complete desktop on docker, and VNC into it, from my local machine. 29 | THis simplifies things like looking at pdfs or html files built on the machine 30 | plus simplifies things like keeping my local machine updated - I can live without 31 | almost any 'new stuff' on the local laptop. 32 | 33 | 34 | So, *anything* that changes I keep in 35 | source control (here in this repo) and my secrets are all stored on a 36 | USB key that I carry with me and plugin to the host - so my GitHub ssh 37 | key is on a USB stick, that when I plug it in, . 38 | 39 | 40 | Documentation can be found at https://workstation.readthedocs.io/en/latest/ 41 | 42 | :: 43 | 44 | `immutableworkstation` can create docker images from config, and 45 | launch those images so that as a developer you can work inside the 46 | container, but using X-applications on the host laptop. 47 | 48 | So you can define your workstation in code, but take it with you 49 | from laptop to home to work. 50 | 51 | 52 | Using X Windows 53 | =============== 54 | 55 | The *essential* parts of this approach are hard to dig out from Google 56 | searches, but I hope this makes them clearer - the below code will 57 | produce a working local docker instance, ssh into it and display an 58 | app *from* docker but *on* the host desktop. 59 | 60 | We build a X11 capable docker image :: 61 | 62 | FROM ubuntu:18.04 63 | 64 | RUN apt-get update && \ 65 | apt-get install -y openssh-server \ 66 | x11-apps 67 | 68 | RUN mkdir -p /var/run/sshd 69 | RUN echo 'root:root' | chpasswd 70 | RUN sed -ri 's/^#PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config 71 | RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config 72 | RUN sed -ri 's/^#AllowTcpForwarding\s+.*/AllowTcpForwarding yes/g' /etc/ssh/sshd_config 73 | RUN sed -ri 's/^#X11Forwarding\s+.*/X11Forwarding yes/g' /etc/ssh/sshd_config 74 | RUN sed -ri 's/^#X11UseLocalhost\s+.*/X11UseLocalhost no/g' /etc/ssh/sshd_config 75 | 76 | EXPOSE 22 77 | CMD ["/usr/sbin/sshd", "-D"] 78 | 79 | We then build the above image:: 80 | 81 | # sudo docker build -t devbox:latest . 82 | 83 | Now run it, listening on the localhost port of 2222, which is then 84 | mapped to 22 on the container:: 85 | 86 | # docker run -d --name devbox-live -v /data/projects:/projects -p 2222:22 devbox:latest 87 | 88 | we should now have a running container listening on port 2222 89 | 90 | So we can ssh tunnel into the container using:: 91 | 92 | # ssh -X root@localhost -p 2222 93 | 94 | There may be some faffing with .XAuthority files. Ignore that for now. 95 | But we should then be able to run :: 96 | 97 | # xeyes 98 | 99 | on the container, and it will appear on the laptop we are running on. 100 | 101 | #TODO: screenshot 102 | 103 | Using Sound 104 | =========== 105 | 106 | There is a developer who (I think) works for Docker and has a list of 107 | YouTube videos showing how to do things like run Skype on Docker. She 108 | developed a `snd` device parameter for `docker run`, which seems to 109 | work fine. I don't do much with it but should expand on it. 110 | 111 | Using Secrets 112 | ============= 113 | 114 | 115 | /etc/fstab on host machine:: 116 | 117 | 118 | # /etc/fstab: static file system information. 119 | .... 120 | UUID=ed74f120-1736-4f59-8752-06098a635c16 /home/pbrian/secrets/usb ext4 user,rw,auto,nofail 0 0 121 | ... 122 | 123 | I used `sudo blkid` to get the UUID for that specific USB key. 124 | 125 | It is then automounted to my home dir, where docker will make it 126 | visible in the docker instance, and I get to use the ssh keys on 127 | the USB stick to authenticate to, for example, github. 128 | 129 | Using Dropbox 130 | ============= 131 | 132 | I have some files I keep on private GitHub repos, but for most 133 | documents (things like Bank statements) it seems easier to just store 134 | them on Dropbox. I merely have my Dropbox folder on my home dir, and 135 | mount it into Docker. It seems to work with no horrible clashes so I 136 | will keep it. At some point it seems sensible to migrate to having the 137 | Dropbox client actually running on the docker instance. 138 | 139 | Its not terribly secure, but it seems good enough. 140 | 141 | Why is this good? 142 | ----------------- 143 | 144 | Quite simply, I can easily control the dev environment, rebuild it at 145 | will, and run programs "on my laptop" when they are not installed or 146 | configured on the laptop. 147 | 148 | In fact I think the best part of this is configuration for my *whole* 149 | dev machine is stored on GitHub, and can be re-created anywhere 150 | easily. 151 | 152 | With the volume mounted, I can then use emacs / console running inside 153 | a container, and adjust files that are stored on my local laptop. 154 | 155 | I then have a consistent dev environment 156 | 157 | Also, I can easily rebuild it 158 | 159 | Also I can spin up a microservice on laptop that also points at the 160 | same volume, and it will thus be using the code I just developed 161 | 162 | This works even if I change underlying OS - which is good for 163 | wandering contractors like me. 164 | 165 | TODO:: 166 | 167 | #TODO:: allow two workstations on same host, so I can play / verify changes 168 | #TODO:: get dropbox installed on docker instance 169 | 170 | 171 | Building on Mac OS 172 | ------------------ 173 | 174 | You will need a XServer running on the Mac. I recommend using XQuartz 175 | - this will need to be installed and running before starting the ssh 176 | -X process so the ssh session can connect to something 177 | 178 | Roadmap 179 | See `Roadmap `_ 180 | 181 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | [X] make emacs build during docker run 2 | [X] build githib/hub from source at https://github.com/github/hub#source 3 | [X] migrate to seperate git repo for the host .immutable - so simmpler then 4 | [x] Easier killing of docker instances 5 | [X] have a minimal example .latest so can start from scratch then have "my" one in my own location. test this on clean instances. 6 | 7 | [ ] setup dropbox 8 | [ ] setup two huge back disk for all photos 9 | [ ] create a dockerfile that builds a terminal only development environment 10 | [ ] and a terminal first user environment (dropbox, firefox, etc.) 11 | [ ] sahre files with shhfs FUSE between dev env and user env 12 | [ ] how to have dockerenv --editable python venvs 13 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.3 -------------------------------------------------------------------------------- /bin/immutableworkstation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | #! -*- coding:utf-8 -*- 3 | 4 | """ 5 | ImmutableWorkstation 6 | ==================== 7 | 8 | This is a single entry point for the `immutableworkstation` project. 9 | 10 | The project is pretty simple - I want to have a consistent, immutable 11 | workstation on any host machine I am developing on - so I am using a 12 | docker instance on a host machine - the instance is my development 13 | "machine", and it can be rebuilt from consistent templates - this 14 | script helps control all that - its supposed to be easier to get 15 | started than a bunch of poorly documneted shell scripts. 16 | 17 | * the start and stopping of the dev instance. 18 | * the compilation of the docker image 19 | * vsarious config and templates used to build to docker image. 20 | 21 | This script does quite a lot, and needs to be installed on 22 | the host machine - do so using 23 | 24 | pip3 install docopt 25 | python3 setup.py install 26 | (I will launch it on PyPI soon) 27 | 28 | Once this is done, you should be able to run 29 | 30 | ./immutableworkstation.py 31 | 32 | 33 | [ ] Implement expect-style testing so we can automate testing. 34 | [x] put the home dir into git seperate to rest of pacakge (ie thats the indivudal part) 35 | [ ] put blog.mikadosoftware onto AWS and run this testing with docker on it. 36 | [ ] migrate rest of the articles there. 37 | [x] create a plain docker instance and just import devstation, see if it works (ie clean install) 38 | [ ] run the get github projects into one place 39 | 40 | [ ] podman system prune : clean up a lot of cruft in docker areas. 41 | 42 | """ 43 | ##### imports ##### 44 | import logging, sys 45 | from docopt import docopt 46 | import subprocess 47 | import time 48 | import os 49 | from pprint import pprint as pp 50 | from mikado.core import config 51 | import shutil 52 | import json 53 | 54 | ##### Module setup ##### 55 | # TODO: split out logging into common module 56 | log = logging.getLogger(__name__) 57 | log.setLevel(logging.INFO) 58 | handler = logging.StreamHandler(sys.stdout) 59 | handler.setLevel(logging.INFO) 60 | log.addHandler(handler) 61 | 62 | DRYRUN = False 63 | PDB = False 64 | OCI_CMD = 'sudo docker' 65 | OCI_CMD = 'podman' 66 | 67 | #: usage defintons 68 | DOCOPT_HELP = """immutableworkstation 69 | 70 | Usage: 71 | immutableworkstation.py config 72 | immutableworkstation.py start (latest | next) [options] 73 | immutableworkstation.py stop (latest | next) [options] 74 | immutableworkstation.py login (latest | next) [options] 75 | immutableworkstation.py buildDocker (latest | next) [options] 76 | immutableworkstation.py next2last 77 | immutableworkstation.py status 78 | immutableworkstation.py quickstart 79 | immutableworkstation.py test 80 | immutableworkstation.py (-h | --help ) 81 | 82 | 83 | Options: 84 | -h --help Show this screen 85 | -d --dryrun dryrun 86 | 87 | """ 88 | 89 | DOCOPT_HELP_SHORT = """immutableworkstation 90 | 91 | Usage: 92 | immutableworkstation.py quickstart 93 | 94 | Options: 95 | -h --help Show this screen 96 | -d --dryrun dryrun 97 | 98 | """ 99 | 100 | ############### Config 101 | LATEST = "latest" 102 | NEXT = "next" 103 | # This is a 'well-known' location 104 | CONFIGDIR = os.path.join(os.path.expanduser("~"), ".immutableworkstation") 105 | CONFIGLOCATION = os.path.join( 106 | os.path.expanduser("~"), ".immutableworkstation/config.ini" 107 | ) 108 | STARTER_CONFIG_URL = "https://github.com/mikadosoftware/immutableworkstation_starter_config/archive/master.zip" 109 | 110 | #: pull out into a dedicated config file?? 111 | def read_disk_config(): 112 | """ 113 | 114 | Volumes - this is tricky to bend .ini files to handle lists of tuples 115 | The encoded json approach was just too fragile. 116 | So a new section is being added 117 | """ 118 | try: 119 | 120 | confd = config.read_ini(CONFIGLOCATION) 121 | if confd["devstation_config_root"].startswith("~/"): 122 | confd["devstation_config_root"] = confd["devstation_config_root"].replace( 123 | "~", os.path.expanduser("~") 124 | ) 125 | volumesd = config.read_ini(CONFIGLOCATION)["volumes"] 126 | #: we want to convert an ini section to a dict. 127 | confd["volumes"] = {} 128 | for k, i in volumesd.items(): 129 | if "~/" in k: 130 | # convert ~/data to /home/user/data 131 | newkey = os.path.join(os.path.expanduser("~"), k.replace("~/", "")) 132 | # we should have volumes = {'/home/user/data': '/var/data'} 133 | confd["volumes"][newkey] = i 134 | hasconfigdir = True 135 | except Exception as e: 136 | log.error("Failed to read config - error is %s", e) 137 | if PDB: 138 | import pdb 139 | 140 | pdb.set_trace() 141 | confd = {} 142 | hasconfigdir = False 143 | return confd, hasconfigdir 144 | 145 | 146 | def write_disk_config(confd): 147 | """ """ 148 | config.write_ini(confd, CONFIGLOCATION) 149 | 150 | 151 | CONFD, HASCONFIGDIR = read_disk_config() 152 | if PDB: 153 | print(CONFD) 154 | 155 | 156 | def build_sshcmd(): 157 | """Create the command used to connect to running docker via ssh.""" 158 | 159 | return "ssh -X {username}@{localhost} -p {ssh_port}".format(**CONFD) 160 | 161 | 162 | def build_dockerrun(latest=True): 163 | """create the command used to start docker instance. 164 | 165 | tagname of image 166 | name of running instance 167 | """ 168 | _latest = LATEST if latest else NEXT 169 | instance_name = "run_{}_{}".format(CONFD["instance_name"], _latest) 170 | image_name = "{}:{}".format(CONFD["tagname"], _latest) 171 | vols = "" 172 | for hostpath, mountpath in CONFD["volumes"].items(): 173 | vols += "-v {}:{} ".format(hostpath, mountpath) 174 | 175 | return [ 176 | "{} container prune -f".format(OCI_CMD), 177 | """{OCI_CMD} run -d \ 178 | {vols} \ 179 | --name {instance_name} \ 180 | --device /dev/snd \ 181 | -p {ssh_port}:22 \ 182 | --privileged \ 183 | {tagname}:{_latest} 184 | """.format( 185 | OCI_CMD=OCI_CMD, 186 | vols=vols, 187 | instance_name=instance_name, 188 | ssh_port=CONFD["ssh_port"], 189 | _latest=_latest, 190 | tagname=CONFD["tagname"], 191 | ), 192 | ] 193 | 194 | 195 | def build_docker_build(latest=True): 196 | """Create command used to (re)build the container. 197 | 198 | We store the Dockerfile (as that name) 199 | in dir .next or .latest so that we can 200 | have various templates and assets and so on 201 | in the 'context' directory. 202 | 203 | """ 204 | tmpl = "{} build -t {{tagname}}:{{tagtag}} {{pathtodockerfile}} --squash".format(OCI_CMD) 205 | _latest = LATEST if latest else NEXT 206 | pathtodockerfile = os.path.join(CONFD["devstation_config_root"], "." + _latest) 207 | return tmpl.format( 208 | tagname=CONFD["tagname"], tagtag=_latest, pathtodockerfile=pathtodockerfile 209 | ) 210 | 211 | def read_subprocess(cmd): 212 | """Run a command and return output """ 213 | 214 | result = subprocess.run(cmd, 215 | stdout=subprocess.PIPE, 216 | stderr=subprocess.PIPE, 217 | universal_newlines=True, 218 | shell=True) 219 | txt = result.stdout 220 | return txt 221 | 222 | 223 | def run_subprocess(cmd, shell=None): 224 | """Run the given command in a subprocess.""" 225 | if DRYRUN: 226 | telluser(cmd) 227 | else: 228 | log.info(cmd) 229 | subprocess.run(cmd, shell=True) 230 | 231 | 232 | def spawn_sibling_console(): 233 | """This script is best thought of as a launcher for other shells we 234 | shall be working in. We want to interact with the console, not 235 | this script much. 236 | 237 | I have played with fork'ing a child console, then passing `fd` 238 | 0,1,2 over to it. But the easiest way seems to be to assume this 239 | is a GUI workstation, and people are using a terminal program 240 | (like Konsole) - so we just spawn konsole and run -e 241 | 242 | """ 243 | 244 | sshcmd = '{} "{}" &'.format(CONFD["terminal_command"], build_sshcmd()) 245 | log.info(sshcmd) 246 | run_subprocess(sshcmd) 247 | 248 | 249 | def show_config(confd=None): 250 | """Display the current config settings 251 | 252 | >>> show_config({'foo': 'bar'}) 253 | foo : bar 254 | 255 | 256 | """ 257 | confd = confd or CONFD # for easy testing 258 | s = "" 259 | for key, item in confd.items(): 260 | s += "{} : {}\n".format(key, item) 261 | telluser(s) 262 | 263 | 264 | def handle_start(args): 265 | """Perform cmsd needed to start the docker and login 266 | 267 | I really need to monitor the success of the underlying 268 | cmds, instead of brute force sleep. 269 | [ ] {milestone} stop using sleep, monitor the subprocess for return values. 270 | """ 271 | # do start up here 272 | cmds = build_dockerrun(args["latest"]) 273 | for cmd in cmds: 274 | # TODO get better solution than sleep 275 | run_subprocess(cmd, shell=True) 276 | time.sleep(8) # brute force give docker time to complete its stuff. 277 | time.sleep(10) # As above, but let docker catch up before login 278 | handle_login(args) 279 | 280 | 281 | def handle_config(args): 282 | show_config() 283 | 284 | 285 | def handle_login(args): 286 | spawn_sibling_console() 287 | 288 | 289 | def handle_buildDocker(args): 290 | """Trigger the processes to create new dockerfile and then build image. """ 291 | makeDocker(latest=args["latest"]) 292 | cmd = build_docker_build(latest=args["latest"]) 293 | run_subprocess(cmd) 294 | 295 | 296 | def handle_status(args): 297 | """Show container status. """ 298 | cmd = "{} container ls".format(OCI_CMD) 299 | run_subprocess(cmd) 300 | cmd = "{} inspect run_devstation_next".format(OCI_CMD) 301 | txt = read_subprocess(cmd) 302 | jsond = json.loads(txt) 303 | ipaddress = jsond[0]['NetworkSettings']['IPAddress'] 304 | print('Use this ip address {}'.format(ipaddress)) 305 | 306 | 307 | def handle_stop(args): 308 | """Kill the specified instance. """ 309 | _latest = LATEST if args["latest"] else NEXT 310 | #: rewrite so this is not in two places 311 | instance_name = "run_{}_{}".format(CONFD["instance_name"], _latest) 312 | cmd = "{} container kill {}".format(OCI_CMD, instance_name) 313 | run_subprocess(cmd) 314 | 315 | 316 | def hasValidConfig(): 317 | """This is a placeholder for future development on checking curr env. """ 318 | has_config_file = os.path.isfile(CONFIGLOCATION) 319 | return all([has_config_file]) 320 | 321 | 322 | def gatherinfo(): 323 | questions = { 324 | "username": "What username should be the default (only) on your immutable workstation?" 325 | } 326 | answers = {} 327 | for label, question in questions.items(): 328 | answer = input(question) 329 | answers[label] = answer 330 | return answers 331 | 332 | 333 | def handle_quickstart(args): 334 | """We have a starter config on github. Pull that down and put in 335 | users homedir, then alter based on questions. 336 | 337 | I am spending too long yak shaving on this app, and so will just 338 | print instructions and look to automate it later. 339 | """ 340 | helpmsg = "" 341 | if hasValidConfig(): 342 | helpmsg += """You appear to have an existing config in {}. 343 | Please adjust it manually - view docs for help.""".format( 344 | CONFIGLOCATION 345 | ) 346 | 347 | if not hasValidConfig(): 348 | helpmsg += """ In the future this app will walk you through a series of 349 | questions, but for now please can you download and unzip into {} the 350 | starter config stored at {}. You should have a directory layout like:: 351 | 352 | .immutableworkstation 353 | | 354 | -config.ini 355 | | 356 | -.next/ 357 | -.latest/ 358 | 359 | You should copy these into *your* github repo, and then update the 360 | templates to your needs, as you find a new package to be added to your 361 | workstation, adjust the config needed. 362 | 363 | """.format( 364 | CONFIGDIR, STARTER_CONFIG_URL 365 | ) 366 | 367 | telluser(helpmsg) 368 | 369 | 370 | def handle_next2last(args): 371 | """ """ 372 | # Note the extra dot 373 | _latestdir = "{}/.{}".format(CONFIGDIR, LATEST) 374 | _nextdir = "{}/.{}".format(CONFIGDIR, NEXT) 375 | _backupdir = "{}/latest.bak".format(CONFIGDIR) 376 | 377 | cmds = [ 378 | "rm -rf {}".format(_backupdir), 379 | "mv -f {} {}".format(_latestdir, _backupdir), 380 | "cp -r {} {}".format(_nextdir, _latestdir), 381 | ] 382 | input( 383 | "About to move {} and replace with {}. Hit any key".format(_latestdir, _nextdir) 384 | ) 385 | for cmd in cmds: 386 | run_subprocess(cmd, shell=True) 387 | 388 | 389 | def handle_unknown(): 390 | telluser("Unknown request please type `devstation --help`") 391 | 392 | 393 | def makeDocker(latest=True): 394 | """Take a .skeleton file, and replace defined markup with 395 | contents of txt files 396 | 397 | Based on 'dockerfile.skeleton', replace any instance of 398 | {{ python }} with the contents of file `templates\python.template` 399 | 400 | This is an *extremely* simple templating tool. It is *not* 401 | supposed to have the complexity even of Jinja2. Its supposed to 402 | be really dumb. Lucky I wrote it then :-). 403 | 404 | 405 | """ 406 | 407 | _latest = "." + LATEST if latest else "." + NEXT 408 | current_folder = os.path.join(CONFD["devstation_config_root"], _latest) 409 | templates_folder = os.path.join(current_folder, "templates") 410 | pathtodockerfile = os.path.join(current_folder, "Dockerfile") 411 | skeleton = "dockerfile.skeleton" 412 | outputs = "" 413 | with open(os.path.join(templates_folder, skeleton)) as fo: 414 | for line in fo: 415 | if line.find("{{") == 0: 416 | file = line.replace("{{", "").replace("}}", "").strip() 417 | filepath = os.path.join(templates_folder, file + ".template") 418 | txt = open(filepath).read() 419 | outputs += "\n### {}\n{}\n".format(line, txt) 420 | else: 421 | outputs += "{}".format(line) 422 | fo = open(pathtodockerfile, "w") 423 | fo.write(outputs) 424 | fo.close() 425 | telluser("Written new Dockerfile at {}".format(pathtodockerfile)) 426 | 427 | 428 | def telluser(msg): 429 | """ aggregate print stmts into one place.""" 430 | # handle my weird formatting 431 | print(msg) 432 | 433 | 434 | def run(args): 435 | 436 | #: start with quickstart as it may be our only options 437 | #: [ ] make this safer with .get 438 | if args["quickstart"]: 439 | handle_quickstart(args) 440 | elif args["config"]: 441 | handle_config(args) 442 | elif args["start"]: 443 | handle_start(args) 444 | elif args["login"]: 445 | handle_login(args) 446 | elif args["buildDocker"]: 447 | handle_buildDocker(args) 448 | elif args["next2last"]: 449 | handle_next2last(args) 450 | elif args["status"]: 451 | handle_status(args) 452 | elif args["stop"]: 453 | handle_stop(args) 454 | elif args["test"]: 455 | runtests() 456 | else: 457 | handle_unknown() 458 | 459 | 460 | def runtests(): 461 | import doctest 462 | 463 | doctest.testmod() 464 | 465 | 466 | def main(): 467 | global DRYRUN 468 | ## if we have not quickstart'd the config dir, only show quickstart option. 469 | if HASCONFIGDIR: 470 | args = docopt(DOCOPT_HELP) 471 | else: 472 | args = docopt(DOCOPT_HELP_SHORT) 473 | if args.get("--dryrun", False): 474 | DRYRUN = True 475 | run(args) 476 | 477 | 478 | if __name__ == "__main__": 479 | main() 480 | -------------------------------------------------------------------------------- /bin/update_ubuntu_host.sh: -------------------------------------------------------------------------------- 1 | sudo apt-get update 2 | sudo apt-get upgrade -y 3 | sudo apt-get dist-upgrade -y 4 | sudo apt-get autoremove -y 5 | sudo apt-get autoclean -y 6 | -------------------------------------------------------------------------------- /docker_vnc_immutable/Dockerfile: -------------------------------------------------------------------------------- 1 | # From skelton 2 | FROM ubuntu:jammy 3 | LABEL maintainer="cyd@9bis.com" 4 | 5 | ARG PROXY_CERT 6 | RUN test -z "${PROXY_CERT}" || { echo "${PROXY_CERT}" | base64 -d | tee /usr/local/share/ca-certificates/ca-local.crt > /dev/null && update-ca-certificates ; } 7 | 8 | # We prepare environment 9 | ARG TZ=${TZ:-Etc/UTC} 10 | ARG DEBIAN_FRONTEND=noninteractive 11 | RUN \ 12 | echo "Timezone and locale" >&2 \ 13 | && apt-get update \ 14 | && apt-get install -y \ 15 | apt-utils \ 16 | software-properties-common \ 17 | tzdata \ 18 | && apt-get clean \ 19 | && apt-get autoremove -y \ 20 | && rm -rf /tmp/* /var/tmp/* \ 21 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 22 | && echo "Timezone and locale OK" >&2 23 | 24 | # Second we install VNC, noVNC and websockify 25 | RUN \ 26 | echo "install VNC, noVNC and websockify" >&2 \ 27 | && apt-get update \ 28 | && apt-get install -y --no-install-recommends \ 29 | libpulse0 \ 30 | x11vnc \ 31 | xvfb \ 32 | && apt-get clean \ 33 | && apt-get autoremove -y \ 34 | && rm -rf /tmp/* /var/tmp/* \ 35 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 36 | && echo "install VNC OK" >&2 37 | 38 | # And finally xfce4 and ratpoison desktop environments 39 | RUN \ 40 | echo "Install xfce4 " >&2 \ 41 | && apt-get update \ 42 | && apt-get install -y --no-install-recommends \ 43 | dbus-x11 \ 44 | && apt-get install -y \ 45 | xfce4 xfce4-terminal xfce4-eyes-plugin \ 46 | xfce4-systemload-plugin xfce4-weather-plugin \ 47 | xfce4-whiskermenu-plugin xfce4-clipman-plugin \ 48 | xserver-xorg-video-dummy \ 49 | && apt-get clean \ 50 | && apt-get autoremove -y \ 51 | && rm -rf /tmp/* /var/tmp/* \ 52 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 53 | && echo "Install xfce4 OK" >&2 54 | 55 | 56 | # We can add additional GUI programs 57 | # Templates can be added here 58 | 59 | 60 | 61 | ### {{ user }} 62 | 63 | #RUN apt-get install sudo 64 | 65 | # We add a simple user with sudo rights 66 | ENV USERNAME=pbrian 67 | ENV USERHOME=/home/pbrian 68 | ARG USR_UID=1000 69 | ARG USR_GID=5000 70 | 71 | RUN groupadd --gid ${USR_GID} ${USERNAME} 72 | RUN useradd --uid ${USR_UID} --create-home --gid ${USR_GID} --shell /bin/bash ${USERNAME} 73 | RUN echo "${USERNAME}:${USERNAME}" | chpasswd 74 | RUN echo ${USERNAME}' ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 75 | 76 | 77 | RUN echo 'root:root' | chpasswd 78 | ### Setup me as only user .... because I will be ! 79 | # RUN useradd -m -c "$USERFULLNAME" $USERNAME --shell /bin/bash 80 | # RUN usermod -aG sudo $USERNAME 81 | # RUN echo "$USERNAME:$USERNAME" | chpasswd 82 | 83 | # change user locale settings (see baseconfig) 84 | RUN echo "export LC_ALL=en_GB.UTF-8" >> $USERHOME/.bashrc 85 | RUN echo "export LC_LANGUAGE=en_GB.UTF-8" >> $USERHOME/.bashrc 86 | 87 | # This is really just the config settings 88 | # any secrfets are added via '/var/secrets' volume per run 89 | # as such this is a "safe" image to keep on say a hub 90 | # COPY rcassets/.emacs $USERHOME 91 | # COPY rcassets/.pylintrc $USERHOME 92 | COPY rcassets/.gitconfig $USERHOME 93 | COPY rcassets/.ssh $USERHOME/.ssh 94 | RUN chown -R $USERNAME:$USERNAME $USERHOME/.ssh 95 | RUN chown -R $USERNAME:$USERNAME $USERHOME/.gitconfig 96 | 97 | #COPY rcassets/ENTRYPOINT.sh $USERHOME 98 | #COPY rcassets/ENTRYPOINT.sh $USERHOME 99 | #RUN chmod 0777 $USERHOME/ENTRYPOINT.sh 100 | 101 | 102 | 103 | 104 | ### {{ apt }} 105 | 106 | # build the basic development world 107 | RUN apt-get update && \ 108 | apt-get install -y apt-transport-https \ 109 | build-essential \ 110 | build-essential \ 111 | dbus-x11 \ 112 | dos2unix \ 113 | fonts-inconsolata \ 114 | git \ 115 | openssh-server \ 116 | software-properties-common \ 117 | wget curl \ 118 | x11-apps \ 119 | && \ 120 | apt-get clean 121 | 122 | # We add some tools 123 | RUN \ 124 | echo "Install some tools" >&2 \ 125 | && apt-get update \ 126 | && apt-get install -y --no-install-recommends \ 127 | curl \ 128 | dumb-init \ 129 | figlet \ 130 | jq \ 131 | libnss3-tools \ 132 | mlocate \ 133 | net-tools \ 134 | vim \ 135 | vlc \ 136 | xz-utils \ 137 | zip \ 138 | && apt-get install -y thunar-archive-plugin \ 139 | && apt-get clean \ 140 | && apt-get autoremove -y \ 141 | && rm -rf /tmp/* /var/tmp/* \ 142 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 143 | && echo "Install some tools OK" >&2 144 | 145 | # We install firefox, directly from Mozilla (not from snap) 146 | RUN \ 147 | echo "Install Firefox from Mozilla" >&2 \ 148 | && apt-get update \ 149 | && add-apt-repository ppa:mozillateam/ppa \ 150 | && printf '\nPackage: *\nPin: release o=LP-PPA-mozillateam\nPin-Priority: 1001\n' > /etc/apt/preferences.d/mozilla-firefox \ 151 | && printf 'Unattended-Upgrade::Allowed-Origins:: "LP-PPA-mozillateam:${distro_codename}";' > /etc/apt/apt.conf.d/51unattended-upgrades-firefox \ 152 | && apt-get update \ 153 | && apt-get install -y firefox --no-install-recommends \ 154 | && apt-get clean \ 155 | && apt-get autoremove -y \ 156 | && rm -rf /tmp/* /var/tmp/* \ 157 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 158 | && echo "Install Firefox from Mozilla OK" >&2 159 | 160 | 161 | 162 | 163 | ### {{ baseconfig }} 164 | 165 | #################### Misc config ########################## 166 | ## Locales 167 | # We dont have any languages setup at this point 168 | # 169 | RUN apt-get update && \ 170 | apt-get install -y locales && \ 171 | locale-gen en_GB.UTF-8 && \ 172 | update-locale LC_ALL=en_GB.UTF-8 LANGUAGE=en_GB.UTF-8 && \ 173 | apt-get clean 174 | 175 | #these need to be run in user sections 176 | #RUN echo "export LC_ALL=en_GB.UTF-8" >> /home/pbrian/.bashrc 177 | #RUN echo "export LC_LANGUAGE=en_GB.UTF-8" >> /home/pbrian/.bashrc 178 | 179 | #THis should now work in our terminal (prints Greek for Thank you) 180 | #python3 -c "print(u''.join([u'\u0395', u'\u03c5', u'\u03c7', u'\u03B1', u'\u03c1', u'\u03B9', u'\u03C3', u'\u03c4', u'\u03c9']))" 181 | 182 | ## fonts 183 | RUN apt-get -y install fonts-inconsolata && apt-get clean 184 | 185 | ENV TZ=Europe/London 186 | ENV DEBIAN_FRONTEND=noninteractive 187 | RUN DEBIAN_FRONTEND=noninteractive && \ 188 | apt-get update && \ 189 | apt-get install -y tzdata \ 190 | && apt-get clean 191 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ 192 | echo $TZ > /etc/timezone 193 | 194 | 195 | 196 | ### {{ otherdevtools }} 197 | 198 | 199 | ### Other Dev Tools 200 | RUN apt-get install -y whois 201 | 202 | 203 | 204 | 205 | ### {{ github_cli }} 206 | 207 | # from cli github page: 208 | RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ 209 | && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ 210 | && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ 211 | && apt update \ 212 | && apt install gh -y \ 213 | && apt-get clean 214 | 215 | RUN echo "READY TO DO PYTHON" 216 | ###### Install with apt 217 | RUN apt-get install -y libpython3.10 \ 218 | python3.10 \ 219 | python3-pip \ 220 | && apt-get clean 221 | 222 | ###### symlinking to have `pip` and `python` 223 | RUN cd /usr/bin \ 224 | && ln -sf python3.10 python \ 225 | && ln -sf python3.10 python3 \ 226 | && ln -sf pip3 pip 227 | 228 | ##### 229 | RUN python3 -m pip install --upgrade pip 230 | #RUN python3 -m pip install pygithub 231 | 232 | ### update python pkgs 233 | # Install any needed packages (ie above those for the runtime) 234 | RUN pip install --trusted-host pypi.python.org sphinx \ 235 | pytest \ 236 | pylint 237 | 238 | 239 | # install requirements file. (why not specify in thisfile???) 240 | COPY rcassets/requirements.txt $WKDIR/ 241 | COPY rcassets/unpinned_requirements.txt $WKDIR/ 242 | 243 | #RUN pip install -r $WKDIR/requirements.txt 244 | RUN pip install -r $WKDIR/unpinned_requirements.txt 245 | # Additonal setup for spacy. I think this is sensible to do specify in this file them 246 | #RUN python -m spacy download en 247 | RUN echo "READY TO DO PYTHON END" 248 | # seems ubunut installs pip_internal again mucking things up 249 | 250 | # lastly redo symlinks 251 | RUN rm /usr/bin/python 252 | RUN ln -s /usr/bin/python3 /usr/bin/python 253 | 254 | 255 | 256 | 257 | 258 | # We add sound 259 | # add root user to group for pulseaudio access 260 | RUN adduser root pulse-access 261 | RUN printf 'default-server = unix:/run/user/1000/pulse/native\nautospawn = no\ndaemon-binary = /bin/true\nenable-shm = false' > /etc/pulse/client.conf 262 | 263 | 264 | # Two ports are availables: 5900 for VNC client, and 6080 for browser access via websockify 265 | EXPOSE 5900 266 | 267 | # We set localtime 268 | RUN if [ "X${TZ}" != "X" ] ; then if [ -f /usr/share/zoneinfo/${TZ} ] ; then rm -f /etc/localtime ; ln -s /usr/share/zoneinfo/${TZ} /etc/localtime ; fi ; fi 269 | 270 | # And here is the statup script, everything else is in there 271 | COPY entrypoint.sh /entrypoint.sh 272 | RUN chmod 755 /entrypoint.sh 273 | 274 | # We do some specials 275 | RUN \ 276 | updatedb ; \ 277 | apt-get clean \ 278 | && apt-get autoremove -y \ 279 | && rm -rf /tmp/* /var/tmp/* \ 280 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* 281 | 282 | # We change user 283 | USER ${USERNAME} 284 | WORKDIR /home/${USERNAME} 285 | COPY functions.sh /home/${USERNAME}/.functions.sh 286 | COPY bgimage.jpg /usr/share/backgrounds/xfce/bgimage.jpg 287 | RUN \ 288 | printf 'if [[ $- = *i* ]] ; then test -f ~/.functions.sh && . ~/.functions.sh ; fi' >> /home/${USERNAME}/.bashrc 289 | 290 | #ENTRYPOINT [ "/usr/bin/dumb-init", "--", "/entrypoint.sh" ] 291 | ENTRYPOINT [ "/entrypoint.sh" ] 292 | -------------------------------------------------------------------------------- /docker_vnc_immutable/bgimage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikadosoftware/workstation/619ca9cae8af0306e026dd9e7663e28c6a6287d1/docker_vnc_immutable/bgimage.jpg -------------------------------------------------------------------------------- /docker_vnc_immutable/buildit.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | #python3 immutableworkstation3.py createDockerfile --templatedir=/home/pbrian/projects/docker_vnc_immutable/templates 4 | logfile="this.log" 5 | tagname="mikado-immutableworkstation" 6 | path2dockerfile="/home/pbrian/projects/workstation/docker_vnc_immutable/Dockerfile" 7 | path2contextdir="/home/pbrian/projects/workstation/docker_vnc_immutable/" 8 | podman build -t ${tagname} -f ${path2dockerfile} ${path2contextdir} --squash --logfile=${logfile} 9 | -------------------------------------------------------------------------------- /docker_vnc_immutable/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # We set USER 4 | export USER=$(whoami) 5 | 6 | # We update apt 7 | ls -lart /usr/local/share/ca-certificates 8 | sudo update-ca-certificates 9 | 10 | mkdir /home/pbrian/foobarwibble 11 | touch /home/pbrian/didthis 12 | echo $USER >> /home/pbrian/didthis 13 | ## THis is run *inside* new docker instnce, but has mounted the 14 | ## secrets volume 15 | 16 | chown -R pbrian:pbrian /home/pbrian/ 17 | chmod -R 0777 /home/pbrian/ 18 | 19 | mkdir /home/pbrian/foobarwibble2 20 | touch /home/pbrian/didthis2 21 | echo $USER >> /home/pbrian/didthis2 22 | 23 | SECRETS_VOL="/var/secrets/usb" 24 | chmod -R 0777 /home/pbrian/.ssh 25 | cp -r $SECRETS_VOL/.ssh/* /home/pbrian/.ssh 26 | chown -R pbrian:pbrian /home/pbrian/.ssh 27 | chmod -R 0700 /home/pbrian/.ssh 28 | 29 | # add aws credentials from host to docker 30 | 31 | mkdir /home/pbrian/.aws 32 | chmod -R 0777 /home/pbrian/.aws 33 | cp -r $SECRETS_VOL/aws-credentials /home/pbrian/.aws/credentials 34 | chown -R pbrian:pbrian /home/pbrian/.aws 35 | chmod 0622 -R /home/pbrian/.aws 36 | 37 | # add github 'hub' credentials from host to here 38 | echo -e "\nexport GITHUB_TOKEN=`cat /var/secrets/usb/github.token`" >> /home/pbrian/.bashrc 39 | 40 | 41 | ## Add local tools 42 | cd /var/projects/mikado-tools 43 | python3 setup.py install 44 | 45 | 46 | 47 | # We check all container parameters 48 | DESKTOP_VNC_PARAMS="" 49 | 50 | # We prepare VNC 51 | mkdir ~/.vnc 52 | 53 | DESKTOP_SIZE=${DESKTOP_SIZE:-1280x1024} 54 | DESKTOP_ENV=${DESKTOP_ENV:-xfce4} 55 | 56 | # We add a password to VNC 57 | if [ "X${DESKTOP_VNC_PASSWORD}" != "X" ] ; then 58 | echo "init password" 59 | x11vnc -storepasswd ${DESKTOP_VNC_PASSWORD:-password} ~/.vnc/passwd && chmod 0600 ~/.vnc/passwd 60 | DESKTOP_VNC_PARAMS=${DESKTOP_VNC_PARAMS}" -passwd ${DESKTOP_VNC_PASSWORD}" 61 | fi 62 | # We set the screen size 63 | if [ "X${DESKTOP_SIZE}" != "X" ] ; then 64 | echo "set screen size" 65 | sudo sed -i -E 's/XVFBARGS="-screen 0 [0-9]+x[0-9]+x[0-9]+"/XVFBARGS="-screen 0 '${DESKTOP_SIZE}'x24"/' /bin/xvfb-run 66 | grep "^XVFBARGS" /bin/xvfb-run 67 | fi 68 | 69 | # Init .xinitrc 70 | #printf 'autocutsel -fork -selection CLIPBOARD\nautocutsel -fork -selection PRIMARY\n' > ~/.xinitrc 71 | 72 | # We install additionnal programs 73 | if [ "X${INSTALL_ADDITIONAL_PROGRAMS}" != "X" ] ; then 74 | echo "Installing ${INSTALL_ADDITIONAL_PROGRAMS}..." 75 | sudo apt-get update > /dev/null 76 | sudo apt-get install -y ${INSTALL_ADDITIONAL_PROGRAMS} 77 | fi 78 | 79 | if [ "X${DESKTOP_ENV}" = "Xratpoison" ] ; then 80 | echo "configure ratpoison" 81 | # We run ratpoison at VNC server startup 82 | echo "exec ratpoison >/dev/null 2>&1" >> ~/.xinitrc 83 | # We start additinnal programs 84 | if [ "X${DESKTOP_ADDITIONAL_PROGRAMS}" != "X" ] ; then 85 | echo "exec ${DESKTOP_ADDITIONAL_PROGRAMS}" >> ~/.ratpoisonrc 86 | else 87 | # We run firefox at ratpoison startup 88 | echo "exec firefox" > ~/.ratpoisonrc && chmod +x ~/.ratpoisonrc 89 | fi 90 | elif [ "X${DESKTOP_ENV}" = "Xxfce4" ] ; then 91 | echo "configure Xfce4" 92 | # We run xfce4 at VNC server startup 93 | echo "exec /usr/bin/startxfce4 >/dev/null 2>&1" >> ~/.xinitrc 94 | # We set keyboard 95 | if [ "X${DESKTOP_KEYBOARD_LAYOUT}" != "X" ] ; then 96 | test -d ~/.config/xfce4/xfconf/xfce-perchannel-xml || mkdir -p ~/.config/xfce4/xfconf/xfce-perchannel-xml 97 | layout=$(echo ${DESKTOP_KEYBOARD_LAYOUT}|sed 's#/.*$##') 98 | variant=$(echo ${DESKTOP_KEYBOARD_LAYOUT}|sed 's#^.*/##') 99 | echo "set ${layout}-${variant} keyboard" 100 | printf ' 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | ' > ~/.config/xfce4/xfconf/xfce-perchannel-xml/keyboard-layout.xml 109 | fi 110 | # We set theme 111 | if [ "X${DESKTOP_THEME}" != "X" ] ; then 112 | test -d ~/.config/xfce4/xfconf/xfce-perchannel-xml || mkdir -p ~/.config/xfce4/xfconf/xfce-perchannel-xml 113 | printf ' 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | ' > ~/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml 155 | fi 156 | # We set background image 157 | if [ "X${DESKTOP_BACKGROUND_IMAGE}" != "X" ] ; then 158 | if [ $(echo "${DESKTOP_BACKGROUND_IMAGE}" | grep -E "^https?:\/\/" | wc -l) -eq 1 ] ; then 159 | wget "${DESKTOP_BACKGROUND_IMAGE}" -O "${HOME}/bgimage.jpg" 160 | DESKTOP_BACKGROUND_IMAGE="${HOME}/bgimage.jpg" 161 | fi 162 | test -d ~/.config/xfce4/xfconf/xfce-perchannel-xml || mkdir -p ~/.config/xfce4/xfconf/xfce-perchannel-xml 163 | test -f "${DESKTOP_BACKGROUND_IMAGE}" && printf ' 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | ' > ~/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-desktop.xml 193 | fi 194 | else 195 | echo "Unknown desktop environment" >&2 196 | exit 1 197 | fi 198 | chmod +x ~/.xinitrc 199 | 200 | # We set repeat is on 201 | sudo sed -i 's/tcp/tcp -ardelay 200 -arinterval 20/' /etc/X11/xinit/xserverrc 202 | 203 | # We read the command-line parameters 204 | if [ $# -ne 0 ] ; then 205 | if [ "${1}" = "help" ] ; then 206 | echo "Available variables:" 207 | echo "DESKTOP_ENV, DESKTOP_VNC_PASSWORD, DESKTOP_SIZE, DESKTOP_THEME, DESKTOP_ADDITIONAL_PROGRAMS" 208 | exit 0 209 | fi 210 | fi 211 | 212 | # We set sound 213 | export PULSE_SERVER=unix:/run/user/$(id -u)/pulse/native 214 | 215 | # We start VNC server 216 | export FD_GEOM=${DESKTOP_SIZE} # To init a screen display when using Xvfb 217 | { 218 | while [ 1 ] ; do 219 | figlet "x11vnc" 220 | x11vnc -create -forever -repeat ${DESKTOP_VNC_PARAMS} 221 | sleep 1 222 | done 223 | } & 224 | 225 | # We set clipboard 226 | test -d ~/.config/autostart || mkdir -p ~/.config/autostart 227 | cp /etc/xdg/autostart/xfce4-clipman-plugin-autostart.desktop ~/.config/autostart/xfce4-clipman-plugin-autostart.desktop 228 | 229 | # We start noVNC 230 | figlet websockify 231 | websockify -D --web=/usr/share/novnc/ --cert=~/novnc.pem 6080 localhost:5900 & 232 | WEBSOCKIFY_PID=$! 233 | 234 | # Prepare addons 235 | echo "wget -qO - https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/master/pub.gpg | gpg --dearmor | sudo dd of=/usr/share/keyrings/vscodium-archive-keyring.gpg 236 | echo 'deb [ signed-by=/usr/share/keyrings/vscodium-archive-keyring.gpg ] https://download.vscodium.com/debs vscodium main' | sudo tee /etc/apt/sources.list.d/vscodium.list 237 | sudo apt update && sudo apt install codium" > ~/codium_install 238 | 239 | # Test for startup script 240 | test -f /startup.sh && { 241 | chmod ugo+x /startup.sh 242 | sudo /startup.sh 243 | } 244 | 245 | # Run an apt update 246 | sudo apt-get update > /dev/null & 247 | 248 | # Is there an option 249 | if [ $# -ne 0 ] ; then 250 | exec "$@" 251 | else 252 | tail -f /dev/null 253 | fi 254 | 255 | kill $WEBSOCKIFY_PID 256 | wait 257 | -------------------------------------------------------------------------------- /docker_vnc_immutable/functions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | declare -F psup >/dev/null 2>&1 || function psup(){ if [ $# -eq 0 ]; then echo "Usage: psup pid" >&2;else while [ $# -gt 0 ]; do if [ ${1} -ne 0 ]; then pid=${1};ps -edf | awk -vpid=${pid} '{ if( $2==pid ) { printf("%s\n", $0); } }';psup `ps -edf | awk -vpid=${pid} '{ if( $2==pid ) printf("%s\n", $3); }'`;else echo;fi;shift;done;fi;} 3 | declare -F psdown >/dev/null 2>&1 || function psdown(){ if [ $# -eq 0 ]; then echo "Usage: psdown pid" >&2;return 1;else psdown_level=`echo $1|awk '{ if(substr($0,1,1)=="p") printf("%s",substr($0,2));else printf("0"); }'`;if [ ${psdown_level} -ne 0 ]; then shift;fi;while [ $# -gt 0 ]; do pid=${1};if [ `ps -edf|awk '{print $2}'|grep ${pid}|wc -l` -gt 0 ]; then ps -edf|awk -vpid=${pid} -vlevel=${psdown_level} '{ if( $2==pid ) { if(level>0) for(i=1;i<=level;i++) printf(" "); printf("%s\n", $0); } }';if [ `ps -edf|awk '{print $3}'|grep ${pid}|wc -l` -gt 0 ]; then psdown_level=$(( ${psdown_level} + 1 ));psdown p${psdown_level} `ps -edf | awk -vpid=${pid} '{ if( $3==pid ) printf("%s ", $2); }'`;psdown_level=$(( ${psdown_level} - 1 ));fi;fi;shift;done;fi;} 4 | declare -F killdown >/dev/null 2>&1 || function killdown(){ if [ $# -eq 0 ]; then echo "Usage: killdown pid" >&2;return 1;else while [ $# -gt 0 ]; do if [ ${1} -ne 1 ]; then if [ `ps -edf|awk '{print $2}'|grep ${1}|wc -l` -gt 0 ]; then if [ `ps -edf|awk '{print $3}'|grep ${1}|wc -l` -gt 0 ]; then killdown `ps -edf | awk -vpid=${1} '{ if( $3==pid ) printf("%s ", $2); }'`;fi;fi;kill -9 ${1} 2> /dev/null;shift;fi;done;fi;} 5 | declare -F addcerts >/dev/null 2>&1 || function addcerts() { 6 | for f in $(ls -1 /usr/local/share/ca-certificates/* 2>/dev/null) ; do 7 | certificateFile="$f" 8 | certificateName="MyCA Name - $f" 9 | for certDB in $(find ~/.mozilla* ~/.thunderbird -name "cert9.db" 2>/dev/null) 10 | do 11 | certDir=$(dirname ${certDB}); 12 | echo "install certificate ${certificateFile} in Mozilla profile ${certDir}" 13 | certutil -A -n "${certificateName}" -t "TCu,Cuw,Tuw" -i ${certificateFile} -d sql:${certDir} 14 | done 15 | done 16 | } -------------------------------------------------------------------------------- /docker_vnc_immutable/immutableworkstation3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | #! -*- coding:utf-8 -*- 3 | 4 | """ 5 | ImmutableWorkstation 6 | ==================== 7 | 8 | This is a single entry point for the `immutableworkstation` project. 9 | 10 | The project is pretty simple - I want to have a consistent, immutable 11 | workstation on any host machine I am developing on - so I am using a 12 | docker instance on a host machine - the instance is my development 13 | "machine", and it can be rebuilt from consistent templates - this 14 | script helps control all that - its supposed to be easier to get 15 | started than a bunch of poorly documneted shell scripts. 16 | 17 | * the start and stopping of the dev instance. 18 | * the compilation of the docker image 19 | * vsarious config and templates used to build to docker image. 20 | 21 | This script does quite a lot, and needs to be installed on 22 | the host machine - do so using 23 | 24 | pip3 install docopt 25 | python3 setup.py install 26 | (I will launch it on PyPI soon) 27 | 28 | Once this is done, you should be able to run 29 | 30 | ./immutableworkstation.py 31 | 32 | usage 33 | ----- 34 | 35 | We expect to have a config .ini file. This is for ease of specifying things like 36 | volume mappings. 37 | 38 | By default the config file is at `~/immuntableworkstation/config.ini` 39 | 40 | [ ] Implement expect-style testing so we can automate testing. 41 | [x] put the home dir into git seperate to rest of pacakge (ie thats the indivudal part) 42 | [ ] put blog.mikadosoftware onto AWS and run this testing with docker on it. 43 | [ ] migrate rest of the articles there. 44 | [x] create a plain docker instance and just import devstation, see if it works (ie clean install) 45 | [ ] run the get github projects into one place 46 | 47 | [ ] podman system prune : clean up a lot of cruft in docker areas. 48 | [x] remove priviledged access with auser name remapping 49 | [ ] improve using https://github.com/mviereck/x11docker 50 | 51 | """ 52 | ##### imports ##### 53 | import logging, sys 54 | from docopt import docopt 55 | import subprocess 56 | import time 57 | import os 58 | from pprint import pprint as pp 59 | from mikado.core import config 60 | import shutil 61 | import json 62 | import lib_config 63 | import operator 64 | 65 | ##### Module setup ##### 66 | # TODO: split out logging into common module 67 | log = logging.getLogger(__name__) 68 | log.setLevel(logging.INFO) 69 | handler = logging.StreamHandler(sys.stdout) 70 | handler.setLevel(logging.INFO) 71 | log.addHandler(handler) 72 | 73 | DRYRUN = False 74 | PDB = False 75 | OCI_CMD = 'sudo docker' 76 | OCI_CMD = 'podman' 77 | 78 | #: usage defintons 79 | DOCOPT_HELP = """immutableworkstation 80 | 81 | Usage: 82 | immutableworkstation.py showconfig [options] 83 | immutableworkstation.py createDockerfile --templatedir= [options] 84 | immutableworkstation.py start tagname [options] 85 | immutableworkstation.py stop tagname [options] 86 | immutableworkstation.py login tagname [options] 87 | immutableworkstation.py buildAnyDocker [options] 88 | immutableworkstation.py status 89 | immutableworkstation.py test 90 | immutableworkstation.py (-h | --help ) 91 | 92 | 93 | Options: 94 | -h --help Show this screen 95 | -d --dryrun dryrun 96 | --configfile= path 2 config ini file 97 | --tagname= Name to tag 98 | --instancename= 99 | --username= 100 | --volumearray= 101 | 102 | 103 | """ 104 | 105 | def parse_docopt(argsd): 106 | '''We want to split into args (), options (--left) and commands (foo.py fire) ''' 107 | args = [] 108 | options = [] 109 | commands = [] 110 | active_commmands = [] 111 | # we assume only one command at a time? 112 | for k,i in argsd.items(): 113 | if k.startswith("--"): 114 | options.append({k:i}) 115 | elif k.startswith("<"): 116 | args.append({k:i}) 117 | else: 118 | commands.append({k:i}) 119 | # 120 | active_commands = [list(d.keys())[0] for d in commands if list(d.values())[0]] 121 | return args, options, commands, active_commands 122 | 123 | ############### Config 124 | def build_sshcmd(): 125 | """Create the command used to connect to running docker via ssh.""" 126 | 127 | return "ssh -X {username}@{localhost} -p {ssh_port}".format(**CONFD) 128 | 129 | def build_dockerrun(latest=True): 130 | """create the command used to start docker instance. 131 | 132 | tagname of image 133 | name of running instance 134 | """ 135 | _latest = LATEST if latest else NEXT 136 | instance_name = "run_{}_{}".format(CONFD["instance_name"], _latest) 137 | image_name = "{}:{}".format(CONFD["tagname"], _latest) 138 | vols = "" 139 | for hostpath, mountpath in CONFD["volumes"].items(): 140 | vols += "-v {}:{} ".format(hostpath, mountpath) 141 | 142 | return [ 143 | "{} container prune -f".format(OCI_CMD), 144 | """{OCI_CMD} run -d \ 145 | {vols} \ 146 | --name {instance_name} \ 147 | --device /dev/snd \ 148 | -p {ssh_port}:22 \ 149 | --privileged \ 150 | {tagname}:{_latest} 151 | """.format( 152 | OCI_CMD=OCI_CMD, 153 | vols=vols, 154 | instance_name=instance_name, 155 | ssh_port=CONFD["ssh_port"], 156 | _latest=_latest, 157 | tagname=CONFD["tagname"], 158 | ), 159 | ] 160 | 161 | 162 | def build_docker_build(latest=True): 163 | """Create command used to (re)build the container. 164 | 165 | We store the Dockerfile (as that name) 166 | in dir .next or .latest so that we can 167 | have various templates and assets and so on 168 | in the 'context' directory. 169 | 170 | """ 171 | tmpl = "{} build -t {{tagname}}:{{tagtag}} {{pathtodockerfile}} --squash".format(OCI_CMD) 172 | _latest = LATEST if latest else NEXT 173 | pathtodockerfile = os.path.join(CONFD["devstation_config_root"], "." + _latest) 174 | return tmpl.format( 175 | tagname=CONFD["tagname"], tagtag=_latest, pathtodockerfile=pathtodockerfile 176 | ) 177 | 178 | def build_docker_any_build(path_to_dockerfile, context_dir): 179 | """Create command used to (re)build the container. 180 | 181 | """ 182 | tmpl = "{} build -t {{tagname}}:{{tagtag}} -f {{path_to_dockerfile}} {{context_dir}} --squash".format(OCI_CMD) 183 | return tmpl.format( 184 | tagname='anybuild', tagtag='0.1', path_to_dockerfile=path_to_dockerfile, context_dir=context_dir 185 | ) 186 | 187 | def read_subprocess(cmd): 188 | """Run a command and return output """ 189 | 190 | result = subprocess.run(cmd, 191 | stdout=subprocess.PIPE, 192 | stderr=subprocess.PIPE, 193 | universal_newlines=True, 194 | shell=True) 195 | txt = result.stdout 196 | return txt 197 | 198 | 199 | def run_subprocess(cmd, shell=None): 200 | """Run the given command in a subprocess.""" 201 | if DRYRUN: 202 | telluser(cmd) 203 | else: 204 | log.info(cmd) 205 | subprocess.run(cmd, shell=True) 206 | 207 | 208 | def spawn_sibling_console(): 209 | """This script is best thought of as a launcher for other shells we 210 | shall be working in. We want to interact with the console, not 211 | this script much. 212 | 213 | I have played with fork'ing a child console, then passing `fd` 214 | 0,1,2 over to it. But the easiest way seems to be to assume this 215 | is a GUI workstation, and people are using a terminal program 216 | (like Konsole) - so we just spawn konsole and run -e 217 | 218 | """ 219 | 220 | sshcmd = '{} "{}" &'.format(CONFD["terminal_command"], build_sshcmd()) 221 | log.info(sshcmd) 222 | run_subprocess(sshcmd) 223 | 224 | def handle_start(args): 225 | """Perform cmsd needed to start the docker and login 226 | 227 | I really need to monitor the success of the underlying 228 | cmds, instead of brute force sleep. 229 | [ ] {milestone} stop using sleep, monitor the subprocess for return values. 230 | """ 231 | # do start up here 232 | cmds = build_dockerrun(args["latest"]) 233 | for cmd in cmds: 234 | # TODO get better solution than sleep 235 | run_subprocess(cmd, shell=True) 236 | time.sleep(8) # brute force give docker time to complete its stuff. 237 | time.sleep(10) # As above, but let docker catch up before login 238 | handle_login(args) 239 | 240 | 241 | ############### Config 242 | # This is a 'well-known' location 243 | CONFIGDIR = os.path.join(os.path.expanduser("~"), ".immutableworkstation") 244 | CONFIGLOCATION = os.path.join( 245 | os.path.expanduser("~"), ".immutableworkstation/config.ini" 246 | ) 247 | def handle_showconfig(args): 248 | print(args['--configfile']) 249 | #lib_config.show_config(confd=CONFD) 250 | 251 | def handle_login(args): 252 | spawn_sibling_console() 253 | 254 | def handle_createDockerfile(args): 255 | makeDocker(args['--templatedir']) 256 | 257 | def handle_buildDocker(args): 258 | """Trigger the processes to create new dockerfile and then build image. """ 259 | makeDocker(latest=args["latest"]) 260 | cmd = build_docker_build(latest=args["latest"]) 261 | run_subprocess(cmd) 262 | 263 | def parse_volumearray(args): 264 | ''' COnvert volumne array to usable instructions 265 | 266 | >>> parse_volumearray(args) 267 | ''' 268 | x = ['~/data=/var/data', 269 | '~/projects=/var/projects', 270 | '~/secrets=/var/secrets:ro', 271 | '~/Dropbox=/var/Dropbox'] 272 | return x 273 | 274 | def handle_buildAnyDocker(args): 275 | """Trigger the processes to create new dockerfile and then build image. """ 276 | #import pdb;pdb.set_trace() 277 | cmd = build_docker_any_build(args[''], args['']) 278 | run_subprocess(cmd) 279 | 280 | def handle_status(args): 281 | """Show container status. """ 282 | cmd = "{} container ls".format(OCI_CMD) 283 | run_subprocess(cmd) 284 | cmd = "{} inspect run_devstation_next".format(OCI_CMD) 285 | txt = read_subprocess(cmd) 286 | jsond = json.loads(txt) 287 | ipaddress = jsond[0]['NetworkSettings']['IPAddress'] 288 | print('Use this ip address {}'.format(ipaddress)) 289 | 290 | 291 | def handle_stop(args): 292 | """Kill the specified instance. """ 293 | _latest = LATEST if args["latest"] else NEXT 294 | #: rewrite so this is not in two places 295 | instance_name = "run_{}_{}".format(CONFD["instance_name"], _latest) 296 | cmd = "{} container kill {}".format(OCI_CMD, instance_name) 297 | run_subprocess(cmd) 298 | 299 | 300 | def hasValidConfig(): 301 | """This is a placeholder for future development on checking curr env. """ 302 | has_config_file = os.path.isfile(CONFIGLOCATION) 303 | return all([has_config_file]) 304 | 305 | 306 | def gatherinfo(): 307 | questions = { 308 | "username": "What username should be the default (only) on your immutable workstation?" 309 | } 310 | answers = {} 311 | for label, question in questions.items(): 312 | answer = input(question) 313 | answers[label] = answer 314 | return answers 315 | 316 | 317 | def handle_quickstart(args): 318 | """We have a starter config on github. Pull that down and put in 319 | users homedir, then alter based on questions. 320 | 321 | I am spending too long yak shaving on this app, and so will just 322 | print instructions and look to automate it later. 323 | """ 324 | helpmsg = "" 325 | if hasValidConfig(): 326 | helpmsg += """You appear to have an existing config in {}. 327 | Please adjust it manually - view docs for help.""".format( 328 | CONFIGLOCATION 329 | ) 330 | 331 | if not hasValidConfig(): 332 | helpmsg += """ In the future this app will walk you through a series of 333 | questions, but for now please can you download and unzip into {} the 334 | starter config stored at {}. You should have a directory layout like:: 335 | 336 | .immutableworkstation 337 | | 338 | -config.ini 339 | | 340 | -.next/ 341 | -.latest/ 342 | 343 | You should copy these into *your* github repo, and then update the 344 | templates to your needs, as you find a new package to be added to your 345 | workstation, adjust the config needed. 346 | 347 | """.format( 348 | CONFIGDIR, STARTER_CONFIG_URL 349 | ) 350 | 351 | telluser(helpmsg) 352 | 353 | def handle_unknown(command, e, args): 354 | telluser(f"Unknown request. We got command: {command} and error: {e}. Full args were {args}") 355 | 356 | 357 | def makeDocker(templatesdir): 358 | """Take a .skeleton file, and replace defined markup with 359 | contents of txt files 360 | 361 | Based on 'dockerfile.skeleton', replace any instance of 362 | {{ python }} with the contents of file `templates\python.template` 363 | 364 | This is an *extremely* simple templating tool. It is *not* 365 | supposed to have the complexity even of Jinja2. Its supposed to 366 | be really dumb. Lucky I wrote it then :-). 367 | 368 | 369 | """ 370 | pathtodockerfile = os.path.join(templatesdir, "../Dockerfile") 371 | skeleton = "dockerfile.skeleton" 372 | outputs = "" 373 | with open(os.path.join(templatesdir, skeleton)) as fo: 374 | for line in fo: 375 | if line.find("{{") == 0: 376 | file = line.replace("{{", "").replace("}}", "").strip() 377 | filepath = os.path.join(templatesdir, file + ".template") 378 | txt = open(filepath).read() 379 | outputs += "\n### {}\n{}\n".format(line, txt) 380 | else: 381 | outputs += "{}".format(line) 382 | fo = open(pathtodockerfile, "w") 383 | fo.write(outputs) 384 | fo.close() 385 | telluser("Written new Dockerfile at {}".format(pathtodockerfile)) 386 | 387 | 388 | def telluser(msg): 389 | """ aggregate print stmts into one place.""" 390 | # handle my weird formatting 391 | print(msg) 392 | 393 | def build_current_confd(args, options, commands, active_commands): 394 | print("args", args, '----\n') 395 | print("options", options, '----\n') 396 | print("commands", commands, '----\n') 397 | print("active commands", active_commands, '----\n') 398 | volumes = parse_volumearray(options) 399 | import sys; sys.exit() 400 | 401 | def run(argsd): 402 | #: start with quickstart as it may be our only options 403 | #: [ ] make this safer with .get 404 | args, options, commands, active_commands = parse_docopt(argsd) 405 | build_current_confd(args, options, commands, active_commands) 406 | for active_command in active_commands: 407 | try: 408 | # in current module, prepend handle_ to the name of the active command and 409 | # look for that in current module, if it exists, call it 410 | current_module = sys.modules[__name__] 411 | fn = operator.attrgetter('handle_{}'.format(active_command))(current_module) 412 | fn.__call__(argsd) 413 | except Exception as e: 414 | handle_unknown(active_command, e, argsd) 415 | 416 | def runtests(): 417 | import doctest 418 | doctest.testmod() 419 | 420 | teststr = ''' 421 | [default] 422 | tagname = workstation 423 | instance_name = devstation 424 | localhost = 127.0.0.1 425 | username = pbrian 426 | ssh_port = 2222 427 | terminal_command = /usr/bin/konsole -e 428 | volume_array: ~/secrets=/var/secrets:ro ~/secrets2=/var/secrets2:ro 429 | 430 | ''' 431 | def main(): 432 | global DRYRUN 433 | args = docopt(DOCOPT_HELP) 434 | if args.get("--dryrun", False): 435 | DRYRUN = True 436 | run(args) 437 | 438 | 439 | if __name__ == "__main__": 440 | main() 441 | -------------------------------------------------------------------------------- /docker_vnc_immutable/lib_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | #! -*- coding:utf-8 -*- 3 | 4 | """ 5 | lib_config 6 | ========== 7 | 8 | A library file for doing config-related things 9 | 10 | usage 11 | ----- 12 | 13 | We expect to have a config .ini file. This is for ease of specifying things like 14 | volume mappings. 15 | 16 | By default the config file is at `~/immuntableworkstation/config.ini` 17 | 18 | 19 | An example 20 | ----------- 21 | 22 | :: 23 | 24 | [default] 25 | tagname = workstation 26 | instance_name = devstation 27 | localhost = 127.0.0.1 28 | username = pbrian 29 | ssh_port = 2222 30 | terminal_command = /usr/bin/konsole -e 31 | [volumes] 32 | ~/data=/var/data 33 | ~/projects=/var/projects 34 | ~/secrets=/var/secrets:ro 35 | ~/Dropbox=/var/Dropbox 36 | ~/.immutableworkstation=/var/.immutableworkstation 37 | volume_1=~/data=/var/data 38 | 39 | """ 40 | ##### imports ##### 41 | import logging 42 | 43 | ######### THird party library for config mgmt ################# 44 | from dynaconf import Dynaconf 45 | 46 | from dynaconf import LazySettings 47 | 48 | # cannot use ~ ??? 49 | settings = LazySettings( 50 | PRELOAD_FOR_DYNACONF=["/home/pbrian/workstation_settings.ini",], # <-- Loaded first 51 | SETTINGS_FILE_FOR_DYNACONF="/etc/immutableworkstation/settings.ini", # <-- Loaded second (the main file) 52 | INCLUDES_FOR_DYNACONF=["other.module.settings", ] # <-- Loaded at the end 53 | ) 54 | 55 | # settings = Dynaconf( 56 | # envvar_prefix="DYNACONF", 57 | # settings_files=[ '.secrets.ini', '~/.env'], 58 | # ) 59 | 60 | # `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`. 61 | # `settings_files` = Load these files in the order. 62 | # '/home/pbrian/.immutableworkstation/settings.ini', 63 | # export DYNACONF_SETTINGS=/home/pbrian/.immutableworkstation/settings.ini 64 | ############################################################### 65 | -------------------------------------------------------------------------------- /docker_vnc_immutable/lib_logging.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | #! -*- coding:utf-8 -*- 3 | 4 | """ 5 | lib_logging 6 | =========== 7 | 8 | """ 9 | ##### imports ##### 10 | import logging, sys 11 | 12 | ##### Module setup ##### 13 | # TODO: split out logging into common module 14 | log = logging.getLogger(__name__) 15 | log.setLevel(logging.INFO) 16 | handler = logging.StreamHandler(sys.stdout) 17 | handler.setLevel(logging.INFO) 18 | log.addHandler(handler) 19 | 20 | -------------------------------------------------------------------------------- /docker_vnc_immutable/notes.rst: -------------------------------------------------------------------------------- 1 | Migrating to docker vnc 2 | ------------------------ 3 | 4 | UID issues 5 | ---------- 6 | 7 | Mounting volumes. 8 | I dont particularly want to have volumes mounted as 'named volumes' 9 | (these are a "filesystem" under Docker control, running on top of the host filesystem). 10 | 11 | I prefer using `bind mounts`, where the docker instance talks to a host filesystem in the 12 | "normal" manner. The only problem here is synchronising the UID/GID *numbers* so that the 13 | host filesystem has privileges for a given host UID, and that the same UID number is assigned 14 | inside the docker container. 15 | 16 | A "cheat" method for this is to start the docker with `userns=keep-id` which ensures that 17 | the UIDs match. 18 | 19 | :: 20 | 21 | --volume /home/pbrian/projects:/var/projects \ 22 | --volume /home/pbrian/secrets:/var/secrets:ro \ 23 | --volume /home/pbrian/Dropbox:/var/Dropbox \ 24 | --userns=keep-id \ 25 | 26 | 27 | groupadd --gid 5000 podgroup 28 | (view in /etc/group) 29 | 30 | usermod -a -G podgroup pbrian 31 | 32 | $ groups pbrian 33 | pbrian : pbrian adm cdrom sudo dip plugdev kvm lpadmin lxd sambashare podgroup 34 | 35 | $ sudo chown -R pbrian:podgroup projects/ 36 | $ sudo chmod -R 2775 projects/ 37 | $ sudo chmod -R 00775 projects/ 38 | ## will clear the setuid applied by 2.... mayeb we need that for shred directoures 39 | 40 | 41 | - use userns approach to ensure get same UID both host and docker 42 | - ensure entrypoint can load up secrets usb 43 | - also clear up entrypoints, 44 | - also reduce size of main python script, use ini file or docopt 45 | 46 | * tempaltesfile location 47 | * optional dockerfile location 48 | * optional tagnmae, build and launch using vncstart, and launch vncviewer 49 | 50 | -------------------------------------------------------------------------------- /docker_vnc_immutable/rcassets/.emacs: -------------------------------------------------------------------------------- 1 | ;; .emacs file for Paul Brian 2 | 3 | ;; init.el --- Emacs configuration 4 | 5 | ;; INSTALL PACKAGES 6 | ;; -------------------------------------- 7 | ;; If a package depeandncy is outdated remeber to M-x package-refresh-contents 8 | 9 | (require 'package) 10 | 11 | (add-to-list 'package-archives 12 | '("melpa" . "http://melpa.org/packages/") t) 13 | 14 | (package-initialize) 15 | (when (not package-archive-contents) 16 | (package-refresh-contents)) 17 | 18 | (defvar myPackages 19 | '(better-defaults 20 | ein 21 | elpy 22 | flycheck 23 | material-theme 24 | py-autopep8)) 25 | 26 | (mapc #'(lambda (package) 27 | (unless (package-installed-p package) 28 | (package-install package))) 29 | myPackages) 30 | 31 | ;; BASIC CUSTOMIZATION 32 | ;; -------------------------------------- 33 | 34 | (set `inhibit-startup-message t) ;; hide the startup message 35 | (load-theme 'material t) ;; load material theme 36 | ;; fonts 37 | 38 | (set-face-attribute 'default nil :family "inconsolata" :height 260) 39 | 40 | ;; dont use ReSt mode for sphinx 41 | (add-to-list 'auto-mode-alist '("\\.rst\\'" . text-mode)) 42 | ;; show current colmun (ie 80) 43 | (setq column-number-mode t) 44 | (setq c-basic-offset 4) ; indents 4 chars 45 | (setq tab-width 4) ; and 4 char wide for TAB 46 | (setq indent-tabs-mode nil) ; And force use of spaces 47 | (setq inhibit-splash-screen t) ; hide welcome screen 48 | (set `default-directory "/projects") 49 | 50 | ;; PYTHON CONFIGURATION 51 | ;; -------------------------------------- 52 | 53 | (elpy-enable) 54 | ;;(elpy-use-ipython) thisis deprecated 55 | (setq python-shell-interpreter "jupyter" 56 | python-shell-interpreter-args "console --simple-prompt") 57 | 58 | ;; use flycheck not flymake with elpy 59 | (when (require 'flycheck nil t) 60 | (setq elpy-modules (delq 'elpy-module-flymake elpy-modules)) 61 | (add-hook 'elpy-mode-hook 'flycheck-mode)) 62 | 63 | ;; enable autopep8 formatting on save 64 | (require 'py-autopep8) 65 | ;; really annoying hook esp with aggressive 66 | ;;(add-hook 'elpy-mode-hook 'py-autopep8-enable-on-save) 67 | 68 | (custom-set-variables 69 | ;; custom-set-variables was added by Custom. 70 | ;; If you edit it by hand, you could mess it up, so be careful. 71 | ;; Your init file should contain only one such instance. 72 | ;; If there is more than one, they won't work right. 73 | '(package-selected-packages (quote (docker-tramp material-theme better-defaults)))) 74 | (custom-set-faces 75 | ;; custom-set-faces was added by Custom. 76 | ;; If you edit it by hand, you could mess it up, so be careful. 77 | ;; Your init file should contain only one such instance. 78 | ;; If there is more than one, they won't work right. 79 | ) 80 | -------------------------------------------------------------------------------- /docker_vnc_immutable/rcassets/.gitconfig: -------------------------------------------------------------------------------- 1 | [user] 2 | email = paul@mikadosoftware.com 3 | name = Paul R Brian 4 | -------------------------------------------------------------------------------- /docker_vnc_immutable/rcassets/.pylintrc: -------------------------------------------------------------------------------- 1 | #pylintrc 2 | -------------------------------------------------------------------------------- /docker_vnc_immutable/rcassets/.ssh/config: -------------------------------------------------------------------------------- 1 | Host github.com 2 | User git 3 | IdentityFile ~/.ssh/common-github -------------------------------------------------------------------------------- /docker_vnc_immutable/rcassets/.ssh/known_hosts: -------------------------------------------------------------------------------- 1 | |1|tk0ub3iofa9PIa3A1TQ47afTqNo=|kejaOFiCbw3wG3h+7Ha4egOU9lI= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== 2 | |1|2atq1rLdBy4M+p9Yq0gDCc+eMSU=|ACmCV0TIw3MdVqw2As4vJZlVmDg= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== 3 | -------------------------------------------------------------------------------- /docker_vnc_immutable/rcassets/ENTRYPOINT.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## THis is run *inside* new docker instnce, but has mounted the 4 | ## secrets volume 5 | 6 | SECRETS_VOL="/var/secrets/usb" 7 | cp -r $SECRETS_VOL/.ssh/* /home/pbrian/.ssh 8 | 9 | chown -R pbrian:pbrian /home/pbrian/ 10 | chown -R pbrian:pbrian /home/pbrian/.ssh 11 | chmod -R 0700 /home/pbrian/.ssh 12 | 13 | # add aws credentials from host to docker 14 | mkdir /home/pbrian/.aws 15 | chmod 0622 -R /home/pbrian/.aws 16 | cp -r $SECRETS_VOL/aws-credentials /home/pbrian/.aws/credentials 17 | 18 | # add github 'hub' credentials from host to here 19 | echo "export GITHUB_TOKEN=`cat /var/secrets/github-token`" >> /home/pbrian/.bashrc 20 | 21 | 22 | ## Add local tools 23 | cd /var/projects/mikado-tools 24 | python3 setup.py install 25 | 26 | 27 | 28 | #: firefox should be run from the docker container so file:// works 29 | echo "export MOZ_NO_REMOTE=1" >> /home/pbrian/.bashrc 30 | 31 | #Now finally start this ready for XForwarding 32 | /usr/sbin/sshd -D 33 | -------------------------------------------------------------------------------- /docker_vnc_immutable/rcassets/own_dev_tools.sh: -------------------------------------------------------------------------------- 1 | sudo pip install git+https://github.com/mikadosoftware/todoinator.git 2 | sudo pip install git+https://github.com/mikadosoftware/mkrepo.git 3 | -------------------------------------------------------------------------------- /docker_vnc_immutable/rcassets/postinit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## THis is run *inside* new docker instnce, but has mounted the secrets volume 4 | 5 | # must not be in docker 6 | SECRETS_VOL="/var/secrets/usb" 7 | cp -r $SECRETS_VOL/.ssh/* ~/.ssh 8 | chmod -R 0600 ~/.ssh 9 | -------------------------------------------------------------------------------- /docker_vnc_immutable/rcassets/requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.9 2 | asn1crypto==0.24.0 3 | astroid==1.6.3 4 | attrs==18.1.0 5 | Babel==2.3.4 6 | boto3==1.5.29 7 | botocore==1.8.43 8 | certifi==2017.7.27.1 9 | cffi 10 | chardet==3.0.4 11 | CommonMark==0.5.4 12 | coverage==4.5.1 13 | cryptography==2.3 14 | docopt==0.6.2 15 | #: pypa.readme_renderer requires docutils>0.13 else cannot package up! 16 | docutils>=0.14 17 | #: pypa.readme_renderer requires bleach>=2.1.0 else cannot package up! 18 | bleach>=2.1.0 19 | ecdsa==0.13 20 | #Fabric3==1.12.post1 21 | idna==2.6 22 | imagesize==0.7.1 23 | isort==4.3.4 24 | Jinja2==2.8 25 | jmespath==0.9.3 26 | keyring==11.0.0 27 | lazy-object-proxy==1.3.1 28 | MarkupSafe==0.23 29 | mccabe==0.6.1 30 | MiniMock==1.2.8 31 | more-itertools==4.1.0 32 | paramiko==2.0.9 33 | pluggy==0.6.0 34 | purepng==0.2.0 35 | py==1.5.3 36 | pycparser==2.18 37 | pycrypto==2.6.1 38 | PyGithub==1.35 39 | Pygments==2.1.3 40 | PyJWT==1.5.3 41 | pylint==1.8.4 42 | pytest==3.5.1 43 | python-dateutil==2.6.1 44 | pytz==2016.7 45 | q==2.6 46 | recommonmark==0.4.0 47 | requests==2.20.0 48 | rinoh-typeface-dejavuserif==0.1.1 49 | rinoh-typeface-texgyrecursor==0.1.1 50 | rinoh-typeface-texgyreheros==0.1.1 51 | rinoh-typeface-texgyrepagella==0.1.1 52 | rinohtype==0.3.1 53 | s3transfer==0.1.13 54 | SecretStorage==2.3.1 55 | six==1.10.0 56 | snowballstemmer==1.2.1 57 | Sphinx==1.4.8 58 | todoinator==0.0.1 59 | tox==3.0.0 60 | urllib3==1.23 61 | virtualenv==15.2.0 62 | wrapt==1.10.11 63 | oauth2client 64 | spacy 65 | google-api-python-client 66 | pyyaml 67 | black 68 | semver 69 | twine 70 | pillow 71 | pyotp 72 | matplotlib 73 | pelican 74 | dnspython 75 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/apt.template: -------------------------------------------------------------------------------- 1 | # build the basic development world 2 | RUN apt-get update && \ 3 | apt-get install -y apt-transport-https \ 4 | build-essential \ 5 | build-essential \ 6 | dbus-x11 \ 7 | dos2unix \ 8 | fonts-inconsolata \ 9 | git \ 10 | openssh-server \ 11 | software-properties-common \ 12 | wget curl \ 13 | x11-apps \ 14 | && \ 15 | apt-get clean 16 | 17 | # We add some tools 18 | RUN \ 19 | echo "Install some tools" >&2 \ 20 | && apt-get update \ 21 | && apt-get install -y --no-install-recommends \ 22 | curl \ 23 | dumb-init \ 24 | figlet \ 25 | jq \ 26 | libnss3-tools \ 27 | mlocate \ 28 | net-tools \ 29 | vim \ 30 | vlc \ 31 | xz-utils \ 32 | zip \ 33 | && apt-get install -y thunar-archive-plugin \ 34 | && apt-get clean \ 35 | && apt-get autoremove -y \ 36 | && rm -rf /tmp/* /var/tmp/* \ 37 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 38 | && echo "Install some tools OK" >&2 39 | 40 | # We install firefox, directly from Mozilla (not from snap) 41 | RUN \ 42 | echo "Install Firefox from Mozilla" >&2 \ 43 | && apt-get update \ 44 | && add-apt-repository ppa:mozillateam/ppa \ 45 | && printf '\nPackage: *\nPin: release o=LP-PPA-mozillateam\nPin-Priority: 1001\n' > /etc/apt/preferences.d/mozilla-firefox \ 46 | && printf 'Unattended-Upgrade::Allowed-Origins:: "LP-PPA-mozillateam:${distro_codename}";' > /etc/apt/apt.conf.d/51unattended-upgrades-firefox \ 47 | && apt-get update \ 48 | && apt-get install -y firefox --no-install-recommends \ 49 | && apt-get clean \ 50 | && apt-get autoremove -y \ 51 | && rm -rf /tmp/* /var/tmp/* \ 52 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 53 | && echo "Install Firefox from Mozilla OK" >&2 54 | 55 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/baseconfig.template: -------------------------------------------------------------------------------- 1 | #################### Misc config ########################## 2 | ## Locales 3 | # We dont have any languages setup at this point 4 | # 5 | RUN apt-get update && \ 6 | apt-get install -y locales && \ 7 | locale-gen en_GB.UTF-8 && \ 8 | update-locale LC_ALL=en_GB.UTF-8 LANGUAGE=en_GB.UTF-8 && \ 9 | apt-get clean 10 | 11 | #these need to be run in user sections 12 | #RUN echo "export LC_ALL=en_GB.UTF-8" >> /home/pbrian/.bashrc 13 | #RUN echo "export LC_LANGUAGE=en_GB.UTF-8" >> /home/pbrian/.bashrc 14 | 15 | #THis should now work in our terminal (prints Greek for Thank you) 16 | #python3 -c "print(u''.join([u'\u0395', u'\u03c5', u'\u03c7', u'\u03B1', u'\u03c1', u'\u03B9', u'\u03C3', u'\u03c4', u'\u03c9']))" 17 | 18 | ## fonts 19 | RUN apt-get -y install fonts-inconsolata 20 | 21 | ENV TZ=Europe/London 22 | #not ideal 23 | ENV DEBIAN_FRONTEND=noninteractive 24 | RUN DEBIAN_FRONTEND=noninteractive && \ 25 | apt-get update && \ 26 | apt-get install -y tzdata 27 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ 28 | echo $TZ > /etc/timezone 29 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/dockerfile.skeleton: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy 2 | LABEL maintainer="paul@mikadosoftware.com" 3 | 4 | ARG PROXY_CERT 5 | RUN test -z "${PROXY_CERT}" || { echo "${PROXY_CERT}" | base64 -d | tee /usr/local/share/ca-certificates/ca-local.crt > /dev/null && update-ca-certificates ; } 6 | 7 | # We prepare environment 8 | ARG TZ=${TZ:-Etc/UTC} 9 | ARG DEBIAN_FRONTEND=noninteractive 10 | RUN \ 11 | echo "Timezone and locale" >&2 \ 12 | && apt-get update \ 13 | && apt-get install -y \ 14 | apt-utils \ 15 | software-properties-common \ 16 | tzdata \ 17 | && apt-get clean \ 18 | && apt-get autoremove -y \ 19 | && rm -rf /tmp/* /var/tmp/* \ 20 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 21 | && echo "Timezone and locale OK" >&2 22 | 23 | # install VNC 24 | RUN \ 25 | echo "install " >&2 \ 26 | && apt-get update \ 27 | && apt-get install -y --no-install-recommends \ 28 | libpulse0 \ 29 | x11vnc \ 30 | xvfb \ 31 | && apt-get clean \ 32 | && apt-get autoremove -y \ 33 | && rm -rf /tmp/* /var/tmp/* \ 34 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 35 | && echo "install VNC OK" >&2 36 | 37 | # xfce4 desktop environment 38 | RUN \ 39 | echo "Install xfce4 " >&2 \ 40 | && apt-get update \ 41 | && apt-get install -y --no-install-recommends \ 42 | dbus-x11 \ 43 | && apt-get install -y \ 44 | xfce4 xfce4-terminal xfce4-eyes-plugin \ 45 | xfce4-systemload-plugin xfce4-weather-plugin \ 46 | xfce4-whiskermenu-plugin xfce4-clipman-plugin \ 47 | xserver-xorg-video-dummy \ 48 | && apt-get clean \ 49 | && apt-get autoremove -y \ 50 | && rm -rf /tmp/* /var/tmp/* \ 51 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 52 | && echo "Install xfce4 OK" >&2 53 | 54 | 55 | # Templates can be added here 56 | # these are replaced with additional templates to enhance dockerfile 57 | 58 | {{ user }} 59 | 60 | {{ apt }} 61 | 62 | {{ baseconfig }} 63 | 64 | {{ otherdevtools }} 65 | 66 | {{ github_cli }} 67 | 68 | #When building a Docker image from the commandline, you can set ARG values using –build-arg: 69 | #$ docker build --build-arg some_variable_name=a_value 70 | # We add a simple user with sudo rights 71 | ENV USERNAME=pbrian 72 | ENV USERHOME=/home/pbrian 73 | ARG USR_UID=1000 74 | ARG USR_GID=5000 75 | 76 | RUN groupadd --gid ${USR_GID} ${USERNAME} 77 | RUN useradd --uid ${USR_UID} --create-home --gid ${USR_GID} --shell /bin/bash ${USERNAME} 78 | RUN echo "${USERNAME}:${USERNAME}" | chpasswd 79 | RUN echo ${USERNAME}' ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 80 | 81 | # It is useful to have a root account even if dont use it much. 82 | # Remeber this is a local machine dev environment, and root changes will be blown away next container run. 83 | RUN echo 'root:${USERNAME}' | chpasswd 84 | 85 | # change user locale settings (see baseconfig) 86 | RUN echo "export LC_ALL=en_GB.UTF-8" >> $USERHOME/.bashrc 87 | RUN echo "export LC_LANGUAGE=en_GB.UTF-8" >> $USERHOME/.bashrc 88 | 89 | # This is really just the config settings 90 | # any secrfets are added via '/var/secrets' volume per run 91 | # as such this is a "safe" image to keep on say a hub 92 | # COPY rcassets/.emacs $USERHOME 93 | # COPY rcassets/.pylintrc $USERHOME 94 | COPY rcassets/.gitconfig $USERHOME 95 | COPY rcassets/.ssh $USERHOME/.ssh 96 | RUN chown -R $USERNAME:$USERNAME $USERHOME/.ssh 97 | RUN chown -R $USERNAME:$USERNAME $USERHOME/.gitconfig 98 | 99 | #COPY rcassets/ENTRYPOINT.sh $USERHOME 100 | #COPY rcassets/ENTRYPOINT.sh $USERHOME 101 | #RUN chmod 0777 $USERHOME/ENTRYPOINT.sh 102 | 103 | 104 | 105 | 106 | ### {{ apt }} 107 | 108 | # build the basic development world 109 | RUN apt-get update && \ 110 | apt-get install -y apt-transport-https \ 111 | build-essential \ 112 | build-essential \ 113 | dbus-x11 \ 114 | dos2unix \ 115 | fonts-inconsolata \ 116 | git \ 117 | openssh-server \ 118 | software-properties-common \ 119 | wget curl \ 120 | x11-apps \ 121 | && \ 122 | apt-get clean 123 | 124 | # We add some tools 125 | RUN \ 126 | echo "Install some tools" >&2 \ 127 | && apt-get update \ 128 | && apt-get install -y --no-install-recommends \ 129 | curl \ 130 | dumb-init \ 131 | figlet \ 132 | jq \ 133 | libnss3-tools \ 134 | mlocate \ 135 | net-tools \ 136 | vim \ 137 | vlc \ 138 | xz-utils \ 139 | zip \ 140 | && apt-get install -y thunar-archive-plugin \ 141 | && apt-get clean \ 142 | && apt-get autoremove -y \ 143 | && rm -rf /tmp/* /var/tmp/* \ 144 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 145 | && echo "Install some tools OK" >&2 146 | 147 | # We install firefox, directly from Mozilla (not from snap) 148 | RUN \ 149 | echo "Install Firefox from Mozilla" >&2 \ 150 | && apt-get update \ 151 | && add-apt-repository ppa:mozillateam/ppa \ 152 | && printf '\nPackage: *\nPin: release o=LP-PPA-mozillateam\nPin-Priority: 1001\n' > /etc/apt/preferences.d/mozilla-firefox \ 153 | && printf 'Unattended-Upgrade::Allowed-Origins:: "LP-PPA-mozillateam:${distro_codename}";' > /etc/apt/apt.conf.d/51unattended-upgrades-firefox \ 154 | && apt-get update \ 155 | && apt-get install -y firefox --no-install-recommends \ 156 | && apt-get clean \ 157 | && apt-get autoremove -y \ 158 | && rm -rf /tmp/* /var/tmp/* \ 159 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 160 | && echo "Install Firefox from Mozilla OK" >&2 161 | 162 | 163 | 164 | 165 | ### {{ baseconfig }} 166 | 167 | #################### Misc config ########################## 168 | ## Locales 169 | # We dont have any languages setup at this point 170 | # 171 | RUN apt-get update && \ 172 | apt-get install -y locales && \ 173 | locale-gen en_GB.UTF-8 && \ 174 | update-locale LC_ALL=en_GB.UTF-8 LANGUAGE=en_GB.UTF-8 && \ 175 | apt-get clean 176 | 177 | #these need to be run in user sections 178 | #RUN echo "export LC_ALL=en_GB.UTF-8" >> /home/pbrian/.bashrc 179 | #RUN echo "export LC_LANGUAGE=en_GB.UTF-8" >> /home/pbrian/.bashrc 180 | 181 | #THis should now work in our terminal (prints Greek for Thank you) 182 | #python3 -c "print(u''.join([u'\u0395', u'\u03c5', u'\u03c7', u'\u03B1', u'\u03c1', u'\u03B9', u'\u03C3', u'\u03c4', u'\u03c9']))" 183 | 184 | ## fonts 185 | RUN apt-get -y install fonts-inconsolata 186 | 187 | ENV TZ=Europe/London 188 | #not ideal 189 | ENV DEBIAN_FRONTEND=noninteractive 190 | RUN DEBIAN_FRONTEND=noninteractive && \ 191 | apt-get update && \ 192 | apt-get install -y tzdata 193 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ 194 | echo $TZ > /etc/timezone 195 | 196 | 197 | 198 | ### {{ otherdevtools }} 199 | 200 | 201 | ### Other Dev Tools 202 | RUN apt-get install -y whois 203 | 204 | 205 | 206 | 207 | ### {{ github_cli }} 208 | 209 | # from cli github page: 210 | RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ 211 | && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ 212 | && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ 213 | && apt update \ 214 | && apt install gh -y 215 | 216 | 217 | 218 | 219 | 220 | 221 | # We add sound 222 | # add root user to group for pulseaudio access 223 | RUN adduser root pulse-access 224 | RUN printf 'default-server = unix:/run/user/1000/pulse/native\nautospawn = no\ndaemon-binary = /bin/true\nenable-shm = false' > /etc/pulse/client.conf 225 | 226 | 227 | # Two ports are availables: 5900 for VNC client, and 6080 for browser access via websockify 228 | EXPOSE 5900 229 | 230 | # We set localtime 231 | RUN if [ "X${TZ}" != "X" ] ; then if [ -f /usr/share/zoneinfo/${TZ} ] ; then rm -f /etc/localtime ; ln -s /usr/share/zoneinfo/${TZ} /etc/localtime ; fi ; fi 232 | 233 | # And here is the statup script, everything else is in there 234 | COPY entrypoint.sh /entrypoint.sh 235 | RUN chmod 755 /entrypoint.sh 236 | 237 | # We do some specials 238 | RUN \ 239 | updatedb ; \ 240 | apt-get clean \ 241 | && apt-get autoremove -y \ 242 | && rm -rf /tmp/* /var/tmp/* \ 243 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* 244 | 245 | # We change user 246 | USER ${USERNAME} 247 | WORKDIR /home/${USERNAME} 248 | COPY functions.sh /home/${USERNAME}/.functions.sh 249 | COPY bgimage.jpg /usr/share/backgrounds/xfce/bgimage.jpg 250 | RUN \ 251 | printf 'if [[ $- = *i* ]] ; then test -f ~/.functions.sh && . ~/.functions.sh ; fi' >> /home/${USERNAME}/.bashrc 252 | 253 | #ENTRYPOINT [ "/usr/bin/dumb-init", "--", "/entrypoint.sh" ] 254 | ENTRYPOINT [ "/entrypoint.sh" ] 255 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/emacs.template: -------------------------------------------------------------------------------- 1 | #USER command in DOCKER to run correctly as me 2 | USER pbrian 3 | RUN emacs -nw --script $USERHOME/.emacs 4 | USER root 5 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/github.template: -------------------------------------------------------------------------------- 1 | # We install `hub` from github 2 | # We need to write our own scripts to use the underlying api calls 3 | 4 | # # post a comment to issue #23 of the current repository 5 | # $ hub api repos/{owner}/{repo}/issues/23/comments --raw-field "body=Nice!" 6 | 7 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/github_cli.template: -------------------------------------------------------------------------------- 1 | # from cli github page: 2 | RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ 3 | && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ 4 | && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ 5 | && apt update \ 6 | && apt install gh -y 7 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/latex.template: -------------------------------------------------------------------------------- 1 | RUN apt-get install -y texlive-base \ 2 | texlive-latex-recommended \ 3 | texlive-latex-extra \ 4 | texlive-fonts-recommended \ 5 | texlive-fonts-extra \ 6 | texlive-latex-base \ 7 | texlive-font-utils && \ 8 | apt-get clean 9 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/otherdevtools.template: -------------------------------------------------------------------------------- 1 | 2 | ### Other Dev Tools 3 | RUN apt-get install -y whois 4 | 5 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/py3.template: -------------------------------------------------------------------------------- 1 | ###### Install with apt 2 | RUN apt-get install -y python3.9 \ 3 | python3.9-dev \ 4 | python3-distutils \ 5 | python3-distlib \ 6 | python3-pip \ 7 | apt-get clean 8 | 9 | ###### symlinking to have `pip` and `python` 10 | RUN cd /usr/bin \ 11 | && ln -sf python3.9 python \ 12 | && ln -sf python3.9 python3 \ 13 | && ln -sf pip3 pip 14 | 15 | ##### 16 | RUN python3 -m pip install --upgrade pip 17 | RUN python3 -m pip install pygithub 18 | 19 | ### update python pkgs 20 | # Install any needed packages (ie above those for the runtime) 21 | RUN pip install --trusted-host pypi.python.org sphinx \ 22 | pytest \ 23 | pylint 24 | 25 | 26 | # install requirements file. (why not specify in thisfile???) 27 | COPY rcassets/requirements.txt $WKDIR/ 28 | RUN pip install -r $WKDIR/requirements.txt 29 | # Additonal setup for spacy. I think this is sensible to do specify in this file them 30 | RUN python -m spacy download en 31 | 32 | # seems ubunut installs pip_internal again mucking things up 33 | 34 | # lastly redo symlinks 35 | RUN rm /usr/bin/python 36 | RUN ln -s /usr/bin/python3 /usr/bin/python 37 | 38 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/sshX.template: -------------------------------------------------------------------------------- 1 | ### Create SSH access to box 2 | ### NB Fix root access 3 | RUN mkdir -p /var/run/sshd 4 | RUN echo 'root:root' | chpasswd 5 | RUN sed -ri 's/^#PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config 6 | RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config 7 | RUN sed -ri 's/^#AllowTcpForwarding\s+.*/AllowTcpForwarding yes/g' /etc/ssh/sshd_config 8 | RUN sed -ri 's/^#X11Forwarding\s+.*/X11Forwarding yes/g' /etc/ssh/sshd_config 9 | RUN sed -ri 's/^#X11UseLocalhost\s+.*/X11UseLocalhost no/g' /etc/ssh/sshd_config 10 | 11 | 12 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/terminal_setup_notes.txt: -------------------------------------------------------------------------------- 1 | 2 | ''' 3 | We want to use the weaver fab lib to run a basic install, on my 4 | laptop, of the terminal settings defined in here. 5 | 6 | I have a libary of functions in fedorafab, that I can use to write 7 | files to the (remote) laptop, and also setup and configure as needed. 8 | 9 | Its all here bar the fat lady singing. 10 | 11 | ''' 12 | 13 | 14 | BASE_TMPL = ''' 15 | !------------------------------------------------------------------------------- 16 | ! Xft settings 17 | !------------------------------------------------------------------------------- 18 | 19 | Xft.dpi: 96 20 | Xft.antialias: false 21 | Xft.rgba: rgb 22 | Xft.hinting: true 23 | Xft.hintstyle: hintslight 24 | 25 | !------------------------------------------------------------------------------- 26 | ! URxvt settings 27 | ! Colours lifted from Solarized (http://ethanschoonover.com/solarized) 28 | ! More info at: 29 | ! http://pod.tst.eu/http://cvs.schmorp.de/rxvt-unicode/doc/rxvt.1.pod 30 | !------------------------------------------------------------------------------- 31 | 32 | URxvt.depth: 32 33 | URxvt.geometry: 90x30 34 | URxvt.transparent: false 35 | URxvt.fading: 0 36 | ! URxvt.urgentOnBell: true 37 | ! URxvt.visualBell: true 38 | URxvt.loginShell: true 39 | URxvt.saveLines: 50 40 | URxvt.internalBorder: 3 41 | URxvt.lineSpace: 0 42 | 43 | ! Fonts 44 | URxvt.allow_bold: false 45 | /* URxvt.font: -*-terminus-medium-r-normal-*-12-120-72-72-c-60-iso8859-1 */ 46 | URxvt*font: -*-terminus-*-*-*-*-32-*-*-*-*-*-*-* 47 | URxvt*boldFont: -*-terminus-*-*-*-*-32-*-*-*-*-*-*-* 48 | 49 | ! Fix font space 50 | URxvt*letterSpace: -1 51 | 52 | ! Scrollbar 53 | URxvt.scrollStyle: rxvt 54 | URxvt.scrollBar: false 55 | 56 | ! Perl extensions 57 | URxvt.perl-ext-common: default,matcher 58 | URxvt.matcher.button: 1 59 | URxvt.urlLauncher: firefox 60 | 61 | ! Cursor 62 | URxvt.cursorBlink: true 63 | URxvt.cursorColor: #657b83 64 | URxvt.cursorUnderline: false 65 | 66 | ! Pointer 67 | URxvt.pointerBlank: true 68 | 69 | !!Source http://github.com/altercation/solarized 70 | 71 | *background: #002b36 72 | *foreground: #657b83 73 | !!*fading: 40 74 | *fadeColor: #002b36 75 | *cursorColor: #93a1a1 76 | *pointerColorBackground: #586e75 77 | *pointerColorForeground: #93a1a1 78 | 79 | !! black dark/light 80 | *color0: #073642 81 | *color8: #002b36 82 | 83 | !! red dark/light 84 | *color1: #dc322f 85 | *color9: #cb4b16 86 | 87 | !! green dark/light 88 | *color2: #859900 89 | *color10: #586e75 90 | 91 | !! yellow dark/light 92 | *color3: #b58900 93 | *color11: #657b83 94 | 95 | !! blue dark/light 96 | *color4: #268bd2 97 | *color12: #839496 98 | 99 | !! magenta dark/light 100 | *color5: #d33682 101 | *color13: #6c71c4 102 | 103 | !! cyan dark/light 104 | *color6: #2aa198 105 | *color14: #93a1a1 106 | 107 | !! white dark/light 108 | *color7: #eee8d5 109 | *color15: #fdf6e3 110 | 111 | ''' 112 | from . import fedorafab 113 | from .fedorafab import run, sudo 114 | 115 | __all__ = ['install_termandemacs',] 116 | 117 | def install_termandemacs(): 118 | setup_xterm() 119 | install_terminal() 120 | install_fonts() 121 | install_emacs() 122 | setup_emacs() 123 | 124 | 125 | def install_emacs(): 126 | """ 127 | """ 128 | sudo("dnf install -y %s " % 'emacs') 129 | 130 | def setup_emacs(): 131 | """ 132 | """ 133 | emacs_tmpl = ''' 134 | ;; fonts etc 135 | ;; I set .Xresources to have an arrary of colours, which emacs picks up by 136 | ;; default, so I am not, for now, using an emacs theme. 137 | (set-default-font "Droid Sans Mono-24") 138 | 139 | ;; Start up options 140 | ;; do not show the intro screen in split 141 | (setq inhibit-startup-screen t) 142 | ;; start in full screen mode. I do not have a tiling WM, and I mostly work 143 | ;; on a laptop. Want a new screen, Alt-tab. 144 | (add-to-list 'default-frame-alist '(fullscreen . maximized)) 145 | ;; Only one window on startup 146 | (add-hook 'emacs-startup-hook 'delete-other-windows t) 147 | 148 | ;; Future changes 149 | ;; https://www.emacswiki.org/emacs/PythonProgrammingInEmacs 150 | ; ropemacs, flycheck, pylint 151 | ;; all goals to get to a robust dev env. 152 | 153 | ;; Make PRIMARY selection (mouse highlight in terminal usually) 154 | ;;; paste-able with SHIFT INSERT 155 | ;; C-W / C-Y still uses clipboard 156 | (setq select-enable-primary `t) 157 | ;; (it used to be x-select-enable-primary but changed on emacs 25 and 158 | ;; the internet has not caught up yet.) 159 | 160 | 161 | ''' 162 | remote_path = '/home/pbrian/.emacs.d/init.el' 163 | fedorafab.replace_remote_file(remote_path, emacs_tmpl) 164 | 165 | def setup_xterm(): 166 | """xrdb is of course and X program so needs to display out to a DISPLAY. 167 | I fake one, in the same env as the call is made 168 | I prob need to look at env var in fabric 169 | """ 170 | remote_path = '/home/pbrian/.Xresources' 171 | fedorafab.replace_remote_file(remote_path, BASE_TMPL) 172 | run('export DISPLAY=:0;xrdb {0} > /dev/null;echo $DISPLAY'.format(remote_path)) 173 | 174 | def install_terminal(): 175 | """ 176 | Using uxrvt 177 | """ 178 | pkgs = ['rxvt-unicode-256color', 179 | 'xorg-x11-apps'] 180 | for pkg in pkgs: 181 | sudo("dnf install -y %s " % pkg) 182 | 183 | def setup_term(): 184 | """We want to have the nice perl extensions 185 | """ 186 | pass 187 | 188 | def install_fonts(): 189 | """ 190 | I want to install inconsolata or similar 191 | """ 192 | 193 | sudo("yum install -y levien-inconsolata-fonts.noarch") 194 | sudo("yum install -y google-droid-sans-mono-fonts.noarch") 195 | sudo("fc-cache -fv") # refresh the cache of fonts 196 | 197 | # set up bash config 198 | def setup_bash(): 199 | """ 200 | """ 201 | #set emacs for most command line usage 202 | #export VISUAL="emacs" 203 | #export EDITOR="$VISUAL" 204 | #export GIT_EDITOR="$VISUAL" 205 | pass 206 | 207 | 208 | 209 | # install python3 210 | # sudo apt-get install python3 python3-pip 211 | # install python from apt ???? 212 | # from source, but ??? wheels first... 213 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/user.template: -------------------------------------------------------------------------------- 1 | #RUN apt-get install sudo 2 | 3 | # We add a simple user with sudo rights 4 | ENV USERNAME=pbrian 5 | ENV USERHOME=/home/pbrian 6 | ARG USR_UID=1000 7 | ARG USR_GID=5000 8 | 9 | RUN groupadd --gid ${USR_GID} ${USERNAME} 10 | RUN useradd --uid ${USR_UID} --create-home --gid ${USR_GID} --shell /bin/bash ${USERNAME} 11 | RUN echo "${USERNAME}:${USERNAME}" | chpasswd 12 | RUN echo ${USERNAME}' ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 13 | 14 | 15 | ### Setup me as only user .... because I will be ! 16 | # RUN useradd -m -c "$USERFULLNAME" $USERNAME --shell /bin/bash 17 | # RUN usermod -aG sudo $USERNAME 18 | # RUN echo "$USERNAME:$USERNAME" | chpasswd 19 | 20 | # change user locale settings (see baseconfig) 21 | RUN echo "export LC_ALL=en_GB.UTF-8" >> $USERHOME/.bashrc 22 | RUN echo "export LC_LANGUAGE=en_GB.UTF-8" >> $USERHOME/.bashrc 23 | 24 | # This is really just the config settings 25 | # any secrfets are added via '/var/secrets' volume per run 26 | # as such this is a "safe" image to keep on say a hub 27 | # COPY rcassets/.emacs $USERHOME 28 | # COPY rcassets/.pylintrc $USERHOME 29 | COPY rcassets/.gitconfig $USERHOME 30 | COPY rcassets/.ssh $USERHOME/.ssh 31 | #COPY rcassets/ENTRYPOINT.sh $USERHOME 32 | #COPY rcassets/ENTRYPOINT.sh $USERHOME 33 | #RUN chmod 0777 $USERHOME/ENTRYPOINT.sh 34 | 35 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/vnc.template: -------------------------------------------------------------------------------- 1 | # build the basic development world 2 | RUN apt-get update && \ 3 | apt-get install -y apt-transport-https \ 4 | ca-certificates \ 5 | software-properties-common \ 6 | xfce4 \ 7 | i3 \ 8 | tigervnc-common \ 9 | tigervnc-standalone-server && \ 10 | apt-get clean 11 | ` 12 | -------------------------------------------------------------------------------- /docker_vnc_immutable/templates/vscode.template: -------------------------------------------------------------------------------- 1 | ### Install VSCode 2 | sudo apt-get install wget gpg 3 | wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg 4 | install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg 5 | sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list' 6 | rm -f packages.microsoft.gpg 7 | 8 | apt install apt-transport-https 9 | apt update 10 | apt install code -------------------------------------------------------------------------------- /docker_vnc_immutable/test1.py: -------------------------------------------------------------------------------- 1 | 2 | #my application 3 | 4 | # env : 5 | # export SETTINGS_FILE_FOR_DYNACONF='["/home/pbrian/.immutableworkstation/settings.ini",]' 6 | 7 | 8 | import os 9 | #print(os.environ) 10 | for i in os.environ.keys(): 11 | if i.startswith('DYNACONF'): 12 | print(i, os.environ[i]) 13 | 14 | from lib_config import settings 15 | #dynaconf_include = ["/home/pbrian/foo.ini"] 16 | print(settings.values()) -------------------------------------------------------------------------------- /docker_vnc_immutable/vncstart.sh: -------------------------------------------------------------------------------- 1 | 2 | catch_err() { 3 | echo "An error occured. Commonly this needs a podman container prune to remove container image with same name." 4 | } 5 | 6 | trap 'catch_err' ERR 7 | # remove any stopped, old versions of the container that prevent rerunning 8 | # best to provide new name 9 | podman container prune 10 | 11 | podman run --rm \ 12 | --interactive \ 13 | --tty \ 14 | --volume /run/user/$(id -u)/pulse/native:/run/user/1000/pulse/native \ 15 | --volume /home/pbrian/data:/var/data:z \ 16 | --volume /home/pbrian/projects:/var/projects \ 17 | --volume /home/pbrian/secrets:/var/secrets:ro \ 18 | --volume /home/pbrian/Dropbox:/var/Dropbox \ 19 | --userns=keep-id \ 20 | --publish ${VNC_PORT:-5900}:5900 \ 21 | --name immutableworkstation \ 22 | --env LANG=en_GB.UTF-8 \ 23 | --env DESKTOP_SIZE="1920x1000" \ 24 | --env DESKTOP_BACKGROUND_IMAGE="https://apod.nasa.gov/apod/image/2210/JovianEclipse1024c.jpg" \ 25 | --detach \ 26 | mikado-immutableworkstation 27 | 28 | sleep 15 29 | vncviewer localhost:0 30 | 31 | # test out sounds with 32 | ## paplay /usr/share/sounds/freedesktop/stereo/bell.oga 33 | ## you should hear a sound on PC speaker and hey presto, desktop. 34 | ## As a confirmed tone-deaf music-o-philistine please do not ask for anything clever in the audio world 35 | 36 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ImmutableWorkstation.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ImmutableWorkstation.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/ImmutableWorkstation" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ImmutableWorkstation" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # ImmutableWorkstation documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Apr 5 08:54:51 2019. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.todo', 36 | 'sphinx.ext.coverage', 37 | 'sphinx.ext.viewcode', 38 | ] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The encoding of source files. 50 | # 51 | # source_encoding = 'utf-8-sig' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = 'ImmutableWorkstation' 58 | copyright = '2019, Paul R Brian' 59 | author = 'Paul R Brian' 60 | 61 | # The version info for the project you're documenting, acts as replacement for 62 | # |version| and |release|, also used in various other places throughout the 63 | # built documents. 64 | # 65 | # The short X.Y version. 66 | version = '0.0.1' 67 | # The full version, including alpha/beta/rc tags. 68 | release = '0.0.1' 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | # 73 | # This is also used if you do content translation via gettext catalogs. 74 | # Usually you set "language" from the command line for these cases. 75 | language = None 76 | 77 | # There are two options for replacing |today|: either, you set today to some 78 | # non-false value, then it is used: 79 | # 80 | # today = '' 81 | # 82 | # Else, today_fmt is used as the format for a strftime call. 83 | # 84 | # today_fmt = '%B %d, %Y' 85 | 86 | # List of patterns, relative to source directory, that match files and 87 | # directories to ignore when looking for source files. 88 | # This patterns also effect to html_static_path and html_extra_path 89 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 90 | 91 | # The reST default role (used for this markup: `text`) to use for all 92 | # documents. 93 | # 94 | # default_role = None 95 | 96 | # If true, '()' will be appended to :func: etc. cross-reference text. 97 | # 98 | # add_function_parentheses = True 99 | 100 | # If true, the current module name will be prepended to all description 101 | # unit titles (such as .. function::). 102 | # 103 | # add_module_names = True 104 | 105 | # If true, sectionauthor and moduleauthor directives will be shown in the 106 | # output. They are ignored by default. 107 | # 108 | # show_authors = False 109 | 110 | # The name of the Pygments (syntax highlighting) style to use. 111 | pygments_style = 'sphinx' 112 | 113 | # A list of ignored prefixes for module index sorting. 114 | # modindex_common_prefix = [] 115 | 116 | # If true, keep warnings as "system message" paragraphs in the built documents. 117 | # keep_warnings = False 118 | 119 | # If true, `todo` and `todoList` produce output, else they produce nothing. 120 | todo_include_todos = True 121 | 122 | 123 | # -- Options for HTML output ---------------------------------------------- 124 | 125 | # The theme to use for HTML and HTML Help pages. See the documentation for 126 | # a list of builtin themes. 127 | # 128 | html_theme = 'alabaster' 129 | 130 | # Theme options are theme-specific and customize the look and feel of a theme 131 | # further. For a list of options available for each theme, see the 132 | # documentation. 133 | # 134 | # html_theme_options = {} 135 | 136 | # Add any paths that contain custom themes here, relative to this directory. 137 | # html_theme_path = [] 138 | 139 | # The name for this set of Sphinx documents. 140 | # " v documentation" by default. 141 | # 142 | # html_title = 'ImmutableWorkstation v0.0.1' 143 | 144 | # A shorter title for the navigation bar. Default is the same as html_title. 145 | # 146 | # html_short_title = None 147 | 148 | # The name of an image file (relative to this directory) to place at the top 149 | # of the sidebar. 150 | # 151 | # html_logo = None 152 | 153 | # The name of an image file (relative to this directory) to use as a favicon of 154 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 155 | # pixels large. 156 | # 157 | # html_favicon = None 158 | 159 | # Add any paths that contain custom static files (such as style sheets) here, 160 | # relative to this directory. They are copied after the builtin static files, 161 | # so a file named "default.css" will overwrite the builtin "default.css". 162 | html_static_path = ['_static'] 163 | 164 | # Add any extra paths that contain custom files (such as robots.txt or 165 | # .htaccess) here, relative to this directory. These files are copied 166 | # directly to the root of the documentation. 167 | # 168 | # html_extra_path = [] 169 | 170 | # If not None, a 'Last updated on:' timestamp is inserted at every page 171 | # bottom, using the given strftime format. 172 | # The empty string is equivalent to '%b %d, %Y'. 173 | # 174 | # html_last_updated_fmt = None 175 | 176 | # If true, SmartyPants will be used to convert quotes and dashes to 177 | # typographically correct entities. 178 | # 179 | # html_use_smartypants = True 180 | 181 | # Custom sidebar templates, maps document names to template names. 182 | # 183 | # html_sidebars = {} 184 | 185 | # Additional templates that should be rendered to pages, maps page names to 186 | # template names. 187 | # 188 | # html_additional_pages = {} 189 | 190 | # If false, no module index is generated. 191 | # 192 | # html_domain_indices = True 193 | 194 | # If false, no index is generated. 195 | # 196 | # html_use_index = True 197 | 198 | # If true, the index is split into individual pages for each letter. 199 | # 200 | # html_split_index = False 201 | 202 | # If true, links to the reST sources are added to the pages. 203 | # 204 | # html_show_sourcelink = True 205 | 206 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 207 | # 208 | # html_show_sphinx = True 209 | 210 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 211 | # 212 | # html_show_copyright = True 213 | 214 | # If true, an OpenSearch description file will be output, and all pages will 215 | # contain a tag referring to it. The value of this option must be the 216 | # base URL from which the finished HTML is served. 217 | # 218 | # html_use_opensearch = '' 219 | 220 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 221 | # html_file_suffix = None 222 | 223 | # Language to be used for generating the HTML full-text search index. 224 | # Sphinx supports the following languages: 225 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 226 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 227 | # 228 | # html_search_language = 'en' 229 | 230 | # A dictionary with options for the search language support, empty by default. 231 | # 'ja' uses this config value. 232 | # 'zh' user can custom change `jieba` dictionary path. 233 | # 234 | # html_search_options = {'type': 'default'} 235 | 236 | # The name of a javascript file (relative to the configuration directory) that 237 | # implements a search results scorer. If empty, the default will be used. 238 | # 239 | # html_search_scorer = 'scorer.js' 240 | 241 | # Output file base name for HTML help builder. 242 | htmlhelp_basename = 'ImmutableWorkstationdoc' 243 | 244 | # -- Options for LaTeX output --------------------------------------------- 245 | 246 | latex_elements = { 247 | # The paper size ('letterpaper' or 'a4paper'). 248 | # 249 | # 'papersize': 'letterpaper', 250 | 251 | # The font size ('10pt', '11pt' or '12pt'). 252 | # 253 | # 'pointsize': '10pt', 254 | 255 | # Additional stuff for the LaTeX preamble. 256 | # 257 | # 'preamble': '', 258 | 259 | # Latex figure (float) alignment 260 | # 261 | # 'figure_align': 'htbp', 262 | } 263 | 264 | # Grouping the document tree into LaTeX files. List of tuples 265 | # (source start file, target name, title, 266 | # author, documentclass [howto, manual, or own class]). 267 | latex_documents = [ 268 | (master_doc, 'ImmutableWorkstation.tex', 'ImmutableWorkstation Documentation', 269 | 'Paul R Brian', 'manual'), 270 | ] 271 | 272 | # The name of an image file (relative to this directory) to place at the top of 273 | # the title page. 274 | # 275 | # latex_logo = None 276 | 277 | # For "manual" documents, if this is true, then toplevel headings are parts, 278 | # not chapters. 279 | # 280 | # latex_use_parts = False 281 | 282 | # If true, show page references after internal links. 283 | # 284 | # latex_show_pagerefs = False 285 | 286 | # If true, show URL addresses after external links. 287 | # 288 | # latex_show_urls = False 289 | 290 | # Documents to append as an appendix to all manuals. 291 | # 292 | # latex_appendices = [] 293 | 294 | # It false, will not define \strong, \code, itleref, \crossref ... but only 295 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 296 | # packages. 297 | # 298 | # latex_keep_old_macro_names = True 299 | 300 | # If false, no module index is generated. 301 | # 302 | # latex_domain_indices = True 303 | 304 | 305 | # -- Options for manual page output --------------------------------------- 306 | 307 | # One entry per manual page. List of tuples 308 | # (source start file, name, description, authors, manual section). 309 | man_pages = [ 310 | (master_doc, 'immutableworkstation', 'ImmutableWorkstation Documentation', 311 | [author], 1) 312 | ] 313 | 314 | # If true, show URL addresses after external links. 315 | # 316 | # man_show_urls = False 317 | 318 | 319 | # -- Options for Texinfo output ------------------------------------------- 320 | 321 | # Grouping the document tree into Texinfo files. List of tuples 322 | # (source start file, target name, title, author, 323 | # dir menu entry, description, category) 324 | texinfo_documents = [ 325 | (master_doc, 'ImmutableWorkstation', 'ImmutableWorkstation Documentation', 326 | author, 'ImmutableWorkstation', 'One line description of project.', 327 | 'Miscellaneous'), 328 | ] 329 | 330 | # Documents to append as an appendix to all manuals. 331 | # 332 | # texinfo_appendices = [] 333 | 334 | # If false, no module index is generated. 335 | # 336 | # texinfo_domain_indices = True 337 | 338 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 339 | # 340 | # texinfo_show_urls = 'footnote' 341 | 342 | # If true, do not generate a @detailmenu in the "Top" node's menu. 343 | # 344 | # texinfo_no_detailmenu = False 345 | -------------------------------------------------------------------------------- /docs/flashdrive.txt: -------------------------------------------------------------------------------- 1 | Keeping things slightly more secure 2 | ----------------------------------- 3 | 4 | I want to format and label a USB key, on which I will store my 5 | super secret ssh key(s) and gpg key(s). 6 | 7 | This will be a practical benefit, as it makes the valuable thing a key I generally carry 8 | with me in my wallet. 9 | 10 | :: 11 | 12 | 13 | pbrian@falcon:~$ umount /dev/sdc1 14 | pbrian@falcon:~$ sudo mkfs.ext4 /dev/sdc -L secretskey 15 | mke2fs 1.44.1 (24-Mar-2018) 16 | /dev/sdc contains a iso9660 filesystem labelled 'Ubuntu 16.04.3 LTS amd64' 17 | Proceed anyway? (y/N) y 18 | Creating filesystem with 1906176 4k blocks and 476720 inodes 19 | Filesystem UUID: ed74f120-1736-4f59-8752-06098a635c16 20 | Superblock backups stored on blocks: 21 | 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632 22 | 23 | Allocating group tables: done 24 | Writing inode tables: done 25 | Creating journal (16384 blocks): done 26 | Writing superblocks and filesystem accounting information: done 27 | 28 | 29 | Now comes the hard part. We add the following line to `/etc/fstab` 30 | The UUID we doscovered by running `blkid`. Then the other entries are described below 31 | 32 | :: 33 | UUID=ed74f120-1736-4f59-8752-06098a635c16 /home/pbrian/secrets/usb ext4 user,rw,auto,nofail 0 0 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Now when I boot up my machine, and plug in my "secrets" USB key, the 43 | secrets on there (ie my private keys) are available, but the 44 | underlying host OS does not permanently have them. 45 | 46 | Now we just need to spin up a Docker instance, with a volume from the above mount point 47 | and my secret private key is then available to me *inside* that docker volume, and so I can 48 | for example, push commits up to github, from my docker instance. 49 | 50 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | ImmutableWorkstation 3 | ===================== 4 | 5 | Intro 6 | ===== 7 | 8 | 9 | The concept of an *immutable server* for production deployment is now 10 | fully mainstream, but the same concepts underpinning servers is less 11 | applied to the workstations on which the developers work. 12 | 13 | We have a tendency to start with a nice clean laptop, a Mac if we are 14 | lucky, and slowly but surely *stuff* creeps on, dependencies we did 15 | not know about appear and we stop trusting the platform we stand on. 16 | 17 | So I have used Docker to make my own *immutable workstation*. It 18 | means that I get *exactly* the same stack running on my 19 | banged-about-on-commute laptop, my wife's nice big screen iMac and 20 | even on my client's Windows box, that I had to use for client's policy 21 | reasons. So wherever I was, I was using the same config of emacs - 22 | using it on a windows machine or a mac or a Linux host, it was the 23 | same emacs, and the same nice set of tools like grep. And it was 24 | running XWindows in those places too. 25 | 26 | Secondly, I get the ratchet effect of continuously improving security 27 | - I can always improve something on the install, and just rerun 28 | `docker build` and I have permanently remembered to fix that security 29 | hole wherever I build my workstation. 30 | 31 | I use X-Forwarding to run the same visual tools, configured the same 32 | way, on any box I am working on, and *anything* that changes I keep in 33 | source control (here in this repo) and my secrets are all stored on a 34 | USB key that I carry with me and plugin to the host - so my GitHub ssh 35 | key is on a USB stick, that when I plug it in, . 36 | 37 | 38 | Documentation can be found at https://workstation.readthedocs.io/en/latest/ 39 | 40 | :: 41 | 42 | `immutableworkstation` can create docker images from config, and 43 | launch those images so that as a developer you can work inside the 44 | container, but using X-applications on the host laptop. 45 | 46 | So you can define your workstation in code, but take it with you 47 | from laptop to home to work. 48 | 49 | 50 | 51 | Getting Started 52 | =============== 53 | 54 | Firstly install the python (3) package on your machine:: 55 | 56 | sudo pip install immutableworkstation 57 | 58 | First run: 59 | 60 | $ immutableworkstation.py 61 | 62 | Usage: 63 | immutableworkstation.py quickstart 64 | 65 | You will need to follow the instructions on setting up 66 | config. Basically we need config stored in your local home dir in 67 | `.immutableworkstation`. This will be the tempaltes etc to configure 68 | docker. The only way to do this right now is copy from the example 69 | set in github. At some point I hope to improve the quickstart 70 | 71 | 72 | After config has been set up :: 73 | 74 | $ immutableworkstation.py 75 | 76 | Usage: 77 | immutableworkstation.py config 78 | immutableworkstation.py start (latest | next) 79 | immutableworkstation.py stop (latest | next) 80 | immutableworkstation.py login (latest | next) 81 | immutableworkstation.py buildDocker (latest | next) 82 | immutableworkstation.py next2last 83 | immutableworkstation.py status 84 | immutableworkstation.py quickstart 85 | immutableworkstation.py (-h | --help ) 86 | 87 | Options: 88 | -h --help Show this screen 89 | 90 | 91 | Daily use case 92 | -------------- 93 | 94 | So to start a fresh docker instance we use:: 95 | 96 | immutableworkstation.py start next 97 | 98 | THis will boot up docker and throw you a ssh login 99 | 100 | We can also open a new terminal into the same docker:: 101 | 102 | immutableworkstation.py login next 103 | 104 | And stop it :: 105 | 106 | immutableworkstation.py stop next 107 | 108 | 109 | 110 | Configuration 111 | ------------- 112 | 113 | Initially `immutableworkstation.py` can run without a config folder, 114 | but pretty much the only thing you can do is `quickstart` which 115 | will help setup a local config folder. 116 | 117 | The example local config folder is stored in the python package and 118 | deployed to your machine as a data file. When we run `quickstart` we 119 | deploy that to `~\.immutableworkstation\` folder in your homedir 120 | 121 | This has 122 | 123 | * a config.ini file 124 | * a `.latest` folder holding the templates to build "latest" Dockerfile 125 | * a `.next` folder holding the templates to build the "next" Dockerfile 126 | 127 | Config file is located as `~/.immutableworkstation/config.ini` 128 | It has following format and items :: 129 | 130 | [default] 131 | tagname = workstation 132 | instance_name =immutableworkstation 133 | localhost = 127.0.0.1 134 | username = pbrian 135 | ssh_port = 2222 136 | devstation_config_root = ~/.immutableworkstation 137 | terminal_command = /usr/bin/konsole -e 138 | volumes = {"~/data": "/var/data", 139 | "~/projects": "/var/projects", 140 | "~/secrets": "/var/secrets:ro", 141 | "~/Dropbox": "/var/Dropbox" 142 | } 143 | 144 | 145 | 146 | `volumes` is a json-formatted string that will be converted during config 147 | reading. 148 | `tagname` is the tagname used to identify the docker *instance* 149 | `image_name` is the name used to identify the built docker image, from 150 | which we will run an instance. You must build a docker instance. 151 | `localhost` is obvious, probably needs to be removed 152 | `username` is the name of the (only?) user who will use the docker instance. 153 | As it is the only name and user that password is set to that as well. 154 | 155 | `ssh_port` port for docker instance to listen on for ssh connections 156 | from the host machine (how we talk to our dev machine) 157 | `devstation_config_root` the location of the config file, plus other templates 158 | `terminal_command` - command to run before sshing to the running docker instance 159 | I am assuming you have `konsole`. if not adjust the config. 160 | 161 | This will have files for the config ready to install - they will be 162 | place on '/usr/local/config' (TODO: rename that location to branded).:: 163 | 164 | $ immutableworkstation.py quickstart 165 | 166 | You will be asked at least one question 167 | 168 | Preparing a dockerfile 169 | ---------------------- 170 | TBD 171 | 172 | Getting started 173 | --------------- 174 | 175 | 1. Set up config - see above 176 | 2. Make a docker file 177 | 3. Build a Docker image 178 | 179 | 180 | 181 | 182 | 183 | we are targetting windows, linux and apple machines so will need 184 | sensible simple scripts else the start up and try me out barrier will 185 | be too high. however having python scripts makes the development part 186 | waaay easier, and the templating is all in python anyhow, so I think 187 | we have to have some road bumps. I think anone wanting to try this 188 | out is going to be capable of installing py3 anyway. Our target 189 | demographic is developers who want more control. 190 | 191 | I am building a one-stop shop developer machine on Docker which means 192 | it is a large Dockerfile - which is becoming unwieldy So I shall have 193 | a template folder, which will hold 194 | 195 | `dockerfile.skeleton` This is the bones of the Dockerfile, with very 196 | simple replace-locations built in such as:: 197 | 198 | FROM ubuntu:18.04 199 | ENV USERHOME /home/pbrian 200 | 201 | {{ apt }} 202 | ^^^^^^^^^^^^^^^^^ 203 | this bit will get replaced with contents of `apt.template` 204 | 205 | Constraints are that the {{ file }} must be on its own line, with only 206 | spaces between it and line start / end It is NOT using Jinja2, it just 207 | looks like it. Because one day it might. 208 | 209 | Its that simple. We can play around with variables if we really need to. 210 | 211 | 212 | 213 | Building Next and Last 214 | ---------------------- 215 | 216 | The idea is that I think of something I should have added to my workstation 217 | such as a python package in `requirements.txt` or some .deb file. 218 | 219 | I go to ~/.immutableworkstation - where the .next and .latest copies of 220 | the config is kept. I change say the requirements.txt file in .next then I 221 | rebuild docker image for next:: 222 | 223 | $ immutableworkstation.py buildDocker next 224 | 225 | Then I can try that out :: 226 | 227 | $ immutableworkstation.py start next 228 | 229 | If all is good I can prep it for my next go with latest:: 230 | 231 | $ immutableworkstation.py next2last 232 | 233 | (this will move the old .latest files and replace them with 234 | .next. You will be prompted) 235 | 236 | $ immutableworkstation.py buildDocker latest 237 | 238 | Now we can `start latest` again 239 | -------------------------------------------------------------------------------- /docs/roadmap.rst: -------------------------------------------------------------------------------- 1 | Roadmap 2 | ======= 3 | 4 | I recently posted this project to Hacker News (Front Page!!!) and was 5 | pleasantly surprised by the positive response. And so I feel a 6 | responsibility to actually improve the project and start ironing out 7 | bugs. 8 | 9 | This is then my roadmap for the next couple of weeks (well probably 10 | months). Ok scratch that. Its been - months and months. Lots has happened 11 | and one big thing is how unmanageable X11 is outside of a linux host machine. 12 | Its frustrating so this leads to two new milestones (sadly). 13 | 14 | Milestones 15 | ========== 16 | 17 | * Drop X11 support outside of Linux hosts. And generally drop X11 support 18 | anyhow. Most things I care about do abysmally in X11-client-server mode across different machines - Firefox just curls up and dies frequently. And this is such a edge case no one will support me hard. And most of the time I only care about using emacs and I can telnet in for that. 19 | 20 | * But I really want a permanent web service running on my workstation that 21 | will act as a *dashboard* for the workstation, plus tell me useful things and 22 | be a simple place for any output to appear - where i might do 'webserver.open('~/foo.html')' what I really want is now 'put foo.html on port 1234 and let the dashboard know it should do somethign to show that' 23 | 24 | 25 | 26 | * Review of literature. 27 | A lot of similar projects have been mentioned - it is well worth covering the good bad and ugly and using that to inform the roadmap 28 | 29 | * Single point of entry 30 | A simple python CLI that will do the work of detecting OS somslighlty modified docker commands can run etc etc 31 | (This has been the bulk of work here - really that took waaaay longer than I expected, epecially 32 | with just doing this on commutes etc.) 33 | 34 | * Clean up docs 35 | Still to do 36 | 37 | * split out the (very) specific hardcoded usernames etc 38 | Mostly done I think 39 | 40 | 41 | * look at recipes / methods to build the final Dockerfile and make it 42 | more robust without losing the basic "it's just string formatting" 43 | part 44 | Yeah thats ok. ish 45 | 46 | * Marketing 47 | 48 | well this was supposed to be just a personal project, and I don't 49 | see it as being the next big thing. But enough people have noticed 50 | that it should be polished and given a run round the block. Once 51 | it's stops being fun I will stop :-) 52 | 53 | 54 | * pre-commit hooks ensuring, lint, test, formatting, doc 55 | I use black ... does that count? No? Ok.. 56 | 57 | 58 | 59 | 2019 60 | ---- 61 | 62 | The plan here is to incorporate a very good suggestion by @bjornicus (#6) 63 | I relly do owe them an apology - early users should be gold. 64 | 65 | Then keep on dogfooding - its *usable* by me - some glitches 66 | (especially around the use of `sleep` and also failing to see the 67 | error response sent by sshd if there is an error (usually its the MiTM 68 | attack warning). 69 | 70 | -------------------------------------------------------------------------------- /docs/ssh-github.rst: -------------------------------------------------------------------------------- 1 | Making keys for github 2 | ---------------------- 3 | 4 | In order to push code up to GitHub, I need a private / public key pair for ssh. 5 | :: 6 | 7 | ssh-keygen -l -f sshkey -E md5 8 | ssh-keygen -t rsa -b 4096 -C paul@mikadosoftware.com 9 | 10 | I name the keypair `common-github`. The keys are in my ~/.ssh folder. 11 | I visit www.github.com and in my settings add the *public* key to have permissions on my account 12 | 13 | Now I setup .ssh/config file like this:: 14 | 15 | Host github.com 16 | User git 17 | IdentityFile ~/.ssh/common-github 18 | 19 | If I then run :: 20 | 21 | ssh -T git@github.com -i .ssh/common-github 22 | 23 | I get access approval message and all is good. 24 | 25 | -------------------------------------------------------------------------------- /docs/testing_with_docker.rst: -------------------------------------------------------------------------------- 1 | =========================================== 2 | Using Docker to test my application install 3 | =========================================== 4 | 5 | I launched `immutable-workstation` as a bunch of shell files. 6 | Which was *fine* except I wanted to make it eaasy to start using. 7 | 8 | So I went yak shaving, and have written python command line tool, that will 9 | run various workstation commands, most notably, it will do a intial quicksetup. 10 | 11 | Well I hope it will, and that leads me to *testing* it. 12 | 13 | Mostly this is a problem of first time setup. Luckily Docker excels at 14 | being a testbed for first time setup ! Hurrah. 15 | 16 | 17 | 18 | Next - using github on command line to respond to issues etc 19 | 20 | 21 | I am creating a linux docker that I will use to install and test the quicksetup feature 22 | of Docker. 23 | 24 | Dockerfile is kept short and simple :: 25 | 26 | FROM ubuntu:18.04 27 | MAINTAINER pbrian 28 | LABEL Name="pbriandev" Version=0.0.1 29 | 30 | ### Constants 31 | ENV WKDIR /staging 32 | ENV USERHOME /home/pbrian 33 | RUN mkdir $WKDIR 34 | ## {{ py3 }} 35 | 36 | ###### Install with apt 37 | RUN apt-get update && \ 38 | apt-get install -y python3.7 \ 39 | python3.7-dev \ 40 | python3-distutils \ 41 | python3-distlib \ 42 | python3-pip 43 | 44 | 45 | CMD ["/bin/bash"] 46 | 47 | sudo docker run -it tag_tester:latest 48 | sudo docker build -t tag_tester . 49 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! -*- coding:utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | import glob 5 | import os 6 | 7 | import sys 8 | from pprint import pprint as pp 9 | 10 | def get_data_files(inrepo_dirlist): 11 | """Python Packaging is (still) awkward. I am writing this longform as 12 | it took a long time to work out and there are no good answers 13 | online as yet. 14 | 15 | In some cases I want to include non-python files in my wheel / 16 | distribution. This might be obvious stuff like License files or 17 | in workstation cases, config files just to be added to allow 18 | configuration of the bin script. 19 | 20 | Python distutils/setuptools has concepts of both `data_files` and 21 | `package_data`. `package_data` is non-python files stored inside 22 | the python package (i.e. the parts marked by `__init__.py` 23 | directories). The aim is to *distribute* those onto target disk 24 | inside the pacakge directories again. 25 | 26 | `data_files` are kept outside of pacakge directories, in the repo 27 | as well as on target disk. `data_files` should be written to 28 | target disk at `sys.prefix()`, but it seems that has changed in 29 | code and is now landing at '/usr/local'. 30 | 31 | the data_files parameter in setup expects a list of tuples like:: 32 | 33 | ('config/.next/templates', ['config/.next/templates/apt.template']) 34 | 35 | Each tuple represents one file to be distributed, globbing is not 36 | working so well. the left hand side is the format of directory 37 | structure to be created on target disk under /usr/local and the 38 | right hand is the path of the file *in the repo* that will be 39 | placed on target disk. 40 | 41 | This is correct for `setuptools.__version__ == '39.0.1'` 42 | 43 | So now we build a helper to get and then retireve the data files 44 | just so I can populate them in the users local dir 45 | 46 | [X] list dirs to search / rebuild 47 | [ ] recovery - how to find where we put them?? !! 48 | 49 | """ 50 | 51 | data_files = [] 52 | for folder in inrepo_dirlist: 53 | for dirpath, dirnames, filenames in os.walk(folder): 54 | data_files.append((dirpath, [os.path.join(dirpath, f) for f in filenames])) 55 | 56 | return data_files 57 | 58 | import shutil 59 | def _copy_my_files(): 60 | os.mkdir('/home/pbrian/.devstation') 61 | shutil.copytree('/usr/local/config/', '/home/pbrian/.devstation') 62 | 63 | # get version data 64 | with open("VERSION") as fo: 65 | version = fo.read() 66 | 67 | setup( 68 | name='workstation', 69 | version=version, 70 | description='An approach to Immutable Workstations on various hosts', 71 | author='See AUTHORS.txt', 72 | packages=find_packages(exclude=('tests')), 73 | 74 | ## for some godforsaken reaosn these are put into /usr/local 75 | ## (close but off accoridng to docs https://docs.python.org/2/distutils/setupscript.html#installing-package-data 76 | 77 | # files outside the package ...? 78 | 79 | # data_files=get_data_files(['config/',]), 80 | #files inside the pacakge ??? 81 | # package_data={'workstation': ['LICENSE', 'AUTHORS', 'config/*']}, 82 | # include_package_data=True, 83 | # Any scripts (i.e. python/bash) here will be added to PATH (/usr/local/bin) 84 | scripts=glob.glob('bin/*'), 85 | 86 | 87 | 88 | install_requires=[ 89 | "mikado-core" 90 | ] 91 | ) 92 | -------------------------------------------------------------------------------- /test/docopt-0.6.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikadosoftware/workstation/619ca9cae8af0306e026dd9e7663e28c6a6287d1/test/docopt-0.6.2.tar.gz -------------------------------------------------------------------------------- /test/testmaker.sh: -------------------------------------------------------------------------------- 1 | # This plus the assoc Dockerfile just gives me a repeatable 2 | # clean ubunut instance so I can repeatebly develop the setup code 3 | 4 | echo "Run the below after getting a container shell, which will start next" 5 | TGT=~/projects/installworkstation.sh 6 | echo pip3 install /var/projects/workstation/dist/workstation-0.0.1-py3-none-any.whl > $TGT 7 | echo pip3 install /var/projects/MikadoLib/dist/mikadolib-0.0.1-py3-none-any.whl >> $TGT 8 | echo pip3 install /var/projects/workstation/docopt-0.6.2.tar.gz >> $TGT 9 | 10 | ## use this to build a container 11 | ## sudo docker build -t tag_tester . 12 | sudo docker run -i \ 13 | -v /home/pbrian/projects:/var/projects \ 14 | -v /home/pbrian/.immutableworkstation:/var/.immutableworkstation \ 15 | -t tag_tester:latest 16 | -------------------------------------------------------------------------------- /test/testpost.sh: -------------------------------------------------------------------------------- 1 | # This plus the assoc Dockerfile just gives me a repeatable 2 | # clean ubunut instance so I can repeatebly develop the setup code 3 | 4 | #echo "Run the below after getting a container shell, which will start next" 5 | pip3 install /var/projects/workstation/dist/workstation-0.0.1-py3-none-any.whl 6 | pip3 install /var/projects/workstation/docopt-0.6.2.tar.gz 7 | pip3 install /var/projects/mikado-core/dist/mikado_core-0.0.3-py3-none-any.whl 8 | 9 | -------------------------------------------------------------------------------- /workstation/Dockerfile: -------------------------------------------------------------------------------- 1 | # From skelton 2 | FROM ubuntu:jammy 3 | LABEL maintainer="cyd@9bis.com" 4 | 5 | #ARG PROXY_CERT 6 | #RUN test -z "${PROXY_CERT}" || { echo "${PROXY_CERT}" | base64 -d | tee /usr/local/share/ca-certificates/ca-local.crt > /dev/null && update-ca-certificates ; } 7 | 8 | # We prepare environment 9 | ARG TZ=${TZ:-Etc/UTC} 10 | ARG DEBIAN_FRONTEND=noninteractive 11 | RUN \ 12 | echo "Timezone and locale" >&2 \ 13 | && apt-get update \ 14 | && apt-get install -y \ 15 | apt-utils \ 16 | software-properties-common \ 17 | tzdata sudo \ 18 | && apt-get clean \ 19 | && apt-get autoremove -y \ 20 | && rm -rf /tmp/* /var/tmp/* \ 21 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 22 | && echo "Timezone and locale OK" >&2 23 | 24 | 25 | 26 | 27 | 28 | 29 | # We add a simple user with sudo rights 30 | ENV USERNAME=pbrian 31 | ENV USERHOME=/home/pbrian 32 | ARG USR_UID=1000 33 | ARG USR_GID=5000 34 | 35 | RUN groupadd --gid ${USR_GID} ${USERNAME} 36 | RUN useradd --uid ${USR_UID} --create-home --gid ${USR_GID} --shell /bin/bash ${USERNAME} 37 | RUN echo "${USERNAME}:${USERNAME}" | chpasswd 38 | RUN echo ${USERNAME}' ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 39 | 40 | 41 | RUN echo 'root:root' | chpasswd 42 | ### Setup me as only user .... because I will be ! 43 | # RUN useradd -m -c "$USERFULLNAME" $USERNAME --shell /bin/bash 44 | # RUN usermod -aG sudo $USERNAME 45 | # RUN echo "$USERNAME:$USERNAME" | chpasswd 46 | 47 | # change user locale settings (see baseconfig) 48 | RUN echo "export LC_ALL=en_GB.UTF-8" >> $USERHOME/.bashrc 49 | RUN echo "export LC_LANGUAGE=en_GB.UTF-8" >> $USERHOME/.bashrc 50 | 51 | # This is really just the config settings 52 | # any secrfets are added via '/var/secrets' volume per run 53 | # as such this is a "safe" image to keep on say a hub 54 | # COPY rcassets/.emacs $USERHOME 55 | # COPY rcassets/.pylintrc $USERHOME 56 | #COPY rcassets/.gitconfig $USERHOME 57 | #COPY rcassets/.ssh $USERHOME/.ssh 58 | #RUN chown -R $USERNAME:$USERNAME $USERHOME/.ssh 59 | #RUN chown -R $USERNAME:$USERNAME $USERHOME/.gitconfig 60 | # 61 | 62 | ### {{ apt }} 63 | 64 | # build the basic development world 65 | RUN apt-get update && \ 66 | apt-get install -y apt-transport-https \ 67 | build-essential \ 68 | dos2unix \ 69 | git \ 70 | openssh-server \ 71 | wget curl \ 72 | && \ 73 | apt-get clean 74 | 75 | # We add some tools 76 | RUN \ 77 | echo "Install some tools" >&2 \ 78 | && apt-get update \ 79 | && apt-get install -y --no-install-recommends \ 80 | curl \ 81 | dumb-init \ 82 | jq \ 83 | libnss3-tools \ 84 | mlocate \ 85 | net-tools \ 86 | xz-utils \ 87 | zip \ 88 | && apt-get install -y thunar-archive-plugin \ 89 | && apt-get clean \ 90 | && apt-get autoremove -y \ 91 | && rm -rf /tmp/* /var/tmp/* \ 92 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 93 | && echo "Install some tools OK" >&2 94 | 95 | ### {{ baseconfig }} 96 | 97 | #################### Misc config ########################## 98 | ## Locales 99 | # We dont have any languages setup at this point 100 | # 101 | RUN apt-get update && \ 102 | apt-get install -y locales && \ 103 | locale-gen en_GB.UTF-8 && \ 104 | update-locale LC_ALL=en_GB.UTF-8 LANGUAGE=en_GB.UTF-8 && \ 105 | apt-get clean 106 | 107 | #these need to be run in user sections 108 | #RUN echo "export LC_ALL=en_GB.UTF-8" >> /home/pbrian/.bashrc 109 | #RUN echo "export LC_LANGUAGE=en_GB.UTF-8" >> /home/pbrian/.bashrc 110 | 111 | #THis should now work in our terminal (prints Greek for Thank you) 112 | #python3 -c "print(u''.join([u'\u0395', u'\u03c5', u'\u03c7', u'\u03B1', u'\u03c1', u'\u03B9', u'\u03C3', u'\u03c4', u'\u03c9']))" 113 | 114 | ## fonts 115 | RUN apt-get -y install fonts-inconsolata && apt-get clean 116 | 117 | ENV TZ=Europe/London 118 | ENV DEBIAN_FRONTEND=noninteractive 119 | RUN DEBIAN_FRONTEND=noninteractive && \ 120 | apt-get update && \ 121 | apt-get install -y tzdata \ 122 | && apt-get clean 123 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ 124 | echo $TZ > /etc/timezone 125 | 126 | 127 | 128 | ### {{ otherdevtools }} 129 | 130 | 131 | ### Other Dev Tools 132 | RUN apt-get install -y whois 133 | 134 | 135 | 136 | 137 | ### {{ github_cli }} 138 | 139 | # from cli github page: 140 | RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ 141 | && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ 142 | && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ 143 | && apt update \ 144 | && apt install gh -y \ 145 | && apt-get clean 146 | 147 | RUN echo "READY TO DO PYTHON" 148 | ###### Install with apt 149 | RUN apt-get install -y libpython3.10 \ 150 | python3.10 \ 151 | python3-pip \ 152 | && apt-get clean 153 | 154 | ###### symlinking to have `pip` and `python` 155 | RUN cd /usr/bin \ 156 | && ln -sf python3.10 python \ 157 | && ln -sf python3.10 python3 \ 158 | && ln -sf pip3 pip 159 | 160 | ##### 161 | RUN python3 -m pip install --upgrade pip 162 | #RUN python3 -m pip install pygithub 163 | 164 | ### update python pkgs 165 | # Install any needed packages (ie above those for the runtime) 166 | RUN pip install --trusted-host pypi.python.org sphinx \ 167 | pytest \ 168 | pylint 169 | 170 | 171 | #RUN pip install -r $WKDIR/requirements.txt 172 | RUN pip install -r $WKDIR/unpinned_requirements.txt 173 | # Additonal setup for spacy. I think this is sensible to do specify in this file them 174 | #RUN python -m spacy download en 175 | RUN echo "READY TO DO PYTHON END" 176 | # seems ubunut installs pip_internal again mucking things up 177 | 178 | # lastly redo symlinks 179 | RUN rm /usr/bin/python 180 | RUN ln -s /usr/bin/python3 /usr/bin/python 181 | 182 | 183 | 184 | 185 | 186 | # We add sound 187 | # add root user to group for pulseaudio access 188 | RUN adduser root pulse-access 189 | RUN printf 'default-server = unix:/run/user/1000/pulse/native\nautospawn = no\ndaemon-binary = /bin/true\nenable-shm = false' > /etc/pulse/client.conf 190 | 191 | 192 | # We set localtime 193 | RUN if [ "X${TZ}" != "X" ] ; then if [ -f /usr/share/zoneinfo/${TZ} ] ; then rm -f /etc/localtime ; ln -s /usr/share/zoneinfo/${TZ} /etc/localtime ; fi ; fi 194 | 195 | # And here is the statup script, everything else is in there 196 | COPY entrypoint.sh /entrypoint.sh 197 | RUN chmod 755 /entrypoint.sh 198 | 199 | # We do some specials 200 | RUN \ 201 | updatedb ; \ 202 | apt-get clean \ 203 | && apt-get autoremove -y \ 204 | && rm -rf /tmp/* /var/tmp/* \ 205 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* 206 | 207 | # We change user 208 | USER ${USERNAME} 209 | WORKDIR /home/${USERNAME} 210 | COPY functions.sh /home/${USERNAME}/.functions.sh 211 | COPY bgimage.jpg /usr/share/backgrounds/xfce/bgimage.jpg 212 | RUN \ 213 | printf 'if [[ $- = *i* ]] ; then test -f ~/.functions.sh && . ~/.functions.sh ; fi' >> /home/${USERNAME}/.bashrc 214 | 215 | #ENTRYPOINT [ "/usr/bin/dumb-init", "--", "/entrypoint.sh" ] 216 | ENTRYPOINT [ "/entrypoint.sh" ] 217 | -------------------------------------------------------------------------------- /workstation/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /workstation/build_workstation.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | logfile="this.log" 4 | tagname="myworkstation" 5 | path2dockerfile="`pwd`/Dockerfile" 6 | path2contextdir="`pwd`/DockerContextDir" 7 | podman build -t ${tagname} -f ${path2dockerfile} ${path2contextdir} --squash --logfile=${logfile} 8 | -------------------------------------------------------------------------------- /workstation/initial_setup/.initial_laptop_install.sh.swo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikadosoftware/workstation/619ca9cae8af0306e026dd9e7663e28c6a6287d1/workstation/initial_setup/.initial_laptop_install.sh.swo -------------------------------------------------------------------------------- /workstation/initial_setup/.initial_laptop_install.sh.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikadosoftware/workstation/619ca9cae8af0306e026dd9e7663e28c6a6287d1/workstation/initial_setup/.initial_laptop_install.sh.swp -------------------------------------------------------------------------------- /workstation/initial_setup/0_system_build.sh: -------------------------------------------------------------------------------- 1 | # system build - ie kickstarter 2 | # firewalls etc 3 | # config for updates 4 | 5 | -------------------------------------------------------------------------------- /workstation/initial_setup/1_base_user_build.sh: -------------------------------------------------------------------------------- 1 | #how to build a base 2 | #------------------- 3 | #I want a (fairly) stable workstation. 4 | #Development environment - python venvs, in a docker, using ssh/terminal access 5 | #user env - firefox, stable updates 6 | #migrate towards nix 7 | # write the book, get a draft for two of them. print out by end of next week. 8 | 9 | 10 | 11 | # base user - getting podman etc ready 12 | # dropbox etc for use 13 | # develop in docker but need email etc - perhasp outdside? is this still a workstation 14 | 15 | #We shall call all (idempotent) bash scripts in 16 | #config_scripts, and each can assume they can use 17 | #the following values. 18 | #THey should also assume they can add a 19 | #text value to manually run after instructions 20 | #POST_RUN_INSTRUCTIONS+="Some notes" 21 | 22 | ######################################################### 23 | 24 | SCRIPT_ROOT_DIR=`pwd` 25 | USERNAME="pbrian" 26 | USER_FULL_NAME="Paul Brian" 27 | USER_EMAIL="paul@mikadosoftware.com" 28 | USER_HOME_DIR="/home/$USERNAME" 29 | POST_RUN_INSTRUCTIONS='' 30 | 31 | ######################################################### 32 | 33 | files=() 34 | search_dir=`pwd`/config_scripts 35 | 36 | for entry in "$search_dir"/* 37 | do 38 | files+=("$entry") 39 | done 40 | 41 | for i in "${files[@]}" 42 | do 43 | echo "-------------------------" 44 | cat "$i" 45 | echo "--------------------------" 46 | read -p "Y?" executethis 47 | if [[ "$executethis" == "Y" ]] 48 | then 49 | echo "running" 50 | source $i 51 | fi 52 | 53 | done 54 | 55 | 56 | -------------------------------------------------------------------------------- /workstation/initial_setup/2_docker_workstation.sh: -------------------------------------------------------------------------------- 1 | #install workstations 2 | 3 | -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/.setup_mikadotools.sh.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikadosoftware/workstation/619ca9cae8af0306e026dd9e7663e28c6a6287d1/workstation/initial_setup/config_scripts/.setup_mikadotools.sh.swp -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/cleanupsnap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo snap set system refresh.retain=2 4 | 5 | #Removes old revisions of snaps 6 | #CLOSE ALL SNAPS BEFORE RUNNING THIS 7 | set -eu 8 | LANG=en_US.UTF-8 snap list --all | awk '/disabled/{print $1, $3}' | 9 | while read snapname revision; do 10 | sudo snap remove "$snapname" --revision="$revision" 11 | done 12 | 13 | sudo apt-get clean 14 | sudo apt-get autoremove --purge 15 | sudo apt install bleachbit 16 | sudo flatpak uninstall --unused 17 | 18 | -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/enable_dropbox.sh: -------------------------------------------------------------------------------- 1 | 2 | 3 | #How to install dropbox 4 | #---------------------- 5 | #cd ~ && wget -O - "https://www.dropbox.com/download?plat=lnx.x86_64" | tar xzf - 6 | #then execute dropboxd in /home/pbrian/.dropbox-dist/dropboxd 7 | #Set it to start up in "startup applications" 8 | #Requires manual login and approval (some kind of OAuth) 9 | 10 | 11 | -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/enable_vmware_horizon.sh: -------------------------------------------------------------------------------- 1 | # VMWare Horizon only works on xOrg 2 | # So we need to edit 3 | # /etc/gdm3/custom.conf 4 | # so the line below is uncommented. 5 | # WaylandEnable=false 6 | # some kind of awk sed thing or custom? 7 | 8 | 9 | #https://customerconnect.vmware.com/en/downloads/details?downloadGroup=CART24FQ4_LIN64_DEBPKG_2312&productId=1027&rPId=115572 10 | 11 | #sudo dpkg --install VMware-Horizon-Client-2312-8.12.0-23149323.x64.deb 12 | 13 | -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/initial_laptop_install.sh: -------------------------------------------------------------------------------- 1 | # 1. package installation 2 | 3 | sudo apt update -y && sudo apt upgrade -y 4 | 5 | targetpkgs=(build-essential 6 | libssl-dev 7 | libffi-dev 8 | python3-dev 9 | python3-pip 10 | python3-venv 11 | podman 12 | ) 13 | 14 | for pkg in ${targetpkgs[@]} 15 | do 16 | sudo apt install $pkg -y 17 | done 18 | 19 | 20 | -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/install_config_vim.sh: -------------------------------------------------------------------------------- 1 | sudo apt install vim=2:8.2.3995-1ubuntu2.15 2 | 3 | git clone https://github.com/gmarik/Vundle.vim.git /home/pbrian/.vim/bundle/Vundle.vim 4 | 5 | 6 | # NOTES 7 | # sudo means the downloaded VUndle has root:root dir ownership and Vundle doe snot process. At end reassign ownerships. or not run as sudo - then struggles. 8 | 9 | cat << EOF > /home/pbrian/.vimrc 10 | set nocompatible " be iMproved, required 11 | filetype off " required 12 | 13 | " set the runtime path to include Vundle and initialize 14 | set rtp+=~/.vim/bundle/Vundle.vim 15 | call vundle#begin() 16 | " alternatively, pass a path where Vundle should install plugins 17 | "call vundle#begin('~/some/path/here') 18 | 19 | " let Vundle manage Vundle, required 20 | Plugin 'VundleVim/Vundle.vim' 21 | 22 | " The following are examples of different formats supported. 23 | " Keep Plugin commands between vundle#begin/end. 24 | " plugin on GitHub repo 25 | Plugin 'tpope/vim-fugitive' 26 | " The sparkup vim script is in a subdirectory of this repo called vim. 27 | " Pass the path to set the runtimepath properly. 28 | Plugin 'rstacruz/sparkup', {'rtp': 'vim/'} 29 | 30 | Plugin 'Syntastic' 31 | Plugin 'scrooloose/nerdtree' 32 | Plugin 'bling/vim-airline' 33 | 34 | " All of your Plugins must be added before the following line 35 | call vundle#end() " required 36 | filetype plugin indent on " required 37 | " To ignore plugin indent changes, instead use: 38 | "filetype plugin on 39 | " 40 | " Brief help 41 | " :PluginList - lists configured plugins 42 | " :PluginInstall - installs plugins; append `!` to update or just :PluginUpdate 43 | " :PluginSearch foo - searches for foo; append `!` to refresh local cache 44 | " :PluginClean - confirms removal of unused plugins; append `!` to auto-approve removal 45 | " 46 | " see :h vundle for more details or wiki for FAQ 47 | " Put your non-Plugin stuff after this line 48 | EOF 49 | 50 | cat << EOF >> /home/pbrian/.vimrc 51 | " NERDTree Settings 52 | " This should neatly append to end of .vimrc. 53 | " it should alos make PageDOwn toggle my NERDTree on and off 54 | "nmap makes a normal mode key map in vim, and is obvious. Then it will run the escape mode instruction found followed by return 55 | nmap :NERDTreeToggle 56 | 57 | """"" WOrd wrapping 58 | set number "(optional - will help to visually verify that it's working) 59 | set textwidth=80 60 | set wrapmargin=0 61 | set formatoptions+=t 62 | set linebreak " (optional - breaks by word rather than character) 63 | 64 | 65 | EOF 66 | 67 | 68 | POST_RUN_INSTRUCTIONS+="Start vim and run :PluginInstall \n" 69 | -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/install_firefox.sh: -------------------------------------------------------------------------------- 1 | # We install firefox, directly from Mozilla (not from snap) 2 | RUN \ 3 | echo "Install Firefox from Mozilla" >&2 \ 4 | && apt-get update \ 5 | && add-apt-repository ppa:mozillateam/ppa \ 6 | && printf '\nPackage: *\nPin: release o=LP-PPA-mozillateam\nPin-Priority: 1001\n' > /etc/apt/preferences.d/mozilla-firefox \ 7 | && printf 'Unattended-Upgrade::Allowed-Origins:: "LP-PPA-mozillateam: ${distro_codename}";' > /etc/apt/apt.conf.d/51unattended-upgrades-firefox \ 8 | && apt-get update \ 9 | && apt-get install -y firefox --no-install-recommends \ 10 | && apt-get clean \ 11 | && apt-get autoremove -y \ 12 | && rm -rf /tmp/* /var/tmp/* \ 13 | && rm -rf /var/lib/apt/lists/* /var/cache/apt/* \ 14 | && echo "Install Firefox from Mozilla OK" >&2 15 | 16 | 17 | -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/install_git.sh: -------------------------------------------------------------------------------- 1 | sudo apt install -y git=1:2.34.1-1ubuntu1.10 2 | # configure correctly 3 | git config --global user.name "$USER_FULL_NAME" 4 | git config --global user.email "$USER_EMAIL" 5 | -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/setup_mikadotools.sh: -------------------------------------------------------------------------------- 1 | python3 -m venv $USER_HOME_DIR 2 | git clone git@github.com:mikadosoftware/mikado-tools.git 3 | 4 | -------------------------------------------------------------------------------- /workstation/initial_setup/config_scripts/setup_nix.sh: -------------------------------------------------------------------------------- 1 | #sudo apt install curl 2 | sh <(curl -L https://nixos.org/nix/install) --daemon 3 | 4 | Then we need to 5 | https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone 6 | 7 | nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager 8 | 9 | nix-channel --update 10 | 11 | run this to setup 12 | nix-shell '' -A install 13 | 14 | add this to ~/.profile 15 | . "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh" 16 | -------------------------------------------------------------------------------- /workstation/initial_setup/mission_statement.txt: -------------------------------------------------------------------------------- 1 | Laptop Workstation Plan 2 | ======================= 3 | 4 | I want to have arepeateable builds for a laptop / workstation. 5 | -------------------------------------------------------------------------------- /workstation/nix_notes.txt: -------------------------------------------------------------------------------- 1 | Trying to follow along on my nix journey 2 | 3 | * I am starting with home-manager, mostly because nixos wont build on my 4GB 4 | laptop 5 | 6 | 7 | -------------------------------------------------------------------------------- /workstation/quickstart.py: -------------------------------------------------------------------------------- 1 | #! -*- coding:utf-8 -*- 2 | 3 | """ 4 | Very simple CLI based library that takes a dict of 'questions' 5 | and asks them on CLI, and then returns answers. THink of quick setup in 6 | sphinx docs 7 | 8 | dict format is 9 | 10 | {'label': [ , ] 11 | ...} 12 | """ 13 | 14 | import os 15 | 16 | def run(questiond): 17 | """ """ 18 | answerd = {} 19 | for label, data in questiond.items(): 20 | questiontext = data[0] 21 | default_function = data[1] 22 | default_answer = default_function() 23 | answer = input(questiontext + " ['{}']: ".format(default_answer)) 24 | if not answer: 25 | answer = default_answer 26 | answerd[label] = answer 27 | 28 | return answerd 29 | 30 | def get_homedir(): 31 | from os.path import expanduser 32 | home = expanduser("~") 33 | return os.path.join(home, ".devstation") 34 | 35 | def example(): 36 | d = {'devstationdir': ['Where to store config data?', 37 | get_homedir] 38 | } 39 | print(run(d)) 40 | # returns {'devstationdir': '/root/.devstation'} 41 | 42 | if __name__ == '__main__': 43 | example() 44 | --------------------------------------------------------------------------------