Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sub path installation #174

Closed
bboehmke opened this issue Aug 10, 2020 · 18 comments
Closed

Sub path installation #174

bboehmke opened this issue Aug 10, 2020 · 18 comments
Assignees
Labels
question Further information is requested

Comments

@bboehmke
Copy link

We want to use listmonk in an internal network hosted on a server shared by multiple services.
Sadly we don't have the ability to use DNS, instead we are using sub paths to separate services for example:

Is it possible to run listmonk in a similar way?

@knadh knadh added the question Further information is requested label Aug 11, 2020
@knadh
Copy link
Owner

knadh commented Aug 11, 2020

This isn't particular to listmonk, but is definitely possible using rewrite rules in Nginx/Apache.

@bboehmke
Copy link
Author

Sadly it is not so easy because the web page access all resources via absolute links (eg /frontend/js/app.70e5f848.js).
This causes 404 errors if the application is running under /listmonk/.
image

Some application are using relative import where this works automatically but it seams not so easy for larger application.
The most applications are able to configure a custom prefix for the links with can be used for such setups (eg GitLab)

I think something similar would be required to get it working in such a setup.

@bboehmke
Copy link
Author

bboehmke commented Sep 7, 2020

I took a closer look at this issue and based on my pure understanding of this java script framework it seams not be so easy to support sub path installation.

The only option I found at the moment is to make a custom build and change this line

publicPath: '/',

Sadly a relative path is not working here (links and API calls broken) and it seams also not really possible to set this value dynamically at runtime.

@knadh
Copy link
Owner

knadh commented Sep 7, 2020

You are right. The frontend has to be recompiled with tweaked paths. You will also have to tweak the static path (/frontend) in handlers.go and compile the Go app too.

@bboehmke
Copy link
Author

bboehmke commented Sep 7, 2020

With a little bit of URL rewriting by the reverse proxy it is enough to modify front end.

Here an example for traefik (file provider):

http:
  routers:
    listmonk:
      rule: "PathPrefix(`/listmonk/`)"
      middlewares:
        - "test-stripprefix"
      service: listmonk

  middlewares:
    test-stripprefix:
      stripPrefix:
        prefixes:
          - "/listmonk"

  services:
    listmonk:
      loadBalancer:
        servers:
          - url: http://127.0.0.1:9000/

Too bad that vuejs/webpack does not support such kind of setup.

@knadh knadh closed this as completed Sep 30, 2020
@bboehmke
Copy link
Author

@knadh Only to make it clear: This configuration does not fix the problem!

This configuration produces the errors shown in my second comment.

@justinbeaty
Copy link
Contributor

justinbeaty commented Jul 14, 2021

While this is not a feature I need, here's a possible idea to solve it:

  • In vue.config.js change publicPath: '/' to publicPath: './'
  • In index.html, add <base href="{{ RootURL }}"> and also <script>var RootURL = '{{ RootURL }}'</script>
  • In handlers.go, string replace {{ RootURL }} with the actual URL before serving the file
  • In router/index.js, change base: process.env.BASE_URL, to base: RootURL,
  • In api/index.js, change baseURL: process.env.BASE_URL, to baseURL: RootURL,

To me, it seems a bit of a hacky way to solve it, but AFAIK there is no other way to dynamically change the base url after being built with webpack. I haven't tested any of the above, but just a thought as I came across this issue.

Edit: Actually, I think the var RootURL = '{{ RootURL }}' part would need to be replaced with just the subpath part, but that would be easily extracted from the full URL from settings.

@knadh
Copy link
Owner

knadh commented Jul 15, 2021

Thanks @justinbeaty, will give this a try. Reopening the issue.

@knadh knadh reopened this Jul 15, 2021
@knadh knadh self-assigned this Jul 15, 2021
@justinbeaty
Copy link
Contributor

@knadh If you do want some help on this, just let me know. I didn't mean to just write how you should fix it, but I figured I'd write out an attempted solution to see if it would be acceptable.

@svenk
Copy link

svenk commented Sep 8, 2021

Given that listmonk currently does not support serving URLs from subdirectories, the settings entry in the GUI should be relabeled. That's the text which currently says Public URL of the installation (no trailing slash). -- that suggests something like http://example.com/foo would work, but in fact it does not. The text should probably say Public domain of the installation (in the format schema://domain.tld but without subpath)).

