@synopackageland/templates 0.9.8 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +27 -27
  2. package/package.json +2 -2
  3. package/templates/_shared/AGENTS.md +53 -15
  4. package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
  5. package/templates/native-go/backend/cmd/app/main.go +15 -1
  6. package/templates/native-go/backend/cmd/app/main_test.go +33 -0
  7. package/templates/native-go/backend/go.mod +1 -1
  8. package/templates/native-go/backend/go.sum +2 -2
  9. package/templates/native-go/i18n/glossary.json +3 -1
  10. package/templates/native-go/i18n/messages/de.json +6 -0
  11. package/templates/native-go/i18n/messages/en.json +6 -0
  12. package/templates/native-go/i18n/messages/es.json +6 -0
  13. package/templates/native-go/i18n/messages/fr.json +6 -0
  14. package/templates/native-go/i18n/messages/it.json +6 -0
  15. package/templates/native-go/i18n/messages/ja.json +6 -0
  16. package/templates/native-go/i18n/messages/ko.json +6 -0
  17. package/templates/native-go/i18n/messages/zh-CN.json +6 -0
  18. package/templates/native-go/i18n/messages/zh-TW.json +6 -0
  19. package/templates/native-go/ui/app.js +2 -1
  20. package/templates/native-go-delegate/frontend/test/capability-panels.test.ts +3 -2
  21. package/templates/native-go-jobs/frontend/test/background-page.test.ts +4 -4
  22. package/templates/native-go-vue/README.md +19 -7
  23. package/templates/native-go-vue/frontend/package-lock.json +9 -9
  24. package/templates/native-go-vue/frontend/package.json +2 -2
  25. package/templates/native-go-vue/frontend/src/App.vue +31 -36
  26. package/templates/native-go-vue/frontend/test/apps.test.ts +65 -12
  27. package/templates/native-go-vue/frontend/test/context.ts +8 -0
  28. package/templates/native-go-vue/frontend/test/native-controls.test.ts +3 -4
  29. package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +2 -2
  30. package/templates/native-node/README.md +153 -0
  31. package/templates/native-node/backend/app/package.json +4 -0
  32. package/templates/native-node/backend/app/permissions.json +5 -0
  33. package/templates/native-node/backend/package-lock.json +59 -0
  34. package/templates/native-node/backend/package.json +17 -0
  35. package/templates/native-node/backend/src/delegate.ts +5 -0
  36. package/templates/native-node/backend/src/jobs.ts +5 -0
  37. package/templates/native-node/backend/src/main.ts +4 -0
  38. package/templates/native-node/backend/src/params.ts +11 -0
  39. package/templates/native-node/backend/src/root-ops.ts +5 -0
  40. package/templates/native-node/backend/src/routes.ts +97 -0
  41. package/templates/native-node/backend/src/service.ts +22 -0
  42. package/templates/native-node/backend/test/routes.test.ts +75 -0
  43. package/templates/native-node/backend/test/support.ts +67 -0
  44. package/templates/native-node/backend/tsconfig.build.json +8 -0
  45. package/templates/native-node/backend/tsconfig.json +18 -0
  46. package/templates/native-node/dsm-package.yaml +80 -0
  47. package/templates/native-node/gitignore +4 -0
  48. package/templates/native-node-delegate/backend/src/delegate.ts +35 -0
  49. package/templates/native-node-delegate/backend/test/delegate.test.ts +51 -0
  50. package/templates/native-node-jobs/backend/src/jobs.ts +38 -0
  51. package/templates/native-node-jobs/backend/test/jobs.test.ts +73 -0
  52. package/templates/native-node-root-ops/backend/src/root-ops.ts +16 -0
  53. package/templates/native-node-root-ops/backend/test/root-ops.test.ts +23 -0
  54. package/templates/native-node-vue/README.md +56 -0
  55. package/templates/native-node-vue/gitignore +4 -0
  56. package/templates/native-python/README.md +150 -0
  57. package/templates/native-python/backend/app/delegate.py +5 -0
  58. package/templates/native-python/backend/app/jobs.py +5 -0
  59. package/templates/native-python/backend/app/main.py +5 -0
  60. package/templates/native-python/backend/app/params.py +10 -0
  61. package/templates/native-python/backend/app/permissions.json +5 -0
  62. package/templates/native-python/backend/app/root_ops.py +5 -0
  63. package/templates/native-python/backend/app/routes.py +107 -0
  64. package/templates/native-python/backend/tests/support.py +72 -0
  65. package/templates/native-python/backend/tests/test_routes.py +73 -0
  66. package/templates/native-python/dsm-package.yaml +77 -0
  67. package/templates/native-python/gitignore +2 -0
  68. package/templates/native-python-delegate/backend/app/delegate.py +42 -0
  69. package/templates/native-python-delegate/backend/tests/test_delegate.py +58 -0
  70. package/templates/native-python-jobs/backend/app/jobs.py +40 -0
  71. package/templates/native-python-jobs/backend/tests/test_jobs.py +72 -0
  72. package/templates/native-python-root-ops/backend/app/root_ops.py +18 -0
  73. package/templates/native-python-root-ops/backend/tests/test_root_ops.py +34 -0
  74. package/templates/native-python-vue/README.md +56 -0
  75. package/templates/native-python-vue/gitignore +3 -0
  76. package/templates/native-rust/README.md +74 -296
  77. package/templates/native-rust/backend/Cargo.lock +6 -6
  78. package/templates/native-rust/backend/Cargo.toml +2 -10
  79. package/templates/native-rust/backend/src/delegate.rs +6 -0
  80. package/templates/native-rust/backend/src/jobs.rs +6 -0
  81. package/templates/native-rust/backend/src/main.rs +12 -0
  82. package/templates/native-rust/backend/src/root_ops.rs +6 -0
  83. package/templates/native-rust/backend/src/routes.rs +352 -0
  84. package/templates/native-rust/backend/src/test_support.rs +91 -0
  85. package/templates/native-rust/dsm-package.yaml +10 -21
  86. package/templates/native-rust-delegate/backend/src/delegate.rs +171 -0
  87. package/templates/native-rust-jobs/backend/src/jobs.rs +240 -0
  88. package/templates/native-rust-root-ops/backend/src/root_ops.rs +83 -0
  89. package/templates/native-rust-vue/README.md +35 -34
  90. package/templates/native-rust/backend/THIRD_PARTY_NOTICES.txt +0 -207
  91. package/templates/native-rust/backend/src/app/background/mod.rs +0 -424
  92. package/templates/native-rust/backend/src/app/background/owner.rs +0 -77
  93. package/templates/native-rust/backend/src/app/background/owner_tests.rs +0 -174
  94. package/templates/native-rust/backend/src/app/background/schedule.rs +0 -126
  95. package/templates/native-rust/backend/src/app/background/schedule_tests.rs +0 -108
  96. package/templates/native-rust/backend/src/app/background/tests.rs +0 -505
  97. package/templates/native-rust/backend/src/app/background/work.rs +0 -78
  98. package/templates/native-rust/backend/src/app/background/work_tests.rs +0 -123
  99. package/templates/native-rust/backend/src/app/background_routes.rs +0 -22
  100. package/templates/native-rust/backend/src/app/delegate_config.rs +0 -188
  101. package/templates/native-rust/backend/src/app/delegate_routes.rs +0 -22
  102. package/templates/native-rust/backend/src/app/identity.rs +0 -56
  103. package/templates/native-rust/backend/src/app/migrations.rs +0 -79
  104. package/templates/native-rust/backend/src/app/mod.rs +0 -30
  105. package/templates/native-rust/backend/src/app/portal.rs +0 -317
  106. package/templates/native-rust/backend/src/app/root_routes.rs +0 -23
  107. package/templates/native-rust/backend/src/app/routes.rs +0 -315
  108. package/templates/native-rust/backend/src/app/session_files.rs +0 -19
  109. package/templates/native-rust/backend/src/app/shared_folder.rs +0 -383
  110. package/templates/native-rust/backend/src/app/test_support.rs +0 -202
  111. package/templates/native-rust/backend/src/app/uninstall.rs +0 -38
  112. package/templates/native-rust/backend/src/bin/api.rs +0 -72
  113. package/templates/native-rust/backend/src/bin/background-worker.rs +0 -37
  114. package/templates/native-rust/backend/src/build_env.rs +0 -155
  115. package/templates/native-rust/backend/src/lib.rs +0 -6
  116. package/templates/native-rust/backend/src/permissions.rs +0 -54
  117. package/templates/native-rust/backend/src/process.rs +0 -91
  118. package/templates/native-rust/i18n/glossary.json +0 -663
  119. package/templates/native-rust/i18n/messages/de.json +0 -30
  120. package/templates/native-rust/i18n/messages/en.json +0 -30
  121. package/templates/native-rust/i18n/messages/es.json +0 -30
  122. package/templates/native-rust/i18n/messages/fr.json +0 -30
  123. package/templates/native-rust/i18n/messages/it.json +0 -30
  124. package/templates/native-rust/i18n/messages/ja.json +0 -30
  125. package/templates/native-rust/i18n/messages/ko.json +0 -30
  126. package/templates/native-rust/i18n/messages/zh-CN.json +0 -30
  127. package/templates/native-rust/i18n/messages/zh-TW.json +0 -30
  128. package/templates/native-rust/ui/app.css +0 -45
  129. package/templates/native-rust/ui/app.js +0 -111
  130. package/templates/native-rust-delegate/backend/src/app/delegate_routes.rs +0 -92
  131. package/templates/native-rust-delegate/backend/src/bin/background-worker.rs +0 -52
  132. package/templates/native-rust-jobs/backend/src/app/background_routes.rs +0 -109
  133. package/templates/native-rust-root-ops/backend/src/app/root_routes.rs +0 -475
