Pull out service ports and volume variables better.

This commit is contained in:
root 2025-09-14 20:15:58 -04:00
parent 1262869fcc
commit 5e2cf888e7
14 changed files with 198 additions and 245 deletions

View file

@ -94,20 +94,6 @@ func ParseServiceConfig(file string) (ServiceConfig, error) {
return c, nil return c, nil
} }
// Get the first host port from a service
func (s ServiceConfig) GetHostPort() (string, error) {
if s.Ports == nil {
return "", undefinedError("ports")
}
ports := strings.Split(s.Ports[0], ":")
if len(ports) == 0 {
return "", undefinedError("ports")
}
return ports[0], nil
}
// Walk a tree of nested structures or maps until the node is found. // Walk a tree of nested structures or maps until the node is found.
// Node must be of type string or array. // Node must be of type string or array.
func GetValue(i interface{}, path string) (interface{}, error) { func GetValue(i interface{}, path string) (interface{}, error) {

View file

@ -22,28 +22,24 @@
"navidrome": { "navidrome": {
"enable": "true", "enable": "true",
"provider": "docker", "provider": "docker",
"volumes": [ "volumes": {
"/data/docker/navidrome:/data", data: "/data/docker/navidrome",
"/data/music:/music:ro" music: "/data/music",
], },
}, },
"forgejo": { "forgejo": {
"enable": "true", "enable": "true",
"provider": "docker", "provider": "docker",
"volumes": [ "volumes": {
"/data/docker/forgejo:/data", data: "/data/docker/forgejo",
], db: "/data/docker/forgejo/postgres",
"config": {
"db": "postgres",
"db_path": "/data/docker/forgejo/postgres",
}, },
"config": { "db": "postgres" },
}, },
"linkding": { "linkding": {
"enable": "true", "enable": "true",
"provider": "docker", "provider": "docker",
"volumes": [ "volumes": { data: "/data/docker/linkding" },
"/data/docker/linkding:/etc/linkding/data",
],
}, },
} }

35
main.go
View file

