diff --git a/contrib/docker-compose.yml.tmpl b/contrib/docker-compose.yml.tmpl new file mode 100644 index 0000000..206e4be --- /dev/null +++ b/contrib/docker-compose.yml.tmpl @@ -0,0 +1,376 @@ +--- + +x-default-environment: &default-environment + ACTION_HOST: {{ or .defaultEnvironment.ACTION_HOST "backendAction" }} + ACTION_PORT: {{ or .defaultEnvironment.ACTION_PORT "9002" }} + AUTH_COOKIE_KEY_FILE: {{ or .defaultEnvironment.AUTH_COOKIE_KEY_FILE "/run/secrets/auth_cookie_key" }} + AUTH_HOST: {{ or .defaultEnvironment.AUTH_HOST "auth" }} + AUTH_PORT: {{ or .defaultEnvironment.AUTH_PORT "9004" }} + AUTH_TOKEN_KEY_FILE: {{ or .defaultEnvironment.AUTH_TOKEN_KEY_FILE "/run/secrets/auth_token_key" }} + AUTOUPDATE_HOST: {{ or .defaultEnvironment.AUTOUPDATE_HOST "autoupdate" }} + AUTOUPDATE_PORT: {{ or .defaultEnvironment.AUTOUPDATE_PORT "9012" }} + CACHE_HOST: {{ or .defaultEnvironment.CACHE_HOST "redis" }} + CACHE_PORT: {{ or .defaultEnvironment.CACHE_PORT "6379" }} + DATABASE_HOST: {{ or .defaultEnvironment.DATABASE_HOST "postgres" }} + DATABASE_NAME: {{ or .defaultEnvironment.DATABASE_NAME "openslides" }} + DATABASE_PASSWORD_FILE: {{ or .defaultEnvironment.DATABASE_PASSWORD_FILE "/run/secrets/postgres_password" }} + DATABASE_PORT: {{ or .defaultEnvironment.DATABASE_PORT "5432" }} + DATABASE_USER: {{ or .defaultEnvironment.DATABASE_USER "openslides" }} + ICC_HOST: {{ or .defaultEnvironment.ICC_HOST "icc" }} + ICC_PORT: {{ or .defaultEnvironment.ICC_PORT "9007" }} + INTERNAL_AUTH_PASSWORD_FILE: {{ or .defaultEnvironment.INTERNAL_AUTH_PASSWORD_FILE "/run/secrets/internal_auth_password" }} + MANAGE_AUTH_PASSWORD_FILE: {{ or .defaultEnvironment.MANAGE_AUTH_PASSWORD_FILE "/run/secrets/manage_auth_password" }} + MANAGE_HOST: {{ or .defaultEnvironment.MANAGE_HOST "manage" }} + MANAGE_PORT: {{ or .defaultEnvironment.MANAGE_PORT "9008" }} + MEDIA_DATABASE_HOST: {{ or .defaultEnvironment.MEDIA_DATABASE_HOST "postgres" }} + MEDIA_DATABASE_NAME: {{ or .defaultEnvironment.MEDIA_DATABASE_NAME "openslides" }} + MEDIA_DATABASE_PASSWORD_FILE: {{ or .defaultEnvironment.MEDIA_DATABASE_PASSWORD_FILE "/run/secrets/postgres_password" }} + MEDIA_DATABASE_PORT: {{ or .defaultEnvironment.MEDIA_DATABASE_PORT "5432" }} + MEDIA_DATABASE_USER: {{ or .defaultEnvironment.MEDIA_DATABASE_USER "openslides" }} + MEDIA_HOST: {{ or .defaultEnvironment.MEDIA_HOST "media" }} + MEDIA_PORT: {{ or .defaultEnvironment.MEDIA_PORT "9006" }} + MESSAGE_BUS_HOST: {{ or .defaultEnvironment.MESSAGE_BUS_HOST "redis" }} + MESSAGE_BUS_PORT: {{ or .defaultEnvironment.MESSAGE_BUS_PORT "6379" }} + OPENSLIDES_DEVELOPMENT: {{ or .defaultEnvironment.OPENSLIDES_DEVELOPMENT "false" }} + OPENSLIDES_LOGLEVEL: {{ or .defaultEnvironment.OPENSLIDES_LOGLEVEL "info" }} + PRESENTER_HOST: {{ or .defaultEnvironment.PRESENTER_HOST "backendPresenter" }} + PRESENTER_PORT: {{ or .defaultEnvironment.PRESENTER_PORT "9003" }} + PROJECTOR_HOST: {{ or .defaultEnvironment.PROJECTOR_HOST "projector" }} + PROJECTOR_PORT: {{ or .defaultEnvironment.PROJECTOR_PORT "9051" }} + RESTRICTER_URL: {{ or .defaultEnvironment.RESTRICTER_URL "http://autoupdate:9012/internal/autoupdate" }} + SEARCH_HOST: {{ or .defaultEnvironment.SEARCH_HOST "search" }} + SEARCH_PORT: {{ or .defaultEnvironment.SEARCH_PORT "9050" }} + SUPERADMIN_PASSWORD_FILE: {{ or .defaultEnvironment.SUPERADMIN_PASSWORD_FILE "/run/secrets/superadmin" }} + VOTE_DATABASE_HOST: {{ or .defaultEnvironment.VOTE_DATABASE_HOST "postgres" }} + VOTE_DATABASE_NAME: {{ or .defaultEnvironment.VOTE_DATABASE_NAME "openslides" }} + VOTE_DATABASE_PASSWORD_FILE: {{ or .defaultEnvironment.VOTE_DATABASE_PASSWORD_FILE "/run/secrets/postgres_password" }} + VOTE_DATABASE_PORT: {{ or .defaultEnvironment.VOTE_DATABASE_PORT "5432" }} + VOTE_DATABASE_USER: {{ or .defaultEnvironment.VOTE_DATABASE_USER "openslides" }} + VOTE_HOST: {{ or .defaultEnvironment.VOTE_HOST "vote" }} + VOTE_PORT: {{ or .defaultEnvironment.VOTE_PORT "9013" }} + +services: + + proxy: + image: {{ or .services.proxy.containerRegistry .defaults.containerRegistry }}/openslides-proxy:{{ or .services.proxy.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - client + - backendAction + - backendPresenter + - autoupdate + - search + - projector + - auth + - media + - icc + - vote + {{- end }} + environment: + << : *default-environment + {{- with .services.proxy.environment }}{{ marshalContent 6 . }}{{- end }} + {{- if .enableLocalHTTPS }} + ENABLE_LOCAL_HTTPS: 1 + HTTPS_CERT_FILE: /run/secrets/cert_crt + HTTPS_KEY_FILE: /run/secrets/cert_key + {{- end }} + {{- if .enableAutoHTTPS }} + ENABLE_AUTO_HTTPS: 1 + {{- end }} + networks: + - uplink + - frontend + ports: + - {{ .host }}:{{ .port }}:8000 + {{- if .enableLocalHTTPS }} + secrets: + - cert_crt + - cert_key + {{- end }} + {{- with .services.proxy.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + client: + image: {{ or .services.client.containerRegistry .defaults.containerRegistry }}/openslides-client:{{ or .services.client.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - backendAction + - backendPresenter + - autoupdate + - search + - projector + - auth + - media + - icc + - vote + {{- end }} + environment: + << : *default-environment + {{- with .services.client.environment }}{{ marshalContent 6 . }}{{- end }} + networks: + - frontend + {{- with .services.client.additionalContent }}{{ marshalContent 4 . }}{{ end }} + + backendAction: + image: {{ or .services.backendAction.containerRegistry .defaults.containerRegistry }}/openslides-backend:{{ or .services.backendAction.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - auth + - media + - vote + - postgres + {{- end }} + environment: + << : *default-environment + {{- with .services.backendAction.environment }}{{ marshalContent 6 . }}{{- end }} + OPENSLIDES_BACKEND_COMPONENT: action + networks: + - frontend + - data + - email + secrets: + - auth_token_key + - auth_cookie_key + - internal_auth_password + - postgres_password + {{- with .services.backendAction.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + backendPresenter: + image: {{ or .services.backendPresenter.containerRegistry .defaults.containerRegistry }}/openslides-backend:{{ or .services.backendPresenter.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - auth + - postgres + {{- end }} + environment: + << : *default-environment + {{- with .services.backendPresenter.environment }}{{ marshalContent 6 . }}{{- end }} + OPENSLIDES_BACKEND_COMPONENT: presenter + networks: + - frontend + - data + secrets: + - auth_token_key + - auth_cookie_key + - postgres_password + {{- with .services.backendPresenter.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + backendManage: + image: {{ or .services.backendManage.containerRegistry .defaults.containerRegistry }}/openslides-backend:{{ or .services.backendManage.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - postgres + {{- end }} + environment: + << : *default-environment + {{- with .services.backendManage.environment }}{{ marshalContent 6 . }}{{- end }} + OPENSLIDES_BACKEND_COMPONENT: action + networks: + - data + - email + ports: + - 127.0.0.1:9002:9002 + secrets: + - auth_token_key + - auth_cookie_key + - internal_auth_password + - postgres_password + - superadmin + {{- with .services.backendManage.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + {{- if not .disablePostgres }} + + postgres: + image: postgres:17.10 + environment: + << : *default-environment + {{- with .services.postgres.environment }}{{ marshalContent 6 . }}{{- end }} + POSTGRES_DB: openslides + POSTGRES_USER: openslides + POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - data + secrets: + - postgres_password + {{- with .services.postgres.additionalContent }}{{ marshalContent 4 . }}{{- end }} + {{- end }} + + autoupdate: + image: {{ or .services.autoupdate.containerRegistry .defaults.containerRegistry }}/openslides-autoupdate:{{ or .services.autoupdate.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - redis + {{- end }} + environment: + << : *default-environment + {{- with .services.autoupdate.environment }}{{ marshalContent 6 . }}{{- end }} + networks: + - frontend + - data + secrets: + - auth_token_key + - auth_cookie_key + - postgres_password + {{- with .services.autoupdate.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + search: + image: {{ or .services.search.containerRegistry .defaults.containerRegistry }}/openslides-search:{{ or .services.search.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - postgres + - autoupdate + {{- end }} + environment: + << : *default-environment + {{- with .services.search.environment }}{{ marshalContent 6 . }}{{- end }} + networks: + - frontend + - data + secrets: + - auth_token_key + - auth_cookie_key + - postgres_password + {{- with .services.search.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + projector: + image: {{ or .services.projector.containerRegistry .defaults.containerRegistry }}/openslides-projector:{{ or .services.projector.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - autoupdate + - backendAction + - postgres + {{- end }} + environment: + << : *default-environment + {{- with .services.projector.environment }}{{ marshalContent 6 . }}{{- end }} + networks: + - frontend + - data + secrets: + - auth_token_key + - auth_cookie_key + - postgres_password + {{- with .services.projector.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + auth: + image: {{ or .services.auth.containerRegistry .defaults.containerRegistry }}/openslides-auth:{{ or .services.auth.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - redis + - postgres + {{- end }} + environment: + << : *default-environment + {{- with .services.auth.environment }}{{ marshalContent 6 . }}{{- end }} + networks: + - frontend + - data + secrets: + - auth_token_key + - auth_cookie_key + - internal_auth_password + - postgres_password + {{- with .services.auth.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + vote: + image: {{ or .services.vote.containerRegistry .defaults.containerRegistry }}/openslides-vote:{{ or .services.vote.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - auth + - autoupdate + - redis + - postgres + {{- end }} + environment: + << : *default-environment + {{- with .services.vote.environment }}{{ marshalContent 6 . }}{{- end }} + networks: + - frontend + - data + secrets: + - auth_token_key + - auth_cookie_key + - postgres_password + {{- with .services.vote.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + redis: + image: redis:alpine + command: redis-server --save "" + environment: + << : *default-environment + {{- with .services.redis.environment }}{{ marshalContent 6 . }}{{- end }} + networks: + - data + {{- with .services.redis.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + media: + image: {{ or .services.media.containerRegistry .defaults.containerRegistry }}/openslides-media:{{ or .services.media.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - postgres + {{- end }} + environment: + << : *default-environment + {{- with .services.media.environment }}{{ marshalContent 6 . }}{{- end }} + networks: + - frontend + - data + secrets: + - auth_token_key + - auth_cookie_key + - postgres_password + {{- with .services.media.additionalContent }}{{ marshalContent 4 . }}{{- end }} + + icc: + image: {{ or .services.icc.containerRegistry .defaults.containerRegistry }}/openslides-icc:{{ or .services.icc.tag .defaults.tag }} + {{- if not .disableDependsOn }} + depends_on: + - postgres + - redis + {{- end }} + environment: + << : *default-environment + {{- with .services.icc.environment }}{{ marshalContent 6 . }}{{- end }} + networks: + - frontend + - data + secrets: + - auth_token_key + - auth_cookie_key + - postgres_password + {{- with .services.icc.additionalContent }}{{ marshalContent 4 . }}{{- end }} + +networks: + uplink: + internal: false + email: + internal: false + frontend: + internal: true + data: + internal: true + +{{- if not .disablePostgres }} + +volumes: + postgres-data: +{{- end }} + +secrets: + auth_token_key: + file: ./secrets/auth_token_key + auth_cookie_key: + file: ./secrets/auth_cookie_key + superadmin: + file: ./secrets/superadmin + internal_auth_password: + file: ./secrets/internal_auth_password + postgres_password: + file: ./secrets/postgres_password +{{- if .enableLocalHTTPS }} + cert_crt: + file: ./secrets/cert_crt + cert_key: + file: ./secrets/cert_key +{{- end }} diff --git a/contrib/example-config.yml b/contrib/example-config.yml new file mode 100644 index 0000000..228fc69 --- /dev/null +++ b/contrib/example-config.yml @@ -0,0 +1,48 @@ +--- + +# Name of the generated YAML file. +filename: docker-compose.yml + +# The OpenSlides proxy service listens on this address. +host: 127.0.0.1 +port: 8000 + +# General global options +disablePostgres: false +disableDependsOn: false +enableLocalHTTPS: true +enableAutoHTTPS: false + +# Defaults for all OpenSlides services. +defaults: + containerRegistry: ghcr.io/openslides/openslides + tag: 4.3.0 + +# You can customize single services using the services property. +services: + backendManage: + environment: + OPENSLIDES_BACKEND_CREATE_INITIAL_DATA: 1 + MIG0100_I_READ_DOCS: '1' + MIG0100_TIMEZONE: 'Europe/Berlin' + +# All properties from the "defaults" section are available here. +# +# Example: +# +# services: +# backendManage: +# tag: my-tag +# autoupdate: +# containerRegistry: example.com/my-registry + +# You can also define some additional content for all services. This will just +# add the object to the respective service blob. +# +# Example: +# +# services: +# autoupdate: +# additionalContent: +# deploy: +# replicas: 4 diff --git a/internal/constants/constants.go b/internal/constants/constants.go index fcd9821..06cb121 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -46,6 +46,9 @@ const ( // DefaultConfigFile is the filename used, if none is set in config file(s) DefaultConfigFile string = "os-config.yaml" + // TemplateSuffix is the recognized suffix for template files + TemplateSuffix string = ".tmpl" + // CertCertName is filename for the HTTPS certificate file CertCertName string = "cert_crt" @@ -59,7 +62,7 @@ const ( SecretsDirPerm fs.FileMode = 0700 // SecretFilePerm is the permission for secret files (owner read/write only) - SecretFilePerm fs.FileMode = 0600 + SecretFilePerm fs.FileMode = 0644 // InstanceDirPerm is the permission for project root directory (owner + others read) InstanceDirPerm fs.FileMode = 0755 diff --git a/internal/instance/config/config.go b/internal/instance/config/config.go index 117dd41..e701e89 100644 --- a/internal/instance/config/config.go +++ b/internal/instance/config/config.go @@ -166,7 +166,7 @@ func createFromTemplateFile(baseDir string, force bool, tplFile string, cfg map[ } // Extract filename from config if present, otherwise use a default - filename := filepath.Join(baseDir, getFilename(cfg)) + filename := filepath.Join(baseDir, getFilename(cfg, tplFile)) return createDeploymentFile(filename, force, data, cfg, baseDir) } @@ -237,10 +237,13 @@ func createDeploymentFile(filename string, force bool, tplData []byte, cfg map[s } // getFilename extracts the filename from config, or returns a default -func getFilename(cfg map[string]any) string { +func getFilename(cfg map[string]any, tplFile string) string { if fn, ok := cfg["filename"].(string); ok && fn != "" { return fn } + if tplFilePretty, found := strings.CutSuffix(tplFile, constants.TemplateSuffix); found { + return tplFilePretty + } return constants.DefaultConfigFile } diff --git a/internal/instance/config/config_test.go b/internal/instance/config/config_test.go index 68401ae..3478ec6 100644 --- a/internal/instance/config/config_test.go +++ b/internal/instance/config/config_test.go @@ -366,29 +366,36 @@ func TestGetFilename(t *testing.T) { cfg := map[string]any{ "filename": "custom.yml", } - result := getFilename(cfg) + result := getFilename(cfg, "myspecial.yaml.tmpl") if result != "custom.yml" { t.Errorf("Expected custom.yml, got %s", result) } }) - - t.Run("without filename in config", func(t *testing.T) { + t.Run("without filename in config, with template file", func(t *testing.T) { cfg := map[string]any{ "other": "value", } - result := getFilename(cfg) + result := getFilename(cfg, "myspecial.yaml.tmpl") + if result != "myspecial.yaml" { + t.Errorf("Expected myspecial.yaml, got %s", result) + } + }) + t.Run("without filename in config, no template suffix", func(t *testing.T) { + cfg := map[string]any{ + "other": "value", + } + result := getFilename(cfg, "myspecial.yaml") if result != constants.DefaultConfigFile { t.Errorf("Expected %s, got %s", constants.DefaultConfigFile, result) } }) - - t.Run("empty filename in config", func(t *testing.T) { + t.Run("empty filename in config, with template file", func(t *testing.T) { cfg := map[string]any{ "filename": "", } - result := getFilename(cfg) - if result != constants.DefaultConfigFile { - t.Errorf("Expected %s for empty filename, got %s", constants.DefaultConfigFile, result) + result := getFilename(cfg, "myspecial.yaml.tmpl") + if result != "myspecial.yaml" { + t.Errorf("Expected myspecial.yaml for empty filename, got %s", result) } }) @@ -396,9 +403,9 @@ func TestGetFilename(t *testing.T) { cfg := map[string]any{ "filename": 123, } - result := getFilename(cfg) - if result != constants.DefaultConfigFile { - t.Errorf("Expected %s for non-string filename, got %s", constants.DefaultConfigFile, result) + result := getFilename(cfg, "myspecial.yaml.tmpl") + if result != "myspecial.yaml" { + t.Errorf("Expected myspecial.yaml for non-string filename, got %s", result) } }) }