@@ -1,37 +0,0 @@
1
- //! The package's fixed background worker. DSM runs it as the package's
2
- //! systemd user unit under the package account; it never runs as root and
3
- //! takes no input except the jobs in its private spool.
4
-
5
- use std::process::exit;
6
- use std::sync::Arc;
7
-
8
- use app::app::hash_work;
9
- use app::build_env::Build;
10
- use app::process::{current_username, until_terminated};
11
- use syno_pkg_land_sdk::BoxError;
12
- use syno_pkg_land_sdk::background::{Runner, WorkerInfo, refuse_root};
13
-
14
- fn main() {
15
- let package = Build::compiled().package_id;
16
- if let Err(e) = run(package) {
17
- eprintln!("{package} background: {e}");
18
- exit(1);
19
- }
20
- }
21
-
22
- fn run(package: &str) -> Result<(), BoxError> {
23
- // SAFETY: these take no arguments and cannot fail.
24
- let (euid, uid, pid) = unsafe { (libc::geteuid(), libc::getuid(), libc::getpid()) };
25
- refuse_root(euid)?;
26
- let info = WorkerInfo {
27
- pid: i64::from(pid),
28
- uid: i64::from(uid),
29
- username: current_username(),
30
- ..WorkerInfo::default()
31
- };
32
- let ctx = until_terminated();
33
- eprintln!("{package} background: started as uid {uid}");
34
- let mut runner = Runner::new(format!("/var/packages/{package}/var/background"), info);
35
- runner.work = Arc::new(hash_work);
36
- runner.run(&ctx)
37
- }
@@ -1,155 +0,0 @@
1
- //! What `synopkgland build` fixes in the binary through SYNOPKGLAND_*
2
- //! environment variables at compile time; never read from a request. An empty
3
- //! or absent variable is unset. The SDK reads SYNOPKGLAND_SDK_PERMISSIONS_DIGEST
4
- //! itself.
5
-
6
- use std::sync::Arc;
7
-
8
- use syno_pkg_land_sdk::filestation::Service;
9
-
10
- use crate::app::{Config, session_files_enabled};
11
-
12
- #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
13
- pub struct Build {
14
- pub package_id: &'static str,
15
- pub version: &'static str,
16
- pub delegate_scope: &'static str,
17
- pub delegate_operations: &'static str,
18
- pub delegate_destinations: &'static str,
19
- pub delegate_lifetime: &'static str,
20
- pub executor_digest: &'static str,
21
- pub policy_digest: &'static str,
22
- /// "standalone" enables the SDK identity adapter; the runtime owns
23
- /// authentication.
24
- pub identity: &'static str,
25
- /// "session" when the project opted into the interactive File Station
26
- /// adapter.
27
- pub file_station: &'static str,
28
- /// dsm-package.yaml entry: "admin-only" or "app-privilege"; empty keeps
29
- /// the route policy alone.
30
- pub entry: &'static str,
31
- /// "on" when dsm-package.yaml declares portal: serves portal.status;
32
- /// "executor" below DSM 7.2, where the executor registers the entry.
33
- pub portal: &'static str,
34
- }
35
-
36
- impl Build {
37
- /// The build this binary was compiled as; direct development builds get
38
- /// the package "dev" at version "0.0.0".
39
- pub fn compiled() -> Build {
40
- Build {
41
- package_id: or_default(option_env!("SYNOPKGLAND_PACKAGE"), "dev"),
42
- version: or_default(option_env!("SYNOPKGLAND_VERSION"), "0.0.0"),
43
- delegate_scope: or_default(option_env!("SYNOPKGLAND_DELEGATE_SCOPE"), ""),
44
- delegate_operations: or_default(option_env!("SYNOPKGLAND_DELEGATE_OPERATIONS"), ""),
45
- delegate_destinations: or_default(option_env!("SYNOPKGLAND_DELEGATE_DESTINATIONS"), ""),
46
- delegate_lifetime: or_default(option_env!("SYNOPKGLAND_DELEGATE_LIFETIME"), ""),
47
- executor_digest: or_default(option_env!("SYNOPKGLAND_EXECUTOR_DIGEST"), ""),
48
- policy_digest: or_default(option_env!("SYNOPKGLAND_POLICY_DIGEST"), ""),
49
- identity: or_default(option_env!("SYNOPKGLAND_IDENTITY"), ""),
50
- file_station: or_default(option_env!("SYNOPKGLAND_FILE_STATION"), ""),
51
- entry: or_default(option_env!("SYNOPKGLAND_ENTRY"), ""),
52
- portal: or_default(option_env!("SYNOPKGLAND_PORTAL"), ""),
53
- }
54
- }
55
-
56
- /// The package's persistent, package-owned directory.
57
- pub fn data_dir(&self) -> String {
58
- format!("/var/packages/{}/var", self.package_id)
59
- }
60
-
61
- /// The App's configuration for this build.
62
- pub fn config(&self) -> Config {
63
- let data_dir = self.data_dir();
64
- Config {
65
- delegate_scope: self.delegate_scope.into(),
66
- delegate_operations_json: self.delegate_operations.into(),
67
- delegate_destinations_json: self.delegate_destinations.into(),
68
- delegate_lifetime: self.delegate_lifetime.into(),
69
- executor_digest: self.executor_digest.into(),
70
- policy_digest: self.policy_digest.into(),
71
- session_files: session_files_enabled(self.file_station)
72
- .then(|| Arc::new(Service::local(data_dir.as_ref()))),
73
- package_id: self.package_id.into(),
74
- version: self.version.into(),
75
- data_dir: data_dir.into(),
76
- portal: !self.portal.is_empty(),
77
- portal_executor: self.portal == "executor",
78
- ..Config::default()
79
- }
80
- }
81
- }
82
-
83
- fn or_default(value: Option<&'static str>, default: &'static str) -> &'static str {
84
- value.filter(|v| !v.is_empty()).unwrap_or(default)
85
- }
86
-
87
- #[cfg(test)]
88
- mod tests {
89
- use super::*;
90
-
91
- #[test]
92
- fn an_unset_or_empty_variable_takes_the_default() {
93
- assert_eq!(or_default(None, "dev"), "dev");
94
- assert_eq!(or_default(Some(""), "dev"), "dev");
95
- assert_eq!(or_default(Some("Hello"), "dev"), "Hello");
96
- }
97
-
98
- #[test]
99
- fn config_derives_the_data_directory_and_portal_modes() {
100
- let build = Build {
101
- package_id: "HelloRust",
102
- version: "1.2.3-0004",
103
- portal: "executor",
104
- ..Build::default()
105
- };
106
- let cfg = build.config();
107
- assert_eq!(cfg.package_id, "HelloRust");
108
- assert_eq!(cfg.version, "1.2.3-0004");
109
- assert_eq!(
110
- cfg.data_dir,
111
- std::path::PathBuf::from("/var/packages/HelloRust/var")
112
- );
113
- assert!(cfg.portal && cfg.portal_executor);
114
- assert!(cfg.session_files.is_none());
115
-
116
- let cfg = Build {
117
- portal: "on",
118
- file_station: "session",
119
- ..build
120
- }
121
- .config();
122
- assert!(cfg.portal && !cfg.portal_executor);
123
- assert!(cfg.session_files.is_some());
124
-
125
- let cfg = Build::default().config();
126
- assert!(!cfg.portal && !cfg.portal_executor);
127
- }
128
-
129
- #[test]
130
- fn config_carries_the_delegate_settings_and_pins() {
131
- let cfg = Build {
132
- delegate_scope: "files",
133
- delegate_operations: r#"["files.read"]"#,
134
- delegate_destinations: r#"["save"]"#,
135
- delegate_lifetime: "until-revoked",
136
- executor_digest: "e1",
137
- policy_digest: "p1",
138
- ..Build::default()
139
- }
140
- .config();
141
- assert_eq!(
142
- (
143
- cfg.delegate_scope.as_str(),
144
- cfg.delegate_operations_json.as_str()
145
- ),
146
- ("files", r#"["files.read"]"#)
147
- );
148
- assert_eq!(cfg.delegate_destinations_json, r#"["save"]"#);
149
- assert_eq!(cfg.delegate_lifetime, "until-revoked");
150
- assert_eq!(
151
- (cfg.executor_digest.as_str(), cfg.policy_digest.as_str()),
152
- ("e1", "p1")
153
- );
154
- }
155
- }
@@ -1,6 +0,0 @@
1
- //! This App's backend: routes, handlers and their policy.
2
-
3
- pub mod app;
4
- pub mod build_env;
5
- pub mod permissions;
6
- pub mod process;
@@ -1,54 +0,0 @@
1
- //! This App's method policy. The SDK's own capability methods keep the SDK
2
- //! policy; permissions.json only configures App methods.
3
-
4
- use std::error::Error;
5
-
6
- use syno_pkg_land_sdk::BoxError;
7
- use syno_pkg_land_sdk::sha256;
8
-
9
- const PERMISSIONS_JSON: &[u8] = include_bytes!("../permissions.json");
10
-
11
- /// Set by `synopkgland build` to bind the policy it validated to the compiled
12
- /// CGI. Unset only in direct development builds.
13
- const EXPECTED_DIGEST: Option<&str> = option_env!("SYNOPKGLAND_PERMISSIONS_DIGEST");
14
-
15
- /// Returns permissions.json, refusing a file that differs from the one the
16
- /// package build validated.
17
- pub fn permissions() -> Result<&'static [u8], BoxError> {
18
- checked(PERMISSIONS_JSON, EXPECTED_DIGEST.unwrap_or(""))
19
- }
20
-
21
- fn checked<'a>(json: &'a [u8], expected_digest: &str) -> Result<&'a [u8], BoxError> {
22
- if !expected_digest.is_empty() && sha256::hex_digest(json) != expected_digest {
23
- return Err(Box::<dyn Error + Send + Sync>::from(
24
- "API permissions do not match package build policy",
25
- ));
26
- }
27
- Ok(json)
28
- }
29
-
30
- #[cfg(test)]
31
- mod tests {
32
- use super::*;
33
-
34
- #[test]
35
- fn permissions_without_a_build_digest_are_the_embedded_file() {
36
- assert_eq!(checked(PERMISSIONS_JSON, "").unwrap(), PERMISSIONS_JSON);
37
- assert_eq!(permissions().unwrap(), PERMISSIONS_JSON);
38
- }
39
-
40
- #[test]
41
- fn permissions_matching_the_build_digest_are_accepted() {
42
- let digest = sha256::hex_digest(PERMISSIONS_JSON);
43
- assert_eq!(
44
- checked(PERMISSIONS_JSON, &digest).unwrap(),
45
- PERMISSIONS_JSON
46
- );
47
- }
48
-
49
- #[test]
50
- fn permissions_differing_from_the_build_digest_are_refused() {
51
- let digest = sha256::hex_digest(b"other");
52
- assert!(checked(PERMISSIONS_JSON, &digest).is_err());
53
- }
54
- }
@@ -1,91 +0,0 @@
1
- //! What the background worker needs of its process: when it is asked to stop
2
- //! and which account it runs as.
3
-
4
- use std::sync::atomic::{AtomicBool, Ordering};
5
- use std::time::Duration;
6
-
7
- use syno_pkg_land_sdk::ctx::Context;
8
-
9
- static TERMINATION_REQUESTED: AtomicBool = AtomicBool::new(false);
10
-
11
- extern "C" fn on_termination(_: libc::c_int) {
12
- TERMINATION_REQUESTED.store(true, Ordering::SeqCst);
13
- }
14
-
15
- /// A context that ends when the process receives SIGTERM or SIGINT, like
16
- /// Go's signal.NotifyContext.
17
- pub fn until_terminated() -> Context {
18
- for signal in [libc::SIGTERM, libc::SIGINT] {
19
- // SAFETY: the handler only stores to an atomic, which is
20
- // async-signal-safe, and the zeroed sigaction is a valid starting
21
- // value for sigaction.
22
- unsafe {
23
- let mut action: libc::sigaction = std::mem::zeroed();
24
- action.sa_sigaction = on_termination as extern "C" fn(libc::c_int) as usize;
25
- action.sa_flags = libc::SA_RESTART;
26
- libc::sigemptyset(&mut action.sa_mask);
27
- libc::sigaction(signal, &action, std::ptr::null_mut());
28
- }
29
- }
30
- let (ctx, cancel) = Context::background().with_cancel();
31
- std::thread::spawn(move || {
32
- while !TERMINATION_REQUESTED.load(Ordering::SeqCst) {
33
- std::thread::sleep(Duration::from_millis(50));
34
- }
35
- cancel.cancel();
36
- });
37
- ctx
38
- }
39
-
40
- /// The account name of the process's real user; empty when it has none.
41
- pub fn current_username() -> String {
42
- let mut size = 1024;
43
- while size <= 1 << 20 {
44
- let mut buf = vec![0 as libc::c_char; size];
45
- // SAFETY: a zeroed passwd is a valid out-parameter for getpwuid_r.
46
- let mut pwd: libc::passwd = unsafe { std::mem::zeroed() };
47
- let mut found = std::ptr::null_mut();
48
- // SAFETY: every pointer refers to a live buffer of the given size.
49
- let rc = unsafe {
50
- libc::getpwuid_r(libc::getuid(), &mut pwd, buf.as_mut_ptr(), size, &mut found)
51
- };
52
- match rc {
53
- 0 if found.is_null() => return String::new(),
54
- 0 => {
55
- // SAFETY: pw_name points into buf and is NUL-terminated.
56
- let name = unsafe { std::ffi::CStr::from_ptr(pwd.pw_name) };
57
- return name.to_string_lossy().into_owned();
58
- }
59
- libc::ERANGE => size *= 2,
60
- _ => return String::new(),
61
- }
62
- }
63
- String::new()
64
- }
65
-
66
- #[cfg(test)]
67
- mod tests {
68
- use super::*;
69
-
70
- #[test]
71
- fn sigterm_ends_the_context() {
72
- let ctx = until_terminated();
73
- assert!(ctx.err().is_none());
74
- // SAFETY: raising a signal whose handler this process installed.
75
- unsafe { libc::raise(libc::SIGTERM) };
76
- assert!(ctx.sleep(Duration::from_secs(5)).is_err());
77
- assert!(ctx.err().is_some());
78
- }
79
-
80
- #[test]
81
- fn the_current_user_resolves_to_the_same_uid() {
82
- let name = current_username();
83
- assert!(!name.is_empty());
84
- // SAFETY: getuid takes no arguments and cannot fail.
85
- let uid = unsafe { libc::getuid() };
86
- assert_eq!(
87
- syno_pkg_land_sdk::delegate::lookup_uid(&name),
88
- uid.to_string()
89
- );
90
- }
91
- }