@ -9,7 +9,7 @@ import (
"strings" "strings"
"text/template" "text/template"
"github.com/ghodss/yaml" _ "github.com/ghodss/yaml"
"dario.cat/mergo" "dario.cat/mergo"
"github.com/otiai10/copy" "github.com/otiai10/copy"
) )
@ -36,8 +36,10 @@ type ServiceConfig struct {
ExtraConfig string ExtraConfig string
Src string Src string
Ports []string Ports map[string]string
Volumes []string ExtraPorts []string
Volumes map[string]string
ExtraVolumes []string
Proxy string Proxy string
Provider string Provider string
@ -94,7 +96,9 @@ func main() {
// Parse service config // Parse service config
mergo.Merge(&s, ServiceConfig{ mergo.Merge(&s, ServiceConfig{
Config: make(map[string]interface{}), Config: make(map[string]interface{}),
Ports: make(map[string]string),
Volumes: make(map[string]string),
}) })
mergo.Merge(&s, service, mergo.WithOverride) mergo.Merge(&s, service, mergo.WithOverride)
config.Services[name] = s config.Services[name] = s
@ -108,15 +112,9 @@ func main() {
s.Packages...) s.Packages...)
// If a proxy definition exists, append it // If a proxy definition exists, append it
// Only look at services.ports.web for proxying
if s.Proxy != "" { if s.Proxy != "" {
port, err := s.GetHostPort() err = AddProxy(s.Proxy, s.Ports["web"])
if err != nil {
fmt.Println(err.Error())
fmt.Println("Error in service", name)
return
}
err = AddProxy(s.Proxy, port)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
fmt.Println("Error adding proxy for service", name) fmt.Println("Error adding proxy for service", name)
@ -133,6 +131,9 @@ func main() {
// Generate service config templates // Generate service config templates
for name, s := range config.Services { for name, s := range config.Services {
// Skip services that aren't enabled
if s.Enable != "true" { continue }
for _, f := range s.ConfigFiles { for _, f := range s.ConfigFiles {
f := strings.Split(f, ":") f := strings.Split(f, ":")
@ -146,7 +147,7 @@ func main() {
tmpl, err := template.New(name).Parse(string(t)) tmpl, err := template.New(name).Parse(string(t))
if err != nil { if err != nil {
fmt.Println("Error reading template") fmt.Println("Error reading template", f[0])
fmt.Println(err) fmt.Println(err)
return return
} }
@ -175,9 +176,10 @@ func main() {
} }
} }
/*
// Convert service configs from JSON to YAML // Convert service configs from JSON to YAML
fmt.Println("Converting docker service configs to YAML") fmt.Println("Converting docker service configs to YAML")
servicesPath := "/config/services/docker/" servicesPath := "/services"
dirents, err := os.ReadDir(servicesPath) dirents, err := os.ReadDir(servicesPath)
for _, d := range dirents { for _, d := range dirents {
if d.IsDir() == true { continue } if d.IsDir() == true { continue }
@ -201,6 +203,7 @@ func main() {
fd.Chmod(0444) fd.Chmod(0444)
fd.Write(y) fd.Write(y)
} }
*/
// Parse package list // Parse package list
{ {
@ -255,9 +258,10 @@ func main() {
// (Re)start services // (Re)start services
/*
// Handle docker services // Handle docker services
fmt.Println("Starting docker services") fmt.Println("Starting docker services")
servicesPath = "/config/services/docker/" servicesPath = "/services/"
dirents, err = os.ReadDir(servicesPath) dirents, err = os.ReadDir(servicesPath)
for _, d := range dirents { for _, d := range dirents {
if d.IsDir() == true { continue } if d.IsDir() == true { continue }
@ -277,4 +281,5 @@ func main() {
return return
} }
} }
*/
} }

View file

@ -8,6 +8,7 @@ import (
// Scripted proxy errors // Scripted proxy errors
var ErrConversion = errors.New("port is not a number") var ErrConversion = errors.New("port is not a number")
var ErrNoPort = errors.New("port is not defined for requested proxy")
var ErrDuplicate = errors.New("duplicate entry") var ErrDuplicate = errors.New("duplicate entry")
type ProxyError struct { type ProxyError struct {
@ -27,6 +28,10 @@ func conversionError(domain, port string) *ProxyError {
return &ProxyError{domain, port, ErrConversion} return &ProxyError{domain, port, ErrConversion}
} }
func noPortError(domain string) *ProxyError {
return &ProxyError{domain, "", ErrNoPort}
}
func duplicateError(domain, port string) *ProxyError { func duplicateError(domain, port string) *ProxyError {
return &ProxyError{domain, port, ErrDuplicate} return &ProxyError{domain, port, ErrDuplicate}
} }
@ -51,10 +56,11 @@ func GetProxy() ([]Proxy) {
} }
func AddProxy(domain, port string) (error) { func AddProxy(domain, port string) (error) {
// If the port doesn't exist this is a no-op
if port == "" { return noPortError(domain) }
p, err := strconv.Atoi(port) p, err := strconv.Atoi(port)
if err != nil { if err != nil { return conversionError(domain, port) }
return conversionError(domain, port)
}
conflict, exists := proxyList[p] conflict, exists := proxyList[p]
if exists == true { if exists == true {

View file

@ -1,14 +1,10 @@
{ {
ports: ["3000:3000", "222:22"], ports: { web: "3000", ssh: "222", },
volumes: [
"/etc/timezone:/etc/timezone:ro",
"/etc/localtime:/etc/localtime:ro",
],
provider: "docker", provider: "docker",
packages: [], packages: [],
src: "codeberg.org/forgejo/forgejo:10", src: "codeberg.org/forgejo/forgejo:10",
configFiles: [ configFiles: [
"forgejo.template:/services/docker/forgejo.json", "forgejo.template:/services/forgejo/compose.yaml",
] ]
} }

View file

@ -1,88 +1,70 @@
{ networks:
"networks": { forgejo:
"forgejo": { external: false
"external": false
}
},
"services": { services:
"server": { server:
"image": "{{.Src}}", image: {{.Src}}
"container_name": "forgejo", container_name: forgejo
"environment": [ environment:
"USER_UID=1000", - USER_UID=1000
"USER_GID=1000", - USER_GID=1000
{{if eq .Config.db "mysql"}} {{- if eq .Config.db "mysql"}}
FORGEJO__database__DB_TYPE=mysql, - FORGEJO__database__DB_TYPE=mysql
FORGEJO__database__HOST=db:3306, - FORGEJO__database__HOST=db:3306
FORGEJO__database__NAME=forgejo, - FORGEJO__database__NAME=forgejo
FORGEJO__database__USER=forgejo, - FORGEJO__database__USER=forgejo
FORGEJO__database__PASSWD=forgejo, - FORGEJO__database__PASSWD=forgejo
{{end}} {{- end}}
{{if eq .Config.db "postgres"}} {{- if eq .Config.db "postgres"}}
FORGEJO__database__DB_TYPE=postgres, - FORGEJO__database__DB_TYPE=postgres
FORGEJO__database__HOST=db:5432, - FORGEJO__database__HOST=db:5432
FORGEJO__database__NAME=forgejo, - FORGEJO__database__NAME=forgejo
FORGEJO__database__USER=forgejo, - FORGEJO__database__USER=forgejo
FORGEJO__database__PASSWD=forgejo, - FORGEJO__database__PASSWD=forgejo
{{end}} {{- end}}
], restart: always
{{with .Config.Restart}}"restart": "{{.}}",{{end}} networks:
"networks": [ - forgejo
"forgejo" volumes:
], - /etc/timezone:/etc/timezone:ro
"volumes": [ - /etc/localtime:/etc/localtime:ro
{{range .Volumes}} - {{.Volumes.data}}:/data
"{{.}}", {{- range .ExtraVolumes}}
{{end}} - {{.}}
], {{- end}}
"ports": [ ports:
{{range .Ports}} - '{{.Ports.web}}:3000'
"{{.}}", - '{{.Ports.ssh}}:22'
{{end}} {{- if .Config.db}}
], depends_on:
{{if .Config.db}} - db
"depends_on": [ {{- end}}
"db"
],
{{end}}
},
{{if eq .Config.db "mysql"}} {{- if eq .Config.db "mysql"}}
"db": { db:
"image": "mysql:8", image: mysql:8
"restart": "always", restart: always
"environment": [ environment:
"MYSQL_ROOT_PASSWORD=forgejo", - MYSQL_ROOT_PASSWORD=forgejo
"MYSQL_USER=forgejo", - MYSQL_USER=forgejo
"MYSQL_PASSWORD=forgejo", - MYSQL_PASSWORD=forgejo
"MYSQL_DATABASE=forgejo" - MYSQL_DATABASE=forgejo
], networks:
"networks": [ - forgejo
"forgejo" volumes:
], - {{.Volumes.db}}:/var/lib/mysql
"volumes": [ {{- end}}
"{{.Config.db_path}}:/var/lib/mysql" {{- if eq .Config.db "postgres"}}
] db:
}, image: postgres:14
{{end}} restart: always
environment:
{{if eq .Config.db "postgres"}} - POSTGRES_USER=forgejo
"db": { - POSTGRES_PASSWORD=forgejo
"image": "postgres:14", - POSTGRES_DB=forgejo
"restart": "always", networks:
"environment": [ - forgejo
"POSTGRES_USER=forgejo", volumes:
"POSTGRES_PASSWORD=forgejo", - {{.Volumes.db}}:/var/lib/postgresql/data
"POSTGRES_DB=forgejo" {{- end}}
],
"networks": [
"forgejo"
],
"volumes": [
"{{.Config.db_path}}:/var/lib/postgresql/data"
]
},
{{end}}
}
}

View file

@ -1,12 +1,13 @@
{ {
# docker-compose -f <linkding.yaml> exec linkding python manage.py createsuperuser --username=joe --email=joe@example.com # docker-compose -f <linkding.yaml> exec linkding python manage.py createsuperuser --username=joe --email=joe@example.com
ports: ["9090:9090"], ports: { web: "9090" },
provider: "docker", provider: "docker",
packages: [], packages: [],
src: "sissbruecker/linkding:latest", src: "sissbruecker/linkding:latest",
configFiles: [ configFiles: [
"linkding.template:/services/docker/linkding.json", "linkding.template:/services/linkding/compose.yaml",
"env.template:/services/linkding/.env",
] ]
} }

View file

@ -0,0 +1,49 @@
# Docker container name
LD_CONTAINER_NAME=linkding
# Port on the host system that the application should be published on
LD_HOST_PORT=9090
# Directory on the host system that should be mounted as data dir into the Docker container
LD_HOST_DATA_DIR=./data
# Can be used to run linkding under a context path, for example: linkding/
# Must end with a slash `/`
LD_CONTEXT_PATH=
# Username of the initial superuser to create, leave empty to not create one
LD_SUPERUSER_NAME=
# Password for the initial superuser, leave empty to disable credentials authentication and rely on proxy authentication instead
LD_SUPERUSER_PASSWORD=
# Option to disable background tasks
LD_DISABLE_BACKGROUND_TASKS=False
# Option to disable URL validation for bookmarks completely
LD_DISABLE_URL_VALIDATION=False
# Enables support for authentication proxies such as Authelia
LD_ENABLE_AUTH_PROXY=False
# Name of the request header that the auth proxy passes to the application to identify the user
# See docs/Options.md for more details
LD_AUTH_PROXY_USERNAME_HEADER=
# The URL that linkding should redirect to after a logout, when using an auth proxy
# See docs/Options.md for more details
LD_AUTH_PROXY_LOGOUT_URL=
# List of trusted origins from which to accept POST requests
# See docs/Options.md for more details
LD_CSRF_TRUSTED_ORIGINS=
# Database settings
# These are currently only required for configuring PostreSQL.
# By default, linkding uses SQLite for which you don't need to configure anything.
# Database engine, can be sqlite (default) or postgres
LD_DB_ENGINE=
# Database name (default: linkding)
LD_DB_DATABASE=
# Username to connect to the database server (default: linkding)
LD_DB_USER=
# Password to connect to the database server
LD_DB_PASSWORD=
# The hostname where the database is hosted (default: localhost)
LD_DB_HOST=
# Port use to connect to the database server
# Should use the default port if not set
LD_DB_PORT=
# Any additional options to pass to the database (default: {})
LD_DB_OPTIONS=

View file

@ -1,70 +1,14 @@
{ services:
"services": { linkding:
"linkding": { container_name: linkding
"container_name": "linkding", image: {{.Src}}
"image": "{{.Src}}", ports:
"ports": [ - "{{.Ports.web}}}:9090"
{{range .Ports}} volumes:
"{{.}}", - "{{.Volumes.data}}:/etc/linkding/data"
{{end}} {{- range .ExtraVolumes}}
], - {{.}}
"volumes": [ {{- end}}
{{range .Volumes}} env_file:
"{{.}}", - .env
{{end}} restart: always
],
"environment": [
## Docker container name
#LD_CONTAINER_NAME=linkding
## Port on the host system that the application should be published on
#LD_HOST_PORT=9090
## Directory on the host system that should be mounted as data dir into the Docker container
#LD_HOST_DATA_DIR=./data
# Can be used to run linkding under a context path, for example: linkding/
# Must end with a slash `/`
LD_CONTEXT_PATH=,
# Username of the initial superuser to create, leave empty to not create one
LD_SUPERUSER_NAME=,
# Password for the initial superuser, leave empty to disable credentials authentication and rely on proxy authentication instead
LD_SUPERUSER_PASSWORD=,
# Option to disable background tasks
LD_DISABLE_BACKGROUND_TASKS=False,
# Option to disable URL validation for bookmarks completely
LD_DISABLE_URL_VALIDATION=False,
# Enables support for authentication proxies such as Authelia
LD_ENABLE_AUTH_PROXY=False,
# Name of the request header that the auth proxy passes to the application to identify the user
# See docs/Options.md for more details
LD_AUTH_PROXY_USERNAME_HEADER=,
# The URL that linkding should redirect to after a logout, when using an auth proxy
# See docs/Options.md for more details
LD_AUTH_PROXY_LOGOUT_URL=,
# List of trusted origins from which to accept POST requests
# See docs/Options.md for more details
LD_CSRF_TRUSTED_ORIGINS=,
# Database settings
# These are currently only required for configuring PostreSQL.
# By default, linkding uses SQLite for which you don't need to configure anything.
# Database engine, can be sqlite (default) or postgres
LD_DB_ENGINE=,
# Database name (default: linkding)
LD_DB_DATABASE=,
# Username to connect to the database server (default: linkding)
LD_DB_USER=,
# Password to connect to the database server
LD_DB_PASSWORD=,
# The hostname where the database is hosted (default: localhost)
LD_DB_HOST=,
# Port use to connect to the database server
# Should use the default port if not set
LD_DB_PORT=,
# Any additional options to pass to the database (default: {})
LD_DB_OPTIONS=,
],
{{with .Config.Restart}}"restart": "{{.}}",{{end}}
}
}
}

View file

@ -1,12 +1,10 @@
{ {
enable: "false", ports: { web: "4533" },
ports: ["4533:4533"],
provider: "docker", provider: "docker",
packages: [], packages: [],
src: "deluan/navidrome:latest", src: "deluan/navidrome:latest",
configFiles: [ configFiles: [
"navidrome.template:/services/docker/navidrome.json", "navidrome.template:/services/navidrome/compose.yaml",
] ]
} }

View file

@ -1,25 +1,19 @@
{ services:
"services": { navidrome:
"navidrome": { image: {{.Src}}
"image": "{{.Src}}", user: 1000:1000 # should be owner of volumes
{{with .Config.Id}}"user": "{{.}}:{{.}}",{{end}} ports:
"ports": [ - "{{.Ports.web}}:4533"
{{range .Ports}} restart: always
"{{.}}", environment:
{{end}} # Optional: put your config options customization here. Examples:
], ND_SCANSCHEDULE: 1h
{{with .Config.Restart}}"restart": "{{.}}",{{end}} ND_LOGLEVEL: info
"environment": { ND_SESSIONTIMEOUT: 24h
"ND_SCANSCHEDULE": "1h", ND_BASEURL: ""
"ND_LOGLEVEL": "info", volumes:
"ND_SESSIONTIMEOUT": "24h", - "{{.Volumes.data}}:/data"
"ND_BASEURL": "", - "{{.Volumes.music}}:/music:ro"
}, {{- range .ExtraVolumes}}
"volumes": [ - {{.}}
{{range .Volumes}} {{- end}}
"{{.}}",
{{end}}
],
}
}
}

View file

@ -1,7 +1,5 @@
{ {
enable: "false", ports: { http: "80", https: "443" },
ports: [ "80", "443" ],
provider: "system", provider: "system",
packages: [ "caddy" ], packages: [ "caddy" ],

View file

@ -1,4 +1,4 @@
{{with .Ports}}Port {{index . 0}}{{end}} {{with .Ports.ssh}}Port {{.}}{{end}}
#AddressFamily any #AddressFamily any
#ListenAddress 0.0.0.0 #ListenAddress 0.0.0.0
#ListenAddress :: #ListenAddress ::

View file

@ -1,7 +1,5 @@
{ {
enable: "true", ports: { ssh: "22" },
ports: [ "22" ],
provider: "system", provider: "system",
packages: [ "openssh-server" ], packages: [ "openssh-server" ],