verified uploaders

This commit is contained in:
byte[] 2019-11-26 20:45:57 -05:00
parent 84abf98b30
commit ae7f3834fd
24 changed files with 170 additions and 61 deletions

View file

@ -18,6 +18,7 @@ config :philomena,
anonymous_name_salt: System.get_env("ANONYMOUS_NAME_SALT"),
password_pepper: System.get_env("PASSWORD_PEPPER"),
avatar_url_root: System.get_env("AVATAR_URL_ROOT"),
image_file_root: System.get_env("IMAGE_FILE_ROOT"),
otp_secret_key: System.get_env("OTP_SECRET_KEY"),
image_url_root: System.get_env("IMAGE_URL_ROOT"),
badge_url_root: System.get_env("BADGE_URL_ROOT"),

View file

@ -46,6 +46,8 @@ defmodule Philomena.Analyzers.Gif do
{output, 0} ->
[width, height] =
output
|> String.split("\n", trim: true)
|> hd()
|> String.trim()
|> String.split(" ")
|> Enum.map(&String.to_integer/1)

View file

@ -14,8 +14,8 @@ defmodule Philomena.Application do
PhilomenaWeb.Endpoint,
# Starts a worker by calling: Philomena.Worker.start_link(arg)
# {Philomena.Worker, arg},
Pow.Store.Backend.MnesiaCache,
Philomena.Servers.ImageProcessor,
Pow.Store.Backend.MnesiaCache,
{Redix, name: :redix}
]

View file

@ -7,18 +7,31 @@ defmodule Philomena.DuplicateReports do
alias Philomena.Repo
alias Philomena.DuplicateReports.DuplicateReport
alias Philomena.ImageIntensities.ImageIntensity
alias Philomena.Images.Image
@doc """
Returns the list of duplicate_reports.
def generate_reports(source) do
source = Repo.preload(source, :intensity)
## Examples
duplicates_of(source.intensity, source.image_aspect_ratio, 0.2, 0.05)
|> where([i, _it], i.id != ^source.id)
|> where([i, _it], i.duplication_checked != true)
|> Repo.all()
|> Enum.map(fn target ->
create_duplicate_report(source, target, %{}, %{"reason" => "Automated Perceptual dedupe match"})
end)
end
iex> list_duplicate_reports()
[%DuplicateReport{}, ...]
"""
def list_duplicate_reports do
Repo.all(DuplicateReport)
def duplicates_of(intensities, aspect_ratio, dist \\ 0.25, aspect_dist \\ 0.05) do
from i in Image,
inner_join: it in ImageIntensity,
on: it.image_id == i.id,
where: it.nw >= ^(intensities.nw - dist) and it.nw <= ^(intensities.nw + dist),
where: it.ne >= ^(intensities.ne - dist) and it.ne <= ^(intensities.ne + dist),
where: it.sw >= ^(intensities.sw - dist) and it.sw <= ^(intensities.sw + dist),
where: it.se >= ^(intensities.se - dist) and it.se <= ^(intensities.se + dist),
where: i.image_aspect_ratio >= ^(aspect_ratio - aspect_dist) and i.image_aspect_ratio >= ^(aspect_ratio + aspect_dist),
limit: 20
end
@doc """
@ -49,9 +62,9 @@ defmodule Philomena.DuplicateReports do
{:error, %Ecto.Changeset{}}
"""
def create_duplicate_report(attrs \\ %{}) do
%DuplicateReport{}
|> DuplicateReport.changeset(attrs)
def create_duplicate_report(source, target, attribution, attrs \\ %{}) do
%DuplicateReport{image_id: source.id, duplicate_of_image_id: target.id}
|> DuplicateReport.creation_changeset(attrs, attribution)
|> Repo.insert()
end

View file