@knadh
Copy link
Owner

knadh commented Sep 10, 2021

@svenk noted.

@justinbeaty I spent a few hours attempting this today. It almost works, but there's still some WebPack/Vue quirk that breaks it. Adding <base> outright breaks all assets. Somehow, WebPack asset loading refuses to respect base.

Changing assetsDir (vue.config.js) from frontend to ./frontend works with, but the relative URL fails on nested pages such as /subscribers/import. If it respected <base> it should've worked.

I've the WIP here in case you're interested in checking it out.

@knadh
Copy link
Owner

knadh commented Oct 27, 2021

Closing this due to inactivity. The WIP I linked is now stale after the v2.0.0 release.

@knadh knadh closed this as completed Oct 27, 2021
@jhrdt
Copy link

jhrdt commented Jan 7, 2022

Hi,

I managed to run listmonk v2.0.0 in a sub path.

Setup

  • nginx
  • haproxy
  • container/server that runs listmonk
NGINX (ssl) --> HAPROXY --> CONTAINER (running listmonk)

Config

NGINX Config

  • Servername: https: //example.com
        [...]

        if ($http_referer ~ "^https://example.com/listmonk") {
                rewrite ^/$ /listmonk/ permanent;
                rewrite ^/(admin|api|font|public|subscription|link|campaing|webhooks)(.*) /listmonk/$1$2 last;
        }

        # Fix for failed font load.
        if ($http_referer ~ "^https://example.com/admin/static/css/app") {
                rewrite ^/(admin|api|font|public|subscription|link|campaing|webhooks)(.*) /listmonk/$1$2 last;
        }

       [...]

        # Listmonk
        location /listmonk {
                proxy_pass http://haproxy.intra/listmonk;
                proxy_set_header X-Forwarded-For $proxy_protocol_addr;
                proxy_set_header X-Forwarded-Proto $scheme;
                sub_filter_types *;
                sub_filter 'http://localhost:9000/' 'https://example.com/listmonk/';
                sub_filter '"http://localhost:9000"' '"https://example.com/listmonk"';
                sub_filter '"/admin/"' '"/listmonk/admin/"';
                sub_filter_once off;
        }

HAPROXY Config

frontend http-in
        [...]
        acl goto_listmonk path_beg /listmonk
        use_backend listmonk if goto_listmonk

        [...]

        backend listmonk
        mode http
        http-request set-path "%[path,regsub(^/listmonk/,/)]"
        http-request add-header X-Forwarded-Proto https
        server container change.to.container.ipv4:9000 check

Container

Runs listmonk on port 9000.


This works for me, but might be helpful to others. I use listmonk as an internal service. There might be more to consider, when using listmonk on a public interface.

@knadh
Copy link
Owner

knadh commented Jan 7, 2022

Nice @jhrdt. Why do you need HAProxy on top of Nginx though? Either one of the proxies should be enough right?

@jhrdt
Copy link

jhrdt commented Jan 7, 2022

Nice @jhrdt. Why do you need HAProxy on top of Nginx though? Either one of the proxies should be enough right?

Hi @knadh. Yes this right, you could drop HAProxy. I use it to handle traffic on my container host. A rewrite rule before the proxy_pass could do the same, I think.

I could try to do a version, only with nginx and listmonk (both on the same host).

@jhrdt
Copy link

jhrdt commented Jan 7, 2022

Hi @knadh,

I created a test environment and installed the following software

  1. listmonk (running on port 9000)
  2. nginx (running on port 80)

NGINX Config

The following config runs listmonk in sub-path /listmonk. Server IPv4 192.168.56.105.

