@synopackageland/templates 0.9.5 → 0.9.6
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.
- package/README.md +27 -4
- package/package.json +2 -2
- package/templates/_shared/AGENTS.md +95 -18
- package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
- package/templates/native-go/backend/cmd/app/main.go +8 -1
- package/templates/native-go/backend/cmd/background-worker/main.go +5 -1
- package/templates/native-go/backend/go.mod +1 -1
- package/templates/native-go/backend/go.sum +2 -2
- package/templates/native-go/backend/internal/app/background.go +19 -8
- package/templates/native-go/backend/internal/app/background_owner_test.go +2 -2
- package/templates/native-go/backend/internal/app/background_test.go +24 -0
- package/templates/native-go/backend/internal/app/background_work.go +94 -0
- package/templates/native-go/backend/internal/app/background_work_test.go +95 -0
- package/templates/native-go/backend/internal/app/uninstall.go +19 -0
- package/templates/native-go/backend/internal/app/uninstall_test.go +33 -0
- package/templates/native-go/dsm-package.yaml +18 -0
- package/templates/native-go-delegate/backend/cmd/background-worker/main.go +1 -1
- package/templates/native-go-jobs/backend/internal/app/background_routes_test.go +4 -0
- package/templates/native-go-jobs/frontend/test/background-page.test.ts +21 -0
- package/templates/native-go-vue/frontend/package-lock.json +4 -4
- package/templates/native-go-vue/frontend/package.json +1 -1
- package/templates/native-rust/README.md +381 -0
- package/templates/native-rust/backend/Cargo.lock +125 -0
- package/templates/native-rust/backend/Cargo.toml +24 -0
- package/templates/native-rust/backend/THIRD_PARTY_NOTICES.txt +207 -0
- package/templates/native-rust/backend/permissions.json +5 -0
- package/templates/native-rust/backend/rust-toolchain.toml +4 -0
- package/templates/native-rust/backend/src/app/background/mod.rs +424 -0
- package/templates/native-rust/backend/src/app/background/owner.rs +77 -0
- package/templates/native-rust/backend/src/app/background/owner_tests.rs +174 -0
- package/templates/native-rust/backend/src/app/background/schedule.rs +126 -0
- package/templates/native-rust/backend/src/app/background/schedule_tests.rs +108 -0
- package/templates/native-rust/backend/src/app/background/tests.rs +505 -0
- package/templates/native-rust/backend/src/app/background/work.rs +78 -0
- package/templates/native-rust/backend/src/app/background/work_tests.rs +123 -0
- package/templates/native-rust/backend/src/app/background_routes.rs +22 -0
- package/templates/native-rust/backend/src/app/delegate_config.rs +188 -0
- package/templates/native-rust/backend/src/app/delegate_routes.rs +22 -0
- package/templates/native-rust/backend/src/app/identity.rs +56 -0
- package/templates/native-rust/backend/src/app/migrations.rs +79 -0
- package/templates/native-rust/backend/src/app/mod.rs +30 -0
- package/templates/native-rust/backend/src/app/portal.rs +317 -0
- package/templates/native-rust/backend/src/app/root_routes.rs +23 -0
- package/templates/native-rust/backend/src/app/routes.rs +315 -0
- package/templates/native-rust/backend/src/app/session_files.rs +19 -0
- package/templates/native-rust/backend/src/app/shared_folder.rs +383 -0
- package/templates/native-rust/backend/src/app/test_support.rs +200 -0
- package/templates/native-rust/backend/src/app/uninstall.rs +38 -0
- package/templates/native-rust/backend/src/bin/api.rs +72 -0
- package/templates/native-rust/backend/src/bin/background-worker.rs +37 -0
- package/templates/native-rust/backend/src/build_env.rs +155 -0
- package/templates/native-rust/backend/src/lib.rs +6 -0
- package/templates/native-rust/backend/src/permissions.rs +54 -0
- package/templates/native-rust/backend/src/process.rs +91 -0
- package/templates/native-rust/dsm-package.yaml +89 -0
- package/templates/native-rust/gitignore +2 -0
- package/templates/native-rust/i18n/glossary.json +663 -0
- package/templates/native-rust/i18n/messages/de.json +29 -0
- package/templates/native-rust/i18n/messages/en.json +29 -0
- package/templates/native-rust/i18n/messages/es.json +29 -0
- package/templates/native-rust/i18n/messages/fr.json +29 -0
- package/templates/native-rust/i18n/messages/it.json +29 -0
- package/templates/native-rust/i18n/messages/ja.json +29 -0
- package/templates/native-rust/i18n/messages/ko.json +29 -0
- package/templates/native-rust/i18n/messages/zh-CN.json +29 -0
- package/templates/native-rust/i18n/messages/zh-TW.json +29 -0
- package/templates/native-rust/ui/app.css +45 -0
- package/templates/native-rust/ui/app.js +111 -0
- package/templates/native-rust-delegate/backend/src/app/delegate_routes.rs +92 -0
- package/templates/native-rust-delegate/backend/src/bin/background-worker.rs +52 -0
- package/templates/native-rust-jobs/backend/src/app/background_routes.rs +109 -0
- package/templates/native-rust-root-ops/backend/src/app/root_routes.rs +475 -0
- package/templates/native-rust-vue/README.md +55 -0
- package/templates/native-rust-vue/gitignore +3 -0
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Standalone native DSM package: an unsigned SPK that runs as its own package
|
|
2
|
+
# `synopkgland build` reads this file.
|
|
3
|
+
schema: 2
|
|
4
|
+
|
|
5
|
+
# INFO.package: letters and digits, starting with a letter. It also names the
|
|
6
|
+
# DSM app (SYNO.SDS.<package>) and the CGI URL /webman/3rdparty/<package>/.
|
|
7
|
+
package: __PACKAGE__
|
|
8
|
+
version: 0.1.0
|
|
9
|
+
# Increase for every SPK you hand out with the same version (INFO 0.1.0-0001).
|
|
10
|
+
build: 1
|
|
11
|
+
displayname: __DISPLAY_NAME__
|
|
12
|
+
description: A native DSM package with a Rust backend and a DSM window UI.
|
|
13
|
+
# Package Center and DSM window icon: a square PNG of at least 256x256, or a
|
|
14
|
+
# folder of icon_<size>.png. Omit to use the SDK default icon.
|
|
15
|
+
# icon: assets/icon.png
|
|
16
|
+
maintainer: Your Name
|
|
17
|
+
# Optional INFO/website metadata. Omit fields you do not need.
|
|
18
|
+
# metadata:
|
|
19
|
+
# contact_email: you@example.com
|
|
20
|
+
# support_email: support@example.com
|
|
21
|
+
# homepage: https://example.com
|
|
22
|
+
# source_url: https://github.com/you/your-app
|
|
23
|
+
# issues_url: https://github.com/you/your-app/issues
|
|
24
|
+
# license: MIT
|
|
25
|
+
# long_description:
|
|
26
|
+
# en: |-
|
|
27
|
+
# Explain what the app does and how to use it.
|
|
28
|
+
# zh-TW: Translated description.
|
|
29
|
+
# changelog: # text, or per-language like long_description
|
|
30
|
+
# en: |-
|
|
31
|
+
# - Most important change first. Omit to use Release notes.
|
|
32
|
+
# zh-TW: |-
|
|
33
|
+
# - Translated changes.
|
|
34
|
+
# screenshots: [https://example.com/screenshot.png]
|
|
35
|
+
os_min_ver: "7.1-42661"
|
|
36
|
+
|
|
37
|
+
# One SPK per architecture. Supported: x86_64, aarch64, armv7 (add it here to
|
|
38
|
+
# build it; armv7 jobs/delegate/root-ops/daemon are built but not NAS-verified).
|
|
39
|
+
arches: [x86_64, aarch64]
|
|
40
|
+
|
|
41
|
+
backend:
|
|
42
|
+
kind: rust
|
|
43
|
+
dir: backend
|
|
44
|
+
package: app
|
|
45
|
+
|
|
46
|
+
ui:
|
|
47
|
+
# Static files served at /webman/3rdparty/<package>/app/. app.js and app.css
|
|
48
|
+
# are required; anything else (images, chunks) is copied as is.
|
|
49
|
+
dir: ui
|
|
50
|
+
|
|
51
|
+
# UI languages: i18n/messages/<locale>.json for en, zh-TW, zh-CN, ja, ko, de,
|
|
52
|
+
# fr, es and it (DSM's other languages fall back to English). build checks
|
|
53
|
+
# that every language has the en.json keys and placeholders, and that the keys
|
|
54
|
+
# listed in the glossary use DSM's own wording.
|
|
55
|
+
i18n:
|
|
56
|
+
messages: i18n/messages
|
|
57
|
+
glossary: i18n/glossary.json
|
|
58
|
+
# Package Center text per language (INFO displayname_<lang>/description_<lang>).
|
|
59
|
+
# English comes from displayname/description above.
|
|
60
|
+
info:
|
|
61
|
+
zh-TW: { description: 以 Rust 後端與 DSM 視窗介面打造的原生 DSM 套件。 }
|
|
62
|
+
zh-CN: { description: 以 Rust 后端与 DSM 窗口界面构建的原生 DSM 套件。 }
|
|
63
|
+
ja: { description: Rust バックエンドと DSM ウィンドウ UI を備えたネイティブ DSM パッケージ。 }
|
|
64
|
+
ko: { description: Rust 백엔드와 DSM 창 UI를 갖춘 네이티브 DSM 패키지. }
|
|
65
|
+
de: { description: Ein natives DSM-Paket mit Rust-Backend und DSM-Fenster-Oberfläche. }
|
|
66
|
+
fr: { description: Un paquet DSM natif avec un backend Rust et une interface en fenêtre DSM. }
|
|
67
|
+
es: { description: Un paquete nativo de DSM con backend en Rust e interfaz en ventana de DSM. }
|
|
68
|
+
it: { description: Un pacchetto DSM nativo con backend Rust e interfaccia in una finestra DSM. }
|
|
69
|
+
|
|
70
|
+
# Optional SDK capabilities; delegate/rootOps share administrator-approved executor setup.
|
|
71
|
+
capabilities: {}
|
|
72
|
+
|
|
73
|
+
# Optional uninstall hook. DSM runs `exec args` as the package account on a
|
|
74
|
+
# real uninstall (not an upgrade), before the executor is removed, with
|
|
75
|
+
# SYNOPKGLAND_DELETE_STATE=0|1 and SYNOPKGLAND_REMOVE_DATA=0|1 from the wizard.
|
|
76
|
+
# A failure or timeout is logged (syslog tag synopkgland-uninstall, output in
|
|
77
|
+
# var/uninstall-hook.log) and never blocks the uninstall. The backend's
|
|
78
|
+
# `uninstall` mode deletes the background job spool when the admin ticks
|
|
79
|
+
# "delete state"; add uninstall.state.jobs to i18n/messages in every language.
|
|
80
|
+
# uninstall:
|
|
81
|
+
# purge:
|
|
82
|
+
# exec: ui/cgi/api.cgi
|
|
83
|
+
# args: [uninstall]
|
|
84
|
+
# timeout: 60 # seconds, 2..120
|
|
85
|
+
# # root: purge # rootOps only: root/purge.sh runs as root at
|
|
86
|
+
# # # uninstall, and only while its approval stands
|
|
87
|
+
# state:
|
|
88
|
+
# items:
|
|
89
|
+
# - key: jobs # i18n key uninstall.state.jobs
|