Compare commits

..

5 Commits

Author SHA1 Message Date
mat ess c650f86a1b Add remote-docker-compose 2022-11-28 21:24:21 -05:00
mat ess 9e2ac0a65e Add actual-server 2022-11-17 21:11:22 -05:00
mat ess 59a3dfba19 Fix comma 2022-11-17 14:43:28 -05:00
mat ess 69300cb426 Add fixes, firefly 2022-11-17 13:44:48 -05:00
mat ess 97b214e0ea Split out compose.libsonnet, add to-docker-compose helper 2022-11-13 18:16:12 -05:00
5 changed files with 159 additions and 82 deletions

1
.envrc
View File

@ -1 +1,2 @@
use flake
dotenv .env

3
.gitignore vendored
View File

@ -1 +1,2 @@
.direnv/
.env
.direnv

76
compose.libsonnet Normal file
View File

@ -0,0 +1,76 @@
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 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.services.%s.loadbalancer.server.port' % name]: port,
['traefik.http.routers.%s.service' % name]: '%s' % name,
'traefik.docker.network': 'traefik',
} + extras);
local mkService(name, svc) = svc {
container_name: name,
networks: ['traefik'],
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: 'always',
};
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 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, ports=[]):: {
image: 'lscr.io/linuxserver/%s:%s' % [name, tag],
environment: mediaEnv + env,
volumes: { ['media_%s_config' % name]: '/config' },
mounts:: mediaMounts(mounts),
webPort:: webPort,
ports: ports,
},
}

View File

@ -9,9 +9,42 @@
outputs = { self, flake-parts, ... }:
flake-parts.lib.mkFlake { inherit self; } {
systems = [ "x86_64-linux" "aarch64-darwin" ];
perSystem = { config, self', inputs', pkgs, system, ... }: {
perSystem = { config, self', inputs', pkgs, system, ... }:
let
to-docker-compose = pkgs.writeShellApplication {
name = "to-docker-compose";
runtimeInputs = [ pkgs.jsonnet ];
text =
let
vars = [ "PLEX_CLAIM" "PLEX_ADVERTISE_IP" "FIREFLY_APP_KEY" ];
varRow = var: "${var}: '\$${var}',";
in
''
jsonnet services.jsonnet \
--tla-code secrets="{
${pkgs.lib.concatMapStrings varRow vars}
}"
'';
};
remote-docker-compose = pkgs.writeShellApplication {
name = "remote-docker-compose";
runtimeInputs = [ to-docker-compose ];
text = ''
host=$1
shift
cmd="docker compose -f - $*"
# shellcheck disable=SC2029
to-docker-compose | ssh "$host" "$cmd"
'';
};
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [ jsonnet ];
buildInputs = with pkgs; [
jsonnet
to-docker-compose
remote-docker-compose
];
};
};
};

View File

