diff options
Diffstat (limited to 'nixpkgs/pkgs/servers/web-apps/lemmy')
-rw-r--r-- | nixpkgs/pkgs/servers/web-apps/lemmy/package.json | 149 | ||||
-rw-r--r-- | nixpkgs/pkgs/servers/web-apps/lemmy/pin.json | 8 | ||||
-rw-r--r-- | nixpkgs/pkgs/servers/web-apps/lemmy/server.nix | 64 | ||||
-rw-r--r-- | nixpkgs/pkgs/servers/web-apps/lemmy/ui.nix | 98 | ||||
-rwxr-xr-x | nixpkgs/pkgs/servers/web-apps/lemmy/update.py | 163 |
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") |