Today, we're excited to announce the release of Linkwarden 2.12! 🥳 This update brings significant improvements and new features to enhance your experience.

For those who are new to Linkwarden, it’s basically a tool for saving and organizing webpages, articles, and documents all in one place. It’s great for bookmarking stuff to read later, and you can also share your resources, create public collections, and collaborate with your team. Linkwarden is available as a Cloud subscription or you can self-host it on your own server.

This release brings a range of updates to make your bookmarking and archiving experience even smoother. Let’s take a look:

What’s new:

🫧 Drag and Drop Support

One of our most requested features is finally here! You can now drag and drop Links onto Collections and Tags. This makes it much easier to organize your bookmarks and keep everything tidy.

📤 Upload from SingleFile

SingleFile is an awesome browser extension that allows you to save complete webpages as a single HTML file on your device. As of Linkwarden 2.12, you can upload your saved links directly from the SingleFile browser extension into Linkwarden. This allows you to easily save articles which are behind paywalls or require authentication directly from your browser.

To use this feature, simply install the SingleFile extension, and then follow the documentation.

🌐 Progressed Translations

We’ve made significant progress in our translations, with many languages now fully supported. If you’re interested in helping out with translations, check out our Crowdin page.

✅ And more...

There are also a bunch of smaller improvements and fixes in this release to keep everything running smoothly.

Full Changelog: https://github.com/linkwarden/linkwarden/compare/v2.11.8...v2.12.0

Want to skip the technical setup?

If you’d rather skip server setup and maintenance, our Cloud Plan takes care of everything for you. It’s a great way to access all of Linkwarden’s features—plus future updates—without the technical overhead.


We hope you enjoy these new enhancements, and as always, we'd like to express our sincere thanks to all of our supporters and contributors. Your feedback and contributions have been invaluable in shaping Linkwarden into what it is today. 🚀

all 35 comments

sorted by: hot top controversial new old
[–] 21 points 1 year ago (4 children)