@ -1,78 +1,15 @@
local optional(object, field) = std.get(object, field, {});
local dockerSocket = '/var/run/docker.sock:/var/run/docker.sock';
local tz = 'America/New_York';
local mediaEnv = {
PUID: 1000,
PGID: 1000,
TZ: tz,
};
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 toCommand = toList('--%s=%s');
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.services.%s.loadbalancer.server.port' % name]: port,
['traefik.http.routers.%s.service' % name]: '%s' % name,
'traefik.docker.network': 'traefik',
} + extras);
local mkService(name, svc) = svc {
container_name: name,
networks: ['traefik'],
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: 'always',
};
local extractVolumes(cfg) = {
[name]: { external: true }
for name in std.flattenArrays([
std.objectFields(optional(svc, 'volumes'))
for svc in std.objectValues(cfg)
])
};
local Compose(cfg) = {
services: std.mapWithKey(mkService, cfg),
volumes: extractVolumes(cfg),
networks: { traefik: { external: true } },
};
local Port(port, src=port, kind='tcp') =
local mapped = '%d:%d' % [port, src];
'%s/%s' % [mapped, kind];
local MediaMounts(mounts) = {
['/media/mat/%s' % path]: mounts[path]
for path in std.objectFields(mounts)
};
local MediaService(name, tag='latest', env={}, mounts={}, webPort, ports=[]) = {
image: 'lscr.io/linuxserver/%s:%s' % [name, tag],
environment: mediaEnv + env,
volumes: { ['%s_config' % name]: '/config' },
mounts:: MediaMounts(mounts),
webPort:: webPort,
ports: ports,
};
local compose = import 'compose.libsonnet';
local Compose = compose.Compose;
local Command = compose.Command;
local Port = compose.Port;
local MediaService = compose.MediaService;
local MediaMounts = compose.MediaMounts;
function(secrets={})
Compose({
traefik: {
image: 'traefik:latest',
command: toCommand({
command: Command({
'log.level': 'ERROR',
'api.insecure': 'true',
'providers.docker': 'true',
@ -81,8 +18,8 @@ function(secrets={})
// 'entrypoints.websecure.address': ':443',
}),
docker:: true,
webPort:: 80,
ports: [Port(80), /* Port(443), */ Port(8080)],
webPort:: 8080,
ports: [Port(80) /* Port(443) */],
traefik:: {
// 'traefik.http.routers.http-catchall.rule': 'hostregexp(`{host:.+}`)'
// 'traefik.http.routers.http-catchall.entrypoints': 'web'
@ -93,7 +30,7 @@ function(secrets={})
portainer: {
image: 'portainer/portainer-ce:latest',
docker:: true,
volumes: { portainer_data: '/data' },
volumes: { portainer_portainer_data: '/data' },
webPort:: 9000,
ports: [Port(9443)],
},
@ -143,11 +80,11 @@ function(secrets={})
plex: {
image: 'plexinc/pms-docker',
environment: {
TZ: tz,
TZ: 'America/New_York',
PLEX_CLAIM: std.get(secrets, 'PLEX_CLAIM'),
ADVERTISE_IP: std.get(secrets, 'ADVERTISE_IP'),
ADVERTISE_IP: std.get(secrets, 'PLEX_ADVERTISE_IP'),
},
volumes: { plex_config: '/config' },
volumes: { media_plex_config: '/config' },
mounts:: MediaMounts({
'torrents/plex-transcode': '/transcode',
'passport-5tb': '/passport-5tb',
@ -169,7 +106,10 @@ function(secrets={})
},
archivebox: {
image: 'archivebox/archivebox:dev',
command: 'server --quick-init 0.0.0.0:8000',
// command: 'server --quick-init 0.0.0.0:8000',
// TODO: hack to workaround https://github.com/ArchiveBox/ArchiveBox/issues/1002
entrypoint: '/bin/bash',
command: '-c "chown -R archivebox:archivebox /app/archivebox/core/migrations && /app/bin/docker_entrypoint.sh server --quick-init 0.0.0.0:8000"',
environment: {
ALLOWED_HOSTS: '*',
MEDIA_MAX_SIZE: '750m',
@ -179,4 +119,30 @@ function(secrets={})
webPort:: 8000,
host:: 'archive',
},
firefly: {
image: 'fireflyiii/core:latest',
environment: {
DB_CONNECTION: 'sqlite',
APP_DEBUG: true,
SITE_OWNER: 'mat@mat.services',
APP_KEY: std.get(secrets, 'FIREFLY_APP_KEY'),
TZ: 'America/New_York',
TRUSTED_PROXIES: '**',
},
volumes: {
firefly_data: '/storage',
firefly_uploads: '/var/www/html/storage/upload',
},
webPort:: 8080,
},
actual: {
image: 'jlongster/actual-server:latest',
environment: {
userFilesPath: '/data/user',
serverFilesPath: '/data/server',
externalPort: 5006,
},
volumes: { actual_data: '/data' },
webPort:: 5006,
},
})