Skip to main content

commonware_deployer/aws/
services.rs

1//! Service configuration for Prometheus, Loki, Grafana, Promtail, and a caller-provided binary
2
3use crate::aws::{
4    s3::{DEPLOYMENTS_PREFIX, TOOLS_BINARIES_PREFIX, TOOLS_CONFIGS_PREFIX, WGET},
5    Architecture,
6};
7
8/// Deployer version used to namespace static configs in S3
9const DEPLOYER_VERSION: &str = env!("CARGO_PKG_VERSION");
10
11/// Version of Prometheus to download and install
12pub const PROMETHEUS_VERSION: &str = "3.2.0";
13
14/// Version of Promtail to download and install
15pub const PROMTAIL_VERSION: &str = "3.4.2";
16
17/// Version of Node Exporter to download and install
18pub const NODE_EXPORTER_VERSION: &str = "1.9.0";
19
20/// Version of Loki to download and install
21pub const LOKI_VERSION: &str = "3.4.2";
22
23/// Version of Tempo to download and install
24pub const TEMPO_VERSION: &str = "2.7.1";
25
26/// Version of Pyroscope to download and install
27pub const PYROSCOPE_VERSION: &str = "1.12.0";
28
29/// Version of Grafana to download and install
30pub const GRAFANA_VERSION: &str = "11.5.2";
31
32/// Version of Samply to download and install
33pub const SAMPLY_VERSION: &str = "0.13.1";
34
35/// Version of libjemalloc2 package for Ubuntu 24.04
36pub const LIBJEMALLOC2_VERSION: &str = "5.3.0-2build1";
37
38/// Version of logrotate package for Ubuntu 24.04
39pub const LOGROTATE_VERSION: &str = "3.21.0-2build1";
40
41/// Version of libfontconfig1 package for Ubuntu 24.04
42pub const LIBFONTCONFIG1_VERSION: &str = "2.15.0-1.1ubuntu2";
43
44/// Version of fontconfig-config package for Ubuntu 24.04
45pub const FONTCONFIG_CONFIG_VERSION: &str = "2.15.0-1.1ubuntu2";
46
47/// Version of unzip package for Ubuntu 24.04
48pub const UNZIP_VERSION: &str = "6.0-28ubuntu4";
49
50/// Version of adduser package for Ubuntu 24.04 (arch-independent)
51pub const ADDUSER_VERSION: &str = "3.137ubuntu1";
52
53/// Version of fonts-dejavu-mono package for Ubuntu 24.04 (arch-independent)
54pub const FONTS_DEJAVU_MONO_VERSION: &str = "2.37-8";
55
56/// Version of fonts-dejavu-core package for Ubuntu 24.04 (arch-independent)
57pub const FONTS_DEJAVU_CORE_VERSION: &str = "2.37-8";
58
59/// Version of musl package for Ubuntu 24.04
60pub const MUSL_VERSION: &str = "1.2.4-2";
61
62/// Ubuntu package archive base URL for arm64
63const UBUNTU_ARCHIVE_ARM64: &str = "http://ports.ubuntu.com/ubuntu-ports/pool";
64
65/// Ubuntu package archive base URL for x86_64
66const UBUNTU_ARCHIVE_X86_64: &str = "http://archive.ubuntu.com/ubuntu/pool";
67
68// S3 key functions for tool binaries
69//
70// Convention: {TOOLS_BINARIES_PREFIX}/{tool}/{version}/{platform}/{filename}
71//
72// The filename matches the upstream download URL exactly. The version is placed
73// in the path (not embedded in the filename) to ensure consistent cache organization
74// across all tools, since some upstream releases include version in the filename
75// (e.g., prometheus-3.2.0.linux-arm64.tar.gz) while others do not
76// (e.g., loki-linux-arm64.zip).
77
78pub(crate) fn prometheus_bin_s3_key(version: &str, architecture: Architecture) -> String {
79    format!(
80        "{TOOLS_BINARIES_PREFIX}/prometheus/{version}/linux-{arch}/prometheus-{version}.linux-{arch}.tar.gz",
81        arch = architecture.as_str()
82    )
83}
84
85pub(crate) fn grafana_bin_s3_key(version: &str, architecture: Architecture) -> String {
86    format!(
87        "{TOOLS_BINARIES_PREFIX}/grafana/{version}/linux-{arch}/grafana_{version}_{arch}.deb",
88        arch = architecture.as_str()
89    )
90}
91
92pub(crate) fn loki_bin_s3_key(version: &str, architecture: Architecture) -> String {
93    format!(
94        "{TOOLS_BINARIES_PREFIX}/loki/{version}/linux-{arch}/loki-linux-{arch}.zip",
95        arch = architecture.as_str()
96    )
97}
98
99pub(crate) fn pyroscope_bin_s3_key(version: &str, architecture: Architecture) -> String {
100    format!(
101        "{TOOLS_BINARIES_PREFIX}/pyroscope/{version}/linux-{arch}/pyroscope_{version}_linux_{arch}.tar.gz",
102        arch = architecture.as_str()
103    )
104}
105
106pub(crate) fn tempo_bin_s3_key(version: &str, architecture: Architecture) -> String {
107    format!(
108        "{TOOLS_BINARIES_PREFIX}/tempo/{version}/linux-{arch}/tempo_{version}_linux_{arch}.tar.gz",
109        arch = architecture.as_str()
110    )
111}
112
113pub(crate) fn node_exporter_bin_s3_key(version: &str, architecture: Architecture) -> String {
114    format!(
115        "{TOOLS_BINARIES_PREFIX}/node-exporter/{version}/linux-{arch}/node_exporter-{version}.linux-{arch}.tar.gz",
116        arch = architecture.as_str()
117    )
118}
119
120pub(crate) fn promtail_bin_s3_key(version: &str, architecture: Architecture) -> String {
121    format!(
122        "{TOOLS_BINARIES_PREFIX}/promtail/{version}/linux-{arch}/promtail-linux-{arch}.zip",
123        arch = architecture.as_str()
124    )
125}
126
127pub(crate) fn samply_bin_s3_key(version: &str, architecture: Architecture) -> String {
128    let arch = match architecture {
129        Architecture::Arm64 => "aarch64",
130        Architecture::X86_64 => "x86_64",
131    };
132    format!("{TOOLS_BINARIES_PREFIX}/samply/{version}/linux-{arch}/samply-{arch}-unknown-linux-gnu.tar.xz")
133}
134
135pub(crate) fn libjemalloc_bin_s3_key(version: &str, architecture: Architecture) -> String {
136    format!(
137        "{TOOLS_BINARIES_PREFIX}/libjemalloc2/{version}/linux-{arch}/libjemalloc2_{version}_{arch}.deb",
138        arch = architecture.as_str()
139    )
140}
141
142pub(crate) fn logrotate_bin_s3_key(version: &str, architecture: Architecture) -> String {
143    format!(
144        "{TOOLS_BINARIES_PREFIX}/logrotate/{version}/linux-{arch}/logrotate_{version}_{arch}.deb",
145        arch = architecture.as_str()
146    )
147}
148
149pub(crate) fn libfontconfig_bin_s3_key(version: &str, architecture: Architecture) -> String {
150    format!(
151        "{TOOLS_BINARIES_PREFIX}/libfontconfig1/{version}/linux-{arch}/libfontconfig1_{version}_{arch}.deb",
152        arch = architecture.as_str()
153    )
154}
155
156/// S3 key for fontconfig-config package
157pub(crate) fn fontconfig_config_bin_s3_key(version: &str, architecture: Architecture) -> String {
158    format!(
159        "{TOOLS_BINARIES_PREFIX}/fontconfig-config/{version}/linux-{arch}/fontconfig-config_{version}_{arch}.deb",
160        arch = architecture.as_str()
161    )
162}
163
164pub(crate) fn unzip_bin_s3_key(version: &str, architecture: Architecture) -> String {
165    format!(
166        "{TOOLS_BINARIES_PREFIX}/unzip/{version}/linux-{arch}/unzip_{version}_{arch}.deb",
167        arch = architecture.as_str()
168    )
169}
170
171/// S3 key for adduser (architecture-independent package)
172pub(crate) fn adduser_bin_s3_key(version: &str) -> String {
173    format!("{TOOLS_BINARIES_PREFIX}/adduser/{version}/adduser_{version}_all.deb")
174}
175
176/// S3 key for fonts-dejavu-mono (architecture-independent package)
177pub(crate) fn fonts_dejavu_mono_bin_s3_key(version: &str) -> String {
178    format!(
179        "{TOOLS_BINARIES_PREFIX}/fonts-dejavu-mono/{version}/fonts-dejavu-mono_{version}_all.deb"
180    )
181}
182
183/// S3 key for fonts-dejavu-core (architecture-independent package)
184pub(crate) fn fonts_dejavu_core_bin_s3_key(version: &str) -> String {
185    format!(
186        "{TOOLS_BINARIES_PREFIX}/fonts-dejavu-core/{version}/fonts-dejavu-core_{version}_all.deb"
187    )
188}
189
190/// S3 key for musl package
191pub(crate) fn musl_bin_s3_key(version: &str, architecture: Architecture) -> String {
192    format!(
193        "{TOOLS_BINARIES_PREFIX}/musl/{version}/linux-{arch}/musl_{version}_{arch}.deb",
194        arch = architecture.as_str()
195    )
196}
197
198// S3 key functions for component configs and services (include deployer version for cache invalidation)
199//
200// Convention: {TOOLS_CONFIGS_PREFIX}/{deployer_version}/{component}/{file}
201
202pub fn prometheus_service_s3_key() -> String {
203    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/prometheus/service")
204}
205
206pub fn grafana_datasources_s3_key() -> String {
207    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/grafana/datasources.yml")
208}
209
210pub fn grafana_dashboards_s3_key() -> String {
211    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/grafana/all.yml")
212}
213
214pub fn loki_config_s3_key() -> String {
215    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/loki/config.yml")
216}
217
218pub fn loki_service_s3_key() -> String {
219    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/loki/service")
220}
221
222pub fn pyroscope_config_s3_key() -> String {
223    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/pyroscope/config.yml")
224}
225
226pub fn pyroscope_service_s3_key() -> String {
227    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/pyroscope/service")
228}
229
230pub fn tempo_config_s3_key() -> String {
231    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/tempo/config.yml")
232}
233
234pub fn tempo_service_s3_key() -> String {
235    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/tempo/service")
236}
237
238pub fn node_exporter_service_s3_key() -> String {
239    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/node-exporter/service")
240}
241
242pub fn promtail_service_s3_key() -> String {
243    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/promtail/service")
244}
245
246// S3 key functions for pyroscope agent (lives with pyroscope component)
247
248pub fn pyroscope_agent_service_s3_key() -> String {
249    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/pyroscope/agent.service")
250}
251
252pub fn pyroscope_agent_timer_s3_key() -> String {
253    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/pyroscope/agent.timer")
254}
255
256// S3 key functions for system configs
257
258pub fn logrotate_config_s3_key() -> String {
259    format!("{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/system/logrotate.conf")
260}
261
262// S3 key functions for binary instance configs
263
264pub(crate) fn binary_service_s3_key_for_arch(architecture: Architecture) -> String {
265    format!(
266        "{TOOLS_CONFIGS_PREFIX}/{DEPLOYER_VERSION}/binary/service-{arch}",
267        arch = architecture.as_str()
268    )
269}
270
271/// Returns the S3 key for an instance's binary by digest (deduplicated within deployment)
272pub fn binary_s3_key(tag: &str, digest: &str) -> String {
273    format!("{DEPLOYMENTS_PREFIX}/{tag}/binaries/{digest}")
274}
275
276/// Returns the S3 key for an instance's config by digest (deduplicated within deployment)
277pub fn config_s3_key(tag: &str, digest: &str) -> String {
278    format!("{DEPLOYMENTS_PREFIX}/{tag}/configs/{digest}")
279}
280
281/// Returns the S3 key for hosts.yaml by digest (deduplicated within deployment)
282pub fn hosts_s3_key(tag: &str, digest: &str) -> String {
283    format!("{DEPLOYMENTS_PREFIX}/{tag}/hosts/{digest}")
284}
285
286/// Returns the S3 key for promtail config by digest (deduplicated within deployment)
287pub fn promtail_s3_key(tag: &str, digest: &str) -> String {
288    format!("{DEPLOYMENTS_PREFIX}/{tag}/promtail/{digest}")
289}
290
291/// Returns the S3 key for pyroscope agent script by digest (deduplicated within deployment)
292pub fn pyroscope_s3_key(tag: &str, digest: &str) -> String {
293    format!("{DEPLOYMENTS_PREFIX}/{tag}/pyroscope/{digest}")
294}
295
296/// Returns the S3 key for monitoring config by digest (deduplicated within deployment)
297pub fn monitoring_s3_key(tag: &str, digest: &str) -> String {
298    format!("{DEPLOYMENTS_PREFIX}/{tag}/monitoring/{digest}")
299}
300
301/// Returns the download URL for Prometheus from GitHub
302pub(crate) fn prometheus_download_url(version: &str, architecture: Architecture) -> String {
303    format!(
304        "https://github.com/prometheus/prometheus/releases/download/v{version}/prometheus-{version}.linux-{arch}.tar.gz",
305        arch = architecture.as_str()
306    )
307}
308
309/// Returns the download URL for Grafana
310pub(crate) fn grafana_download_url(version: &str, architecture: Architecture) -> String {
311    format!(
312        "https://dl.grafana.com/oss/release/grafana_{version}_{arch}.deb",
313        arch = architecture.as_str()
314    )
315}
316
317/// Returns the download URL for Loki from GitHub
318pub(crate) fn loki_download_url(version: &str, architecture: Architecture) -> String {
319    format!(
320        "https://github.com/grafana/loki/releases/download/v{version}/loki-linux-{arch}.zip",
321        arch = architecture.as_str()
322    )
323}
324
325/// Returns the download URL for Pyroscope from GitHub
326pub(crate) fn pyroscope_download_url(version: &str, architecture: Architecture) -> String {
327    format!(
328        "https://github.com/grafana/pyroscope/releases/download/v{version}/pyroscope_{version}_linux_{arch}.tar.gz",
329        arch = architecture.as_str()
330    )
331}
332
333/// Returns the download URL for Tempo from GitHub
334pub(crate) fn tempo_download_url(version: &str, architecture: Architecture) -> String {
335    format!(
336        "https://github.com/grafana/tempo/releases/download/v{version}/tempo_{version}_linux_{arch}.tar.gz",
337        arch = architecture.as_str()
338    )
339}
340
341/// Returns the download URL for Node Exporter from GitHub
342pub(crate) fn node_exporter_download_url(version: &str, architecture: Architecture) -> String {
343    format!(
344        "https://github.com/prometheus/node_exporter/releases/download/v{version}/node_exporter-{version}.linux-{arch}.tar.gz",
345        arch = architecture.as_str()
346    )
347}
348
349/// Returns the download URL for Promtail from GitHub
350pub(crate) fn promtail_download_url(version: &str, architecture: Architecture) -> String {
351    format!(
352        "https://github.com/grafana/loki/releases/download/v{version}/promtail-linux-{arch}.zip",
353        arch = architecture.as_str()
354    )
355}
356
357/// Returns the download URL for Samply from GitHub
358pub(crate) fn samply_download_url(version: &str, architecture: Architecture) -> String {
359    let arch = match architecture {
360        Architecture::Arm64 => "aarch64",
361        Architecture::X86_64 => "x86_64",
362    };
363    format!(
364        "https://github.com/mstange/samply/releases/download/samply-v{version}/samply-{arch}-unknown-linux-gnu.tar.xz"
365    )
366}
367
368/// Returns the download URL for libjemalloc2 from Ubuntu archive
369pub(crate) fn libjemalloc_download_url(version: &str, architecture: Architecture) -> String {
370    let base = match architecture {
371        Architecture::Arm64 => UBUNTU_ARCHIVE_ARM64,
372        Architecture::X86_64 => UBUNTU_ARCHIVE_X86_64,
373    };
374    format!(
375        "{base}/universe/j/jemalloc/libjemalloc2_{version}_{arch}.deb",
376        arch = architecture.as_str()
377    )
378}
379
380/// Returns the download URL for logrotate from Ubuntu archive
381pub(crate) fn logrotate_download_url(version: &str, architecture: Architecture) -> String {
382    let base = match architecture {
383        Architecture::Arm64 => UBUNTU_ARCHIVE_ARM64,
384        Architecture::X86_64 => UBUNTU_ARCHIVE_X86_64,
385    };
386    format!(
387        "{base}/main/l/logrotate/logrotate_{version}_{arch}.deb",
388        arch = architecture.as_str()
389    )
390}
391
392/// Returns the download URL for libfontconfig1 from Ubuntu archive
393pub(crate) fn libfontconfig_download_url(version: &str, architecture: Architecture) -> String {
394    let base = match architecture {
395        Architecture::Arm64 => UBUNTU_ARCHIVE_ARM64,
396        Architecture::X86_64 => UBUNTU_ARCHIVE_X86_64,
397    };
398    format!(
399        "{base}/main/f/fontconfig/libfontconfig1_{version}_{arch}.deb",
400        arch = architecture.as_str()
401    )
402}
403
404/// Returns the download URL for fontconfig-config from Ubuntu archive
405pub(crate) fn fontconfig_config_download_url(version: &str, architecture: Architecture) -> String {
406    let base = match architecture {
407        Architecture::Arm64 => UBUNTU_ARCHIVE_ARM64,
408        Architecture::X86_64 => UBUNTU_ARCHIVE_X86_64,
409    };
410    format!(
411        "{base}/main/f/fontconfig/fontconfig-config_{version}_{arch}.deb",
412        arch = architecture.as_str()
413    )
414}
415
416/// Returns the download URL for unzip from Ubuntu archive
417pub(crate) fn unzip_download_url(version: &str, architecture: Architecture) -> String {
418    let base = match architecture {
419        Architecture::Arm64 => UBUNTU_ARCHIVE_ARM64,
420        Architecture::X86_64 => UBUNTU_ARCHIVE_X86_64,
421    };
422    format!(
423        "{base}/main/u/unzip/unzip_{version}_{arch}.deb",
424        arch = architecture.as_str()
425    )
426}
427
428/// Returns the download URL for adduser from Ubuntu archive (arch-independent)
429pub(crate) fn adduser_download_url(version: &str) -> String {
430    format!("{UBUNTU_ARCHIVE_X86_64}/main/a/adduser/adduser_{version}_all.deb")
431}
432
433/// Returns the download URL for fonts-dejavu-mono from Ubuntu archive (arch-independent)
434pub(crate) fn fonts_dejavu_mono_download_url(version: &str) -> String {
435    format!("{UBUNTU_ARCHIVE_X86_64}/main/f/fonts-dejavu/fonts-dejavu-mono_{version}_all.deb")
436}
437
438/// Returns the download URL for fonts-dejavu-core from Ubuntu archive (arch-independent)
439pub(crate) fn fonts_dejavu_core_download_url(version: &str) -> String {
440    format!("{UBUNTU_ARCHIVE_X86_64}/main/f/fonts-dejavu/fonts-dejavu-core_{version}_all.deb")
441}
442
443pub(crate) fn musl_download_url(version: &str, architecture: Architecture) -> String {
444    let base = match architecture {
445        Architecture::Arm64 => UBUNTU_ARCHIVE_ARM64,
446        Architecture::X86_64 => UBUNTU_ARCHIVE_X86_64,
447    };
448    format!(
449        "{base}/universe/m/musl/musl_{version}_{arch}.deb",
450        arch = architecture.as_str()
451    )
452}
453
454/// YAML configuration for Grafana datasources (Prometheus, Loki, Tempo, and Pyroscope)
455pub const DATASOURCES_YML: &str = r#"
456apiVersion: 1
457datasources:
458  - name: Prometheus
459    type: prometheus
460    url: http://localhost:9090
461    access: proxy
462    isDefault: true
463  - name: Loki
464    type: loki
465    url: http://localhost:3100
466    access: proxy
467    isDefault: false
468  - name: Tempo
469    type: tempo
470    url: http://localhost:3200
471    access: proxy
472    isDefault: false
473  - name: Pyroscope
474    type: grafana-pyroscope-datasource
475    url: http://localhost:4040
476    access: proxy
477    isDefault: false
478"#;
479
480/// YAML configuration for Grafana dashboard providers
481pub const ALL_YML: &str = r#"
482apiVersion: 1
483providers:
484  - name: 'default'
485    orgId: 1
486    folder: ''
487    type: file
488    options:
489      path: /var/lib/grafana/dashboards
490"#;
491
492/// Systemd service file content for Prometheus
493pub const PROMETHEUS_SERVICE: &str = r#"[Unit]
494Description=Prometheus Monitoring Service
495After=network.target
496
497[Service]
498ExecStart=/opt/prometheus/prometheus --config.file=/opt/prometheus/prometheus.yml --storage.tsdb.path=/opt/prometheus/data
499TimeoutStopSec=60
500Restart=always
501User=ubuntu
502LimitNOFILE=infinity
503
504[Install]
505WantedBy=multi-user.target
506"#;
507
508/// Systemd service file content for Promtail
509pub const PROMTAIL_SERVICE: &str = r#"[Unit]
510Description=Promtail Log Forwarder
511After=network.target
512
513[Service]
514ExecStart=/opt/promtail/promtail -config.file=/etc/promtail/promtail.yml
515TimeoutStopSec=60
516Restart=always
517User=ubuntu
518LimitNOFILE=infinity
519
520[Install]
521WantedBy=multi-user.target
522"#;
523
524/// Systemd service file content for Loki
525pub const LOKI_SERVICE: &str = r#"[Unit]
526Description=Loki Log Aggregation Service
527After=network.target
528
529[Service]
530ExecStart=/opt/loki/loki -config.file=/etc/loki/loki.yml
531TimeoutStopSec=60
532Restart=always
533User=ubuntu
534LimitNOFILE=infinity
535
536[Install]
537WantedBy=multi-user.target
538"#;
539
540/// YAML configuration for Loki
541pub const LOKI_CONFIG: &str = r#"
542auth_enabled: false
543target: all
544server:
545  http_listen_port: 3100
546  grpc_listen_port: 9095
547common:
548  ring:
549    kvstore:
550      store: inmemory
551  replication_factor: 1
552  instance_addr: 127.0.0.1
553schema_config:
554  configs:
555    - from: 2020-10-24
556      store: tsdb
557      object_store: filesystem
558      schema: v13
559      index:
560        prefix: index_
561        period: 24h
562storage_config:
563  tsdb_shipper:
564    active_index_directory: /loki/index
565    cache_location: /loki/index_cache
566  filesystem:
567    directory: /loki/chunks
568table_manager:
569  retention_deletes_enabled: true
570  retention_period: 12h
571compactor:
572  working_directory: /loki/compactor
573ingester:
574  wal:
575    dir: /loki/wal
576"#;
577
578/// YAML configuration for Pyroscope
579pub const PYROSCOPE_CONFIG: &str = r#"
580target: all
581server:
582  http_listen_port: 4040
583  grpc_listen_port: 0
584pyroscopedb:
585  data_path: /var/lib/pyroscope
586self_profiling:
587  disable_push: true
588"#;
589
590/// Systemd service file content for Pyroscope
591pub const PYROSCOPE_SERVICE: &str = r#"[Unit]
592Description=Pyroscope Profiling Service
593After=network.target
594
595[Service]
596ExecStart=/opt/pyroscope/pyroscope --config.file=/etc/pyroscope/pyroscope.yml
597TimeoutStopSec=60
598Restart=always
599User=ubuntu
600LimitNOFILE=infinity
601
602[Install]
603WantedBy=multi-user.target
604"#;
605
606/// Systemd service file content for Tempo
607pub const TEMPO_SERVICE: &str = r#"[Unit]
608Description=Tempo Tracing Service
609After=network.target
610[Service]
611ExecStart=/opt/tempo/tempo -config.file=/etc/tempo/tempo.yml
612TimeoutStopSec=60
613Restart=always
614User=ubuntu
615LimitNOFILE=infinity
616[Install]
617WantedBy=multi-user.target
618"#;
619
620/// YAML configuration for Tempo
621pub const TEMPO_CONFIG: &str = r#"
622server:
623  grpc_listen_port: 9096
624  http_listen_port: 3200
625distributor:
626  receivers:
627    otlp:
628      protocols:
629        http:
630          endpoint: "0.0.0.0:4318"
631storage:
632  trace:
633    backend: local
634    local:
635      path: /tempo/traces
636    wal:
637      path: /tempo/wal
638ingester:
639  max_block_duration: 1h
640compactor:
641  compaction:
642    block_retention: 1h
643    compaction_cycle: 1h
644"#;
645
646/// URLs for monitoring service installation
647pub struct MonitoringUrls {
648    pub prometheus_bin: String,
649    pub grafana_bin: String,
650    pub loki_bin: String,
651    pub pyroscope_bin: String,
652    pub tempo_bin: String,
653    pub node_exporter_bin: String,
654    pub fonts_dejavu_mono_deb: String,
655    pub fonts_dejavu_core_deb: String,
656    pub fontconfig_config_deb: String,
657    pub libfontconfig_deb: String,
658    pub unzip_deb: String,
659    pub adduser_deb: String,
660    pub musl_deb: String,
661    pub prometheus_config: String,
662    pub datasources_yml: String,
663    pub all_yml: String,
664    pub dashboard: String,
665    pub loki_yml: String,
666    pub pyroscope_yml: String,
667    pub tempo_yml: String,
668    pub prometheus_service: String,
669    pub loki_service: String,
670    pub pyroscope_service: String,
671    pub tempo_service: String,
672    pub node_exporter_service: String,
673}
674
675/// Phase 1: Download files from S3 on monitoring instance
676pub(crate) fn install_monitoring_download_cmd(urls: &MonitoringUrls) -> String {
677    format!(
678        r#"
679# Clean up any previous download artifacts (allows retries to re-download fresh copies)
680rm -f /home/ubuntu/prometheus.tar.gz /home/ubuntu/loki.zip /home/ubuntu/pyroscope.tar.gz \
681      /home/ubuntu/tempo.tar.gz /home/ubuntu/node_exporter.tar.gz /home/ubuntu/fonts-dejavu-mono.deb \
682      /home/ubuntu/fonts-dejavu-core.deb /home/ubuntu/fontconfig-config.deb /home/ubuntu/libfontconfig1.deb \
683      /home/ubuntu/unzip.deb /home/ubuntu/adduser.deb /home/ubuntu/musl.deb
684rm -rf /home/ubuntu/prometheus-* /home/ubuntu/loki-linux-* /home/ubuntu/pyroscope \
685       /home/ubuntu/tempo /home/ubuntu/node_exporter-*
686
687# Unmask services in case previous attempt left them masked
688sudo systemctl unmask prometheus loki pyroscope tempo node_exporter grafana-server 2>/dev/null || true
689
690# Download all files from S3 concurrently via pre-signed URLs
691{WGET} -O /home/ubuntu/prometheus.tar.gz '{}' &
692{WGET} -O /home/ubuntu/grafana.deb '{}' &
693{WGET} -O /home/ubuntu/loki.zip '{}' &
694{WGET} -O /home/ubuntu/pyroscope.tar.gz '{}' &
695{WGET} -O /home/ubuntu/tempo.tar.gz '{}' &
696{WGET} -O /home/ubuntu/node_exporter.tar.gz '{}' &
697{WGET} -O /home/ubuntu/fonts-dejavu-mono.deb '{}' &
698{WGET} -O /home/ubuntu/fonts-dejavu-core.deb '{}' &
699{WGET} -O /home/ubuntu/fontconfig-config.deb '{}' &
700{WGET} -O /home/ubuntu/libfontconfig1.deb '{}' &
701{WGET} -O /home/ubuntu/unzip.deb '{}' &
702{WGET} -O /home/ubuntu/adduser.deb '{}' &
703{WGET} -O /home/ubuntu/musl.deb '{}' &
704{WGET} -O /home/ubuntu/prometheus.yml '{}' &
705{WGET} -O /home/ubuntu/datasources.yml '{}' &
706{WGET} -O /home/ubuntu/all.yml '{}' &
707{WGET} -O /home/ubuntu/dashboard.json '{}' &
708{WGET} -O /home/ubuntu/loki.yml '{}' &
709{WGET} -O /home/ubuntu/pyroscope.yml '{}' &
710{WGET} -O /home/ubuntu/tempo.yml '{}' &
711{WGET} -O /home/ubuntu/prometheus.service '{}' &
712{WGET} -O /home/ubuntu/loki.service '{}' &
713{WGET} -O /home/ubuntu/pyroscope.service '{}' &
714{WGET} -O /home/ubuntu/tempo.service '{}' &
715{WGET} -O /home/ubuntu/node_exporter.service '{}' &
716wait
717
718# Verify all downloads succeeded
719for f in prometheus.tar.gz grafana.deb loki.zip pyroscope.tar.gz tempo.tar.gz node_exporter.tar.gz \
720         fonts-dejavu-mono.deb fonts-dejavu-core.deb fontconfig-config.deb libfontconfig1.deb unzip.deb adduser.deb musl.deb prometheus.yml datasources.yml all.yml dashboard.json \
721         loki.yml pyroscope.yml tempo.yml prometheus.service loki.service pyroscope.service \
722         tempo.service node_exporter.service; do
723    if [ ! -f "/home/ubuntu/$f" ]; then
724        echo "ERROR: Failed to download $f" >&2
725        exit 1
726    fi
727done
728"#,
729        urls.prometheus_bin,
730        urls.grafana_bin,
731        urls.loki_bin,
732        urls.pyroscope_bin,
733        urls.tempo_bin,
734        urls.node_exporter_bin,
735        urls.fonts_dejavu_mono_deb,
736        urls.fonts_dejavu_core_deb,
737        urls.fontconfig_config_deb,
738        urls.libfontconfig_deb,
739        urls.unzip_deb,
740        urls.adduser_deb,
741        urls.musl_deb,
742        urls.prometheus_config,
743        urls.datasources_yml,
744        urls.all_yml,
745        urls.dashboard,
746        urls.loki_yml,
747        urls.pyroscope_yml,
748        urls.tempo_yml,
749        urls.prometheus_service,
750        urls.loki_service,
751        urls.pyroscope_service,
752        urls.tempo_service,
753        urls.node_exporter_service,
754    )
755}
756
757/// Phase 2: Setup services on monitoring instance (does not start them)
758pub(crate) fn install_monitoring_setup_cmd(
759    prometheus_version: &str,
760    architecture: Architecture,
761) -> String {
762    let arch = architecture.as_str();
763    format!(
764        r#"set -e
765
766# Enable BBR congestion control
767echo -e "net.core.default_qdisc=fq\nnet.ipv4.tcp_congestion_control=bbr" | sudo tee /etc/sysctl.d/99-bbr.conf >/dev/null && sudo sysctl -p /etc/sysctl.d/99-bbr.conf
768
769# Install deb packages
770sudo dpkg -i /home/ubuntu/unzip.deb
771sudo dpkg -i /home/ubuntu/adduser.deb
772
773# Install Prometheus
774sudo mkdir -p /opt/prometheus /opt/prometheus/data
775sudo chown -R ubuntu:ubuntu /opt/prometheus
776tar xvfz /home/ubuntu/prometheus.tar.gz -C /home/ubuntu
777sudo rm -rf /opt/prometheus/prometheus-{prometheus_version}.linux-{arch}
778sudo mv /home/ubuntu/prometheus-{prometheus_version}.linux-{arch} /opt/prometheus/prometheus-{prometheus_version}.linux-{arch}
779sudo ln -sf /opt/prometheus/prometheus-{prometheus_version}.linux-{arch}/prometheus /opt/prometheus/prometheus
780sudo chmod +x /opt/prometheus/prometheus
781
782# Install Grafana dependencies (fonts-dejavu-mono, fonts-dejavu-core, fontconfig-config, libfontconfig1, musl) and Grafana
783sudo dpkg -i /home/ubuntu/fonts-dejavu-mono.deb
784sudo dpkg -i /home/ubuntu/fonts-dejavu-core.deb
785sudo dpkg -i /home/ubuntu/fontconfig-config.deb
786sudo dpkg -i /home/ubuntu/libfontconfig1.deb
787sudo dpkg -i /home/ubuntu/musl.deb
788sudo dpkg -i /home/ubuntu/grafana.deb
789
790# Install Loki
791sudo mkdir -p /opt/loki /loki/index /loki/index_cache /loki/chunks /loki/compactor /loki/wal
792sudo chown -R ubuntu:ubuntu /opt/loki /loki
793unzip -o /home/ubuntu/loki.zip -d /home/ubuntu
794sudo rm -f /opt/loki/loki
795sudo mv /home/ubuntu/loki-linux-{arch} /opt/loki/loki
796sudo chmod +x /opt/loki/loki
797
798# Install Pyroscope
799sudo mkdir -p /opt/pyroscope /var/lib/pyroscope
800sudo chown -R ubuntu:ubuntu /opt/pyroscope /var/lib/pyroscope
801tar xvfz /home/ubuntu/pyroscope.tar.gz -C /home/ubuntu
802sudo rm -f /opt/pyroscope/pyroscope
803sudo mv /home/ubuntu/pyroscope /opt/pyroscope/pyroscope
804sudo chmod +x /opt/pyroscope/pyroscope
805
806# Install Tempo
807sudo mkdir -p /opt/tempo /tempo/traces /tempo/wal
808sudo chown -R ubuntu:ubuntu /opt/tempo /tempo
809tar xvfz /home/ubuntu/tempo.tar.gz -C /home/ubuntu
810sudo rm -f /opt/tempo/tempo
811sudo mv /home/ubuntu/tempo /opt/tempo/tempo
812sudo chmod +x /opt/tempo/tempo
813
814# Install Node Exporter
815sudo mkdir -p /opt/node_exporter
816sudo chown -R ubuntu:ubuntu /opt/node_exporter
817tar xvfz /home/ubuntu/node_exporter.tar.gz -C /home/ubuntu
818sudo rm -rf /opt/node_exporter/node_exporter-*.linux-{arch}
819sudo mv /home/ubuntu/node_exporter-*.linux-{arch} /opt/node_exporter/
820sudo ln -sf /opt/node_exporter/node_exporter-*.linux-{arch}/node_exporter /opt/node_exporter/node_exporter
821sudo chmod +x /opt/node_exporter/node_exporter
822
823# Configure Grafana
824sudo sed -i '/^\[auth.anonymous\]$/,/^\[/ {{ /^; *enabled = /s/.*/enabled = true/; /^; *org_role = /s/.*/org_role = Admin/ }}' /etc/grafana/grafana.ini
825sudo mkdir -p /etc/grafana/provisioning/datasources /etc/grafana/provisioning/dashboards /var/lib/grafana/dashboards
826
827# Install configuration files
828sudo mv /home/ubuntu/prometheus.yml /opt/prometheus/prometheus.yml
829sudo mv /home/ubuntu/datasources.yml /etc/grafana/provisioning/datasources/datasources.yml
830sudo mv /home/ubuntu/all.yml /etc/grafana/provisioning/dashboards/all.yml
831sudo mv /home/ubuntu/dashboard.json /var/lib/grafana/dashboards/dashboard.json
832sudo mkdir -p /etc/loki
833sudo mv /home/ubuntu/loki.yml /etc/loki/loki.yml
834sudo chown root:root /etc/loki/loki.yml
835sudo mkdir -p /etc/pyroscope
836sudo mv /home/ubuntu/pyroscope.yml /etc/pyroscope/pyroscope.yml
837sudo chown root:root /etc/pyroscope/pyroscope.yml
838sudo mkdir -p /etc/tempo
839sudo mv /home/ubuntu/tempo.yml /etc/tempo/tempo.yml
840sudo chown root:root /etc/tempo/tempo.yml
841
842# Install service files
843sudo mv /home/ubuntu/prometheus.service /etc/systemd/system/prometheus.service
844sudo mv /home/ubuntu/loki.service /etc/systemd/system/loki.service
845sudo mv /home/ubuntu/pyroscope.service /etc/systemd/system/pyroscope.service
846sudo mv /home/ubuntu/tempo.service /etc/systemd/system/tempo.service
847sudo mv /home/ubuntu/node_exporter.service /etc/systemd/system/node_exporter.service
848"#
849    )
850}
851
852/// Continuation of monitoring install command (services startup)
853pub const fn start_monitoring_services_cmd() -> &'static str {
854    r#"set -e
855
856sudo chown -R grafana:grafana /etc/grafana /var/lib/grafana
857
858# Start services
859sudo systemctl daemon-reload
860sudo systemctl start node_exporter
861sudo systemctl enable node_exporter
862sudo systemctl start prometheus
863sudo systemctl enable prometheus
864sudo systemctl start loki
865sudo systemctl enable loki
866sudo systemctl start pyroscope
867sudo systemctl enable pyroscope
868sudo systemctl start tempo
869sudo systemctl enable tempo
870sudo systemctl restart grafana-server
871sudo systemctl enable grafana-server
872"#
873}
874
875/// URLs for binary instance installation
876pub struct InstanceUrls {
877    pub binary: String,
878    pub config: String,
879    pub hosts: String,
880    pub promtail_bin: String,
881    pub promtail_config: String,
882    pub promtail_service: String,
883    pub node_exporter_bin: String,
884    pub node_exporter_service: String,
885    pub binary_service: String,
886    pub logrotate_conf: String,
887    pub pyroscope_script: String,
888    pub pyroscope_service: String,
889    pub pyroscope_timer: String,
890    pub libjemalloc_deb: String,
891    pub logrotate_deb: String,
892    pub unzip_deb: String,
893}
894
895/// Phase 1 (optional): Install apt packages on binary instances
896/// Only needed when profiling is enabled (for linux-tools)
897pub(crate) const fn install_binary_apt_cmd(profiling: bool) -> Option<&'static str> {
898    if profiling {
899        Some(
900            r#"set -e
901sudo apt-get update -y
902sudo apt-get install -y linux-tools-common linux-tools-generic linux-tools-$(uname -r)
903"#,
904        )
905    } else {
906        None
907    }
908}
909
910/// Phase 2: Download files from S3 on binary instances
911pub(crate) fn install_binary_download_cmd(urls: &InstanceUrls) -> String {
912    format!(
913        r#"
914# Clean up any previous download artifacts (allows retries to re-download fresh copies)
915rm -f /home/ubuntu/promtail.zip /home/ubuntu/node_exporter.tar.gz \
916      /home/ubuntu/libjemalloc2.deb /home/ubuntu/logrotate.deb /home/ubuntu/unzip.deb
917rm -rf /home/ubuntu/promtail-linux-* /home/ubuntu/node_exporter-*
918
919# Unmask services in case previous attempt left them masked
920sudo systemctl unmask promtail node_exporter binary 2>/dev/null || true
921
922# Download all files from S3 concurrently via pre-signed URLs
923{WGET} -O /home/ubuntu/binary '{}' &
924{WGET} -O /home/ubuntu/config.conf '{}' &
925{WGET} -O /home/ubuntu/hosts.yaml '{}' &
926{WGET} -O /home/ubuntu/promtail.zip '{}' &
927{WGET} -O /home/ubuntu/promtail.yml '{}' &
928{WGET} -O /home/ubuntu/promtail.service '{}' &
929{WGET} -O /home/ubuntu/node_exporter.tar.gz '{}' &
930{WGET} -O /home/ubuntu/node_exporter.service '{}' &
931{WGET} -O /home/ubuntu/binary.service '{}' &
932{WGET} -O /home/ubuntu/logrotate.conf '{}' &
933{WGET} -O /home/ubuntu/pyroscope-agent.sh '{}' &
934{WGET} -O /home/ubuntu/pyroscope-agent.service '{}' &
935{WGET} -O /home/ubuntu/pyroscope-agent.timer '{}' &
936{WGET} -O /home/ubuntu/libjemalloc2.deb '{}' &
937{WGET} -O /home/ubuntu/logrotate.deb '{}' &
938{WGET} -O /home/ubuntu/unzip.deb '{}' &
939wait
940
941# Verify all downloads succeeded
942for f in binary config.conf hosts.yaml promtail.zip promtail.yml promtail.service \
943         node_exporter.tar.gz node_exporter.service binary.service logrotate.conf \
944         pyroscope-agent.sh pyroscope-agent.service pyroscope-agent.timer \
945         libjemalloc2.deb logrotate.deb unzip.deb; do
946    if [ ! -f "/home/ubuntu/$f" ]; then
947        echo "ERROR: Failed to download $f" >&2
948        exit 1
949    fi
950done
951"#,
952        urls.binary,
953        urls.config,
954        urls.hosts,
955        urls.promtail_bin,
956        urls.promtail_config,
957        urls.promtail_service,
958        urls.node_exporter_bin,
959        urls.node_exporter_service,
960        urls.binary_service,
961        urls.logrotate_conf,
962        urls.pyroscope_script,
963        urls.pyroscope_service,
964        urls.pyroscope_timer,
965        urls.libjemalloc_deb,
966        urls.logrotate_deb,
967        urls.unzip_deb,
968    )
969}
970
971/// Phase 3: Setup and start services on binary instances
972pub(crate) fn install_binary_setup_cmd(profiling: bool, architecture: Architecture) -> String {
973    let arch = architecture.as_str();
974    let perf_setup = if profiling {
975        r#"
976# Setup pyroscope agent (perf symlink must be created after linux-tools installed via apt)
977sudo ln -sf "$(find /usr/lib/linux-tools/*/perf | head -1)" /usr/local/bin/perf
978sudo chmod +x /home/ubuntu/pyroscope-agent.sh
979sudo mv /home/ubuntu/pyroscope-agent.service /etc/systemd/system/pyroscope-agent.service
980sudo mv /home/ubuntu/pyroscope-agent.timer /etc/systemd/system/pyroscope-agent.timer
981"#
982    } else {
983        ""
984    };
985    let pyroscope_enable = if profiling {
986        "\nsudo systemctl enable --now pyroscope-agent.timer\n"
987    } else {
988        ""
989    };
990    format!(
991        r#"set -e
992
993# Enable BBR congestion control
994echo -e "net.core.default_qdisc=fq\nnet.ipv4.tcp_congestion_control=bbr" | sudo tee /etc/sysctl.d/99-bbr.conf >/dev/null && sudo sysctl -p /etc/sysctl.d/99-bbr.conf
995
996# Install deb packages
997sudo dpkg -i /home/ubuntu/unzip.deb
998sudo dpkg -i /home/ubuntu/libjemalloc2.deb
999sudo dpkg -i /home/ubuntu/logrotate.deb
1000
1001# Install Promtail
1002sudo mkdir -p /opt/promtail /etc/promtail
1003sudo chown -R ubuntu:ubuntu /opt/promtail
1004unzip -o /home/ubuntu/promtail.zip -d /home/ubuntu
1005sudo rm -f /opt/promtail/promtail
1006sudo mv /home/ubuntu/promtail-linux-{arch} /opt/promtail/promtail
1007sudo chmod +x /opt/promtail/promtail
1008sudo mv /home/ubuntu/promtail.yml /etc/promtail/promtail.yml
1009sudo mv /home/ubuntu/promtail.service /etc/systemd/system/promtail.service
1010sudo chown root:root /etc/promtail/promtail.yml
1011
1012# Install Node Exporter
1013sudo mkdir -p /opt/node_exporter
1014sudo chown -R ubuntu:ubuntu /opt/node_exporter
1015tar xvfz /home/ubuntu/node_exporter.tar.gz -C /home/ubuntu
1016sudo rm -rf /opt/node_exporter/node_exporter-*.linux-{arch}
1017sudo mv /home/ubuntu/node_exporter-*.linux-{arch} /opt/node_exporter/
1018sudo ln -sf /opt/node_exporter/node_exporter-*.linux-{arch}/node_exporter /opt/node_exporter/node_exporter
1019sudo chmod +x /opt/node_exporter/node_exporter
1020sudo mv /home/ubuntu/node_exporter.service /etc/systemd/system/node_exporter.service
1021
1022# Setup binary
1023chmod +x /home/ubuntu/binary
1024sudo touch /var/log/binary.log && sudo chown ubuntu:ubuntu /var/log/binary.log
1025sudo mv /home/ubuntu/binary.service /etc/systemd/system/binary.service
1026
1027# Setup logrotate
1028sudo mv /home/ubuntu/logrotate.conf /etc/logrotate.d/binary
1029sudo chown root:root /etc/logrotate.d/binary
1030echo "0 * * * * /usr/sbin/logrotate /etc/logrotate.d/binary" | crontab -
1031{perf_setup}
1032# Start services
1033sudo systemctl daemon-reload
1034sudo systemctl enable --now promtail
1035sudo systemctl enable --now node_exporter
1036sudo systemctl enable --now binary{pyroscope_enable}"#
1037    )
1038}
1039
1040/// Generates Promtail configuration with the monitoring instance's private IP and instance name
1041pub fn promtail_config(
1042    monitoring_private_ip: &str,
1043    instance_name: &str,
1044    ip: &str,
1045    region: &str,
1046    arch: &str,
1047) -> String {
1048    format!(
1049        r#"
1050server:
1051  http_listen_port: 9080
1052  grpc_listen_port: 0
1053positions:
1054  filename: /tmp/positions.yaml
1055clients:
1056  - url: http://{monitoring_private_ip}:3100/loki/api/v1/push
1057scrape_configs:
1058  - job_name: binary_logs
1059    static_configs:
1060      - targets:
1061          - localhost
1062        labels:
1063          deployer_name: {instance_name}
1064          deployer_ip: {ip}
1065          deployer_region: {region}
1066          deployer_arch: {arch}
1067          __path__: /var/log/binary.log
1068"#
1069    )
1070}
1071
1072/// Systemd service file content for Node Exporter
1073pub const NODE_EXPORTER_SERVICE: &str = r#"[Unit]
1074Description=Node Exporter
1075After=network.target
1076
1077[Service]
1078ExecStart=/opt/node_exporter/node_exporter
1079TimeoutStopSec=60
1080Restart=always
1081User=ubuntu
1082LimitNOFILE=infinity
1083
1084[Install]
1085WantedBy=multi-user.target
1086"#;
1087
1088/// Generates Prometheus configuration with scrape targets for all instance IPs
1089pub fn generate_prometheus_config(instances: &[(&str, &str, &str, &str)]) -> String {
1090    let mut config = String::from(
1091        r#"
1092global:
1093  scrape_interval: 15s
1094scrape_configs:
1095  - job_name: 'monitoring_system'
1096    static_configs:
1097      - targets: ['localhost:9100']
1098"#,
1099    );
1100    for (name, ip, region, arch) in instances {
1101        config.push_str(&format!(
1102            r#"
1103  - job_name: '{name}_binary'
1104    static_configs:
1105      - targets: ['{ip}:9090']
1106        labels:
1107          deployer_name: '{name}'
1108          deployer_ip: '{ip}'
1109          deployer_region: '{region}'
1110          deployer_arch: '{arch}'
1111  - job_name: '{name}_system'
1112    static_configs:
1113      - targets: ['{ip}:9100']
1114        labels:
1115          deployer_name: '{name}'
1116          deployer_ip: '{ip}'
1117          deployer_region: '{region}'
1118          deployer_arch: '{arch}'
1119"#
1120        ));
1121    }
1122    config
1123}
1124
1125/// Logrotate configuration for binary logs
1126pub const LOGROTATE_CONF: &str = r#"
1127/var/log/binary.log {
1128    rotate 0
1129    copytruncate
1130    missingok
1131    notifempty
1132}
1133"#;
1134
1135/// Generates systemd service file content for the deployed binary
1136pub(crate) fn binary_service(architecture: Architecture) -> String {
1137    let lib_arch = architecture.linux_lib();
1138    format!(
1139        r#"[Unit]
1140Description=Deployed Binary Service
1141After=network.target
1142
1143[Service]
1144Environment="LD_PRELOAD=/usr/lib/{lib_arch}/libjemalloc.so.2"
1145ExecStart=/home/ubuntu/binary --hosts=/home/ubuntu/hosts.yaml --config=/home/ubuntu/config.conf
1146TimeoutStopSec=60
1147Restart=always
1148User=ubuntu
1149LimitNOFILE=infinity
1150StandardOutput=append:/var/log/binary.log
1151StandardError=append:/var/log/binary.log
1152
1153[Install]
1154WantedBy=multi-user.target
1155"#
1156    )
1157}
1158
1159/// Shell script content for the Pyroscope agent (perf + wget)
1160pub fn generate_pyroscope_script(
1161    monitoring_private_ip: &str,
1162    name: &str,
1163    ip: &str,
1164    region: &str,
1165    arch: &str,
1166) -> String {
1167    format!(
1168        r#"#!/bin/bash
1169set -e
1170
1171SERVICE_NAME="binary.service"
1172PERF_DATA_FILE="/tmp/perf.data"
1173PERF_STACK_FILE="/tmp/perf.stack"
1174PROFILE_DURATION=60 # seconds
1175PERF_FREQ=100 # Hz
1176
1177# Construct the Pyroscope application name with tags (URL-encoded)
1178RAW_APP_NAME="binary{{deployer_name={name},deployer_ip={ip},deployer_region={region},deployer_arch={arch}}}"
1179APP_NAME=$(printf '%s' "$RAW_APP_NAME" | python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.stdin.read()))")
1180
1181# Get the PID of the binary service
1182PID=$(systemctl show --property MainPID ${{SERVICE_NAME}} | cut -d= -f2)
1183if [ -z "$PID" ] || [ "$PID" -eq 0 ]; then
1184  echo "Error: Could not get PID for ${{SERVICE_NAME}}." >&2
1185  exit 1
1186fi
1187
1188# Record performance data
1189echo "Recording perf data for PID ${{PID}}..."
1190sudo perf record -F ${{PERF_FREQ}} -p ${{PID}} -o ${{PERF_DATA_FILE}} -g --call-graph fp -- sleep ${{PROFILE_DURATION}}
1191
1192# Generate folded stack report
1193echo "Generating folded stack report..."
1194sudo perf report -i ${{PERF_DATA_FILE}} --stdio --no-children -g folded,0,caller,count -s comm | \
1195    awk '/^[0-9]+\.[0-9]+%/ {{ comm = $2 }} /^[0-9]/ {{ print comm ";" substr($0, index($0, $2)), $1 }}' > ${{PERF_STACK_FILE}}
1196
1197# Check if stack file is empty (perf might fail silently sometimes)
1198if [ ! -s "${{PERF_STACK_FILE}}" ]; then
1199    echo "Warning: ${{PERF_STACK_FILE}} is empty. Skipping upload." >&2
1200    # Clean up empty perf.data
1201    sudo rm -f ${{PERF_DATA_FILE}} ${{PERF_STACK_FILE}}
1202    exit 0
1203fi
1204
1205# Calculate timestamps
1206UNTIL_TS=$(date +%s)
1207FROM_TS=$((UNTIL_TS - PROFILE_DURATION))
1208
1209# Upload to Pyroscope
1210echo "Uploading profile to Pyroscope at {monitoring_private_ip}..."
1211wget --post-file="${{PERF_STACK_FILE}}" \
1212    --header="Content-Type: text/plain" \
1213    --quiet \
1214    -O /dev/null \
1215    "http://{monitoring_private_ip}:4040/ingest?name=${{APP_NAME}}&format=folded&units=samples&aggregationType=sum&sampleType=cpu&from=${{FROM_TS}}&until=${{UNTIL_TS}}&spyName=perf"
1216
1217echo "Profile upload complete."
1218sudo rm -f ${{PERF_DATA_FILE}} ${{PERF_STACK_FILE}}
1219"#
1220    )
1221}
1222
1223/// Systemd service file content for the Pyroscope agent script
1224pub const PYROSCOPE_AGENT_SERVICE: &str = r#"[Unit]
1225Description=Pyroscope Agent (Perf Script Runner)
1226Wants=network-online.target
1227After=network-online.target binary.service
1228
1229[Service]
1230Type=oneshot
1231User=ubuntu
1232ExecStart=/home/ubuntu/pyroscope-agent.sh
1233
1234[Install]
1235WantedBy=multi-user.target
1236"#;
1237
1238/// Systemd timer file content for the Pyroscope agent service
1239pub const PYROSCOPE_AGENT_TIMER: &str = r#"[Unit]
1240Description=Run Pyroscope Agent periodically
1241
1242[Timer]
1243# Wait a bit after boot before the first run
1244OnBootSec=2min
1245# Run roughly every minute after the last run finished
1246OnUnitInactiveSec=1min
1247Unit=pyroscope-agent.service
1248# Randomize the delay to avoid thundering herd
1249RandomizedDelaySec=10s
1250
1251[Install]
1252WantedBy=timers.target
1253"#;
1254
1255#[cfg(test)]
1256mod tests {
1257    use super::*;
1258
1259    #[test]
1260    fn test_binary_s3_keys_arm64() {
1261        let arch = Architecture::Arm64;
1262        assert_eq!(
1263            prometheus_bin_s3_key("3.2.0", arch),
1264            "tools/binaries/prometheus/3.2.0/linux-arm64/prometheus-3.2.0.linux-arm64.tar.gz"
1265        );
1266        assert_eq!(
1267            grafana_bin_s3_key("11.5.2", arch),
1268            "tools/binaries/grafana/11.5.2/linux-arm64/grafana_11.5.2_arm64.deb"
1269        );
1270        assert_eq!(
1271            loki_bin_s3_key("3.4.2", arch),
1272            "tools/binaries/loki/3.4.2/linux-arm64/loki-linux-arm64.zip"
1273        );
1274        assert_eq!(
1275            pyroscope_bin_s3_key("1.12.0", arch),
1276            "tools/binaries/pyroscope/1.12.0/linux-arm64/pyroscope_1.12.0_linux_arm64.tar.gz"
1277        );
1278        assert_eq!(
1279            tempo_bin_s3_key("2.7.1", arch),
1280            "tools/binaries/tempo/2.7.1/linux-arm64/tempo_2.7.1_linux_arm64.tar.gz"
1281        );
1282        assert_eq!(
1283            node_exporter_bin_s3_key("1.9.0", arch),
1284            "tools/binaries/node-exporter/1.9.0/linux-arm64/node_exporter-1.9.0.linux-arm64.tar.gz"
1285        );
1286        assert_eq!(
1287            promtail_bin_s3_key("3.4.2", arch),
1288            "tools/binaries/promtail/3.4.2/linux-arm64/promtail-linux-arm64.zip"
1289        );
1290        assert_eq!(
1291            libjemalloc_bin_s3_key("5.3.0-2build1", arch),
1292            "tools/binaries/libjemalloc2/5.3.0-2build1/linux-arm64/libjemalloc2_5.3.0-2build1_arm64.deb"
1293        );
1294        assert_eq!(
1295            logrotate_bin_s3_key("3.21.0-2build1", arch),
1296            "tools/binaries/logrotate/3.21.0-2build1/linux-arm64/logrotate_3.21.0-2build1_arm64.deb"
1297        );
1298        assert_eq!(
1299            libfontconfig_bin_s3_key("2.15.0-1.1ubuntu2", arch),
1300            "tools/binaries/libfontconfig1/2.15.0-1.1ubuntu2/linux-arm64/libfontconfig1_2.15.0-1.1ubuntu2_arm64.deb"
1301        );
1302        assert_eq!(
1303            unzip_bin_s3_key("6.0-28ubuntu4", arch),
1304            "tools/binaries/unzip/6.0-28ubuntu4/linux-arm64/unzip_6.0-28ubuntu4_arm64.deb"
1305        );
1306    }
1307
1308    #[test]
1309    fn test_binary_s3_keys_x86_64() {
1310        let arch = Architecture::X86_64;
1311        assert_eq!(
1312            prometheus_bin_s3_key("3.2.0", arch),
1313            "tools/binaries/prometheus/3.2.0/linux-amd64/prometheus-3.2.0.linux-amd64.tar.gz"
1314        );
1315        assert_eq!(
1316            grafana_bin_s3_key("11.5.2", arch),
1317            "tools/binaries/grafana/11.5.2/linux-amd64/grafana_11.5.2_amd64.deb"
1318        );
1319        assert_eq!(
1320            loki_bin_s3_key("3.4.2", arch),
1321            "tools/binaries/loki/3.4.2/linux-amd64/loki-linux-amd64.zip"
1322        );
1323        assert_eq!(
1324            pyroscope_bin_s3_key("1.12.0", arch),
1325            "tools/binaries/pyroscope/1.12.0/linux-amd64/pyroscope_1.12.0_linux_amd64.tar.gz"
1326        );
1327        assert_eq!(
1328            tempo_bin_s3_key("2.7.1", arch),
1329            "tools/binaries/tempo/2.7.1/linux-amd64/tempo_2.7.1_linux_amd64.tar.gz"
1330        );
1331        assert_eq!(
1332            node_exporter_bin_s3_key("1.9.0", arch),
1333            "tools/binaries/node-exporter/1.9.0/linux-amd64/node_exporter-1.9.0.linux-amd64.tar.gz"
1334        );
1335        assert_eq!(
1336            promtail_bin_s3_key("3.4.2", arch),
1337            "tools/binaries/promtail/3.4.2/linux-amd64/promtail-linux-amd64.zip"
1338        );
1339        assert_eq!(
1340            libjemalloc_bin_s3_key("5.3.0-2build1", arch),
1341            "tools/binaries/libjemalloc2/5.3.0-2build1/linux-amd64/libjemalloc2_5.3.0-2build1_amd64.deb"
1342        );
1343        assert_eq!(
1344            logrotate_bin_s3_key("3.21.0-2build1", arch),
1345            "tools/binaries/logrotate/3.21.0-2build1/linux-amd64/logrotate_3.21.0-2build1_amd64.deb"
1346        );
1347        assert_eq!(
1348            libfontconfig_bin_s3_key("2.15.0-1.1ubuntu2", arch),
1349            "tools/binaries/libfontconfig1/2.15.0-1.1ubuntu2/linux-amd64/libfontconfig1_2.15.0-1.1ubuntu2_amd64.deb"
1350        );
1351        assert_eq!(
1352            unzip_bin_s3_key("6.0-28ubuntu4", arch),
1353            "tools/binaries/unzip/6.0-28ubuntu4/linux-amd64/unzip_6.0-28ubuntu4_amd64.deb"
1354        );
1355    }
1356
1357    #[test]
1358    fn test_config_s3_keys() {
1359        let version = DEPLOYER_VERSION;
1360
1361        assert_eq!(
1362            prometheus_service_s3_key(),
1363            format!("tools/configs/{version}/prometheus/service")
1364        );
1365        assert_eq!(
1366            grafana_datasources_s3_key(),
1367            format!("tools/configs/{version}/grafana/datasources.yml")
1368        );
1369        assert_eq!(
1370            grafana_dashboards_s3_key(),
1371            format!("tools/configs/{version}/grafana/all.yml")
1372        );
1373        assert_eq!(
1374            loki_config_s3_key(),
1375            format!("tools/configs/{version}/loki/config.yml")
1376        );
1377        assert_eq!(
1378            loki_service_s3_key(),
1379            format!("tools/configs/{version}/loki/service")
1380        );
1381        assert_eq!(
1382            pyroscope_config_s3_key(),
1383            format!("tools/configs/{version}/pyroscope/config.yml")
1384        );
1385        assert_eq!(
1386            pyroscope_service_s3_key(),
1387            format!("tools/configs/{version}/pyroscope/service")
1388        );
1389        assert_eq!(
1390            tempo_config_s3_key(),
1391            format!("tools/configs/{version}/tempo/config.yml")
1392        );
1393        assert_eq!(
1394            tempo_service_s3_key(),
1395            format!("tools/configs/{version}/tempo/service")
1396        );
1397        assert_eq!(
1398            node_exporter_service_s3_key(),
1399            format!("tools/configs/{version}/node-exporter/service")
1400        );
1401        assert_eq!(
1402            promtail_service_s3_key(),
1403            format!("tools/configs/{version}/promtail/service")
1404        );
1405        assert_eq!(
1406            pyroscope_agent_service_s3_key(),
1407            format!("tools/configs/{version}/pyroscope/agent.service")
1408        );
1409        assert_eq!(
1410            pyroscope_agent_timer_s3_key(),
1411            format!("tools/configs/{version}/pyroscope/agent.timer")
1412        );
1413        assert_eq!(
1414            logrotate_config_s3_key(),
1415            format!("tools/configs/{version}/system/logrotate.conf")
1416        );
1417        assert_eq!(
1418            binary_service_s3_key_for_arch(Architecture::Arm64),
1419            format!("tools/configs/{version}/binary/service-arm64")
1420        );
1421        assert_eq!(
1422            binary_service_s3_key_for_arch(Architecture::X86_64),
1423            format!("tools/configs/{version}/binary/service-amd64")
1424        );
1425    }
1426
1427    #[test]
1428    fn test_deployment_s3_keys() {
1429        let digest = "abc123def456";
1430        assert_eq!(
1431            binary_s3_key("my-tag", digest),
1432            "deployments/my-tag/binaries/abc123def456"
1433        );
1434        assert_eq!(
1435            config_s3_key("my-tag", digest),
1436            "deployments/my-tag/configs/abc123def456"
1437        );
1438        assert_eq!(
1439            hosts_s3_key("my-tag", digest),
1440            "deployments/my-tag/hosts/abc123def456"
1441        );
1442        assert_eq!(
1443            promtail_s3_key("my-tag", digest),
1444            "deployments/my-tag/promtail/abc123def456"
1445        );
1446        assert_eq!(
1447            pyroscope_s3_key("my-tag", digest),
1448            "deployments/my-tag/pyroscope/abc123def456"
1449        );
1450        assert_eq!(
1451            monitoring_s3_key("my-tag", digest),
1452            "deployments/my-tag/monitoring/abc123def456"
1453        );
1454    }
1455}