# Default server configuration
#
server {
	listen 80 default_server;
	listen [::]:80 default_server;

	# SSL configuration
	#
	# listen 443 ssl default_server;
	# listen [::]:443 ssl default_server;
	#
	# Note: You should disable gzip for SSL traffic.
	# See: https://bugs.debian.org/773332
	#
	# Read up on ssl_ciphers to ensure a secure configuration.
	# See: https://bugs.debian.org/765782
	#
	# Self signed certs generated by the ssl-cert package
	# Don't use them in a production server!
	#
	# include snippets/snakeoil.conf;

	root /var/www/html;

	# Add index.php to the list if you are using PHP
	index index.html index.htm index.nginx-debian.html;

	server_name _;

        if ($http_referer ~ "^http://192.168.56.105/listmonk") {
                rewrite ^/$ /listmonk/ permanent;
                rewrite ^/(admin|api|font|public|subscription|link|campaign|webhooks)(.*) /listmonk/$1$2 last;
        }

        # Fix font load.
        if ($http_referer ~ "^http://192.168.56.105/admin/static/css/app") {
                rewrite ^/admin(.*) /listmonk/admin$1 last;
        }

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		#try_files $uri $uri/ =404;
		return 404;
	}

	location /listmonk {
		rewrite ^/listmonk(.*) $1 break;
                proxy_pass http://127.0.0.1:9000;
                proxy_set_header X-Forwarded-For $proxy_protocol_addr;
                proxy_set_header X-Forwarded-Proto $scheme;
                sub_filter_types *;
                sub_filter 'http://localhost:9000/' 'http://192.168.56.105/listmonk/';
                sub_filter '"http://localhost:9000"' '"http://192.168.56.105/listmonk"';
                sub_filter '"/admin/"' '"/listmonk/admin/"';
                sub_filter_once off;
	}
}

@mrhydejc
Copy link

mrhydejc commented Feb 21, 2022

Can I run multiple instance of listmonk with multiple subpath in a kubernetes cluster ?
Thanks to @jhrdt, I finally managed to make it work.
I found that the Font load fix is not necessary as the CSS use relative path to load font. Maybe @knadh can confirm.

k8s config

With this configuration you can install multiple instance of listmonk with subpath /listmonk/instance
The config below use an nginx side-car.
This config should work with an ingress snippet instead of a sidecar, but I haven’t tested it. In an Ingress, you should replace var manually.

Nginx proxy side-car config

This config is mount at /etc/nginx/conf.d/default.conf.template

server {
        listen 80 default;
        resolver kube-dns.kube-system ipv6=off;
        server_name  localhost;

        location /listmonk/$INSTANCE {
            rewrite ^/listmonk/$INSTANCE(.*) $1 break;
            proxy_pass http://$UPSTREAM;
            proxy_set_header X-Forwarded-For $proxy_protocol_addr;
            proxy_set_header X-Forwarded-Proto $scheme;
            sub_filter_types *;
            sub_filter 'http://localhost:9000/' 'https://$DOMAIN/listmonk/$INSTANCE';
            sub_filter '"http://localhost:9000"' '"https://$DOMAIN/listmonk/$INSTANCE"';
            sub_filter '"/admin/"' '"/listmonk/$INSTANCE/admin/"';
            sub_filter 'href="/' 'href="';
            sub_filter 'src="/' 'src="';
            sub_filter_once off;
        }
    }

The envar substitution is done with a configMapRef in envFrom and a custom command :

          command: ["/bin/bash", "-c"]
          args:
            - |
              envsubst '$UPSTREAM $INSTANCE $DOMAIN'< /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf;
              nginx -g 'daemon off;';

Ingress controller snippet

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-ingress-conf
data:
    #listmonk config
    if ($http_referer ~ "mydomain\.com/listmonk/([^/]+)/") {
        set $instance $1;
        rewrite ^/$ /listmonk/$instance/ permanent;
        rewrite ^/(admin|api|font|public|subscription|link|campaign|webhooks)(.*) /listmonk/$instance/$1$2 last;
    }

@TheTechmage
Copy link

Given that listmonk currently does not support serving URLs from subdirectories, the settings entry in the GUI should be relabeled. That's the text which currently says Public URL of the installation (no trailing slash). -- that suggests something like http://example.com/foo would work, but in fact it does not. The text should probably say Public domain of the installation (in the format schema://domain.tld but without subpath)).

I just spent a couple hours trying to get this to work, wondering why I was only getting 404s, thinking I had done something wrong. The text implies that it's a simple config option when in-fact, it is not (see all the workarounds that people have made). I really like @svenk's suggestion and it would have saved me all the time debugging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

7 participants