@ -23,4 +23,12 @@ defmodule Philomena.DuplicateReports.DuplicateReport do
|> cast(attrs, [])
|> validate_required([])
end
@doc false
def creation_changeset(duplicate_report, attrs, attribution) do
duplicate_report
|> cast(attrs, [:reason])
|> put_assoc(:user, attribution[:user])
|> validate_length(:reason, max: 250, count: :bytes)
end
end

View file

@ -56,12 +56,20 @@ defmodule Philomena.Images do
Multi.new
|> Multi.insert(:image, image)
|> Multi.run(:added_tag_count, fn repo, %{image: image} ->
tag_ids = image.added_tags |> Enum.map(& &1.id)
tags = Tag |> where([t], t.id in ^tag_ids)
{count, nil} = repo.update_all(tags, inc: [images_count: 1])
{:ok, count}
end)
|> Multi.run(:after, fn _repo, %{image: image} ->
Processors.after_insert(image)
{:ok, nil}
end)
|> Repo.transaction()
|> Repo.isolated_transaction(:serializable)
end
@doc """

View file

@ -8,6 +8,7 @@ defmodule Philomena.Images.Image do
import Ecto.Changeset
alias Philomena.ImageIntensities.ImageIntensity
alias Philomena.ImageVotes.ImageVote
alias Philomena.ImageFaves.ImageFave
alias Philomena.ImageHides.ImageHide
@ -39,6 +40,7 @@ defmodule Philomena.Images.Image do
has_many :favers, through: [:faves, :user]
has_many :hiders, through: [:hides, :user]
many_to_many :tags, Tag, join_through: "image_taggings", on_replace: :delete
has_one :intensity, ImageIntensity
field :image, :string
field :image_name, :string
@ -112,7 +114,7 @@ defmodule Philomena.Images.Image do
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
image
|> cast(attrs, [:source_url, :description])
|> cast(attrs, [:anonymous, :source_url, :description])
|> change(first_seen_at: now)
|> change(attribution)
end

View file

@ -9,6 +9,7 @@ defmodule Philomena.Images.TagDiffer do
old_set = to_set(old_tags)
new_set = to_set(new_tags)
image_id = changeset |> get_field(:id)
tags = changeset |> get_field(:tags)
added_tags = added_set(old_set, new_set)
removed_tags = removed_set(old_set, new_set)
@ -16,14 +17,15 @@ defmodule Philomena.Images.TagDiffer do
{tags, actually_added, actually_removed} =
apply_changes(tags, added_tags, removed_tags)
{tag_list_cache, tag_list_plus_alias_cache} =
create_caches(tags)
{tag_list_cache, tag_list_plus_alias_cache, file_name_cache} =
create_caches(image_id, tags)
changeset
|> put_change(:added_tags, actually_added)
|> put_change(:removed_tags, actually_removed)
|> put_change(:tag_list_cache, tag_list_cache)
|> put_change(:tag_list_plus_alias_cache, tag_list_plus_alias_cache)
|> put_change(:file_name_cache, file_name_cache)
|> put_assoc(:tags, tags)
end
@ -94,10 +96,11 @@ defmodule Philomena.Images.TagDiffer do
{tags, actually_added, actually_removed}
end
defp create_caches(tags) do
defp create_caches(image_id, tags) do
tags = Tag.display_order(tags)
tag_list_cache =
tags
|> Tag.display_order()
|> Enum.map_join(", ", & &1.name)
tag_ids =
@ -113,6 +116,17 @@ defmodule Philomena.Images.TagDiffer do
|> Tag.display_order()
|> Enum.map_join(", ", & &1.name)
{tag_list_cache, tag_list_plus_alias_cache}
# Trunate filename to 150 characters, making room for the path + filename on Windows
# https://stackoverflow.com/questions/265769/maximum-filename-length-in-ntfs-windows-xp-and-windows-vista
file_name_slug_fragment =
tags
|> Enum.map_join("_", & &1.slug)
|> String.replace("%2F", "")
|> String.replace("/", "")
|> String.slice(0..150)
file_name_cache = "#{image_id}__#{file_name_slug_fragment}"
{tag_list_cache, tag_list_plus_alias_cache, file_name_cache}
end
end

View file

@ -1,10 +1,10 @@
defmodule Philomena.Processors do
alias Philomena.Images.Image
alias Philomena.DuplicateReports
alias Philomena.ImageIntensities
alias Philomena.Repo
alias Philomena.Mime
alias Philomena.Sha512
alias Philomena.Servers.ImageProcessor
@mimes %{
"image/gif" => "image/gif",
@ -66,7 +66,6 @@ defmodule Philomena.Processors do
dir = Path.dirname(file)
File.mkdir_p!(dir)
File.cp!(image.uploaded_image, file)
ImageProcessor.cast(self(), image.id)
end
def process_image(image_id) do
@ -83,8 +82,11 @@ defmodule Philomena.Processors do
sha512 = Sha512.file(file)
changeset = Image.thumbnail_changeset(image, %{"image_sha512_hash" => sha512})
image = Repo.update!(changeset)
spawn fn -> DuplicateReports.generate_reports(image) end
processor.post_process(analysis, file)
process = processor.post_process(analysis, file)
apply_edit_script(image, process)
sha512 = Sha512.file(file)
changeset = Image.process_changeset(image, %{"image_sha512_hash" => sha512})
Repo.update!(changeset)

View file

@ -68,7 +68,7 @@ defmodule Philomena.Processors.Gif do
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
palette_filter = "paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle"
filter_graph = "#{scale_filter} [x]; [x][1:v] #{palette_filter}"
filter_graph = "[0:v] #{scale_filter} [x]; [x][1:v] #{palette_filter}"
{_output, 0} =
System.cmd("ffmpeg", ["-loglevel", "0", "-y", "-i", file, "-i", palette, "-lavfi", filter_graph, scaled])

View file

@ -22,22 +22,22 @@ defmodule Philomena.Processors.Png do
optimized = Briefly.create!(extname: ".png")
{_output, 0} =
System.cmd("optipng", ["-fix", "-i0", "-o2", file, "-out", optimized])
System.cmd("optipng", ["-fix", "-i0", "-o2", "-quiet", "-clobber", file, "-out", optimized])
optimized
end
defp scale_if_smaller(_file, _dimensions, {:full, _target_dim}) do
[{:symlink_original, "full.jpg"}]
[{:symlink_original, "full.png"}]
end
defp scale_if_smaller(file, {width, height}, {thumb_name, {target_width, target_height}}) do
if width > target_width or height > target_height do
scaled = scale(file, {target_width, target_height})
[{:copy, scaled, "#{thumb_name}.jpg"}]
[{:copy, scaled, "#{thumb_name}.png"}]
else
[{:symlink_original, "#{thumb_name}.jpg"}]
[{:symlink_original, "#{thumb_name}.png"}]
end
end
@ -48,7 +48,7 @@ defmodule Philomena.Processors.Png do
{_output, 0} =
System.cmd("ffmpeg", ["-loglevel", "0", "-y", "-i", file, "-vf", scale_filter, scaled])
{_output, 0} =
System.cmd("optipng", ["-i0", "-o1", scaled])
System.cmd("optipng", ["-i0", "-o1", "-quiet", "-clobber", scaled])
scaled
end

View file

@ -20,7 +20,7 @@ defmodule Philomena.Processors.Svg do
preview = Briefly.create!(extname: ".png")
{_output, 0} =
System.cmd("inkscape", [file, "--export-png", preview])
System.cmd("safe-rsvg-convert", [file, preview])
preview
end
@ -40,9 +40,9 @@ defmodule Philomena.Processors.Svg do
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
{_output, 0} =
System.cmd("ffmpeg", ["-y", "-i", preview, "-vf", scale_filter, scaled])
System.cmd("ffmpeg", ["-loglevel", "0", "-y", "-i", preview, "-vf", scale_filter, scaled])
{_output, 0} =
System.cmd("optipng", ["-i0", "-o1", scaled])
System.cmd("optipng", ["-i0", "-o1", "-quiet", "-clobber", scaled])
scaled
end

View file

@ -29,8 +29,8 @@ defmodule Philomena.Processors.Webm do
preview
end
defp scale_if_smaller(palette, file, dimensions, {:full, _target_dim}) do
{webm, mp4} = scale_videos(file, palette, dimensions)
defp scale_if_smaller(file, palette, dimensions, {:full, _target_dim}) do
{webm, mp4} = scale_videos(file, palette, dimensions, dimensions)
[
{:copy, webm, "full.webm"},
@ -38,8 +38,8 @@ defmodule Philomena.Processors.Webm do
]
end
defp scale_if_smaller(palette, file, _dimensions, {thumb_name, {target_width, target_height}}) do
{webm, mp4} = scale_videos(file, palette, {target_width, target_height})
defp scale_if_smaller(file, palette, dimensions, {thumb_name, {target_width, target_height}}) do
{webm, mp4} = scale_videos(file, palette, dimensions, {target_width, target_height})
cond do
thumb_name in [:thumb, :thumb_small, :thumb_tiny] ->
@ -59,14 +59,14 @@ defmodule Philomena.Processors.Webm do
end
end
defp scale_videos(file, _palette, dimensions) do
{width, height} = normalize_dimensions(dimensions)
defp scale_videos(file, _palette, dimensions, target_dimensions) do
{width, height} = box_dimensions(dimensions, target_dimensions)
webm = Briefly.create!(extname: ".webm")
mp4 = Briefly.create!(extname: ".mp4")
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
scale_filter = "scale=w=#{width}:h=#{height}"
{_output, 0} =
System.cmd("ffmpeg", ["-loglevel", "0", "-y", "-i", file, "-c:v", "libvpx", "-crf", "10", "-b:v", "5M", "-vf", scale_filter, webm])
System.cmd("ffmpeg", ["-loglevel", "0", "-y", "-i", file, "-c:v", "libvpx", "-auto-alt-ref", "0", "-crf", "10", "-b:v", "5M", "-vf", scale_filter, webm])
{_output, 0} =
System.cmd("ffmpeg", ["-loglevel", "0", "-y", "-i", file, "-c:v", "libx264", "-preset", "medium", "-crf", "18", "-b:v", "5M", "-vf", scale_filter, mp4])
@ -77,7 +77,7 @@ defmodule Philomena.Processors.Webm do
gif = Briefly.create!(extname: ".gif")
scale_filter = "scale=w=#{width}:h=#{height}:force_original_aspect_ratio=decrease"
palette_filter = "paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle"
filter_graph = "#{scale_filter} [x]; [x][1:v] #{palette_filter}"
filter_graph = "[0:v] #{scale_filter} [x]; [x][1:v] #{palette_filter}"
{_output, 0} =
System.cmd("ffmpeg", ["-loglevel", "0", "-y", "-i", file, "-i", palette, "-lavfi", filter_graph, gif])
@ -94,9 +94,13 @@ defmodule Philomena.Processors.Webm do
palette
end
# Force dimensions to be a multiple of 2. This is required by the
# libvpx and x264 encoders.
defp normalize_dimensions({width, height}) do
{width &&& (~~~1), height &&& (~~~1)}
# x264 requires image dimensions to be a multiple of 2
# -2 = ~1
def box_dimensions({width, height}, {target_width, target_height}) do
ratio = min(target_width / width, target_height / height)
new_width = min(max(trunc(width * ratio) &&& -2, 2), target_width)
new_height = min(max(trunc(height * ratio) &&& -2, 2), target_height)
{new_width, new_height}
end
end

View file

@ -5,12 +5,14 @@ defmodule Philomena.Servers.ImageProcessor do
GenServer.start_link(__MODULE__, default)
end
def cast(pid, image_id) do
def cast(image_id) do
pid = Process.whereis(:processor)
GenServer.cast(pid, {:enqueue, image_id})
end
@impl true
def init([]) do
Process.register(self(), :processor)
{:ok, []}
end
@ -19,7 +21,7 @@ defmodule Philomena.Servers.ImageProcessor do
# Ensure that tempfiles get cleaned up by reaping
# the process after it is done
Task.async(fn -> process(image_id) end)
|> Task.await()
|> Task.await(:infinity)
{:noreply, []}
end

View file

@ -2,8 +2,10 @@ defmodule PhilomenaWeb.ImageController do
use PhilomenaWeb, :controller
alias Philomena.{Images, Images.Image, Comments.Comment, Textile.Renderer}
alias Philomena.Servers.ImageProcessor
alias Philomena.Interactions
alias Philomena.Comments
alias Philomena.Tags
alias Philomena.Repo
import Ecto.Query
@ -95,7 +97,9 @@ defmodule PhilomenaWeb.ImageController do
case Images.create_image(attributes, image_params) do
{:ok, %{image: image}} ->
ImageProcessor.cast(image.id)
Images.reindex_image(image)
Tags.reindex_tags(image.added_tags)
conn
|> put_flash(:info, "Image created successfully.")

View file

@ -30,7 +30,7 @@ defmodule PhilomenaWeb.Endpoint do
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
parsers: [:urlencoded, {:multipart, length: 30_000_000}, :json],
pass: ["*/*"],
json_decoder: Phoenix.json_library()

View file

@ -13,16 +13,19 @@ div
/ | because:
/ =<> comment.edit_reason
div
- link_path = "/images/#{@comment.image.id}#comment_#{@comment.id}"
- link_path = Routes.image_path(@conn, :show, @comment.image) <> "#comment_#{@comment.id}"
- safe_author = PhilomenaWeb.PostView.textile_safe_author(@comment)
- quote_body = if @comment.hidden_from_users, do: "", else: @comment.body
a.communication__interaction title="Link to comment" href=link_path
i.fa.fa-link>
' Link
/=<> link_to link_path, 'data-author': safe_author(comment), 'data-reply-url': link_path, 'data-post': (comment.hidden_from_users ? '' : comment.body), class: 'communication__interaction post-reply post-reply-quote' do
a.communication__interaction.post-reply.post-reply-quote href=link_path data-reply-url=link_path data-author="" data-post=""
a.communication__interaction.post-reply.post-reply-quote href=link_path data-reply-url=link_path data-author=safe_author data-post=quote_body
i.fa.fa-quote-right>
' Quote
/=<> link_to link_path, 'data-author': safe_author(comment), 'data-reply-url': link_path, class: 'communication__interaction post-reply' do
a.communication__interaction.post-reply href=link_path data-reply-url=link_path data-author=""
a.communication__interaction.post-reply href=link_path data-reply-url=link_path data-author=safe_author
i.fa.fa-reply
' Reply
/span.owner-options.hidden

View file

@ -22,4 +22,6 @@
' [Loading preview...]
.block__content.communication-edit__actions
= submit "Post", class: "button"
=> submit "Post", class: "button"
= checkbox f, :anonymous
= label f, :anonymous, "Anonymous"

View file

@ -14,15 +14,18 @@ div
/ =<> post.edit_reason
div
- link_path = Routes.forum_topic_path(@conn, :show, @post.topic.forum, @post.topic, post_id: @post.id) <> "#post_#{@post.id}"
- safe_author = textile_safe_author(@post)
- quote_body = if @post.hidden_from_users, do: "", else: @post.body
a.communication__interaction title="Link to post" href=link_path
i.fa.fa-link>
' Link
/=<> link_to link_path, 'data-author': safe_author(post), 'data-reply-url': link_path, 'data-post': (post.hidden_from_users ? '' : post.body), class: 'communication__interaction post-reply post-reply-quote' do
a.communication__interaction.post-reply.post-reply-quote href=link_path data-reply-url=link_path data-author="" data-post=""
a.communication__interaction.post-reply.post-reply-quote href=link_path data-reply-url=link_path data-author=safe_author data-post=quote_body
i.fa.fa-quote-right>
' Quote
/=<> link_to link_path, 'data-author': safe_author(post), 'data-reply-url': link_path, class: 'communication__interaction post-reply' do
a.communication__interaction.post-reply href=link_path data-reply-url=link_path data-author=""
a.communication__interaction.post-reply href=link_path data-reply-url=link_path data-author=safe_author
i.fa.fa-reply
' Reply
/span.owner-options.hidden

View file

@ -5,5 +5,4 @@
= render PhilomenaWeb.ProfileView, "_awards.html", awards: @object.user.awards
- else
strong<>
| Background Pony #
= anonymous_name(@object)

View file

@ -1,3 +1,31 @@
defmodule PhilomenaWeb.PostView do
alias Philomena.Attribution
alias Textile.Parser
use PhilomenaWeb, :view
def textile_safe_author(object) do
author_name = author_name(object)
Parser.parse(%Parser{image_transform: & &1}, author_name)
|> case do
[{:text, ^author_name}] ->
author_name
_ ->
# Cover *all* possibilities.
literal =
author_name
|> String.replace("==]", "==]==][==")
"[==#{literal}==]"
end
end
defp author_name(object) do
case Attribution.anonymous?(object) do
true -> PhilomenaWeb.UserAttributionView.anonymous_name(object)
false -> object.user.name
end
end
end

View file

@ -12,8 +12,11 @@ defmodule PhilomenaWeb.UserAttributionView do
id = Attribution.object_identifier(object)
user_id = Attribution.best_user_identifier(object)
(:erlang.crc32(salt <> id <> user_id) &&& 0xffff)
|> Integer.to_string(16)
hash =
(:erlang.crc32(salt <> id <> user_id) &&& 0xffff)
|> Integer.to_string(16)
"Background Pony ##{hash}"
end
def anonymous_avatar(_object, class \\ "avatar--100px") do

View file

@ -44,6 +44,7 @@
"plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"porcelain": {:git, "https://github.com/walkr/porcelain.git", "4a497495beb8cab7af0d47ae50f720d31a13f039", [ref: "4a49749"]},
"postgrex": {:hex, :postgrex, "0.15.1", "23ce3417de70f4c0e9e7419ad85bdabcc6860a6925fe2c6f3b1b5b1e8e47bf2f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"pot": {:hex, :pot, "0.10.1", "af7dc220fd45478719b821fb4c1222975132516478483213507f95026298d8ab", [:rebar3], [], "hexpm"},
"pow": {:hex, :pow, "1.0.15", "9267b5c75df2d59968585c042e2a0ec6217b1959d3afd629817461f0a20e903c", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3.0 or ~> 1.4.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 2.0.0 and <= 3.0.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, ">= 1.5.0 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},

View file

@ -69,6 +69,16 @@ if [ ! -f /usr/local/bin/image-intensities ]; then
popd
fi
if [ ! -f /usr/local/bin/safe-rsvg-convert ]; then
# passing input on stdin prevents the loading of any
# external resources
echo '
#!/bin/sh
rsvg-convert < "$1" -o "$2"
' > /usr/local/bin/safe-rsvg-convert
chmod +x /usr/local/bin/safe-rsvg-convert
fi
sed -i -e 's/\(-Xm[sx]\)1g/\1256m/' /etc/elasticsearch/jvm.options
systemctl enable elasticsearch 2>/dev/null
service elasticsearch start