summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-12-26 10:57:03 -0500
committerBen Sima <ben@bensima.com>2025-12-26 10:57:03 -0500
commit0e7cc8d0970c24cf24c0e3be221427981a799efb (patch)
tree43f46aa1fb4dbe076bbdcddceef871b695d08b0a
parent7c63dca29cfe6c2e402d917efedfb426fb3b8fe6 (diff)
fix UTF-8 encoding in deployed services
- Systemd.hs: add LANG and LC_ALL defaults (en_US.utf8) to all generated unit files to ensure proper UTF-8 handling - Systemd.hs: add generateUnitWithLocale that reads LOCALE_ARCHIVE from the deployer's environment and injects it into generated units - Telegram.hs: add safePutText wrapper that catches encoding errors in logging to prevent them from killing message sends The root cause was NixOS systemd services not inheriting locale settings from the system, causing emoji characters to fail encoding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
-rw-r--r--Omni/Agent/Telegram.hs11
-rw-r--r--Omni/Deploy/Systemd.hs23
2 files changed, 30 insertions, 4 deletions
diff --git a/Omni/Agent/Telegram.hs b/Omni/Agent/Telegram.hs
index 2bf7aed..57420a4 100644
--- a/Omni/Agent/Telegram.hs
+++ b/Omni/Agent/Telegram.hs
@@ -116,6 +116,13 @@ import qualified Omni.Test as Test
import System.Environment (lookupEnv)
import Text.Printf (printf)
+safePutText :: Text -> IO ()
+safePutText msg = do
+ result <- try @SomeException (putText msg)
+ case result of
+ Left _ -> putText "[log encoding error - message contained unrepresentable characters]"
+ Right () -> pure ()
+
defaultTelegramConfig :: Text -> [Int] -> Maybe Text -> Text -> Types.TelegramConfig
defaultTelegramConfig = Types.defaultTelegramConfig
@@ -623,7 +630,7 @@ runTelegramBot tgConfig provider = do
handleBotAddedToGroup tgConfig addedEvent
Nothing -> case Types.parseUpdate rawUpdate of
Just msg -> do
- putText <| "Received message from " <> Types.tmUserFirstName msg <> " in chat " <> tshow (Types.tmChatId msg) <> " (type: " <> tshow (Types.tmChatType msg) <> "): " <> Text.take 50 (Types.tmText msg)
+ safePutText <| "Received message from " <> Types.tmUserFirstName msg <> " in chat " <> tshow (Types.tmChatId msg) <> " (type: " <> tshow (Types.tmChatType msg) <> "): " <> Text.take 50 (Types.tmText msg)
atomically (writeTVar offsetVar (Types.tmUpdateId msg + 1))
IncomingQueue.enqueueIncoming incomingQueues IncomingQueue.defaultBatchWindowSeconds msg
Nothing -> do
@@ -1329,7 +1336,7 @@ processEngagedMessage tgConfig provider engineCfg msg uid userName chatId userMe
let baseResponse = Engine.resultFinalMessage agentResult
response = baseResponse <> traceLink
threadId = Types.tmThreadId msg
- putText <| "Response text: " <> Text.take 200 response
+ safePutText <| "Response text: " <> Text.take 200 response
if isGroup
then void <| Memory.saveGroupMessage chatId threadId Memory.AssistantRole "Ava" response
diff --git a/Omni/Deploy/Systemd.hs b/Omni/Deploy/Systemd.hs
index 8c6d416..99d4820 100644
--- a/Omni/Deploy/Systemd.hs
+++ b/Omni/Deploy/Systemd.hs
@@ -8,6 +8,7 @@
-- : dep directory
module Omni.Deploy.Systemd
( generateUnit,
+ generateUnitWithLocale,
writeUnit,
createSymlink,
reloadAndRestart,
@@ -27,12 +28,24 @@ import qualified Data.Text.IO as Text.IO
import Omni.Deploy.Manifest (Artifact (..), Exec (..), Hardening (..), Service (..), Systemd (..))
import qualified Omni.Test as Test
import qualified System.Directory as Dir
+import System.Environment (lookupEnv)
import System.FilePath ((</>))
import qualified System.Process as Process
servicesDir :: FilePath
servicesDir = "/var/lib/deployer/services"
+-- | Generate unit with locale settings from the current environment.
+-- This reads LOCALE_ARCHIVE at generation time and injects it into the unit.
+generateUnitWithLocale :: Service -> IO Text
+generateUnitWithLocale svc = do
+ maybeLocaleArchive <- lookupEnv "LOCALE_ARCHIVE"
+ let localeEnv = case maybeLocaleArchive of
+ Just path -> Map.singleton "LOCALE_ARCHIVE" (Text.pack path)
+ Nothing -> mempty
+ svcWithLocale = svc {serviceEnv = Map.union (serviceEnv svc) localeEnv}
+ pure (generateUnit svcWithLocale)
+
generateUnit :: Service -> Text
generateUnit Service {..} =
Text.unlines <| unitSection ++ serviceSection ++ hardeningSection ++ installSection
@@ -69,9 +82,15 @@ generateUnit Service {..} =
Just dir -> ["WorkingDirectory=" <> dir]
envLines =
- Map.toList serviceEnv
+ Map.toList envWithLocale
|> map (\(k, v) -> "Environment=\"" <> k <> "=" <> v <> "\"")
+ -- Add locale settings for NixOS if not already set
+ envWithLocale =
+ Map.insertWith (\_ old -> old) "LANG" "en_US.utf8"
+ <| Map.insertWith (\_ old -> old) "LC_ALL" "en_US.utf8"
+ <| serviceEnv
+
envFileLine = case serviceEnvFile of
Nothing -> []
Just path -> ["EnvironmentFile=" <> path]
@@ -104,7 +123,7 @@ writeUnit :: FilePath -> Service -> IO FilePath
writeUnit baseDir svc = do
Dir.createDirectoryIfMissing True baseDir
let path = baseDir </> Text.unpack (serviceName svc) <> ".service"
- content = generateUnit svc
+ content <- generateUnitWithLocale svc
Text.IO.writeFile path content
pure path