about summary refs log tree commit diff
path: root/nixpkgs/pkgs/servers/web-apps/lemmy
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/pkgs/servers/web-apps/lemmy')
-rw-r--r--nixpkgs/pkgs/servers/web-apps/lemmy/package.json149
-rw-r--r--nixpkgs/pkgs/servers/web-apps/lemmy/pin.json8
-rw-r--r--nixpkgs/pkgs/servers/web-apps/lemmy/server.nix64
-rw-r--r--nixpkgs/pkgs/servers/web-apps/lemmy/ui.nix98
-rwxr-xr-xnixpkgs/pkgs/servers/web-apps/lemmy/update.py163
5 files changed, 482 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/servers/web-apps/lemmy/package.json b/nixpkgs/pkgs/servers/web-apps/lemmy/package.json
new file mode 100644
index 000000000000..97600d6801a2
--- /dev/null
+++ b/nixpkgs/pkgs/servers/web-apps/lemmy/package.json
@@ -0,0 +1,149 @@
+{
+  "name": "lemmy-ui",
+  "description": "An isomorphic UI for lemmy",
+  "version": "0.19.3",
+  "author": "Dessalines <tyhou13@gmx.com>",
+  "license": "AGPL-3.0-only",
+  "scripts": {
+    "analyze": "webpack --mode=none",
+    "build:dev": "webpack --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=development",
+    "build:prod": "webpack --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=production",
+    "clean": "yarn run rimraf dist",
+    "dev": "yarn build:dev --watch",
+    "lint": "yarn translations:generate && tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx \"src/**\" && prettier --check \"src/**/*.{ts,tsx,js,css,scss}\"",
+    "postinstall": "husky install",
+    "prebuild:dev": "yarn clean && node generate_translations.js",
+    "prebuild:prod": "yarn clean && node generate_translations.js",
+    "prepare": "husky install",
+    "themes:build": "sass src/assets/css/themes/:src/assets/css/themes",
+    "themes:watch": "sass --watch src/assets/css/themes/:src/assets/css/themes",
+    "translations:generate": "node generate_translations.js",
+    "translations:init": "git submodule init && yarn translations:update",
+    "translations:update": "git submodule update --remote --recursive"
+  },
+  "repository": "https://github.com/LemmyNet/lemmy-ui",
+  "engines": {
+    "node": ">=8.9.0"
+  },
+  "dependencies": {
+    "@babel/plugin-proposal-class-properties": "^7.18.6",
+    "@babel/plugin-proposal-decorators": "^7.23.7",
+    "@babel/plugin-transform-runtime": "^7.23.7",
+    "@babel/plugin-transform-typescript": "^7.23.6",
+    "@babel/preset-env": "^7.23.8",
+    "@babel/preset-typescript": "^7.21.5",
+    "@babel/runtime": "^7.23.8",
+    "@emoji-mart/data": "^1.1.0",
+    "@shortcm/qr-image": "^9.0.2",
+    "autosize": "^6.0.1",
+    "babel-loader": "^9.1.3",
+    "babel-plugin-inferno": "^6.7.1",
+    "bootstrap": "^5.3.1",
+    "check-password-strength": "^2.0.7",
+    "classnames": "^2.5.1",
+    "clean-webpack-plugin": "^4.0.0",
+    "cookie": "^0.6.0",
+    "copy-webpack-plugin": "^12.0.2",
+    "css-loader": "^6.9.1",
+    "date-fns": "^3.2.0",
+    "emoji-mart": "^5.4.0",
+    "emoji-short-name": "^2.0.0",
+    "express": "~4.18.2",
+    "history": "^5.3.0",
+    "html-to-text": "^9.0.5",
+    "husky": "^8.0.3",
+    "i18next": "^23.7.16",
+    "inferno": "^8.2.3",
+    "inferno-create-element": "^8.2.3",
+    "inferno-helmet": "^5.2.1",
+    "inferno-hydrate": "^8.2.3",
+    "inferno-i18next-dess": "0.0.2",
+    "inferno-router": "^8.2.3",
+    "inferno-server": "^8.2.3",
+    "jwt-decode": "^4.0.0",
+    "lemmy-js-client": "0.19.2-alpha.1",
+    "lodash.isequal": "^4.5.0",
+    "markdown-it": "^14.0.0",
+    "markdown-it-bidi": "^0.1.0",
+    "markdown-it-container": "^4.0.0",
+    "markdown-it-emoji": "^3.0.0",
+    "markdown-it-footnote": "^4.0.0",
+    "markdown-it-highlightjs": "^4.0.1",
+    "markdown-it-html5-embed": "^1.0.0",
+    "markdown-it-ruby": "^0.1.1",
+    "markdown-it-sub": "^2.0.0",
+    "markdown-it-sup": "^2.0.0",
+    "mini-css-extract-plugin": "^2.7.7",
+    "register-service-worker": "^1.7.2",
+    "run-node-webpack-plugin": "^1.3.0",
+    "rxjs": "^7.8.1",
+    "sanitize-html": "^2.11.0",
+    "sass": "^1.70.0",
+    "sass-loader": "^14.0.0",
+    "serialize-javascript": "^6.0.2",
+    "service-worker-webpack": "^1.0.0",
+    "sharp": "0.32.6",
+    "tippy.js": "^6.3.7",
+    "toastify-js": "^1.12.0",
+    "tributejs": "^5.1.3",
+    "webpack": "^5.89.0",
+    "webpack-cli": "^5.1.4",
+    "webpack-node-externals": "^3.0.0"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.23.7",
+    "@types/autosize": "^4.0.0",
+    "@types/bootstrap": "^5.2.6",
+    "@types/cookie": "^0.6.0",
+    "@types/express": "^4.17.17",
+    "@types/html-to-text": "^9.0.0",
+    "@types/lodash.isequal": "^4.5.6",
+    "@types/markdown-it": "^13.0.7",
+    "@types/markdown-it-container": "^2.0.6",
+    "@types/node": "^20.11.5",
+    "@types/path-browserify": "^1.0.0",
+    "@types/sanitize-html": "^2.9.0",
+    "@types/serialize-javascript": "^5.0.1",
+    "@types/toastify-js": "^1.12.0",
+    "@typescript-eslint/eslint-plugin": "^6.19.0",
+    "@typescript-eslint/parser": "^6.19.0",
+    "eslint": "^8.56.0",
+    "eslint-plugin-inferno": "^7.33.3",
+    "eslint-plugin-jsx-a11y": "^6.7.1",
+    "eslint-plugin-prettier": "^5.1.3",
+    "import-sort-style-module": "^6.0.0",
+    "lint-staged": "^15.2.0",
+    "prettier": "^3.2.4",
+    "prettier-plugin-import-sort": "^0.0.7",
+    "prettier-plugin-organize-imports": "^3.2.3",
+    "prettier-plugin-packagejson": "^2.4.9",
+    "rimraf": "^5.0.0",
+    "sortpack": "^2.4.0",
+    "style-loader": "^3.3.4",
+    "terser": "^5.27.0",
+    "typescript": "^5.3.3",
+    "typescript-language-server": "^4.3.1",
+    "webpack-bundle-analyzer": "^4.9.0",
+    "webpack-dev-server": "4.15.1"
+  },
+  "lint-staged": {
+    "*.{css, scss}": [
+      "prettier --write"
+    ],
+    "*.{ts,tsx,js}": [
+      "prettier --write",
+      "eslint --fix"
+    ],
+    "package.json": [
+      "sortpack"
+    ]
+  },
+  "packageManager": "yarn@1.22.19",
+  "engineStrict": true,
+  "importSort": {
+    ".js, .jsx, .ts, .tsx": {
+      "style": "module",
+      "parser": "typescript"
+    }
+  }
+}
diff --git a/nixpkgs/pkgs/servers/web-apps/lemmy/pin.json b/nixpkgs/pkgs/servers/web-apps/lemmy/pin.json
new file mode 100644
index 000000000000..fdab1c9d63d6
--- /dev/null
+++ b/nixpkgs/pkgs/servers/web-apps/lemmy/pin.json
@@ -0,0 +1,8 @@
+{
+  "serverVersion": "0.19.3",
+  "uiVersion": "0.19.3",
+  "serverHash": "sha256-iO7bY2oChx0cZbwgXMrrV4e1kffop9s4xmepNywnApU=",
+  "serverCargoHash": "sha256-XXfg0o/LQ/imnsHsREoBXMDP1hU5Stxv0s6AP+o+USc=",
+  "uiHash": "sha256-6GGiKCNL0PALdz0W0d1OOPyMIA5kaoL3148j9GWzrMM=",
+  "uiYarnDepsHash": "sha256-UQ+B2vF34L+HuisyO7wdW2zCfEEGa8YdnoaB4jHi+DY="
+}
diff --git a/nixpkgs/pkgs/servers/web-apps/lemmy/server.nix b/nixpkgs/pkgs/servers/web-apps/lemmy/server.nix
new file mode 100644
index 000000000000..4f605b0b2dbd
--- /dev/null
+++ b/nixpkgs/pkgs/servers/web-apps/lemmy/server.nix
@@ -0,0 +1,64 @@
+{ lib
+, stdenv
+, rustPlatform
+, fetchFromGitHub
+, openssl
+, postgresql
+, libiconv
+, Security
+, protobuf
+, rustfmt
+, nixosTests
+}:
+let
+  pinData = lib.importJSON ./pin.json;
+  version = pinData.serverVersion;
+in
+rustPlatform.buildRustPackage rec {
+  inherit version;
+  pname = "lemmy-server";
+
+  src = fetchFromGitHub {
+    owner = "LemmyNet";
+    repo = "lemmy";
+    rev = version;
+    hash = pinData.serverHash;
+    fetchSubmodules = true;
+  };
+
+  preConfigure = ''
+    echo 'pub const VERSION: &str = "${version}";' > crates/utils/src/version.rs
+  '';
+
+  cargoHash = pinData.serverCargoHash;
+
+  buildInputs = [ postgresql ]
+    ++ lib.optionals stdenv.isDarwin [ libiconv Security ];
+
+  # Using OPENSSL_NO_VENDOR is not an option on darwin
+  # As of version 0.10.35 rust-openssl looks for openssl on darwin
+  # with a hardcoded path to /usr/lib/libssl.x.x.x.dylib
+  # https://github.com/sfackler/rust-openssl/blob/master/openssl-sys/build/find_normal.rs#L115
+  OPENSSL_LIB_DIR = "${lib.getLib openssl}/lib";
+  OPENSSL_INCLUDE_DIR = "${openssl.dev}/include";
+
+  PROTOC = "${protobuf}/bin/protoc";
+  PROTOC_INCLUDE = "${protobuf}/include";
+  nativeBuildInputs = [ protobuf rustfmt ];
+
+  checkFlags = [
+    # test requires database access
+    "--skip=session_middleware::tests::test_session_auth"
+  ];
+
+  passthru.updateScript = ./update.py;
+  passthru.tests.lemmy-server = nixosTests.lemmy;
+
+  meta = with lib; {
+    description = "🐀 Building a federated alternative to reddit in rust";
+    homepage = "https://join-lemmy.org/";
+    license = licenses.agpl3Only;
+    maintainers = with maintainers; [ happysalada billewanick ];
+    mainProgram = "lemmy_server";
+  };
+}
diff --git a/nixpkgs/pkgs/servers/web-apps/lemmy/ui.nix b/nixpkgs/pkgs/servers/web-apps/lemmy/ui.nix
new file mode 100644
index 000000000000..2bb1ccaeb842
--- /dev/null
+++ b/nixpkgs/pkgs/servers/web-apps/lemmy/ui.nix
@@ -0,0 +1,98 @@
+{ lib
+, mkYarnPackage
+, libsass
+, nodejs
+, python3
+, pkg-config
+, fetchFromGitHub
+, fetchYarnDeps
+, nixosTests
+, vips
+, nodePackages
+}:
+
+let
+  pinData = lib.importJSON ./pin.json;
+
+  pkgConfig = {
+    node-sass = {
+      nativeBuildInputs = [ pkg-config ];
+      buildInputs = [ libsass python3 ];
+      postInstall = ''
+        LIBSASS_EXT=auto yarn --offline run build
+        rm build/config.gypi
+      '';
+    };
+    sharp = {
+      nativeBuildInputs = [ pkg-config nodePackages.node-gyp nodePackages.semver ];
+      buildInputs = [ vips ];
+      postInstall = ''
+        yarn --offline run install
+      '';
+    };
+  };
+
+  name = "lemmy-ui";
+  version = pinData.uiVersion;
+
+  src = fetchFromGitHub {
+    owner = "LemmyNet";
+    repo = name;
+    rev = version;
+    fetchSubmodules = true;
+    hash = pinData.uiHash;
+  };
+in
+mkYarnPackage {
+
+  inherit src pkgConfig name version;
+
+  extraBuildInputs = [ libsass ];
+
+  packageJSON = ./package.json;
+  offlineCache = fetchYarnDeps {
+    yarnLock = src + "/yarn.lock";
+    hash = pinData.uiYarnDepsHash;
+  };
+
+  patchPhase = ''
+    substituteInPlace ./package.json \
+      --replace '$(git rev-parse --short HEAD)' "${src.rev}" \
+      --replace 'yarn clean' 'yarn --offline clean' \
+      --replace 'yarn run rimraf dist' 'yarn --offline run rimraf dist'
+  '';
+
+  yarnPreBuild = ''
+    export npm_config_nodedir=${nodejs}
+  '';
+
+  buildPhase = ''
+    # Yarn writes cache directories etc to $HOME.
+    export HOME=$PWD/yarn_home
+
+    ln -sf $PWD/node_modules $PWD/deps/lemmy-ui/
+    echo 'export const VERSION = "${version}";' > $PWD/deps/lemmy-ui/src/shared/version.ts
+
+    yarn --offline build:prod
+  '';
+
+  preInstall = ''
+    mkdir $out
+    cp -R ./deps/lemmy-ui/dist $out
+    cp -R ./node_modules $out
+  '';
+
+  distPhase = "true";
+
+  passthru.updateScript = ./update.py;
+  passthru.tests.lemmy-ui = nixosTests.lemmy;
+  passthru.commit_sha = src.rev;
+
+  meta = with lib; {
+    description = "Building a federated alternative to reddit in rust";
+    homepage = "https://join-lemmy.org/";
+    license = licenses.agpl3Only;
+    maintainers = with maintainers; [ happysalada billewanick ];
+    inherit (nodejs.meta) platforms;
+  };
+}
diff --git a/nixpkgs/pkgs/servers/web-apps/lemmy/update.py b/nixpkgs/pkgs/servers/web-apps/lemmy/update.py
new file mode 100755
index 000000000000..4e867553b790
--- /dev/null
+++ b/nixpkgs/pkgs/servers/web-apps/lemmy/update.py
@@ -0,0 +1,163 @@
+#! /usr/bin/env nix-shell
+#! nix-shell -i python3 -p python3 python3.pkgs.semver nix-prefetch-github
+from urllib.request import Request, urlopen
+import dataclasses
+import subprocess
+import os.path
+import semver
+from typing import (
+    Optional,
+    Dict,
+    List,
+)
+import json
+import os
+
+
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+NIXPKGS = os.path.abspath(os.path.join(SCRIPT_DIR, "../../../../"))
+
+
+OWNER = "LemmyNet"
+UI_REPO = "lemmy-ui"
+SERVER_REPO = "lemmy"
+
+
+@dataclasses.dataclass
+class Pin:
+    serverVersion: str
+    uiVersion: str
+    serverHash: str = ""
+    serverCargoHash: str = ""
+    uiHash: str = ""
+    uiYarnDepsHash: str = ""
+
+    filename: Optional[str] = None
+
+    def write(self) -> None:
+        if not self.filename:
+            raise ValueError("No filename set")
+
+        with open(self.filename, "w") as fd:
+            pin = dataclasses.asdict(self)
+            del pin["filename"]
+            json.dump(pin, fd, indent=2)
+            fd.write("\n")
+
+
+def github_get(path: str) -> Dict:
+    """Send a GET request to GitHub, optionally adding GITHUB_TOKEN auth header"""
+    url = f"https://api.github.com/{path.lstrip('/')}"
+    print(f"Retrieving {url}")
+
+    req = Request(url)
+
+    if "GITHUB_TOKEN" in os.environ:
+        req.add_header("authorization", f"Bearer {os.environ['GITHUB_TOKEN']}")
+
+    with urlopen(req) as resp:
+        return json.loads(resp.read())
+
+
+def get_latest_release(owner: str, repo: str) -> str:
+    return github_get(f"/repos/{owner}/{repo}/releases/latest")["tag_name"]
+
+
+def prefetch_github(owner: str, repo: str, rev: str) -> str:
+    """Prefetch GitHub rev and return SRI hash"""
+    print(f"Prefetching {owner}/{repo}({rev})")
+
+    proc = subprocess.run(
+        ["nix-prefetch-github", owner, repo, "--rev", rev, "--fetch-submodules"],
+        check=True,
+        stdout=subprocess.PIPE,
+    )
+
+    return json.loads(proc.stdout)["hash"]
+
+
+def get_latest_tag(owner: str, repo: str, prerelease: bool = False) -> str:
+    """Get the latest tag from a GitHub Repo"""
+    tags: List[str] = []
+
+    # As the GitHub API doesn't have any notion of "latest" for tags we need to
+    # collect all of them and sort so we can figure out the latest one.
+    i = 0
+    while i <= 100:  # Prevent infinite looping
+        i += 1
+        resp = github_get(f"/repos/{owner}/{repo}/tags?page={i}")
+        if not resp:
+            break
+
+        # Filter out unparseable tags
+        for tag in resp:
+            try:
+                parsed = semver.Version.parse(tag["name"])
+                if (
+                    semver.Version.parse(tag["name"])
+                    and not prerelease
+                    and parsed.prerelease
+                ):  # Filter out release candidates
+                    continue
+            except ValueError:
+                continue
+            else:
+                tags.append(tag["name"])
+
+    # Sort and return latest
+    return sorted(tags, key=lambda name: semver.Version.parse(name))[-1]
+
+
+def get_fod_hash(attr: str) -> str:
+    """
+    Get fixed output hash for attribute.
+    This depends on a fixed output derivation with an empty hash.
+    """
+
+    print(f"Getting fixed output hash for {attr}")
+
+    proc = subprocess.run(["nix-build", NIXPKGS, "-A", attr], stderr=subprocess.PIPE)
+    if proc.returncode != 1:
+        raise ValueError("Expected nix-build to fail")
+
+    # Iterate list in reverse order so we get the "got:" line early
+    for line in proc.stderr.decode().split("\n")[::-1]:
+        cols = line.split()
+        if cols and cols[0] == "got:":
+            return cols[1]
+
+    raise ValueError("No fixed output hash found")
+
+
+def make_server_pin(pin: Pin, attr: str) -> None:
+    pin.serverHash = prefetch_github(OWNER, SERVER_REPO, pin.serverVersion)
+    pin.write()
+    pin.serverCargoHash = get_fod_hash(attr)
+    pin.write()
+
+
+def make_ui_pin(pin: Pin, package_json: str, attr: str) -> None:
+    # Save a copy of package.json
+    print("Getting package.json")
+    with urlopen(
+        f"https://raw.githubusercontent.com/{OWNER}/{UI_REPO}/{pin.uiVersion}/package.json"
+    ) as resp:
+        with open(os.path.join(SCRIPT_DIR, package_json), "wb") as fd:
+            fd.write(resp.read())
+
+    pin.uiHash = prefetch_github(OWNER, UI_REPO, pin.uiVersion)
+    pin.write()
+    pin.uiYarnDepsHash = get_fod_hash(attr)
+    pin.write()
+
+
+if __name__ == "__main__":
+    # Get server version
+    server_version = get_latest_tag(OWNER, SERVER_REPO)
+
+    # Get UI version (not always the same as lemmy-server)
+    ui_version = get_latest_tag(OWNER, UI_REPO)
+
+    pin = Pin(server_version, ui_version, filename=os.path.join(SCRIPT_DIR, "pin.json"))
+    make_server_pin(pin, "lemmy-server")
+    make_ui_pin(pin, "package.json", "lemmy-ui")