2023-04-23 15:59:29 +02:00
|
|
|
|
2023-04-24 18:43:04 +02:00
|
|
|
require "path"
|
|
|
|
require "file_utils"
|
2024-02-14 14:51:15 +01:00
|
|
|
require "socket"
|
2023-04-24 18:43:04 +02:00
|
|
|
|
2023-04-25 12:28:16 +02:00
|
|
|
require "./module"
|
2023-04-23 15:59:29 +02:00
|
|
|
require "./config"
|
|
|
|
|
|
|
|
module DocMachine::Build
|
|
|
|
class Run
|
2023-04-25 12:28:16 +02:00
|
|
|
Log = DocMachine::Build::Log.for("run")
|
2023-04-24 18:43:04 +02:00
|
|
|
|
2023-04-23 15:59:29 +02:00
|
|
|
def initialize(@config : DocMachine::Build::Config)
|
|
|
|
@basehash = Digest::SHA256.hexdigest(@config.data_dir)[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()
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "basedir = #{@config.data_dir}" }
|
|
|
|
Log.info { "docker_image = #{@docker_image}" }
|
|
|
|
Log.info { "action = #{@config.action}" }
|
2023-04-23 15:59:29 +02:00
|
|
|
|
2023-04-24 18:43:04 +02:00
|
|
|
self._avoid_duplicates()
|
|
|
|
self._pull_image()
|
|
|
|
end
|
|
|
|
|
2024-02-14 14:29:41 +01:00
|
|
|
private def _find_port(port_base)
|
|
|
|
(port_base..65535).each do |port|
|
|
|
|
return port if _port_available?(port)
|
|
|
|
end
|
|
|
|
raise "No port available"
|
|
|
|
end
|
|
|
|
|
|
|
|
private def _port_available?(port)
|
2024-02-14 14:51:15 +01:00
|
|
|
sock = Socket.new(Socket::Family::INET, Socket::Type::STREAM)
|
|
|
|
sock.bind(Socket::IPAddress.new("0.0.0.0", port))
|
|
|
|
sock.close
|
|
|
|
return true
|
|
|
|
rescue ex : Socket::BindError
|
|
|
|
return false
|
2024-02-14 14:29:41 +01:00
|
|
|
end
|
|
|
|
|
2023-04-24 18:43:04 +02:00
|
|
|
private def _avoid_duplicates
|
2023-04-23 15:59:29 +02:00
|
|
|
docker_cid = %x{docker ps -f "name=#{@docker_name}" -q}.strip
|
|
|
|
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "docker_name: #{@docker_name}" }
|
|
|
|
Log.info { "docker_cid: #{docker_cid}" }
|
2023-04-23 15:59:29 +02:00
|
|
|
|
|
|
|
if !docker_cid.empty?
|
|
|
|
Process.run("docker", ["kill", @docker_name])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-04-24 18:43:04 +02:00
|
|
|
def _pull_image
|
2023-04-25 12:28:16 +02:00
|
|
|
# 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
|
2023-04-24 18:43:04 +02:00
|
|
|
|
|
|
|
data_cache_file = data_cache_dir / "image.tar"
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "Checking cache #{data_cache_file}..." }
|
2023-04-24 18:43:04 +02:00
|
|
|
if ! File.exists? data_cache_file.to_s
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "Downloading #{@docker_image} image..." }
|
2023-04-24 18:43:04 +02:00
|
|
|
Process.run("docker", ["pull", @docker_image], output: STDOUT)
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "Building cache for image (#{data_cache_dir})" }
|
2023-04-24 18:43:04 +02:00
|
|
|
FileUtils.mkdir_p(data_cache_dir)
|
2023-04-27 09:50:11 +02:00
|
|
|
status = Process.run(
|
2023-04-25 12:28:16 +02:00
|
|
|
"docker",
|
|
|
|
["image", "save", @docker_image, "-o", data_cache_file.to_s],
|
|
|
|
output: STDOUT
|
|
|
|
)
|
2023-04-27 09:50:11 +02:00
|
|
|
if status.success?
|
|
|
|
Log.info { "done" }
|
|
|
|
else
|
|
|
|
Log.error { "Unable to save cache image" }
|
|
|
|
exit 1
|
|
|
|
end
|
|
|
|
|
2023-04-24 18:43:04 +02:00
|
|
|
else
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "Cache already exist. Skipping." }
|
2023-04-24 18:43:04 +02:00
|
|
|
end
|
2023-04-25 12:28:16 +02:00
|
|
|
|
|
|
|
Log.info { "Loading #{@docker_image} image from cache..." }
|
|
|
|
docker_image_loaded = false
|
2023-04-27 09:50:11 +02:00
|
|
|
status = Process.run(
|
2023-04-25 12:28:16 +02:00
|
|
|
"docker",
|
2023-04-27 09:50:11 +02:00
|
|
|
["image", "load", "-i", data_cache_file.to_s],
|
2023-04-25 12:28:16 +02:00
|
|
|
output: STDOUT
|
|
|
|
)
|
2023-04-27 09:50:11 +02:00
|
|
|
if status.success?
|
|
|
|
Log.info { "done" }
|
|
|
|
else
|
|
|
|
Log.error { "Unable to load cache image" }
|
|
|
|
exit 1
|
|
|
|
end
|
2023-04-24 18:43:04 +02:00
|
|
|
end
|
|
|
|
|
2023-04-23 15:59:29 +02:00
|
|
|
def start()
|
|
|
|
uid = %x{id -u}.strip
|
|
|
|
gid = %x{id -g}.strip
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "uid: #{uid}" }
|
|
|
|
Log.info { "cid: #{gid}" }
|
2023-04-23 15:59:29 +02:00
|
|
|
|
|
|
|
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
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "Theme: detected Marp files. Adding option to command line (#{docker_opt_marp_theme})" }
|
2023-04-23 15:59:29 +02:00
|
|
|
else
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "Theme: no theme detected. Using default files" }
|
2023-04-23 15:59:29 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
## Detect Mkdocs configuration - old format (full)
|
|
|
|
if File.exists?("#{@config.data_dir}/mkdocs.yml")
|
2024-02-14 14:29:41 +01:00
|
|
|
Log.info { "Docs: detected mkdocs.yml file. Please rename to mkdocs-patch.yml" }
|
2023-04-23 15:59:29 +02:00
|
|
|
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
|
2024-02-14 14:29:41 +01:00
|
|
|
Log.info { "Docs: detected mkdocs-patch.yml file. Adding option to command line (#{docker_opt_mkdocs_config})" }
|
2023-04-23 15:59:29 +02:00
|
|
|
else
|
2024-02-14 14:29:41 +01:00
|
|
|
Log.info { "Docs: no mkdocs-patch.yml detected. Using default files" }
|
2023-04-23 15:59:29 +02:00
|
|
|
end
|
|
|
|
|
2024-02-14 14:29:41 +01:00
|
|
|
## Detect docs
|
|
|
|
if Dir.exists?("#{@config.data_dir}/docs")
|
|
|
|
Log.info { "Docs: detected docs directory." }
|
|
|
|
mkdocs_port = _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" }
|
2024-02-14 14:34:29 +01:00
|
|
|
Log.info { "Docs: Adding option to command line (#{docker_opt_mkdocs_port})" }
|
2024-02-14 14:29:41 +01:00
|
|
|
else
|
|
|
|
Log.info { "Docs: no docs detected." }
|
|
|
|
end
|
|
|
|
|
2023-04-23 15:59:29 +02:00
|
|
|
## Detect slides
|
|
|
|
if Dir.exists?("#{@config.data_dir}/slides")
|
2024-02-14 14:29:41 +01:00
|
|
|
Log.info { "Slides: detected slides directory." }
|
|
|
|
marp_port = _find_port(@config.port+100)
|
|
|
|
docker_opt_marp_port = ["-p", "#{marp_port}:5200"]
|
2023-04-23 15:59:29 +02:00
|
|
|
docker_opts.concat docker_opt_marp_port
|
2024-02-14 14:29:41 +01:00
|
|
|
Log.info { "Slides: Adding option to command line (#{docker_opt_marp_port})" }
|
|
|
|
Log.notice { "Slides: Using port #{marp_port} for slides" }
|
2023-04-23 15:59:29 +02:00
|
|
|
else
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { "Slides: no slides directory detected." }
|
2023-04-23 15:59:29 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
docker_opts << @docker_image
|
|
|
|
docker_opts << @config.action
|
|
|
|
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.info { docker_opts.inspect.colorize(:yellow) }
|
2023-04-23 15:59:29 +02:00
|
|
|
@process = Process.new("docker", docker_opts, output: STDOUT, error: STDERR)
|
|
|
|
end
|
|
|
|
|
|
|
|
def wait()
|
|
|
|
process = @process
|
|
|
|
return if process.nil?
|
|
|
|
|
|
|
|
Signal::INT.trap do
|
2023-04-25 12:28:16 +02:00
|
|
|
Log.warn { "Received CTRL-C" }
|
2023-04-23 15:59:29 +02:00
|
|
|
process.signal(Signal::KILL)
|
|
|
|
Process.run("docker", ["kill", @docker_name])
|
|
|
|
end
|
|
|
|
process.wait
|
|
|
|
end
|
|
|
|
|
|
|
|
def stop()
|
|
|
|
end
|
|
|
|
|
|
|
|
def docker_opts()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|