require "path" require "file_utils" require "socket" require "./module" require "./config" require "../common/network" module DocMachine::Build class Run Log = DocMachine::Build::Log.for("run") def initialize(@config : DocMachine::Build::Config) data = "#{@config.data_dir}:#{@config.port}" @basehash = Digest::SHA256.hexdigest(data)[0..6] @docker_name = "docmachine-#{@basehash}" @docker_image = "glenux/docmachine:latest" @docker_opts = [] of String @process = nil end # cleanup environment # create directories # setup permissions def prepare() Log.info { "basedir = #{@config.data_dir}" } Log.info { "docker_image = #{@docker_image}" } Log.info { "action = #{@config.action}" } self._pull_image() self._avoid_duplicates() unless @config.enable_multiple end private def _avoid_duplicates Log.info { "Multiple Instances: stopping duplicate containers (for #{@docker_name})" } docker_cid = %x{docker ps -f "name=#{@docker_name}" -q}.strip Log.info { "Multiple Instances: docker_name: #{@docker_name}" } Log.info { "Multiple Instances: docker_cid: #{docker_cid || "-"}" } if !docker_cid.empty? Process.run("docker", ["kill", @docker_name]) Process.run("docker", ["rm", @docker_name]) end end def _pull_image # FIXME: add option to force update data_cache_dir = if ENV["XDG_CACHE_HOME"]? Path[ENV["XDG_CACHE_HOME"], "docmachine"] else Path[ENV["HOME"], ".cache", "docmachine"] end ## Build cache if it doesnt exist data_cache_file = data_cache_dir / "image.tar" Log.info { "Checking cache #{data_cache_file}..." } if ! File.exists? data_cache_file.to_s Log.info { "Downloading #{@docker_image} image..." } Process.run("docker", ["pull", @docker_image], output: STDOUT) Log.info { "Building cache for image (#{data_cache_dir})" } FileUtils.mkdir_p(data_cache_dir) status = Process.run( "docker", ["image", "save", @docker_image, "-o", data_cache_file.to_s], output: STDOUT ) if status.success? Log.info { "done" } else Log.error { "Unable to save cache image" } exit 1 end else Log.info { "Cache already exist. Skipping." } end if @config.enable_cache Log.info { "Loading #{@docker_image} image from cache..." } docker_image_loaded = false status = Process.run( "docker", ["image", "load", "-i", data_cache_file.to_s], output: STDOUT ) if status.success? Log.info { "done" } else Log.error { "Unable to load cache image" } exit 1 end else Log.info { "Loading #{@docker_image} image from local registry..." } # FIXME: check that local image exists end end def start() uid = %x{id -u}.strip gid = %x{id -g}.strip Log.info { "uid: #{uid}" } Log.info { "cid: #{gid}" } docker_opts = [] of String docker_opts << "run" docker_opts << "-i" # add tty support docker_opts << "-t" if @config.enable_tty # add container name docker_opts.concat ["--name", @docker_name] docker_opts << "--rm" docker_opts << "--shm-size=1gb" docker_opts.concat ["-e", "EXT_UID=#{uid}"] docker_opts.concat ["-e", "EXT_GID=#{gid}"] docker_opts.concat ["-v", "#{@config.data_dir}/docs:/app/docs"] docker_opts.concat ["-v", "#{@config.data_dir}/slides:/app/slides"] docker_opts.concat ["-v", "#{@config.data_dir}/images:/app/images"] docker_opts.concat ["-v", "#{@config.data_dir}/_build:/app/_build"] ## Detect Marp SCSS if File.exists?("#{@config.data_dir}/.marp/theme.scss") docker_opt_marp_theme = ["-v", "#{@config.data_dir}/.marp:/app/.marp"] docker_opts.concat docker_opt_marp_theme Log.info { "Theme: detected Marp files. Adding option to command line (#{docker_opt_marp_theme})" } else Log.info { "Theme: no theme detected. Using default files" } end ## Detect Mkdocs configuration - old format (full) if File.exists?("#{@config.data_dir}/mkdocs.yml") Log.info { "Docs: detected mkdocs.yml file. Please rename to mkdocs-patch.yml" } exit 1 end ## Detect Mkdocs configuration - new format (patch) if File.exists?("#{@config.data_dir}/mkdocs-patch.yml") docker_opt_mkdocs_config = ["-v", "#{@config.data_dir}/mkdocs-patch.yml:/app/mkdocs-patch.yml"] docker_opts.concat docker_opt_mkdocs_config Log.info { "Docs: detected mkdocs-patch.yml file. Adding option to command line (#{docker_opt_mkdocs_config})" } else Log.info { "Docs: no mkdocs-patch.yml detected. Using default files" } end ## Detect docs if Dir.exists?("#{@config.data_dir}/docs") Log.info { "Docs: detected docs directory." } mkdocs_port = Network.find_port(@config.port) docker_opt_mkdocs_port = ["-p", "#{mkdocs_port}:5100"] docker_opts.concat docker_opt_mkdocs_port Log.notice { "Using port #{mkdocs_port} for docs" } Log.info { "Docs: Adding option to command line (#{docker_opt_mkdocs_port})" } else Log.info { "Docs: no docs detected." } end ## Detect slides if Dir.exists?("#{@config.data_dir}/slides") Log.info { "Slides: detected slides directory." } marp_port = Network.find_port(@config.port+100) docker_opt_marp_port = ["-p", "#{marp_port}:5200"] docker_opts.concat docker_opt_marp_port Log.info { "Slides: Adding option to command line (#{docker_opt_marp_port})" } Log.notice { "Slides: Using port #{marp_port} for slides" } else Log.info { "Slides: no slides directory detected." } end docker_opts << @docker_image docker_opts << @config.action Log.info { docker_opts.inspect.colorize(:yellow) } @process = Process.new("docker", docker_opts, output: STDOUT, error: STDERR) end def wait() process = @process return if process.nil? Signal::INT.trap do Log.warn { "Received CTRL-C" } process.signal(Signal::KILL) Process.run("docker", ["kill", @docker_name]) end process.wait end def stop() end def docker_opts() end end end