neiam
/oh-my-ex-oh-ircd
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
Log in to mark this repository.
IRC host cloaking + oper vhosts, topic persistence, and images feature
3a1226c · gmorell · 2026-06-27 17:40
Message
{commit_body(@commit)}
Files changed
modified
README.md
+9
−2
| 35 | 35 | or `:`. A `JOIN` of anything else is refused with `403`. |
| 36 | 36 | * Channel modes: `+b/+e/+I` (ban / except / invite-exception masks |
| 37 | 37 | with glob matching), `+i` (invite-only), `+m` (moderated), |
| 38 | − `+n` (no external), `+k <key>`, `+l <limit>`. Mode views via | |
| 39 | − `MODE #ch b` etc. ban-list-style numerics (367/348/346). | |
| 38 | + `+n` (no external), `+k <key>`, `+l <limit>`, `+f` | |
| 39 | + (flood-exempt — members of the channel bypass PRIVMSG/NOTICE | |
| 40 | + flood control; founder-only to set/clear since it disables a | |
| 41 | + built-in protection), and `+A` (auto-join — every connecting | |
| 42 | + user is force-joined to the channel after registration; | |
| 43 | + IRC-operator-only to set/clear, and only effective on a | |
| 44 | + registered channel since the flag is read back from | |
| 45 | + persistence at connect time). Mode views via `MODE #ch b` etc. | |
| 46 | + ban-list-style numerics (367/348/346). | |
| 40 | 47 | * ChanServ service with `REGISTER / DROP / INFO / OP / DEOP / HOP |
| 41 | 48 | / DEHOP / VOICE / DEVOICE / TRANSFER / HELP`. Service nicks are |
| 42 | 49 | configurable and reserved at the registry level. |
modified
config/config.exs
+26
−0
| 30 | 30 | config :oh_my_ex_oh_ircd, :services, |
| 31 | 31 | chanserv_nick: "ChanServ", |
| 32 | 32 | scrobbleserv_nick: "ScrobbleServ", |
| 33 | + imgserv_nick: "ImgServ", | |
| 33 | 34 | service_host: "services." |
| 34 | 35 | |
| 36 | +# ImgServ image hosting. Images are stored on the local filesystem under | |
| 37 | +# `upload_dir` and served from `/i/<filename>`. NOTE: in a multi-node | |
| 38 | +# cluster this assumes a shared volume (NFS / PVC) mounted at `upload_dir` | |
| 39 | +# on every node — otherwise an image uploaded on one node can't be served | |
| 40 | +# or GC'd from another. Override `upload_dir` per environment in runtime.exs. | |
| 41 | +config :oh_my_ex_oh_ircd, :imgserv, | |
| 42 | + # Where image files live. Resolved at runtime to the app's priv/uploads | |
| 43 | + # when unset; override with IMGSERV_UPLOAD_DIR (see runtime.exs). | |
| 44 | + upload_dir: nil, | |
| 45 | + # Max accepted upload size, bytes. | |
| 46 | + max_bytes: 10_485_760, | |
| 47 | + allowed_types: ~w(image/png image/jpeg image/gif image/webp), | |
| 48 | + # Default retention window; channels may override (NULL = use this). | |
| 49 | + retention_days: 30, | |
| 50 | + # Per-user budget (MB) of pinned, GC-exempt images. | |
| 51 | + pin_budget_mb: 100, | |
| 52 | + # How often the retention sweep runs. | |
| 53 | + gc_interval_ms: 3_600_000 | |
| 54 | + | |
| 35 | 55 | # WebIRC LiveView toggle. When enabled, logged-in users can open |
| 36 | 56 | # /webirc to participate in the cluster from a browser tab. |
| 37 | 57 | config :oh_my_ex_oh_ircd, :webirc, enabled: false |
| 115 | 135 | # Default `false` so direct TCP keeps working. Flip via |
| 116 | 136 | # `IRC_PROXY_PROTOCOL=true` at runtime. |
| 117 | 137 | proxy_protocol: false, |
| 138 | + # When set to a base domain, client IPs are cloaked as | |
| 139 | + # `<hash>.<cloak_host>` in prefixes/WHOIS; opers still see the real IP via | |
| 140 | + # WHOIS 338, and oper-assigned vhosts override the cloak. nil disables | |
| 141 | + # cloaking (real IP shown). Set via `IRC_CLOAK_HOST` at runtime; in prod | |
| 142 | + # it defaults to `PHX_HOST`. | |
| 143 | + cloak_host: nil, | |
| 118 | 144 | motd: [ |
| 119 | 145 | "Welcome to OhMyExOhIRC.", |
| 120 | 146 | "", |
modified
config/runtime.exs
+21
−0
| 40 | 40 | config :oh_my_ex_oh_ircd, OhMyExOhIrcd.IRC.Listener, network_name: net |
| 41 | 41 | end |
| 42 | 42 | |
| 43 | +# Where ImgServ stores hosted image files. In a multi-node deployment this | |
| 44 | +# should be a shared volume mounted at the same path on every node. | |
| 45 | +if dir = System.get_env("IMGSERV_UPLOAD_DIR") do | |
| 46 | + config :oh_my_ex_oh_ircd, :imgserv, upload_dir: dir | |
| 47 | +end | |
| 48 | + | |
| 43 | 49 | # Expect a PROXY v1/v2 header on every connection. Required when the |
| 44 | 50 | # IRC pod is behind a TCP proxy (Traefik IngressRouteTCP, HAProxy, AWS |
| 45 | 51 | # NLB...) — otherwise every client looks like the proxy's IP. |
| 48 | 54 | proxy_protocol: pp in ~w(1 true yes on) |
| 49 | 55 | end |
| 50 | 56 | |
| 57 | +# Host cloaking base domain. Client IPs are shown as `<hash>.<cloak_host>` | |
| 58 | +# to non-opers. Explicit `IRC_CLOAK_HOST` wins; otherwise prod defaults to | |
| 59 | +# `PHX_HOST` so it's on out of the box. Disable with | |
| 60 | +# `IRC_CLOAK_HOST=off` (or empty/false/none). | |
| 61 | +cloak_host = | |
| 62 | + case System.get_env("IRC_CLOAK_HOST") do | |
| 63 | + nil -> if config_env() == :prod, do: System.get_env("PHX_HOST") | |
| 64 | + v when v in ["", "off", "false", "none"] -> nil | |
| 65 | + v -> v | |
| 66 | + end | |
| 67 | + | |
| 68 | +if cloak_host do | |
| 69 | + config :oh_my_ex_oh_ircd, OhMyExOhIrcd.IRC.Listener, cloak_host: cloak_host | |
| 70 | +end | |
| 71 | + | |
| 51 | 72 | # Anonymous IRC access — default ON in prod releases (set |
| 52 | 73 | # IRC_ALLOW_ANONYMOUS=false to lock it down). Dev/test compile-time |
| 53 | 74 | # config wins outside prod. |
modified
config/test.exs
+4
−0
| 8 | 8 | # Tests that exercise the invite gate can `Application.put_env/3` per-test. |
| 9 | 9 | config :oh_my_ex_oh_ircd, :registration_mode, :open |
| 10 | 10 | |
| 11 | +# Don't run the ImgServ retention sweeper during tests — it would race the | |
| 12 | +# SQL sandbox. Tests call `OhMyExOhIrcd.Images.gc/0` directly instead. | |
| 13 | +config :oh_my_ex_oh_ircd, :imgserv, gc_enabled: false | |
| 14 | + | |
| 11 | 15 | # A static O-line so oper-command tests can `OPER testoper testpass` |
| 12 | 16 | # without provisioning a DB-flagged account. Hash is `testpass`. |
| 13 | 17 | config :oh_my_ex_oh_ircd, :opers, %{ |
modified
lib/oh_my_ex_oh_ircd/accounts.ex
+21
−0
| 296 | 296 | def oper?(%User{is_oper: true}), do: true |
| 297 | 297 | def oper?(_), do: false |
| 298 | 298 | |
| 299 | + @doc """ | |
| 300 | + Set (or clear, with `nil`) a user's persistent vhost. Applied to their | |
| 301 | + IRC prefix on the next connection; `OhMyExOhIrcd.IRC.Vhost.set_live/3` | |
| 302 | + also pushes it to any currently-connected session. Returns the updated | |
| 303 | + `%User{}` or `{:error, changeset}`. | |
| 304 | + """ | |
| 305 | + def set_vhost(%User{} = user, vhost) when is_binary(vhost) or is_nil(vhost) do | |
| 306 | + user |> User.vhost_changeset(vhost) |> Repo.update() | |
| 307 | + end | |
| 308 | + | |
| 309 | + @doc """ | |
| 310 | + Set a persistent vhost by account email (for offline users). Returns | |
| 311 | + `{:error, :not_found}` if no such account. | |
| 312 | + """ | |
| 313 | + def set_vhost_by_email(email, vhost) when is_binary(email) do | |
| 314 | + case get_user_by_email(email) do | |
| 315 | + %User{} = user -> set_vhost(user, vhost) | |
| 316 | + nil -> {:error, :not_found} | |
| 317 | + end | |
| 318 | + end | |
| 319 | + | |
| 299 | 320 | ## Invites (invite-only registration) |
| 300 | 321 | |
| 301 | 322 | alias OhMyExOhIrcd.Accounts.Invite |
modified
lib/oh_my_ex_oh_ircd/accounts/user.ex
+21
−0
Click to load diff…
modified
lib/oh_my_ex_oh_ircd/application.ex
+1
−0
Click to load diff…
modified
lib/oh_my_ex_oh_ircd/channels.ex
+41
−10
Click to load diff…
added
lib/oh_my_ex_oh_ircd/channels/channel_topic.ex
+33
−0
Click to load diff…
added
lib/oh_my_ex_oh_ircd/images.ex
+486
−0
Click to load diff…
added
lib/oh_my_ex_oh_ircd/images/channel_config.ex
+77
−0
Click to load diff…
added
lib/oh_my_ex_oh_ircd/images/garbage_collector.ex
+47
−0
Click to load diff…
added
lib/oh_my_ex_oh_ircd/images/image.ex
+50
−0
Click to load diff…
added
lib/oh_my_ex_oh_ircd/images/request_log.ex
+31
−0
Click to load diff…
added
lib/oh_my_ex_oh_ircd/images/token.ex
+78
−0
Click to load diff…
modified
lib/oh_my_ex_oh_ircd/irc/channel.ex
+53
−27
Click to load diff…
added
lib/oh_my_ex_oh_ircd/irc/cloak.ex
+53
−0
Click to load diff…
modified
lib/oh_my_ex_oh_ircd/irc/connection.ex
+192
−11
Click to load diff…
added
lib/oh_my_ex_oh_ircd/irc/img_serv.ex
+629
−0
Click to load diff…
modified
lib/oh_my_ex_oh_ircd/irc/listener.ex
+4
−1
Click to load diff…
modified
lib/oh_my_ex_oh_ircd/irc/services.ex
+8
−3
Click to load diff…
modified
lib/oh_my_ex_oh_ircd/irc/stats.ex
+3
−1
Click to load diff…
modified
lib/oh_my_ex_oh_ircd/irc/tls_listener.ex
+4
−1
Click to load diff…
added
lib/oh_my_ex_oh_ircd/irc/vhost.ex
+48
−0
Click to load diff…
added
lib/oh_my_ex_oh_ircd_web/controllers/image_controller.ex
+178
−0
Click to load diff…
modified
lib/oh_my_ex_oh_ircd_web/live/admin_live.ex
+119
−4
Click to load diff…
modified
lib/oh_my_ex_oh_ircd_web/live/user_live/channels.ex
+14
−6
Click to load diff…
added
lib/oh_my_ex_oh_ircd_web/live/user_live/img_serv.ex
+541
−0
Click to load diff…
added
lib/oh_my_ex_oh_ircd_web/plugs/image_auth.ex
+32
−0
Click to load diff…
modified
lib/oh_my_ex_oh_ircd_web/router.ex
+18
−0
Click to load diff…
added
priv/repo/migrations/20260610000000_create_image_channel_configs.exs
+26
−0
Click to load diff…
added
priv/repo/migrations/20260611000000_create_image_tokens.exs
+24
−0
Click to load diff…
added
priv/repo/migrations/20260612000000_create_images.exs
+29
−0
Click to load diff…
added
priv/repo/migrations/20260613000000_create_image_request_logs.exs
+16
−0
Click to load diff…
added
priv/repo/migrations/20260627000000_add_vhost_to_users.exs
+12
−0
Click to load diff…
added
priv/repo/migrations/20260627010000_create_channel_topics.exs
+22
−0
Click to load diff…
added
test/oh_my_ex_oh_ircd/images_test.exs
+152
−0
Click to load diff…
added
test/oh_my_ex_oh_ircd/irc/auto_join_test.exs
+117
−0
Click to load diff…
modified
test/oh_my_ex_oh_ircd/irc/channel_modes_test.exs
+2
−2
Click to load diff…
modified
test/oh_my_ex_oh_ircd/irc/channel_persistence_test.exs
+41
−0
Click to load diff…
added
test/oh_my_ex_oh_ircd/irc/cloak_test.exs
+34
−0
Click to load diff…
added
test/oh_my_ex_oh_ircd/irc/img_serv_test.exs
+134
−0
Click to load diff…
added
test/oh_my_ex_oh_ircd/irc/vhost_test.exs
+37
−0
Click to load diff…
added
test/oh_my_ex_oh_ircd_web/controllers/image_controller_test.exs
+128
−0
Click to load diff…
added
test/oh_my_ex_oh_ircd_web/live/user_live/img_serv_test.exs
+112
−0
Click to load diff…
Parents: ca5f84e