Linkwarden user here. Can confirm - it's a great tool to dump links for later. I've setup an iOS shortcut that lets me share links directly to linkwarden. Super handy

  • source
  • hideshow 4 child comments
  • [–] 4 points 1 year ago (2 children)

    For however needs it, LinkGuardian on Android (izzyondroid for fdroid) offers the same thing!

  • source
  • parent
  • hideshow 2 child comments
  • [–] 1 point 1 year ago (1 child)

    I've been using this which works great.

    https://f-droid.org/packages/com.sbv.linkdroid/

    Works with my single sign on setup as well which was critical. Creates a nice share target on android so any share button gives the option of sending the link to linkwarden

  • source
  • parent
  • hideshow 1 child comment
  • [–] 11 points 1 year ago (1 child)

    Freaking quality post, OP! Lots of information about what's new, barely even have to visit the link to know what's going on here. Love to see it.

    👍👍

  • source
  • hideshow 1 child comment
  • [–] 11 points 1 year ago (16 children)

    This is a fantastic tool, but I’d love to confidently expose the API to the internet for the shortcut. To do that you need read-only and running as a user; I saw that that’s not a thing that works from the issues.

    Any thoughts on getting those security features working? Cause the app itself is so smooth I’d let my parents use it and be confident they wouldn’t need to be herded constantly.

  • source
  • hideshow 16 child comments
  • [–] [S] 5 points 1 year ago (15 children)

    So you want to disable registration? It's possible.

  • source
  • parent
  • hideshow 15 child comments
  • [–] 10 points 1 year ago (14 children)

    No what I said isn't about user registration; it's about adding these to the docker-compose.yml:

            read_only: true
            user: 6969:6969
    

    to prevent running as root and making the file system read-only. The API needs to be exposed without a VPN or other proxy login since my parents' can't handle that, so if I was able to implement these recommended security steps I'd feel like I could open up the container to the internet at large without too much risk.

    Per this issue https://github.com/linkwarden/linkwarden/issues/799 it seems like there's a lot of steps to take to get these settings to work.

    It would be also ideal if I didn't have to give the container (but not a deal-breaker):

            cap_add:
              - CAP_SYS_ADMIN
              - CAP_SYS_CHROOT
    

    as the issue also states is required for the headless chrome scraper browser.

    I am using it internally now and it's really good, but to open it up for my parents (which I think they'd dig) I'd definitely want these security settings on without major issues. Linkwarden is an internet-facing application so these recommended security practicies are in its wheel-house, feature-wise, as well.

    Hope that helps clear up my comment!

  • source
  • parent
  • hideshow 14 child comments
  • [–] 1 point 1 year ago (4 children)

    How would you add new links if its read only?

  • source
  • parent
  • hideshow 4 child comments
  • [–] 3 points 1 year ago (3 children)

    As always you store data you want to keep in the volumes section.

    With read-only you prevent new binaries from being added in the image space. You can add ‘noexec’ to your volumes/tmpfs preventing binaries to the areas that are writable. Then ideally you are using an image with minimal surface area (e.g., only sh and the exact binaries needed to make it go) and it’s very secure! It’s still plenty secure without a minimal image.

  • source
  • parent
  • hideshow 3 child comments
  • [–] 1 point 1 year ago (2 children)

    Couldn't the attacker just drop a dangerous binary into the data volume then?

  • source
  • parent
  • hideshow 2 child comments
  • [–] 3 points 1 year ago* (1 child)

    Not if you annotate your data volume with said ‘noexec’ which prevents execution from anything in the data volume. It looks like this, you can slam it on any volume you like - no volumes should have executables in them anyways.

    Also I’m pretty sure ‘noexec’ is the default, so that’s by default protected. But I can’t confirm that from a quick search so not 100% on that.

    ‘/mnt/data:/container/place/it/wants:rw,noexec,nosuid,nodev,Z’

    ‘rw’ means read/write. You can change it to ‘ro’ for read-only if the volume shouldn’t write to it (maybe a config file).

    Z is for selinux that means “only one program can read/write tho this”. You can change it to ‘z’ lowercase in case more than one needs to read/write. Only case I’ve found for little z is crowdsec needing to watch Caddy’s log for blocking.

    So overall, the idea is that your volume mounts can’t be used to execute arbitrary binaries AND the image file system is frozen so that arbitrary binaries cannot be loaded into the image (which is by default all executable, a requirement to run anything in it). So if someone was able to hack into an internet-facing container, they won’t be able to load up whatever they want. They’ll be limited to what’s built into the image (which ideally are secure and limited in scope).

  • source
  • parent
  • hideshow 1 child comment
  • [–] 1 point 1 year ago* (last edited 1 year ago) (6 children)

    So I have mine running in a podman quadlet. It runs as root in the container but it is unpriviledged. Mine has NET_ADMIN and SYS_MODULE but I honestly can't remember why... SYS_ADMIN seems extreme though

    Edit: I'm dumb, and the linkwarden container has no capabilities set. I set them for the tailscale container which definitely needs it.

  • source
  • parent
  • hideshow 6 child comments
  • [–] 1 point 1 year ago* (5 children)

    Care to share your quartet? I’m just getting into the quads with trixie out - and I haven’t gotten this working yet…

    The permissions do seem intense; if you’re getting by without maybe those aren’t quite needed!

  • source
  • parent
  • hideshow 5 child comments
  • [–] 3 points 1 year ago* (last edited 1 year ago) (4 children)

    Sure thing, I'll edit this reply when I get back to my computer. Just note that I also have a tailscale and nginx container in the pod which are not necessary.

    You'll see my nginx config which reverse proxies to the port the service is running on. On public servers I have another nginx running with SSL that proxies to the port I map the pod's port 80 to.

    I usually run my pods as an unpriviledged user with loginctl enable-linger which starts the enabled systemctl --user services on boot.

    All that being said I haven't publically exposed linkwarden yet, mainly because it's the second most resource intensive service I run and I have all my public stuff on a shitty vps.

    Edit: My opsec is so bad hahaha

    Edit2: I just realized the caps I gave were to the tailscale container, not the linkwarden container. Linkwarden can run with no caps :)

    I added the tailscale stuff back

    files:

    linkwarden-pod.kube:

    [Install]
    WantedBy=default.target
    
    [Kube]
    # Point to the yaml in the same directory
    Yaml=linkwarden-pod.yml
    PublishPort=127.0.0.1:7777:80
    AutoUpdate=registry
    
    [Service]
    Restart=always
    

    linkwarden-pod.yml:

    ***
    apiVersion: v1
    kind: Pod
    metadata:
      name: linkwarden
    spec:
      containers:
        - name: ts-linkwarden
          image: docker.io/tailscale/tailscale:latest
          env:
            - name: TS_HOSTNAME
              value: "link"
            - name: TS_STATE_DIR
              value: /var/lib/tailscale
            - name: TS_AUTHKEY
              valueFrom:
                secretKeyRef:
                  name: ts-auth-kube
                  key: ts-auth
          volumeMounts:
            - name: linkwarden-ts-storage
              mountPath: /var/lib/tailscale
          securityContext:
            capabilities:
              add:
                - NET_ADMIN
                - SYS_MODULE
    
        - name: linkwarden
          image: ghcr.io/linkwarden/linkwarden:latest
          env:
            - name: INSTANCE_NAME
              value: link.mydomain.com
            - name: AUTH_URL
              value: http://linkwarden:3000/api/v1/auth
            - name: NEXTAUTH_SECRET
              value: LOL_I_JUST_PUBLISHED_THIS_I_CHANGED_IT
            - name: DATABASE_URL
              value: postgresql://postgres:password@linkwarden-postgres:5432/postgres
            - name: NEXT_PUBLIC_DISABLE_REGISTRATION
              value: "true"
    
        - name: linkwarden-nginx
          image: docker.io/library/nginx:alpine
          volumeMounts:
            - name: linkwarden-nginx-conf
              subPath: nginx.conf
              mountPath: /etc/nginx/nginx.conf
              readOnly: true
    
        - name: linkwarden-postgres
          image: docker.io/library/postgres:latest
          env:
            - name: POSTGRES_PASSWORD
              value: "password"
          volumeMounts:
            - name: linkwarden-postgres-db
              mountPath: /var/lib/postgresql/data
    
      volumes:
        - name: linkwarden-nginx-conf
          configMap:
            name: linkwarden-nginx-conf
            items:
              - key: nginx.conf
                path: nginx.conf
        - name: linkwarden-postgres-db
          persistentVolumeClaim:
            claimName: linkwarden-postgres-db-claim
        - name: linkwarden-ts-storage
          persistentVolumeClaim:
            claimName: linkwarden-ts-pv-claim
    
    ***
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: linkwarden-nginx-conf
    data:
      nginx.conf: |
        #user  nobody;
        worker_processes  1;
         #pid        logs/nginx.pid;
    
    
        events {
            worker_connections  1024;
        }
    
    
        http {
            include       mime.types;
            default_type  application/octet-stream;
    
    
            sendfile        on;
    
            #keepalive_timeout  0;
            keepalive_timeout  65;
    
            gzip  off;
    
            # set_real_ip_from cw.55.55.1;
            real_ip_header X-Forwarded-For;
            real_ip_recursive on;
    
            server {
                listen       80;
                server_name  _;
    
                location / {
                        proxy_pass http://localhost:3000/;
    
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                        proxy_set_header X-Forwarded-Port $server_port;
                        proxy_set_header X-Forwarded-Scheme $scheme;
                        proxy_set_header X-Forwarded-Proto  $scheme;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header Accept-Encoding "";
                        proxy_set_header Host $host;
                }
            }
        }
    
    

    I also have a little helper script you might like

    copy.sh:

    #!/bin/bash
    
    SYSTEMD_DIRECTORY="${HOME}/.config/containers/systemd"
    POD_NAME="linkwarden-pod"
    
    mkdir -p "$SYSTEMD_DIRECTORY"
    cp "${POD_NAME}".{kube,yml} "${SYSTEMD_DIRECTORY}"/
    
    systemctl --user daemon-reload
    
    
  • source
  • parent
  • hideshow 4 child comments
  • [–] 1 point 1 year ago (2 children)

    Just curious why you chose a kube quadlet instead of the typical podman container quadlets?

  • source
  • parent
  • hideshow 2 child comments
  • [–] 2 points 1 year ago (1 child)

    I think it's cool that I can take that config and drop it into kubernetes and it usually just works. I don't have a cluster anymore, but if I decide to use one in the future, the overhead will be negligible

  • source
  • parent
  • hideshow 1 child comment
  • [–] 1 point 1 year ago

    Fair enough! I toyed with the idea of doing it that way because the systemd component would just reference a single yaml file for each service, which feels portable. That said though, my quadlets as they are are pretty portable too. Thanks for sharing!

  • source
  • parent
  • [–] 7 points 1 year ago (1 child)

    Sweet! Single-file looks like a decent workaround for those links that get locked behind captcha when they're being saved. Always frustrates me when I go to review a link and it turns out it only saved a cloudflare redirect lol

  • source
  • hideshow 1 child comment
  • [–] 2 points 1 year ago

    Linkwarden kicks butt! I use it all the time now.

  • source
  • [–] 2 points 1 year ago (1 child)

    I'd love to move to it, but I cannot find any easy way to export from Readeck and import to Linkwarden. At least a list of pages.

  • source
  • hideshow 1 child comment
  • [–] 2 points 1 year ago (2 children)

    I do host an instance of it but rarely use it. I'd love to see more in depth archival features like deep link archiving and archiving resources behind authorization. My use case is more archiving than bookmarking.

  • source
  • hideshow 2 child comments
  • [–] 1 point 1 year ago (1 child)

    Nice. Started using it just a couple weeks ago. I tested a big chunk of them and while Linkwarden isn't perfect, it does everything better than everything else I tried.

    Do we self-hosters need to do anything special? I remember looking at some docs about upgrading versions, but I don't know how to tell which version I have.

  • source
  • hideshow 1 child comment
  • [–] 2 points 1 year ago

    I remember looking at some docs about upgrading versions, but I don't know how to tell which version I have.

    • User icon (top right) > ≡ menu (top left) to open side bar. Current version is displayed at the bottom of the side bar

    As a general rule when installing anything with Docker Compose, rather than using "latest" I prefer to specify a version as it makes it easier to roll back should i find issues with an update.

  • source
  • parent
  • [–] 1 point 1 year ago

    Love it 😊

  • source
  • 35 comments in c/selfhosted@lemmy.world view that post
    27 comments in c/selfhosted@lemmy.world view that post
    27 comments in c/selfhosted@lemmy.world view that post