local optional(object, field) = std.get(object, field, {}); local dockerSocket = '/var/run/docker.sock:/var/run/docker.sock'; local mediaEnv = { PUID: 1000, PGID: 1000, TZ: 'America/New_York', }; local formatHelper(fmt) = function(name, option) fmt % [name, option]; local toList(fmt) = function(object) std.objectValues(std.mapWithKey(formatHelper(fmt), object)); local toLabels = toList('%s=%s'); local toVolumes = toList('%s:%s'); local homelabDomain = '0w0.casa'; local traefikLabels(name, host, port, extras) = toLabels({ 'traefik.enable': 'true', ['traefik.http.routers.%s.rule' % name]: 'Host(`%s.mat`)' % host, ['traefik.http.routers.%s.entrypoints' % name]: 'web', ['traefik.http.routers.%s-tls.rule' % name]: 'Host(`%s.%s`)' % [host, homelabDomain], ['traefik.http.routers.%s-tls.entrypoints' % name]: 'web-tls', 'traefik.docker.network': 'traefik', } + extras + if port == null then {} else { ['traefik.http.services.%s.loadbalancer.server.port' % name]: port }); local mkNetwork(svc) = if std.get(svc, 'gluetun', false) then { network_mode: 'service:gluetun', } else { networks: ['traefik'], }; local mkService(name, svc) = svc { container_name: name, volumes: toVolumes(optional(svc, 'volumes')) + toVolumes(optional(svc, 'mounts')) + if std.get(svc, 'docker', false) then [dockerSocket] else [], labels: traefikLabels( name, std.get(svc, 'host', name), svc.webPort, optional(svc, 'traefik') ), restart: std.get(svc, 'restart', 'always'), } + mkNetwork(svc); local extractVolumes(cfg) = { [name]: { // this is very ugly, a data driven approach would be better but very verbose external: std.length(std.findSubstr('_', name)) >= 2, } for name in std.flattenArrays([ std.objectFields(optional(svc, 'volumes')) for svc in std.objectValues(cfg) ]) }; local mediaVolumes(name, external) = local volume = if external then 'media_%s_config' else '%s_config'; { [volume % name]: '/config', }; local mediaMounts(mounts) = { ['/media/mat/%s' % path]: mounts[path] for path in std.objectFields(mounts) }; { Compose(cfg):: { services: std.mapWithKey(mkService, cfg), volumes: extractVolumes(cfg), networks: { traefik: { external: true } }, }, Port(port, src=port, kind='tcp'):: local mapped = '%d:%d' % [port, src]; '%s/%s' % [mapped, kind], Command:: toList('--%s=%s'), MediaMounts:: mediaMounts, MediaService(name, tag='latest', env={}, mounts={}, webPort=null, ports=[], extras={}, external=false):: { image: 'lscr.io/linuxserver/%s:%s' % [name, tag], environment: mediaEnv + env, volumes: mediaVolumes(name, external), mounts:: mediaMounts(mounts), webPort:: webPort, ports: ports, } + extras, HomelabDomain:: homelabDomain, }