mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-12 10:22:14 +01:00
Merge pull request #88 from tbflw/feature/run_non_root_and_log_startup
Feature/run non root and log startup
This commit is contained in:
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
scripts
|
||||
test
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -0,0 +1 @@
|
||||
.idea
|
||||
3
Makefile
3
Makefile
@@ -2,6 +2,7 @@ GOLANG_VERSION=1.12
|
||||
VERSION=snapshot
|
||||
DOCKER_USER=
|
||||
DOCKER_PASSWORD=
|
||||
DOCKER_REPO=thecodingmachine
|
||||
|
||||
# generate documentation.
|
||||
doc:
|
||||
@@ -27,7 +28,7 @@ tests:
|
||||
# build Docker image.
|
||||
image:
|
||||
docker build -t thecodingmachine/gotenberg:base -f build/base/Dockerfile .
|
||||
docker build --build-arg GOLANG_VERSION=$(GOLANG_VERSION) --build-arg VERSION=$(VERSION) -t thecodingmachine/gotenberg:$(VERSION) -f build/package/Dockerfile .
|
||||
docker build --build-arg GOLANG_VERSION=$(GOLANG_VERSION) --build-arg VERSION=$(VERSION) -t $(DOCKER_REPO)/gotenberg:$(VERSION) -f build/package/Dockerfile .
|
||||
|
||||
# start the API using previously built Docker image.
|
||||
gotenberg:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:9.5-slim
|
||||
FROM debian:9-slim
|
||||
|
||||
# |--------------------------------------------------------------------------
|
||||
# | Common libraries
|
||||
@@ -91,3 +91,16 @@ RUN apt-get install -y \
|
||||
fonts-unfonts-core
|
||||
|
||||
COPY build/base/fonts.conf /etc/fonts/conf.d/100-gotenberg.conf
|
||||
|
||||
# |--------------------------------------------------------------------------
|
||||
# | Default user
|
||||
# |--------------------------------------------------------------------------
|
||||
# |
|
||||
# | TODO find a correct description for why we're doing this.
|
||||
# |
|
||||
|
||||
RUN groupadd --gid 1001 gotenberg \
|
||||
&& useradd --uid 1001 --gid gotenberg --shell /bin/bash --no-create-home gotenberg \
|
||||
&& mkdir /gotenberg \
|
||||
&& chown gotenberg: /gotenberg
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ You may start it with:
|
||||
$ docker run --rm -p 3000:3000 thecodingmachine/gotenberg:5
|
||||
```
|
||||
|
||||
All processes in the docker container runs as a dedicated non-root user called `gotenberg` with user id `1001` from the working dir `/gotenberg`.
|
||||
|
||||
> The API will be available at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
## Docker Compose
|
||||
@@ -38,5 +40,12 @@ Otherwise the API will not be able to launch Google Chrome and LibreOffice (unoc
|
||||
|
||||
> The more resources are granted, the quicker will be the conversions.
|
||||
|
||||
Also, in the deployment spec of the pod, specify the uid `1001` of the user `gotenberg`:
|
||||
```
|
||||
securityContext:
|
||||
privileged: false
|
||||
runAsUser: 1001
|
||||
```
|
||||
|
||||
In the following examples, we will assume your
|
||||
Gotenberg API is available at [http://localhost:3000](http://localhost:3000).
|
||||
@@ -8,7 +8,7 @@ You may customize the API behaviour thanks to environment variables.
|
||||
|
||||
In order to save some resources, the Gotenberg image accepts the environment variable `DISABLE_GOOGLE_CHROME`.
|
||||
|
||||
It takes the strings `"0"` or `"1"` as value.
|
||||
It takes the strings `"0"` or `"1"` as value where `1` means `true`
|
||||
|
||||
> If Google Chrome is disabled, the following conversions will **not** be available anymore:
|
||||
> [HTML](#html), [URL](#url) and [Markdown](#markdown)
|
||||
@@ -37,12 +37,19 @@ By default, the API will add a log entry when the [healthcheck endpoint](#ping)
|
||||
|
||||
You may turn off this logging so as to avoid unnecessary entries in your logs with the environment variable `DISABLE_HEALTHCHECK_LOGGING`.
|
||||
|
||||
This environment variable operates in the same manner as the `DISABLE_GOOGLE_CHROME` and `DISABLE_UNOCONV` variables operate in that it accepts the strings `"0"` or `"1"` as values.
|
||||
|
||||
This environment variable operates in the same manner as the `DISABLE_GOOGLE_CHROME` and `DISABLE_UNOCONV` variables operate in that it accepts the strings `"0"` or `"1"` as values, where `1` is enabled.
|
||||
## Default listen port
|
||||
|
||||
By default, the API will listen on port `3000`. For most use cases this is perfectly fine, but at times there may be cases where you need to change this due to port conflicts.
|
||||
|
||||
You may customize this port location with the environment variable `DEFAULT_LISTEN_PORT`.
|
||||
|
||||
This environment variable accepts any string that can be turned into a port number (e.g., the string `"0"` up to the string `"65535"`).
|
||||
This environment variable accepts any string that can be turned into a port number (e.g., the string `"0"` up to the string `"65535"`).
|
||||
|
||||
## Debug logging of process startup
|
||||
|
||||
By default, stdout and stderr messages from the started processes are disabled.
|
||||
|
||||
You may enable some debug logging from starting the process by setting the environment variable `DEBUG_PROCESS_STARTUP`.
|
||||
|
||||
This environment variable operates in the same manner as the `DISABLE_GOOGLE_CHROME` and `DISABLE_UNOCONV` variables operate in that it accepts the strings `"0"` or `"1"` as values, where `1` means `true`.
|
||||
@@ -19,7 +19,11 @@ ENV GOOS=linux \
|
||||
WORKDIR /gotenberg
|
||||
|
||||
# Copy our source code.
|
||||
COPY . .
|
||||
COPY internal ./internal
|
||||
COPY cmd ./cmd
|
||||
COPY go.sum go.sum
|
||||
COPY go.mod go.mod
|
||||
|
||||
|
||||
# Build our binary.
|
||||
RUN go build -o /gotenberg/gotenberg -ldflags "-X main.version=${VERSION}" cmd/gotenberg/main.go
|
||||
@@ -37,6 +41,8 @@ LABEL authors="Julien Neuhart <j.neuhart@thecodingmachine.com>"
|
||||
|
||||
COPY --from=golang /gotenberg/gotenberg /usr/local/bin/
|
||||
|
||||
ENV PM2_HOME=/gotenberg/.pm2
|
||||
USER gotenberg
|
||||
WORKDIR /gotenberg
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
disableGoogleChromeEnvVar = "DISABLE_GOOGLE_CHROME"
|
||||
disableUnoconvEnvVar = "DISABLE_UNOCONV"
|
||||
disableHealthcheckLoggingEnvVar = "DISABLE_HEALTHCHECK_LOGGING"
|
||||
debugProcessStartup = "DEBUG_PROCESS_STARTUP"
|
||||
)
|
||||
|
||||
func mustParseEnvVar() *api.Options {
|
||||
@@ -49,37 +50,31 @@ func mustParseEnvVar() *api.Options {
|
||||
}
|
||||
opts.DefaultListenPort = v
|
||||
}
|
||||
if v, ok := os.LookupEnv(disableGoogleChromeEnvVar); ok {
|
||||
if v != "1" && v != "0" {
|
||||
notify.ErrPrint(fmt.Errorf("%s: wrong value: want \"0\" or \"1\" got %v", disableGoogleChromeEnvVar, v))
|
||||
os.Exit(1)
|
||||
//checkBoolEnv is a convenience function for reading an env var with a bool value where `1` is true and `0` is false
|
||||
checkBoolEnv := func(name string) bool {
|
||||
if v, ok := os.LookupEnv(name); ok {
|
||||
if v != "1" && v != "0" {
|
||||
notify.ErrPrint(fmt.Errorf("%s: wrong value: want \"0\" or \"1\" got %v", name, v))
|
||||
os.Exit(1)
|
||||
}
|
||||
return v == "1"
|
||||
}
|
||||
opts.EnableChromeEndpoints = v != "1"
|
||||
}
|
||||
if v, ok := os.LookupEnv(disableUnoconvEnvVar); ok {
|
||||
if v != "1" && v != "0" {
|
||||
notify.ErrPrint(fmt.Errorf("%s: wrong value: want \"0\" or \"1\" got %v", disableUnoconvEnvVar, v))
|
||||
os.Exit(1)
|
||||
}
|
||||
opts.EnableUnoconvEndpoints = v != "1"
|
||||
}
|
||||
if v, ok := os.LookupEnv(disableHealthcheckLoggingEnvVar); ok {
|
||||
if v != "1" && v != "0" {
|
||||
notify.ErrPrint(fmt.Errorf("%s: wrong value: want \"0\" or \"1\" got %v", disableHealthcheckLoggingEnvVar, v))
|
||||
os.Exit(1)
|
||||
}
|
||||
opts.EnableHealthcheckLogging = v != "1"
|
||||
return false
|
||||
}
|
||||
opts.EnableChromeEndpoints = !checkBoolEnv(disableGoogleChromeEnvVar)
|
||||
opts.EnableUnoconvEndpoints = !checkBoolEnv(disableUnoconvEnvVar)
|
||||
opts.EnableHealthcheckLogging = !checkBoolEnv(disableHealthcheckLoggingEnvVar)
|
||||
opts.DebugProcessStartup = checkBoolEnv(debugProcessStartup)
|
||||
return opts
|
||||
}
|
||||
|
||||
func mustStartProcesses(opts *api.Options) []pm2.Process {
|
||||
var processes []pm2.Process
|
||||
if opts.EnableChromeEndpoints {
|
||||
processes = append(processes, pm2.NewChrome())
|
||||
processes = append(processes, pm2.NewChrome(opts.DebugProcessStartup))
|
||||
}
|
||||
if opts.EnableUnoconvEndpoints {
|
||||
processes = append(processes, pm2.NewUnoconv())
|
||||
processes = append(processes, pm2.NewUnoconv(opts.DebugProcessStartup))
|
||||
}
|
||||
for _, p := range processes {
|
||||
notify.Printf("starting %s with PM2...", p.Fullname())
|
||||
|
||||
@@ -139,6 +139,8 @@
|
||||
<pre class="chroma">$ docker run --rm -p <span class="m">3000</span>:3000 thecodingmachine/gotenberg:5
|
||||
</pre>
|
||||
|
||||
<p>All processes in the docker container runs as a dedicated non-root user called <code>gotenberg</code> with user id <code>1001</code> from the working dir <code>/gotenberg</code>.</p>
|
||||
|
||||
<blockquote>
|
||||
<p>The API will be available at <a href="http://localhost:3000">http://localhost:3000</a>.</p>
|
||||
</blockquote>
|
||||
@@ -176,6 +178,13 @@ Otherwise the API will not be able to launch Google Chrome and LibreOffice (unoc
|
||||
<p>The more resources are granted, the quicker will be the conversions.</p>
|
||||
</blockquote>
|
||||
|
||||
<p>Also, in the deployment spec of the pod, specify the uid <code>1001</code> of the user <code>gotenberg</code>:</p>
|
||||
|
||||
<pre class="chroma"> securityContext:
|
||||
privileged: false
|
||||
runAsUser: 1001
|
||||
</pre>
|
||||
|
||||
<p>In the following examples, we will assume your
|
||||
Gotenberg API is available at <a href="http://localhost:3000">http://localhost:3000</a>.</p>
|
||||
|
||||
@@ -222,7 +231,7 @@ Gotenberg API is available at <a href="http://localhost:3000">http://localhost:3
|
||||
|
||||
<p>In order to save some resources, the Gotenberg image accepts the environment variable <code>DISABLE_GOOGLE_CHROME</code>.</p>
|
||||
|
||||
<p>It takes the strings <code>"0"</code> or <code>"1"</code> as value.</p>
|
||||
<p>It takes the strings <code>"0"</code> or <code>"1"</code> as value where <code>1</code> means <code>true</code></p>
|
||||
|
||||
<blockquote>
|
||||
<p>If Google Chrome is disabled, the following conversions will <strong>not</strong> be available anymore:
|
||||
@@ -263,7 +272,7 @@ See the <a href="#timeout">timeout section</a>.</p>
|
||||
|
||||
<p>You may turn off this logging so as to avoid unnecessary entries in your logs with the environment variable <code>DISABLE_HEALTHCHECK_LOGGING</code>.</p>
|
||||
|
||||
<p>This environment variable operates in the same manner as the <code>DISABLE_GOOGLE_CHROME</code> and <code>DISABLE_UNOCONV</code> variables operate in that it accepts the strings <code>"0"</code> or <code>"1"</code> as values.</p>
|
||||
<p>This environment variable operates in the same manner as the <code>DISABLE_GOOGLE_CHROME</code> and <code>DISABLE_UNOCONV</code> variables operate in that it accepts the strings <code>"0"</code> or <code>"1"</code> as values, where <code>1</code> is enabled.</p>
|
||||
|
||||
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="environment_variables.default_listen_port" href="#environment_variables.default_listen_port">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||
@@ -275,6 +284,16 @@ See the <a href="#timeout">timeout section</a>.</p>
|
||||
|
||||
<p>This environment variable accepts any string that can be turned into a port number (e.g., the string <code>"0"</code> up to the string <code>"65535"</code>).</p>
|
||||
|
||||
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="environment_variables.debug_logging_of_process_startup" href="#environment_variables.debug_logging_of_process_startup">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||
</a>Debug logging of process startup</h2>
|
||||
|
||||
<p>By default, stdout and stderr messages from the started processes are disabled.</p>
|
||||
|
||||
<p>You may enable some debug logging from starting the process by setting the environment variable <code>DEBUG_PROCESS_STARTUP</code>.</p>
|
||||
|
||||
<p>This environment variable operates in the same manner as the <code>DISABLE_GOOGLE_CHROME</code> and <code>DISABLE_UNOCONV</code> variables operate in that it accepts the strings <code>"0"</code> or <code>"1"</code> as values, where <code>1</code> means <code>true</code>.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="Page" id="html">
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
import "github.com/labstack/echo/v4"
|
||||
|
||||
const pingEndpoint = "/ping"
|
||||
|
||||
@@ -14,6 +12,7 @@ type Options struct {
|
||||
EnableChromeEndpoints bool
|
||||
EnableUnoconvEndpoints bool
|
||||
EnableHealthcheckLogging bool
|
||||
DebugProcessStartup bool
|
||||
}
|
||||
|
||||
// DefaultOptions returns default options.
|
||||
|
||||
@@ -7,15 +7,19 @@ import (
|
||||
"github.com/mafredri/cdp/devtool"
|
||||
)
|
||||
|
||||
const (
|
||||
warmupTime = 10 * time.Second
|
||||
)
|
||||
|
||||
type chrome struct {
|
||||
manager *processManager
|
||||
}
|
||||
|
||||
// NewChrome retruns a Google Chrome
|
||||
// headless process.
|
||||
func NewChrome() Process {
|
||||
func NewChrome(debug bool) Process {
|
||||
return &chrome{
|
||||
manager: &processManager{},
|
||||
manager: &processManager{verbose: debug},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,12 +62,20 @@ func (p *chrome) viable() bool {
|
||||
// check if Google Chrome is correctly running.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
_, err := devtool.New("http://localhost:9222").Version(ctx)
|
||||
return err == nil
|
||||
p.manager.notifyf(`%v: checking Chrome liveness via debug version endpoint
|
||||
'http://localhost:9222/json/version'`, p.name())
|
||||
v, err := devtool.New("http://localhost:9222").Version(ctx)
|
||||
if err != nil {
|
||||
p.manager.notifyf("%s: %s version endpoint returned error: %v", p.name(), p.Fullname(), err)
|
||||
return false
|
||||
}
|
||||
p.manager.notifyf("%s: %s returned version info: %+v", p.name(), p.Fullname(), *v)
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *chrome) warmup() {
|
||||
time.Sleep(5 * time.Second)
|
||||
p.manager.notifyf("%s: allowing %s %v to startup", p.name(), p.Fullname(), warmupTime)
|
||||
time.Sleep(warmupTime)
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure type implements desired interfaces.
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
)
|
||||
|
||||
func TestChromeStart(t *testing.T) {
|
||||
p := NewChrome()
|
||||
p := NewChrome(false)
|
||||
err := p.Start()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestChromeShutdown(t *testing.T) {
|
||||
p := NewChrome()
|
||||
p := NewChrome(false)
|
||||
err := p.Shutdown()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package pm2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/notify"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,6 +31,7 @@ type Process interface {
|
||||
|
||||
type processManager struct {
|
||||
heuristicState int32
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func (m *processManager) start(p Process) error {
|
||||
@@ -76,8 +83,43 @@ func (m *processManager) pm2(p Process, cmdName string) error {
|
||||
"pm2",
|
||||
cmdArgs...,
|
||||
)
|
||||
m.notifyf("executing command '%v'", strings.Join(cmd.Args, " "))
|
||||
if m.verbose {
|
||||
processStdErr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting stderr from '%s': %v", p.Fullname(), err)
|
||||
}
|
||||
processStdOut, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting stdout from '%s': %v", p.Fullname(), err)
|
||||
}
|
||||
readFromPipe := func(name string, reader io.ReadCloser) {
|
||||
r := bufio.NewReader(reader)
|
||||
defer reader.Close() // nolint: errcheck
|
||||
for {
|
||||
line, _, err := r.ReadLine()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
m.notifyf("error reading from %v for process '%v'", name, p.Fullname())
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(line) != 0 {
|
||||
m.notifyf("%v %v: %s", p.name(), name, string(line))
|
||||
}
|
||||
}
|
||||
}
|
||||
go readFromPipe("stdout", processStdOut)
|
||||
go readFromPipe("stderr", processStdErr)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("%s %s with PM2: %v", cmdName, p.Fullname(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *processManager) notifyf(format string, args ...interface{}) {
|
||||
if m.verbose {
|
||||
notify.Printf(fmt.Sprintf("%v: %s", time.Now().Format(time.RFC3339), format), args...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ type unoconv struct {
|
||||
|
||||
// NewUnoconv retruns a unoconv listener
|
||||
// process.
|
||||
func NewUnoconv() Process {
|
||||
func NewUnoconv(debug bool) Process {
|
||||
return &unoconv{
|
||||
manager: &processManager{},
|
||||
manager: &processManager{verbose: debug},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
)
|
||||
|
||||
func TestUnoconvStart(t *testing.T) {
|
||||
p := NewUnoconv()
|
||||
p := NewUnoconv(false)
|
||||
err := p.Start()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestUnoconvShutdown(t *testing.T) {
|
||||
p := NewUnoconv()
|
||||
p := NewUnoconv(false)
|
||||
err := p.Shutdown()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user